From fb139da484830035fd20271595823d52be7981ce Mon Sep 17 00:00:00 2001 From: ivcosla Date: Thu, 1 Aug 2019 19:52:07 +0200 Subject: [PATCH] app mock --- cmd/therealssh-cli/commands/root.go | 34 ++++++ internal/therealssh/channel.go | 2 + internal/therealssh/channel_pty_test.go | 1 + internal/therealssh/pty_test.go | 150 ++++++++++++++++++++++++ internal/therealssh/server.go | 2 + pkg/app/testing.go | 16 +++ 6 files changed, 205 insertions(+) create mode 100644 internal/therealssh/pty_test.go create mode 100644 pkg/app/testing.go diff --git a/cmd/therealssh-cli/commands/root.go b/cmd/therealssh-cli/commands/root.go index 2d7d5580c..c234c4b97 100644 --- a/cmd/therealssh-cli/commands/root.go +++ b/cmd/therealssh-cli/commands/root.go @@ -7,6 +7,7 @@ import ( "net" "net/rpc" "os" + "os/exec" "os/signal" "os/user" "strings" @@ -145,3 +146,36 @@ func Execute() { log.Fatal(err) } } + +func runInPTY() error { + c := exec.Command("sh") + ptmx, err := pty.Start(c) + + // Make sure to close the pty at the end. + defer func() { _ = ptmx.Close() }() // Best effort. + + // Handle pty size. + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGWINCH) + go func() { + for range ch { + if err := pty.InheritSize(os.Stdin, ptmx); err != nil { + log.Printf("error resizing pty: %s", err) + } + } + }() + ch <- syscall.SIGWINCH // Initial resize. + + // Set stdin in raw mode. + oldState, err := terminal.MakeRaw(int(os.Stdin.Fd())) + if err != nil { + panic(err) + } + defer func() { _ = terminal.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort. + + // Copy stdin to the pty and the pty to stdout. + go func() { _, _ = io.Copy(ptmx, os.Stdin) }() + _, _ = io.Copy(os.Stdout, ptmx) + + return nil +} \ No newline at end of file diff --git a/internal/therealssh/channel.go b/internal/therealssh/channel.go index 135fb0595..f0d442bfb 100644 --- a/internal/therealssh/channel.go +++ b/internal/therealssh/channel.go @@ -117,6 +117,8 @@ func (sshCh *SSHChannel) Serve() error { err = sshCh.Shell() case RequestExec: err = sshCh.Start(string(data[1:])) + //case RequestExecWithoutShell: + case RequestWindowChange: cols := binary.BigEndian.Uint32(data[1:]) rows := binary.BigEndian.Uint32(data[5:]) diff --git a/internal/therealssh/channel_pty_test.go b/internal/therealssh/channel_pty_test.go index e255d1d1e..1c405f127 100644 --- a/internal/therealssh/channel_pty_test.go +++ b/internal/therealssh/channel_pty_test.go @@ -40,6 +40,7 @@ func TestChannelServe(t *testing.T) { assert.Equal(t, CmdChannelResponse, buf[0]) assert.Equal(t, ResponseConfirm, buf[5]) + require.NotNil(t, ch.session) ch.msgCh <- []byte{byte(RequestShell)} diff --git a/internal/therealssh/pty_test.go b/internal/therealssh/pty_test.go new file mode 100644 index 000000000..b3922f027 --- /dev/null +++ b/internal/therealssh/pty_test.go @@ -0,0 +1,150 @@ +package therealssh + +import ( + "errors" + "fmt" + "github.com/creack/pty" + "github.com/skycoin/dmsg/cipher" + "github.com/skycoin/skywire/pkg/app" + "github.com/skycoin/skywire/pkg/routing" + "github.com/stretchr/testify/require" + "io/ioutil" + "net" + "os/exec" + "testing" +) + +func runInPTY(command string) ([]byte, error) { + c := exec.Command(command) + ptmx, err := pty.Start(c) + if err != nil { + return nil, err + } + + // Make sure to close the pty at the end. + defer func() { _ = ptmx.Close() }() // Best effort. + + // as stated in https://github.com/creack/pty/issues/21#issuecomment-513069505 we can ignore this error + res, _ := ioutil.ReadAll(ptmx) // nolint: err + return res, nil +} + +func runInRemote(t *testing.T, pk cipher.PubKey) ([]byte, error) { + in, out := net.Pipe() + ch := OpenChannel(1, routing.Addr{PubKey: pk, Port: Port}, in) + + errCh := make(chan error) + go func() { + errCh <- ch.Send(CmdChannelOpen, []byte("foo")) + }() + + server := NewServer(MockAuthorizer{}) + go func(){ + err := server.Serve(out) + require.NoError(t, err) + }() + + type data struct { + res []byte + err error + } + resCh := make(chan data) + go func() { + res, err := ch.Request(RequestExec, []byte("ls")) + resCh <- data{res, err} + }() + + d := <- resCh + require.NoError(t, d.err) + fmt.Println(d.res) + return d.res, d.err +} + +func TestRunInPTY(t *testing.T) { + serverPK, _ := cipher.GenerateKeyPair() + //clientPK, _ := cipher.GenerateKeyPair() + + clientConn, serverConn := net.Pipe() + + serverApp := createDefaultServerApp(t, serverConn) + clientApp := createDefaultClientApp(t, clientConn) + + + // server.Serve + server := NewServer(MockAuthorizer{}) + go func() { + conn, err := serverApp.Accept() + require.NoError(t, err) + + require.NoError(t, server.Serve(conn)) + }() + + // client + _, client, err := NewClient(":9999", clientApp) + require.NoError(t, err) + + go func() { + conn, err := clientApp.Dial(routing.Addr{PubKey: serverPK, Port: 2}) + require.NoError(t, err) + + require.NoError(t, client.serveConn(conn)) + }() + + _, ch, err := client.OpenChannel(serverPK) + require.NoError(t, err) + res, err := ch.Request(RequestExec, []byte("ls")) + require.NoError(t, err) + fmt.Println(res) + + require.NoError(t, serverApp.Close()) + require.NoError(t, clientApp.Close()) + require.NoError(t, server.Close()) + require.NoError(t, client.Close()) +} + +func createDefaultServerApp(t *testing.T, conn net.Conn) *app.App { + sshApp := app.NewAppMock(conn) + + go func() { + f := func(f app.Frame, p []byte) (interface{}, error) { + if f == app.FrameCreateLoop { + return &routing.Addr{PubKey: lpk, Port: 2}, nil + } + + if f == app.FrameClose { + go func() { dataCh <- p }() + return nil, nil + } + + return nil, errors.New("unexpected frame") + } + }() + + return sshApp +} + +func createDefaultClientApp(t *testing.T, conn net.Conn) *app.App { + sshApp := app.NewAppMock(conn) + go func() { + f := func(f app.Frame, p []byte) (interface{}, error) { + if f == app.FrameCreateLoop { + return &routing.Addr{PubKey: lpk, Port: 2}, nil + } + + if f == app.FrameClose { + return nil, nil + } + + return nil, errors.New("unexpected frame") + } + serveErrCh <- proto.Serve(f) + }() + + return sshApp +} + +type MockAuthorizer struct {} + +func (MockAuthorizer) Authorize(pk cipher.PubKey) error { + return nil +} diff --git a/internal/therealssh/server.go b/internal/therealssh/server.go index 1a57fea04..1da55897d 100644 --- a/internal/therealssh/server.go +++ b/internal/therealssh/server.go @@ -40,6 +40,8 @@ const ( RequestShell // RequestExec represents request for new process. RequestExec + // RequestExec without shell, for use in integration testing. +// RequestExecWithoutShell // RequestWindowChange represents request for PTY size change. RequestWindowChange ) diff --git a/pkg/app/testing.go b/pkg/app/testing.go new file mode 100644 index 000000000..783051de4 --- /dev/null +++ b/pkg/app/testing.go @@ -0,0 +1,16 @@ +package app + +import ( + "github.com/skycoin/skywire/pkg/routing" + "io" + "net" +) + +func NewAppMock(conn net.Conn) *App { + app := &App{proto: NewProtocol(conn), acceptChan: make(chan [2]routing.Addr), + doneChan: make(chan struct{}), + conns: make(map[routing.Loop]io.ReadWriteCloser)} + go app.handleProto() + + return app +}