Test_sessionStart_orphan tests running a command that takes a long time to exit normally, and terminate the SSH session context early to verify that we return quickly and don't leave the command running as an "orphan" with no active SSH session.
(t *testing.T)
| 31 | // return quickly and don't leave the command running as an "orphan" with no |
| 32 | // active SSH session. |
| 33 | func Test_sessionStart_orphan(t *testing.T) { |
| 34 | t.Parallel() |
| 35 | |
| 36 | ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium) |
| 37 | defer cancel() |
| 38 | logger := testutil.Logger(t) |
| 39 | s, err := NewServer(ctx, logger, prometheus.NewRegistry(), afero.NewMemMapFs(), agentexec.DefaultExecer, nil) |
| 40 | require.NoError(t, err) |
| 41 | defer s.Close() |
| 42 | err = s.UpdateHostSigner(42) |
| 43 | assert.NoError(t, err) |
| 44 | |
| 45 | // Here we're going to call the handler directly with a faked SSH session |
| 46 | // that just uses io.Pipes instead of a network socket. There is a large |
| 47 | // variation in the time between closing the socket from the client side and |
| 48 | // the SSH server canceling the session Context, which would lead to a flaky |
| 49 | // test if we did it that way. So instead, we directly cancel the context |
| 50 | // in this test. |
| 51 | sessionCtx, sessionCancel := context.WithCancel(ctx) |
| 52 | toClient, fromClient, sess := newTestSession(sessionCtx) |
| 53 | ptyInfo := gliderssh.Pty{} |
| 54 | windowSize := make(chan gliderssh.Window) |
| 55 | close(windowSize) |
| 56 | // the command gets the session context so that Go will terminate it when |
| 57 | // the session expires. |
| 58 | cmd := pty.CommandContext(sessionCtx, "sh", "-c", longScript) |
| 59 | |
| 60 | done := make(chan struct{}) |
| 61 | go func() { |
| 62 | defer close(done) |
| 63 | |
| 64 | // we don't really care what the error is here. In the larger scenario, |
| 65 | // the client has disconnected, so we can't return any error information |
| 66 | // to them. |
| 67 | _ = s.startPTYSession(logger, sess, "ssh", cmd, ptyInfo, windowSize) |
| 68 | }() |
| 69 | |
| 70 | readDone := make(chan struct{}) |
| 71 | go func() { |
| 72 | defer close(readDone) |
| 73 | s := bufio.NewScanner(toClient) |
| 74 | assert.True(t, s.Scan()) |
| 75 | txt := s.Text() |
| 76 | assert.Equal(t, "started", txt, "output corrupted") |
| 77 | }() |
| 78 | |
| 79 | waitForChan(ctx, t, readDone, "read timeout") |
| 80 | // process is started, and should be sleeping for ~30 seconds |
| 81 | |
| 82 | sessionCancel() |
| 83 | |
| 84 | // now, we wait for the handler to complete. If it does so before the |
| 85 | // main test timeout, we consider this a pass. If not, it indicates |
| 86 | // that the server isn't properly shutting down sessions when they are |
| 87 | // disconnected client side, which could lead to processes hanging around |
| 88 | // indefinitely. |
| 89 | waitForChan(ctx, t, done, "handler timeout") |
| 90 |
nothing calls this directly
no test coverage detected