From bfa6ce32a6a379dcaaeaffd28afa47c4f4c2d98a Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Tue, 2 Jun 2026 07:53:24 -0400 Subject: [PATCH] test: batch 02 of refactoring CLI tests not to use PTY (#25931) Part of [coder/internal#1400](https://github.com/coder/internal/issues/1400) Batch of refactored CLI tests to avoid creating PTYs. --- cli/show_test.go | 18 ++-- cli/ssh_test.go | 204 +++++++++++++++++++++------------------- cli/start_test.go | 82 ++++++++-------- cli/task_delete_test.go | 12 ++- cli/task_list_test.go | 18 ++-- cli/task_pause_test.go | 20 ++-- cli/task_resume_test.go | 20 ++-- cli/task_send_test.go | 14 +-- 8 files changed, 207 insertions(+), 181 deletions(-) diff --git a/cli/show_test.go b/cli/show_test.go index f078273403..cb4ab0293c 100644 --- a/cli/show_test.go +++ b/cli/show_test.go @@ -15,14 +15,15 @@ import ( "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/codersdk" - "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" ) func TestShow(t *testing.T) { t.Parallel() t.Run("Exists", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) owner := coderdtest.CreateFirstUser(t, client) member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) @@ -39,7 +40,8 @@ func TestShow(t *testing.T) { inv, root := clitest.New(t, args...) clitest.SetupConfig(t, member, root) doneChan := make(chan struct{}) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) ctx := testutil.Context(t, testutil.WaitShort) go func() { defer close(doneChan) @@ -58,9 +60,9 @@ func TestShow(t *testing.T) { {match: "coder ssh " + workspace.Name}, } for _, m := range matches { - pty.ExpectMatchContext(ctx, m.match) + stdout.ExpectMatchContext(ctx, m.match) if len(m.write) > 0 { - pty.WriteLine(m.write) + stdin.WriteLine(m.write) } } _ = testutil.TryReceive(ctx, t, doneChan) @@ -71,6 +73,7 @@ func TestShow(t *testing.T) { // UUID and fetched by ID (which 404s). t.Run("WorkspaceWithUUIDLikeName", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) owner := coderdtest.CreateFirstUser(t, client) member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) @@ -92,7 +95,8 @@ func TestShow(t *testing.T) { inv, root := clitest.New(t, args...) clitest.SetupConfig(t, member, root) doneChan := make(chan struct{}) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) ctx := testutil.Context(t, testutil.WaitShort) go func() { defer close(doneChan) @@ -111,9 +115,9 @@ func TestShow(t *testing.T) { {match: "coder ssh " + workspace.Name}, } for _, m := range matches { - pty.ExpectMatchContext(ctx, m.match) + stdout.ExpectMatchContext(ctx, m.match) if len(m.write) > 0 { - pty.WriteLine(m.write) + stdin.WriteLine(m.write) } } _ = testutil.TryReceive(ctx, t, doneChan) diff --git a/cli/ssh_test.go b/cli/ssh_test.go index 6b8392060c..0cf5a83665 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -55,8 +55,8 @@ import ( "github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/pty" - "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" ) func setupWorkspaceForAgent(t *testing.T, mutations ...func([]*proto.Agent) []*proto.Agent) (*codersdk.Client, database.WorkspaceTable, string) { @@ -82,10 +82,12 @@ func TestSSH(t *testing.T) { t.Run("ImmediateExit", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client, workspace, agentToken := setupWorkspaceForAgent(t) inv, root := clitest.New(t, "ssh", workspace.Name) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() @@ -94,13 +96,13 @@ func TestSSH(t *testing.T) { err := inv.WithContext(ctx).Run() assert.NoError(t, err) }) - pty.ExpectMatch("Waiting") + stdout.ExpectMatchContext(ctx, "Waiting") _ = agenttest.New(t, client.URL, agentToken) coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) // Shells on Mac, Windows, and Linux all exit shells with the "exit" command. - pty.WriteLine("exit") + stdin.WriteLine("exit") <-cmdDone }) t.Run("WorkspaceNameInput", func(t *testing.T) { @@ -121,6 +123,7 @@ func TestSSH(t *testing.T) { for _, tc := range cases { t.Run(tc, func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() @@ -128,19 +131,20 @@ func TestSSH(t *testing.T) { inv, root := clitest.New(t, "ssh", tc) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) cmdDone := tGo(t, func() { err := inv.WithContext(ctx).Run() assert.NoError(t, err) }) - pty.ExpectMatch("Waiting") + stdout.ExpectMatchContext(ctx, "Waiting") _ = agenttest.New(t, client.URL, agentToken) coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) // Shells on Mac, Windows, and Linux all exit shells with the "exit" command. - pty.WriteLine("exit") + stdin.WriteLine("exit") <-cmdDone }) } @@ -148,6 +152,7 @@ func TestSSH(t *testing.T) { t.Run("StartStoppedWorkspace", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) authToken := uuid.NewString() ownerClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) owner := coderdtest.CreateFirstUser(t, ownerClient) @@ -168,7 +173,7 @@ func TestSSH(t *testing.T) { // SSH to the workspace which should autostart it inv, root := clitest.New(t, "ssh", workspace.Name) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitSuperLong) defer cancel() @@ -192,7 +197,7 @@ func TestSSH(t *testing.T) { coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) // Shells on Mac, Windows, and Linux all exit shells with the "exit" command. - pty.WriteLine("exit") + stdin.WriteLine("exit") <-cmdDone }) t.Run("StartStoppedWorkspaceConflict", func(t *testing.T) { @@ -253,21 +258,20 @@ func TestSSH(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium) defer cancel() - var ptys []*ptytest.PTY + var stdouts []*expecter.Expecter for i := 0; i < 3; i++ { // SSH to the workspace which should autostart it inv, root := clitest.New(t, "ssh", workspace.Name) - pty := ptytest.New(t).Attach(inv) - ptys = append(ptys, pty) + stdouts = append(stdouts, expecter.NewAttachedToInvocation(t, inv)) clitest.SetupConfig(t, client, root) testutil.Go(t, func() { _ = inv.WithContext(ctx).Run() }) } - for _, pty := range ptys { - pty.ExpectMatchContext(ctx, "Workspace was stopped, starting workspace to allow connecting to") + for _, stdout := range stdouts { + stdout.ExpectMatchContext(ctx, "Workspace was stopped, starting workspace to allow connecting to") } // Allow one build to complete. @@ -275,15 +279,15 @@ func TestSSH(t *testing.T) { testutil.TryReceive(ctx, t, buildDone) // Allow the remaining builds to continue. - for i := 0; i < len(ptys)-1; i++ { + for i := 0; i < len(stdouts)-1; i++ { testutil.RequireSend(ctx, t, buildPause, false) } var foundConflict int - for _, pty := range ptys { + for _, stdout := range stdouts { // Either allow the command to start the workspace or fail // due to conflict (race), in which case it retries. - match := pty.ExpectRegexMatchContext(ctx, "Waiting for the workspace agent to connect") + match := stdout.ExpectRegexMatchContext(ctx, "Waiting for the workspace agent to connect") if strings.Contains(match, "Unable to start the workspace due to conflict, the workspace may be starting, retrying without autostart...") { foundConflict++ } @@ -293,6 +297,7 @@ func TestSSH(t *testing.T) { t.Run("RequireActiveVersion", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) authToken := uuid.NewString() ownerClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) owner := coderdtest.CreateFirstUser(t, ownerClient) @@ -334,7 +339,7 @@ func TestSSH(t *testing.T) { // SSH to the workspace which should auto-update and autostart it inv, root := clitest.New(t, "ssh", workspace.Name) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() @@ -350,7 +355,7 @@ func TestSSH(t *testing.T) { coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) // Shells on Mac, Windows, and Linux all exit shells with the "exit" command. - pty.WriteLine("exit") + stdin.WriteLine("exit") <-cmdDone // Double-check if workspace's template version is up-to-date @@ -374,10 +379,7 @@ func TestSSH(t *testing.T) { }) inv, root := clitest.New(t, "ssh", workspace.Name) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdin = pty.Input() - inv.Stderr = pty.Output() - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() @@ -386,7 +388,7 @@ func TestSSH(t *testing.T) { err := inv.WithContext(ctx).Run() assert.ErrorIs(t, err, cliui.ErrCanceled) }) - pty.ExpectMatch(wantURL) + stdout.ExpectMatchContext(ctx, wantURL) cancel() <-cmdDone }) @@ -397,6 +399,7 @@ func TestSSH(t *testing.T) { t.Skip("Windows doesn't seem to clean up the process, maybe #7100 will fix it") } + logger := testutil.Logger(t) store, ps := dbtestutil.NewDB(t) client := coderdtest.New(t, &coderdtest.Options{Pubsub: ps, Database: store}) client.SetLogger(testutil.Logger(t).Named("client")) @@ -408,7 +411,8 @@ func TestSSH(t *testing.T) { }).WithAgent().Do() inv, root := clitest.New(t, "ssh", r.Workspace.Name) clitest.SetupConfig(t, userClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() @@ -417,14 +421,14 @@ func TestSSH(t *testing.T) { err := inv.WithContext(ctx).Run() assert.Error(t, err) }) - pty.ExpectMatch("Waiting") + stdout.ExpectMatchContext(ctx, "Waiting") _ = agenttest.New(t, client.URL, r.AgentToken) coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID) // Ensure the agent is connected. - pty.WriteLine("echo hell'o'") - pty.ExpectMatchContext(ctx, "hello") + stdin.WriteLine("echo hell'o'") + stdout.ExpectMatchContext(ctx, "hello") _ = dbfake.WorkspaceBuild(t, store, r.Workspace). Seed(database.WorkspaceBuild{ @@ -1121,6 +1125,7 @@ func TestSSH(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client, workspace, agentToken := setupWorkspaceForAgent(t) _ = agenttest.New(t, client.URL, agentToken) @@ -1168,8 +1173,8 @@ func TestSSH(t *testing.T) { "--identity-agent", agentSock, // Overrides $SSH_AUTH_SOCK. ) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) cmdDone := tGo(t, func() { err := inv.WithContext(ctx).Run() assert.NoError(t, err, "ssh command failed") @@ -1177,21 +1182,21 @@ func TestSSH(t *testing.T) { // Wait for the prompt or any output really to indicate the command has // started and accepting input on stdin. - _ = pty.Peek(ctx, 1) + _ = stdout.Peek(ctx, 1) // Ensure that SSH_AUTH_SOCK is set. // Linux: /tmp/auth-agent3167016167/listener.sock // macOS: /var/folders/ng/m1q0wft14hj0t3rtjxrdnzsr0000gn/T/auth-agent3245553419/listener.sock - pty.WriteLine(`env | grep SSH_AUTH_SOCK=`) - pty.ExpectMatch("SSH_AUTH_SOCK=") + stdin.WriteLine(`env | grep SSH_AUTH_SOCK=`) + stdout.ExpectMatchContext(ctx, "SSH_AUTH_SOCK=") // Ensure that ssh-add lists our key. - pty.WriteLine("ssh-add -L") + stdin.WriteLine("ssh-add -L") keys, err := kr.List() require.NoError(t, err, "list keys failed") - pty.ExpectMatch(keys[0].String()) + stdout.ExpectMatchContext(ctx, keys[0].String()) // And we're done. - pty.WriteLine("exit") + stdin.WriteLine("exit") <-cmdDone }) @@ -1259,6 +1264,7 @@ func TestSSH(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client, workspace, agentToken := setupWorkspaceForAgent(t) _ = agenttest.New(t, client.URL, agentToken) coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) @@ -1271,8 +1277,8 @@ func TestSSH(t *testing.T) { ) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) // Wait super long so this doesn't flake on -race test. ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitSuperLong) @@ -1284,15 +1290,15 @@ func TestSSH(t *testing.T) { // Since something was output, it should be safe to write input. // This could show a prompt or "running startup scripts", so it's // not indicative of the SSH connection being ready. - _ = pty.Peek(ctx, 1) + _ = stdout.Peek(ctx, 1) // Ensure the SSH connection is ready by testing the shell // input/output. - pty.WriteLine("echo $foo $baz") - pty.ExpectMatchContext(ctx, "bar qux") + stdin.WriteLine("echo $foo $baz") + stdout.ExpectMatchContext(ctx, "bar qux") // And we're done. - pty.WriteLine("exit") + stdin.WriteLine("exit") }) t.Run("RemoteForwardUnixSocket", func(t *testing.T) { @@ -1302,6 +1308,7 @@ func TestSSH(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client, workspace, agentToken := setupWorkspaceForAgent(t) _ = agenttest.New(t, client.URL, agentToken) @@ -1321,8 +1328,8 @@ func TestSSH(t *testing.T) { fmt.Sprintf("%s:%s", remoteSock, localSock), ) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) w := clitest.StartWithWaiter(t, inv.WithContext(ctx)) defer w.Wait() // We don't care about any exit error (exit code 255: SSH connection ended unexpectedly). @@ -1330,12 +1337,12 @@ func TestSSH(t *testing.T) { // Since something was output, it should be safe to write input. // This could show a prompt or "running startup scripts", so it's // not indicative of the SSH connection being ready. - _ = pty.Peek(ctx, 1) + _ = stdout.Peek(ctx, 1) // Ensure the SSH connection is ready by testing the shell // input/output. - pty.WriteLine("echo ping' 'pong") - pty.ExpectMatchContext(ctx, "ping pong") + stdin.WriteLine("echo ping' 'pong") + stdout.ExpectMatchContext(ctx, "ping pong") // Start the listener on the "local machine". l, err := net.Listen("unix", localSock) @@ -1378,7 +1385,7 @@ func TestSSH(t *testing.T) { require.Equal(t, "hello world", string(buf)) // And we're done. - pty.WriteLine("exit") + stdin.WriteLine("exit") }) // Test that we can forward a local unix socket to a remote unix socket and @@ -1391,6 +1398,7 @@ func TestSSH(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client, workspace, agentToken := setupWorkspaceForAgent(t) _ = agenttest.New(t, client.URL, agentToken) @@ -1440,8 +1448,8 @@ func TestSSH(t *testing.T) { ) inv.Logger = inv.Logger.Named(id) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) cmdDone := tGo(t, func() { err := inv.WithContext(ctx).Run() assert.NoError(t, err, "ssh command failed: %s", id) @@ -1450,12 +1458,12 @@ func TestSSH(t *testing.T) { // Since something was output, it should be safe to write input. // This could show a prompt or "running startup scripts", so it's // not indicative of the SSH connection being ready. - _ = pty.Peek(ctx, 1) + _ = stdout.Peek(ctx, 1) // Ensure the SSH connection is ready by testing the shell // input/output. - pty.WriteLine("echo ping' 'pong") - pty.ExpectMatchContext(ctx, "ping pong") + stdin.WriteLine("echo ping' 'pong") + stdout.ExpectMatchContext(ctx, "ping pong") d := &net.Dialer{} fd, err := d.DialContext(ctx, "unix", remoteSock) @@ -1481,7 +1489,7 @@ func TestSSH(t *testing.T) { assert.NoError(t, err, id) assert.Equal(t, "hello world", string(buf), id) - pty.WriteLine("exit") + stdin.WriteLine("exit") <-cmdDone return nil }) @@ -1504,6 +1512,7 @@ func TestSSH(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client, workspace, agentToken := setupWorkspaceForAgent(t) _ = agenttest.New(t, client.URL, agentToken) @@ -1534,8 +1543,8 @@ func TestSSH(t *testing.T) { inv, root := clitest.New(t, args...) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) w := clitest.StartWithWaiter(t, inv.WithContext(ctx)) defer w.Wait() // We don't care about any exit error (exit code 255: SSH connection ended unexpectedly). @@ -1543,12 +1552,12 @@ func TestSSH(t *testing.T) { // Since something was output, it should be safe to write input. // This could show a prompt or "running startup scripts", so it's // not indicative of the SSH connection being ready. - _ = pty.Peek(ctx, 1) + _ = stdout.Peek(ctx, 1) // Ensure the SSH connection is ready by testing the shell // input/output. - pty.WriteLine("echo ping' 'pong") - pty.ExpectMatchContext(ctx, "ping pong") + stdin.WriteLine("echo ping' 'pong") + stdout.ExpectMatchContext(ctx, "ping pong") for i, sock := range sockets { // Start the listener on the "local machine". @@ -1593,27 +1602,30 @@ func TestSSH(t *testing.T) { } // And we're done. - pty.WriteLine("exit") + stdin.WriteLine("exit") }) t.Run("FileLogging", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) logDir := t.TempDir() client, workspace, agentToken := setupWorkspaceForAgent(t) inv, root := clitest.New(t, "ssh", "-l", logDir, workspace.Name) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) + ctx := testutil.Context(t, testutil.WaitMedium) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) w := clitest.StartWithWaiter(t, inv) - pty.ExpectMatch("Waiting") + stdout.ExpectMatchContext(ctx, "Waiting") agenttest.New(t, client.URL, agentToken) coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) // Shells on Mac, Windows, and Linux all exit shells with the "exit" command. - pty.WriteLine("exit") + stdin.WriteLine("exit") w.RequireSuccess() ents, err := os.ReadDir(logDir) @@ -1681,6 +1693,7 @@ func TestSSH(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) dv := coderdtest.DeploymentValues(t) if tc.experiment { dv.Experiments = []string{string(codersdk.ExperimentWorkspaceUsage)} @@ -1703,7 +1716,8 @@ func TestSSH(t *testing.T) { agentToken := r.AgentToken inv, root := clitest.New(t, "ssh", workspace.Name, fmt.Sprintf("--usage-app=%s", tc.usageAppName)) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() @@ -1712,13 +1726,13 @@ func TestSSH(t *testing.T) { err := inv.WithContext(ctx).Run() assert.NoError(t, err) }) - pty.ExpectMatch("Waiting") + stdout.ExpectMatchContext(ctx, "Waiting") _ = agenttest.New(t, client.URL, agentToken) coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) // Shells on Mac, Windows, and Linux all exit shells with the "exit" command. - pty.WriteLine("exit") + stdin.WriteLine("exit") <-cmdDone require.EqualValues(t, tc.expectedCalls, batcher.Called) @@ -1974,16 +1988,15 @@ Expire-Date: 0 }) coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) + logger := testutil.Logger(t) inv, root := clitest.New(t, "ssh", workspace.Name, "--forward-gpg", ) clitest.SetupConfig(t, client, root) - tpty := ptytest.New(t) - inv.Stdin = tpty.Input() - inv.Stdout = tpty.Output() - inv.Stderr = tpty.Output() + invOut := expecter.NewAttachedToInvocation(t, inv) + invIn := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) cmdDone := tGo(t, func() { err := inv.WithContext(ctx).Run() assert.NoError(t, err, "ssh command failed") @@ -1997,24 +2010,24 @@ Expire-Date: 0 // Wait for the prompt or any output really to indicate the command has // started and accepting input on stdin. - _ = tpty.Peek(ctx, 1) + _ = invOut.Peek(ctx, 1) - tpty.WriteLine("echo hello 'world'") - tpty.ExpectMatch("hello world") + invIn.WriteLine("echo hello 'world'") + invOut.ExpectMatchContext(ctx, "hello world") // Check the GNUPGHOME was correctly inherited via shell. - tpty.WriteLine("env && echo env-''-command-done") - match := tpty.ExpectMatch("env--command-done") + invIn.WriteLine("env && echo env-''-command-done") + match := invOut.ExpectMatchContext(ctx, "env--command-done") require.Contains(t, match, "GNUPGHOME="+gnupgHomeWorkspace, match) // Get the agent extra socket path in the "workspace" via shell. - tpty.WriteLine("gpgconf --list-dir agent-socket && echo gpgconf-''-agentsocket-command-done") - tpty.ExpectMatch(workspaceAgentSocketPath) - tpty.ExpectMatch("gpgconf--agentsocket-command-done") + invIn.WriteLine("gpgconf --list-dir agent-socket && echo gpgconf-''-agentsocket-command-done") + invOut.ExpectMatchContext(ctx, workspaceAgentSocketPath) + invOut.ExpectMatchContext(ctx, "gpgconf--agentsocket-command-done") // List the keys in the "workspace". - tpty.WriteLine("gpg --list-keys && echo gpg-''-listkeys-command-done") - listKeysOutput := tpty.ExpectMatch("gpg--listkeys-command-done") + invIn.WriteLine("gpg --list-keys && echo gpg-''-listkeys-command-done") + listKeysOutput := invOut.ExpectMatchContext(ctx, "gpg--listkeys-command-done") require.Contains(t, listKeysOutput, "[ultimate] Coder Test ") // It's fine that this key is expired. We're just testing that the key trust // gets synced properly. @@ -2023,14 +2036,14 @@ Expire-Date: 0 // Try to sign something. This demonstrates that the forwarding is // working as expected, since the workspace doesn't have access to the // private key directly and must use the forwarded agent. - tpty.WriteLine("echo 'hello world' | gpg --clearsign && echo gpg-''-sign-command-done") - tpty.ExpectMatch("BEGIN PGP SIGNED MESSAGE") - tpty.ExpectMatch("Hash:") - tpty.ExpectMatch("hello world") - tpty.ExpectMatch("gpg--sign-command-done") + invIn.WriteLine("echo 'hello world' | gpg --clearsign && echo gpg-''-sign-command-done") + invOut.ExpectMatchContext(ctx, "BEGIN PGP SIGNED MESSAGE") + invOut.ExpectMatchContext(ctx, "Hash:") + invOut.ExpectMatchContext(ctx, "hello world") + invOut.ExpectMatchContext(ctx, "gpg--sign-command-done") // And we're done. - tpty.WriteLine("exit") + invIn.WriteLine("exit") <-cmdDone } @@ -2043,6 +2056,7 @@ func TestSSH_Container(t *testing.T) { t.Run("OK", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client, workspace, agentToken := setupWorkspaceForAgent(t) pool, err := dockertest.NewPool("") require.NoError(t, err, "Could not connect to docker") @@ -2076,7 +2090,8 @@ func TestSSH_Container(t *testing.T) { inv, root := clitest.New(t, "ssh", workspace.Name, "-c", ct.Container.ID) clitest.SetupConfig(t, client, root) - ptty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) ctx := testutil.Context(t, testutil.WaitLong) cmdDone := tGo(t, func() { @@ -2084,10 +2099,10 @@ func TestSSH_Container(t *testing.T) { assert.NoError(t, err) }) - ptty.ExpectMatchContext(ctx, " #") - ptty.WriteLine("hostname") - ptty.ExpectMatchContext(ctx, ct.Container.Config.Hostname) - ptty.WriteLine("exit") + stdout.ExpectMatchContext(ctx, " #") + stdin.WriteLine("hostname") + stdout.ExpectMatchContext(ctx, ct.Container.Config.Hostname) + stdin.WriteLine("exit") <-cmdDone }) @@ -2120,15 +2135,15 @@ func TestSSH_Container(t *testing.T) { cID := uuid.NewString() inv, root := clitest.New(t, "ssh", workspace.Name, "-c", cID) clitest.SetupConfig(t, client, root) - ptty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) cmdDone := tGo(t, func() { err := inv.WithContext(ctx).Run() assert.NoError(t, err) }) - ptty.ExpectMatch(fmt.Sprintf("Container not found: %q", cID)) - ptty.ExpectMatch("Available containers: [something_completely_different]") + stdout.ExpectMatchContext(ctx, fmt.Sprintf("Container not found: %q", cID)) + stdout.ExpectMatchContext(ctx, "Available containers: [something_completely_different]") <-cmdDone }) @@ -2163,7 +2178,6 @@ func TestSSH_CoderConnect(t *testing.T) { client, workspace, agentToken := setupWorkspaceForAgent(t) inv, root := clitest.New(t, "ssh", workspace.Name, "--network-info-dir", "/net", "--stdio") clitest.SetupConfig(t, client, root) - _ = ptytest.New(t).Attach(inv) ctx = cli.WithTestOnlyCoderConnectDialer(ctx, &fakeCoderConnectDialer{}) ctx = withCoderConnectRunning(ctx) diff --git a/cli/start_test.go b/cli/start_test.go index 4a682a4309..8e8ac70c0c 100644 --- a/cli/start_test.go +++ b/cli/start_test.go @@ -6,7 +6,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "golang.org/x/net/context" "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/coderd/coderdtest" @@ -16,8 +15,8 @@ import ( "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/provisionersdk/proto" - "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" ) const ( @@ -109,6 +108,7 @@ func TestStart(t *testing.T) { t.Run("BuildOptions", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) owner := coderdtest.CreateFirstUser(t, client) member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) @@ -132,7 +132,9 @@ func TestStart(t *testing.T) { inv, root := clitest.New(t, "start", workspace.Name, "--prompt-ephemeral-parameters") clitest.SetupConfig(t, member, root) doneChan := make(chan struct{}) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) + ctx := testutil.Context(t, testutil.WaitMedium) go func() { defer close(doneChan) err := inv.Run() @@ -146,18 +148,15 @@ func TestStart(t *testing.T) { for i := 0; i < len(matches); i += 2 { match := matches[i] value := matches[i+1] - pty.ExpectMatch(match) + stdout.ExpectMatchContext(ctx, match) if value != "" { - pty.WriteLine(value) + stdin.WriteLine(value) } } <-doneChan // Verify if ephemeral parameter is set - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) - defer cancel() - workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID) @@ -195,20 +194,18 @@ func TestStart(t *testing.T) { "--ephemeral-parameter", fmt.Sprintf("%s=%s", ephemeralParameterName, ephemeralParameterValue)) clitest.SetupConfig(t, member, root) doneChan := make(chan struct{}) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitMedium) go func() { defer close(doneChan) err := inv.Run() assert.NoError(t, err) }() - pty.ExpectMatch("workspace has been started") + stdout.ExpectMatchContext(ctx, "workspace has been started") <-doneChan // Verify if ephemeral parameter is set - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) - defer cancel() - workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID) @@ -251,20 +248,18 @@ func TestStartWithParameters(t *testing.T) { inv, root := clitest.New(t, "start", workspace.Name) clitest.SetupConfig(t, member, root) doneChan := make(chan struct{}) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitMedium) go func() { defer close(doneChan) err := inv.Run() assert.NoError(t, err) }() - pty.ExpectMatch("workspace has been started") + stdout.ExpectMatchContext(ctx, "workspace has been started") <-doneChan // Verify if immutable parameter is set - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) - defer cancel() - workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID) @@ -278,6 +273,7 @@ func TestStartWithParameters(t *testing.T) { t.Run("AlwaysPrompt", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) // Create the workspace client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) owner := coderdtest.CreateFirstUser(t, client) @@ -303,7 +299,9 @@ func TestStartWithParameters(t *testing.T) { inv, root := clitest.New(t, "start", workspace.Name, "--always-prompt") clitest.SetupConfig(t, member, root) doneChan := make(chan struct{}) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) + ctx := testutil.Context(t, testutil.WaitMedium) go func() { defer close(doneChan) err := inv.Run() @@ -311,15 +309,12 @@ func TestStartWithParameters(t *testing.T) { }() newValue := "xyz" - pty.ExpectMatch(mutableParameterName) - pty.WriteLine(newValue) - pty.ExpectMatch("workspace has been started") + stdout.ExpectMatchContext(ctx, mutableParameterName) + stdin.WriteLine(newValue) + stdout.ExpectMatchContext(ctx, "workspace has been started") <-doneChan // Verify that the updated values are persisted. - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) - defer cancel() - workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID) @@ -368,7 +363,7 @@ func TestStartUseParameterDefaults(t *testing.T) { // The new parameter should be auto-accepted. inv, root := clitest.New(t, "start", workspace.Name, "--use-parameter-defaults") clitest.SetupConfig(t, member, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) doneChan := make(chan struct{}) go func() { defer close(doneChan) @@ -376,7 +371,7 @@ func TestStartUseParameterDefaults(t *testing.T) { assert.NoError(t, err) }() - pty.ExpectMatchContext(ctx, "workspace has been started") + stdout.ExpectMatchContext(ctx, "workspace has been started") _ = testutil.TryReceive(ctx, t, doneChan) // Verify the new parameter was resolved to its default. @@ -420,6 +415,7 @@ func TestStartAutoUpdate(t *testing.T) { t.Run(c.Name, func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) owner := coderdtest.CreateFirstUser(t, client) member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) @@ -446,15 +442,17 @@ func TestStartAutoUpdate(t *testing.T) { inv, root := clitest.New(t, c.Cmd, "-y", workspace.Name) clitest.SetupConfig(t, member, root) doneChan := make(chan struct{}) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) + ctx := testutil.Context(t, testutil.WaitMedium) go func() { defer close(doneChan) err := inv.Run() assert.NoError(t, err) }() - pty.ExpectMatch(stringParameterName) - pty.WriteLine(stringParameterValue) + stdout.ExpectMatchContext(ctx, stringParameterName) + stdin.WriteLine(stringParameterValue) <-doneChan workspace = coderdtest.MustWorkspace(t, member, workspace.ID) @@ -478,14 +476,14 @@ func TestStart_AlreadyRunning(t *testing.T) { inv, root := clitest.New(t, "start", r.Workspace.Name) clitest.SetupConfig(t, memberClient, root) doneChan := make(chan struct{}) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) go func() { defer close(doneChan) err := inv.Run() assert.NoError(t, err) }() - pty.ExpectMatch("workspace is already running") + stdout.ExpectMatchContext(ctx, "workspace is already running") _ = testutil.TryReceive(ctx, t, doneChan) } @@ -507,17 +505,17 @@ func TestStart_Starting(t *testing.T) { inv, root := clitest.New(t, "start", r.Workspace.Name) clitest.SetupConfig(t, memberClient, root) doneChan := make(chan struct{}) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) go func() { defer close(doneChan) err := inv.Run() assert.NoError(t, err) }() - pty.ExpectMatch("workspace is already starting") + stdout.ExpectMatchContext(ctx, "workspace is already starting") _ = dbfake.JobComplete(t, store, r.Build.JobID).Pubsub(ps).Do() - pty.ExpectMatch("workspace has been started") + stdout.ExpectMatchContext(ctx, "workspace has been started") _ = testutil.TryReceive(ctx, t, doneChan) } @@ -544,14 +542,14 @@ func TestStart_NoWait(t *testing.T) { inv, root := clitest.New(t, "start", workspace.Name, "--no-wait") clitest.SetupConfig(t, member, root) doneChan := make(chan struct{}) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) go func() { defer close(doneChan) err := inv.Run() assert.NoError(t, err) }() - pty.ExpectMatch("workspace has been started in no-wait mode") + stdout.ExpectMatchContext(ctx, "workspace has been started in no-wait mode") _ = testutil.TryReceive(ctx, t, doneChan) } @@ -577,14 +575,14 @@ func TestStart_WithReason(t *testing.T) { inv, root := clitest.New(t, "start", workspace.Name, "--reason", "cli") clitest.SetupConfig(t, member, root) doneChan := make(chan struct{}) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) go func() { defer close(doneChan) err := inv.Run() assert.NoError(t, err) }() - pty.ExpectMatch("workspace has been started") + stdout.ExpectMatchContext(ctx, "workspace has been started") _ = testutil.TryReceive(ctx, t, doneChan) workspace = coderdtest.MustWorkspace(t, member, workspace.ID) @@ -628,7 +626,7 @@ func TestStart_FailedStartCleansUp(t *testing.T) { inv, root := clitest.New(t, "start", workspace.Name) clitest.SetupConfig(t, memberClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) doneChan := make(chan struct{}) go func() { defer close(doneChan) @@ -637,8 +635,8 @@ func TestStart_FailedStartCleansUp(t *testing.T) { }() // The CLI should detect the failed start and clean up first. - pty.ExpectMatch("Cleaning up before retrying") - pty.ExpectMatch("workspace has been started") + stdout.ExpectMatchContext(ctx, "Cleaning up before retrying") + stdout.ExpectMatchContext(ctx, "workspace has been started") _ = testutil.TryReceive(ctx, t, doneChan) } diff --git a/cli/task_delete_test.go b/cli/task_delete_test.go index 2d28845c73..c105f9f0fa 100644 --- a/cli/task_delete_test.go +++ b/cli/task_delete_test.go @@ -15,8 +15,8 @@ import ( "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/codersdk" - "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" ) func TestExpTaskDelete(t *testing.T) { @@ -186,6 +186,7 @@ func TestExpTaskDelete(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitMedium) + logger := testutil.Logger(t) var counters testCounters srv := httptest.NewServer(tc.buildHandler(&counters)) @@ -201,12 +202,13 @@ func TestExpTaskDelete(t *testing.T) { var runErr error var outBuf bytes.Buffer if tc.promptYes { - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) w := clitest.StartWithWaiter(t, inv) - pty.ExpectMatch("Delete these tasks:") - pty.WriteLine("yes") + stdout.ExpectMatchContext(ctx, "Delete these tasks:") + stdin.WriteLine("yes") runErr = w.Wait() - outBuf.Write(pty.ReadAll()) + outBuf.Write(stdout.ReadAll()) } else { inv.Stdout = &outBuf inv.Stderr = &outBuf diff --git a/cli/task_list_test.go b/cli/task_list_test.go index 4a055efeb0..6e2b984dd9 100644 --- a/cli/task_list_test.go +++ b/cli/task_list_test.go @@ -20,8 +20,8 @@ import ( "github.com/coder/coder/v2/coderd/database/dbfake" "github.com/coder/coder/v2/coderd/util/slice" "github.com/coder/coder/v2/codersdk" - "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" ) // makeAITask creates an AI-task workspace. @@ -71,13 +71,13 @@ func TestExpTaskList(t *testing.T) { inv, root := clitest.New(t, "task", "list") clitest.SetupConfig(t, memberClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) ctx := testutil.Context(t, testutil.WaitShort) err := inv.WithContext(ctx).Run() require.NoError(t, err) - pty.ExpectMatch("No tasks found.") + stdout.ExpectMatchContext(ctx, "No tasks found.") }) t.Run("Single_Table", func(t *testing.T) { @@ -95,16 +95,16 @@ func TestExpTaskList(t *testing.T) { inv, root := clitest.New(t, "task", "list", "--column", "id,name,status,initial prompt") clitest.SetupConfig(t, memberClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) ctx := testutil.Context(t, testutil.WaitShort) err := inv.WithContext(ctx).Run() require.NoError(t, err) // Validate the table includes the task and status. - pty.ExpectMatch(task.Name) - pty.ExpectMatch("initializing") - pty.ExpectMatch(wantPrompt) + stdout.ExpectMatchContext(ctx, task.Name) + stdout.ExpectMatchContext(ctx, "initializing") + stdout.ExpectMatchContext(ctx, wantPrompt) }) t.Run("StatusFilter_JSON", func(t *testing.T) { @@ -156,13 +156,13 @@ func TestExpTaskList(t *testing.T) { //nolint:gocritic // Owner client is intended here smoke test the member task not showing up. clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) ctx := testutil.Context(t, testutil.WaitShort) err := inv.WithContext(ctx).Run() require.NoError(t, err) - pty.ExpectMatch(task.Name) + stdout.ExpectMatchContext(ctx, task.Name) }) t.Run("Quiet", func(t *testing.T) { diff --git a/cli/task_pause_test.go b/cli/task_pause_test.go index 83151a8457..a8e24d130f 100644 --- a/cli/task_pause_test.go +++ b/cli/task_pause_test.go @@ -8,8 +8,8 @@ import ( "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/codersdk" - "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" ) func TestExpTaskPause(t *testing.T) { @@ -67,6 +67,7 @@ func TestExpTaskPause(t *testing.T) { t.Run("PromptConfirm", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) // Given: A running task setupCtx := testutil.Context(t, testutil.WaitLong) setup := setupCLITaskTest(setupCtx, t, nil) @@ -78,13 +79,14 @@ func TestExpTaskPause(t *testing.T) { // And: We confirm we want to pause the task ctx := testutil.Context(t, testutil.WaitMedium) inv = inv.WithContext(ctx) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) w := clitest.StartWithWaiter(t, inv) - pty.ExpectMatchContext(ctx, "Pause task") - pty.WriteLine("yes") + stdout.ExpectMatchContext(ctx, "Pause task") + stdin.WriteLine("yes") // Then: We expect the task to be paused - pty.ExpectMatchContext(ctx, "has been paused") + stdout.ExpectMatchContext(ctx, "has been paused") require.NoError(t, w.Wait()) updated, err := setup.userClient.TaskByIdentifier(ctx, setup.task.Name) @@ -95,6 +97,7 @@ func TestExpTaskPause(t *testing.T) { t.Run("PromptDecline", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) // Given: A running task setupCtx := testutil.Context(t, testutil.WaitLong) setup := setupCLITaskTest(setupCtx, t, nil) @@ -106,10 +109,11 @@ func TestExpTaskPause(t *testing.T) { // But: We say no at the confirmation screen ctx := testutil.Context(t, testutil.WaitMedium) inv = inv.WithContext(ctx) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) w := clitest.StartWithWaiter(t, inv) - pty.ExpectMatchContext(ctx, "Pause task") - pty.WriteLine("no") + stdout.ExpectMatchContext(ctx, "Pause task") + stdin.WriteLine("no") require.Error(t, w.Wait()) // Then: We expect the task to not be paused diff --git a/cli/task_resume_test.go b/cli/task_resume_test.go index 8ed8c42ece..94ebd0fa97 100644 --- a/cli/task_resume_test.go +++ b/cli/task_resume_test.go @@ -9,8 +9,8 @@ import ( "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/codersdk" - "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" ) func TestExpTaskResume(t *testing.T) { @@ -99,6 +99,7 @@ func TestExpTaskResume(t *testing.T) { t.Run("PromptConfirm", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) // Given: A paused task setupCtx := testutil.Context(t, testutil.WaitLong) setup := setupCLITaskTest(setupCtx, t, nil) @@ -111,13 +112,14 @@ func TestExpTaskResume(t *testing.T) { // And: We confirm we want to resume the task ctx := testutil.Context(t, testutil.WaitMedium) inv = inv.WithContext(ctx) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) w := clitest.StartWithWaiter(t, inv) - pty.ExpectMatchContext(ctx, "Resume task") - pty.WriteLine("yes") + stdout.ExpectMatchContext(ctx, "Resume task") + stdin.WriteLine("yes") // Then: We expect the task to be resumed - pty.ExpectMatchContext(ctx, "has been resumed") + stdout.ExpectMatchContext(ctx, "has been resumed") require.NoError(t, w.Wait()) updated, err := setup.userClient.TaskByIdentifier(ctx, setup.task.Name) @@ -128,6 +130,7 @@ func TestExpTaskResume(t *testing.T) { t.Run("PromptDecline", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) // Given: A paused task setupCtx := testutil.Context(t, testutil.WaitLong) setup := setupCLITaskTest(setupCtx, t, nil) @@ -140,10 +143,11 @@ func TestExpTaskResume(t *testing.T) { // But: Say no at the confirmation screen ctx := testutil.Context(t, testutil.WaitMedium) inv = inv.WithContext(ctx) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) w := clitest.StartWithWaiter(t, inv) - pty.ExpectMatchContext(ctx, "Resume task") - pty.WriteLine("no") + stdout.ExpectMatchContext(ctx, "Resume task") + stdin.WriteLine("no") require.Error(t, w.Wait()) // Then: We expect the task to still be paused diff --git a/cli/task_send_test.go b/cli/task_send_test.go index 1590bcab29..e32f435242 100644 --- a/cli/task_send_test.go +++ b/cli/task_send_test.go @@ -19,8 +19,8 @@ import ( "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" - "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" "github.com/coder/quartz" ) @@ -151,13 +151,13 @@ func Test_TaskSend(t *testing.T) { // Use a pty so we can wait for the command to produce build // output, confirming it has entered the initializing code // path before we connect the agent. - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) w := clitest.StartWithWaiter(t, inv) // Wait for the command to observe the initializing state and // start watching the workspace build. This ensures the command // has entered the waiting code path. - pty.ExpectMatchContext(ctx, "Queued") + stdout.ExpectMatchContext(ctx, "Queued") // Connect a new agent so the task can transition to active. agentClient := agentsdk.New(setup.userClient.URL, agentsdk.WithFixedToken(setup.agentToken)) @@ -203,12 +203,12 @@ func Test_TaskSend(t *testing.T) { // Use a pty so we can wait for the command to produce build // output, confirming it has entered the paused code path and // triggered a resume before we connect the agent. - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) w := clitest.StartWithWaiter(t, inv) // Wait for the command to observe the paused state, trigger // a resume, and start watching the workspace build. - pty.ExpectMatchContext(ctx, "Queued") + stdout.ExpectMatchContext(ctx, "Queued") // Connect a new agent so the task can transition to active. agentClient := agentsdk.New(setup.userClient.URL, agentsdk.WithFixedToken(setup.agentToken)) @@ -260,12 +260,12 @@ func Test_TaskSend(t *testing.T) { ctx := testutil.Context(t, testutil.WaitLong) inv = inv.WithContext(ctx) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) w := clitest.StartWithWaiter(t, inv) // Wait for the command to enter the build-watching phase // of waitForTaskIdle. - pty.ExpectMatchContext(ctx, "Waiting for task to become idle") + stdout.ExpectMatchContext(ctx, "Waiting for task to become idle") // Wait for ticker creation and release it. tickCall := tickTrap.MustWait(ctx)