//go:build !windows package sshd import ( "errors" "strings" "testing" "golang.org/x/crypto/ssh" ) func session(t *testing.T) (*ssh.Session, func()) { t.Helper() good := newSigner(t) addr, _, _ := start(t, good) c, err := dial(addr, ssh.PublicKeys(good)) if err != nil { t.Fatal(err) } s, err := c.NewSession() if err != nil { t.Fatal(err) } return s, func() { s.Close(); c.Close() } } func TestExec(t *testing.T) { s, done := session(t) defer done() out, err := s.Output("echo hello; echo oops >&2") if err != nil || string(out) != "hello\n" { t.Fatalf("got %q, %v", out, err) } } func TestExecExitStatus(t *testing.T) { s, done := session(t) defer done() err := s.Run("exit 3") var ee *ssh.ExitError if !errors.As(err, &ee) || ee.ExitStatus() != 3 { t.Fatalf("want exit status 3, got %v", err) } } func TestExecReadsStdin(t *testing.T) { s, done := session(t) defer done() s.Stdin = strings.NewReader("from the client\n") out, err := s.Output("cat") if err != nil || string(out) != "from the client\n" { t.Fatalf("got %q, %v", out, err) } } func TestPtyExec(t *testing.T) { s, done := session(t) defer done() if err := s.RequestPty("xterm", 24, 80, ssh.TerminalModes{ssh.ECHO: 0}); err != nil { t.Fatal(err) } out, err := s.CombinedOutput(`echo term=$TERM; test -t 0 && echo tty`) if err != nil || !strings.Contains(string(out), "term=xterm") || !strings.Contains(string(out), "tty") { t.Fatalf("got %q, %v", out, err) } } func TestShell(t *testing.T) { s, done := session(t) defer done() s.Stdin = strings.NewReader("echo from-shell\nexit 4\n") var out strings.Builder s.Stdout = &out err := s.Shell() if err != nil { t.Fatal(err) } err = s.Wait() var ee *ssh.ExitError if !errors.As(err, &ee) || ee.ExitStatus() != 4 || !strings.Contains(out.String(), "from-shell") { t.Fatalf("got %q, %v", out.String(), err) } } // A machine with no shell (the published container image is one) must say so, not just hang up. func TestNoShellIsExplained(t *testing.T) { t.Setenv("SHELL", "/nonexistent/shell") for name, usePty := range map[string]bool{"exec": false, "pty shell": true} { s, done := session(t) var stderr, stdout strings.Builder s.Stderr, s.Stdout = &stderr, &stdout var err error if usePty { if err = s.RequestPty("xterm", 24, 80, ssh.TerminalModes{}); err == nil { err = s.Shell() if err == nil { err = s.Wait() } } } else { err = s.Run("true") } done() var ee *ssh.ExitError if !errors.As(err, &ee) || ee.ExitStatus() != 127 { t.Errorf("%s: want exit status 127, got %v", name, err) } if !strings.Contains(stderr.String()+stdout.String(), "no shell to run") { t.Errorf("%s: no explanation (stderr %q, stdout %q)", name, stderr.String(), stdout.String()) } } }