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.
This commit is contained in:
Spike Curtis
2026-06-02 07:53:24 -04:00
committed by GitHub
parent 32aee9ea4c
commit bfa6ce32a6
8 changed files with 207 additions and 181 deletions
+11 -7
View File
@@ -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)
+109 -95
View File
@@ -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 <test@coder.com>")
// 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)
+40 -42
View File
@@ -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)
}
+7 -5
View File
@@ -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
+9 -9
View File
@@ -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) {
+12 -8
View File
@@ -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
+12 -8
View File
@@ -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
+7 -7
View File
@@ -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)