diff --git a/cli/organization_test.go b/cli/organization_test.go index 8c4997f4ae..ab5751b513 100644 --- a/cli/organization_test.go +++ b/cli/organization_test.go @@ -17,7 +17,8 @@ import ( "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/cli/cliui" "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" "github.com/coder/pretty" ) @@ -29,6 +30,7 @@ func TestCurrentOrganization(t *testing.T) { // 2. The user is connecting to an older Coder instance. t.Run("no-default", func(t *testing.T) { t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) orgID := uuid.New() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -49,13 +51,13 @@ func TestCurrentOrganization(t *testing.T) { client := codersdk.New(must(url.Parse(srv.URL))) inv, root := clitest.New(t, "organizations", "show", "selected") clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) errC := make(chan error) go func() { errC <- inv.Run() }() require.NoError(t, <-errC) - pty.ExpectMatch(orgID.String()) + stdout.ExpectMatchContext(ctx, orgID.String()) }) } @@ -140,6 +142,8 @@ func TestOrganizationDelete(t *testing.T) { t.Run("Prompted", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) + ctx := testutil.Context(t, testutil.WaitMedium) orgID := uuid.New() var deleteCalled atomic.Bool @@ -167,15 +171,16 @@ func TestOrganizationDelete(t *testing.T) { client := codersdk.New(must(url.Parse(server.URL))) inv, root := clitest.New(t, "organizations", "delete", "my-org") clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) execDone := make(chan error) go func() { execDone <- inv.Run() }() - pty.ExpectMatch(fmt.Sprintf("Delete organization %s?", pretty.Sprint(cliui.DefaultStyles.Code, "my-org"))) - pty.WriteLine("yes") + stdout.ExpectMatchContext(ctx, fmt.Sprintf("Delete organization %s?", pretty.Sprint(cliui.DefaultStyles.Code, "my-org"))) + stdin.WriteLine("yes") require.NoError(t, <-execDone) require.True(t, deleteCalled.Load(), "expected delete request") diff --git a/cli/portforward_test.go b/cli/portforward_test.go index 91c13efabe..d0cfeeb8fb 100644 --- a/cli/portforward_test.go +++ b/cli/portforward_test.go @@ -25,8 +25,8 @@ import ( "github.com/coder/coder/v2/coderd/database/dbfake" "github.com/coder/coder/v2/coderd/database/dbtime" "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 TestPortForward_None(t *testing.T) { @@ -160,10 +160,7 @@ func TestPortForward(t *testing.T) { // the "local" listener. inv, root := clitest.New(t, "-v", "port-forward", workspace.Name, flag) clitest.SetupConfig(t, member, root) - pty := ptytest.New(t) - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) iNet := testutil.NewInProcNet() inv.Net = iNet @@ -175,7 +172,7 @@ func TestPortForward(t *testing.T) { t.Logf("command complete; err=%s", err.Error()) errC <- err }() - pty.ExpectMatchContext(ctx, "Ready!") + stdout.ExpectMatchContext(ctx, "Ready!") // Open two connections simultaneously and test them out of // sync. @@ -216,10 +213,7 @@ func TestPortForward(t *testing.T) { // the "local" listeners. inv, root := clitest.New(t, "-v", "port-forward", workspace.Name, flag1, flag2) clitest.SetupConfig(t, member, root) - pty := ptytest.New(t) - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) iNet := testutil.NewInProcNet() inv.Net = iNet @@ -229,7 +223,7 @@ func TestPortForward(t *testing.T) { go func() { errC <- inv.WithContext(ctx).Run() }() - pty.ExpectMatchContext(ctx, "Ready!") + stdout.ExpectMatchContext(ctx, "Ready!") // Open a connection to both listener 1 and 2 simultaneously and // then test them out of order. @@ -277,8 +271,7 @@ func TestPortForward(t *testing.T) { // the "local" listeners. inv, root := clitest.New(t, append([]string{"-v", "port-forward", workspace.Name}, flags...)...) clitest.SetupConfig(t, member, root) - pty := ptytest.New(t).Attach(inv) - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) iNet := testutil.NewInProcNet() inv.Net = iNet @@ -288,7 +281,7 @@ func TestPortForward(t *testing.T) { go func() { errC <- inv.WithContext(ctx).Run() }() - pty.ExpectMatchContext(ctx, "Ready!") + stdout.ExpectMatchContext(ctx, "Ready!") // Open connections to all items in the "dial" array. var ( @@ -338,10 +331,7 @@ func TestPortForward(t *testing.T) { // the "local" listener. inv, root := clitest.New(t, "-v", "port-forward", workspace.Name, flag) clitest.SetupConfig(t, member, root) - pty := ptytest.New(t) - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) iNet := testutil.NewInProcNet() inv.Net = iNet @@ -359,7 +349,7 @@ func TestPortForward(t *testing.T) { t.Logf("command complete; err=%s", err.Error()) errC <- err }() - pty.ExpectMatchContext(ctx, "Ready!") + stdout.ExpectMatchContext(ctx, "Ready!") // Test IPv4 still works dialCtx, dialCtxCancel := context.WithTimeout(ctx, testutil.WaitShort) diff --git a/cli/rename_test.go b/cli/rename_test.go index 31d14e5e08..e9aa8d480d 100644 --- a/cli/rename_test.go +++ b/cli/rename_test.go @@ -8,12 +8,13 @@ import ( "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/coderd/coderdtest" - "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" ) func TestRename(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, AllowWorkspaceRenames: true}) owner := coderdtest.CreateFirstUser(t, client) @@ -30,13 +31,13 @@ func TestRename(t *testing.T) { want := coderdtest.RandomUsername(t) inv, root := clitest.New(t, "rename", workspace.Name, want, "--yes") clitest.SetupConfig(t, member, root) - pty := ptytest.New(t) - pty.Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) clitest.Start(t, inv) - pty.ExpectMatch("confirm rename:") - pty.WriteLine(workspace.Name) - pty.ExpectMatch("renamed to") + stdout.ExpectMatchContext(ctx, "confirm rename:") + stdin.WriteLine(workspace.Name) + stdout.ExpectMatchContext(ctx, "renamed to") ws, err := client.Workspace(ctx, workspace.ID) assert.NoError(t, err) diff --git a/cli/restart_test.go b/cli/restart_test.go index a8cd7ee5f3..3506d313a2 100644 --- a/cli/restart_test.go +++ b/cli/restart_test.go @@ -1,7 +1,6 @@ package cli_test import ( - "context" "fmt" "testing" @@ -14,8 +13,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" ) func TestRestart(t *testing.T) { @@ -49,15 +48,15 @@ func TestRestart(t *testing.T) { inv, root := clitest.New(t, "restart", workspace.Name, "--yes") clitest.SetupConfig(t, member, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) done := make(chan error, 1) go func() { done <- inv.WithContext(ctx).Run() }() - pty.ExpectMatch("Stopping workspace") - pty.ExpectMatch("Starting workspace") - pty.ExpectMatch("workspace has been restarted") + stdout.ExpectMatchContext(ctx, "Stopping workspace") + stdout.ExpectMatchContext(ctx, "Starting workspace") + stdout.ExpectMatchContext(ctx, "workspace has been restarted") err := <-done require.NoError(t, err, "execute failed") @@ -66,6 +65,7 @@ func TestRestart(t *testing.T) { t.Run("PromptEphemeralParameters", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) owner := coderdtest.CreateFirstUser(t, client) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) @@ -84,13 +84,15 @@ func TestRestart(t *testing.T) { inv, root := clitest.New(t, "restart", 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) go func() { defer close(doneChan) err := inv.Run() assert.NoError(t, err) }() + ctx := testutil.Context(t, testutil.WaitShort) matches := []string{ ephemeralParameterDescription, ephemeralParameterValue, "Restart workspace?", "yes", @@ -101,18 +103,15 @@ func TestRestart(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 build option is set - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) - defer cancel() - workspace, err := client.WorkspaceByOwnerAndName(ctx, memberUser.ID.String(), workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID) @@ -126,6 +125,7 @@ func TestRestart(t *testing.T) { t.Run("EphemeralParameterFlags", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) owner := coderdtest.CreateFirstUser(t, client) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) @@ -143,13 +143,15 @@ func TestRestart(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) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) go func() { defer close(doneChan) err := inv.Run() assert.NoError(t, err) }() + ctx := testutil.Context(t, testutil.WaitShort) matches := []string{ "Restart workspace?", "yes", "Stopping workspace", "", @@ -159,18 +161,15 @@ func TestRestart(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 build option is set - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) - defer cancel() - workspace, err := client.WorkspaceByOwnerAndName(ctx, memberUser.ID.String(), workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID) @@ -184,6 +183,7 @@ func TestRestart(t *testing.T) { t.Run("with deprecated build-options flag", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) owner := coderdtest.CreateFirstUser(t, client) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) @@ -202,13 +202,15 @@ func TestRestart(t *testing.T) { inv, root := clitest.New(t, "restart", workspace.Name, "--build-options") 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) go func() { defer close(doneChan) err := inv.Run() assert.NoError(t, err) }() + ctx := testutil.Context(t, testutil.WaitShort) matches := []string{ ephemeralParameterDescription, ephemeralParameterValue, "Restart workspace?", "yes", @@ -219,18 +221,15 @@ func TestRestart(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 build option is set - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) - defer cancel() - workspace, err := client.WorkspaceByOwnerAndName(ctx, memberUser.ID.String(), workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID) @@ -244,6 +243,7 @@ func TestRestart(t *testing.T) { t.Run("with deprecated build-option flag", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) owner := coderdtest.CreateFirstUser(t, client) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) @@ -261,13 +261,15 @@ func TestRestart(t *testing.T) { "--build-option", 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) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) go func() { defer close(doneChan) err := inv.Run() assert.NoError(t, err) }() + ctx := testutil.Context(t, testutil.WaitShort) matches := []string{ "Restart workspace?", "yes", "Stopping workspace", "", @@ -277,18 +279,15 @@ func TestRestart(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 build option is set - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) - defer cancel() - workspace, err := client.WorkspaceByOwnerAndName(ctx, memberUser.ID.String(), workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID) @@ -349,20 +348,18 @@ func TestRestartWithParameters(t *testing.T) { inv, root := clitest.New(t, "restart", workspace.Name, "-y") 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) }() + ctx := testutil.Context(t, testutil.WaitShort) - pty.ExpectMatch("workspace has been restarted") + stdout.ExpectMatchContext(ctx, "workspace has been restarted") <-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) @@ -376,6 +373,7 @@ func TestRestartWithParameters(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) @@ -396,24 +394,23 @@ func TestRestartWithParameters(t *testing.T) { inv, root := clitest.New(t, "restart", workspace.Name, "-y", "--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) go func() { defer close(doneChan) err := inv.Run() assert.NoError(t, err) }() + ctx := testutil.Context(t, testutil.WaitShort) // We should be prompted for the parameters again. newValue := "xyz" - pty.ExpectMatch(mutableParameterName) - pty.WriteLine(newValue) - pty.ExpectMatch("workspace has been restarted") + stdout.ExpectMatchContext(ctx, mutableParameterName) + stdin.WriteLine(newValue) + stdout.ExpectMatchContext(ctx, "workspace has been restarted") <-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) diff --git a/cli/schedule_test.go b/cli/schedule_test.go index ed9c5b1743..c9f61345a1 100644 --- a/cli/schedule_test.go +++ b/cli/schedule_test.go @@ -19,8 +19,8 @@ import ( "github.com/coder/coder/v2/coderd/schedule/cron" "github.com/coder/coder/v2/coderd/util/tz" "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" ) // setupTestSchedule creates 4 workspaces: @@ -97,20 +97,21 @@ func TestScheduleShow(t *testing.T) { inv, root := clitest.New(t, "schedule", "show") //nolint:gocritic // Testing that owner user sees all clitest.SetupConfig(t, ownerClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitShort) require.NoError(t, inv.Run()) // Then: they should see their own workspaces. // 1st workspace: a-owner-ws1 has both autostart and autostop enabled. - pty.ExpectMatch(ws[0].OwnerName + "/" + ws[0].Name) - pty.ExpectMatch(sched.Humanize()) - pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339)) - pty.ExpectMatch("8h") - pty.ExpectMatch(ws[0].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) + stdout.ExpectMatchContext(ctx, ws[0].OwnerName+"/"+ws[0].Name) + stdout.ExpectMatchContext(ctx, sched.Humanize()) + stdout.ExpectMatchContext(ctx, sched.Next(now).In(loc).Format(time.RFC3339)) + stdout.ExpectMatchContext(ctx, "8h") + stdout.ExpectMatchContext(ctx, ws[0].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) // 2nd workspace: b-owner-ws2 has only autostart enabled. - pty.ExpectMatch(ws[1].OwnerName + "/" + ws[1].Name) - pty.ExpectMatch(sched.Humanize()) - pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339)) + stdout.ExpectMatchContext(ctx, ws[1].OwnerName+"/"+ws[1].Name) + stdout.ExpectMatchContext(ctx, sched.Humanize()) + stdout.ExpectMatchContext(ctx, sched.Next(now).In(loc).Format(time.RFC3339)) }) t.Run("OwnerAll", func(t *testing.T) { @@ -118,26 +119,27 @@ func TestScheduleShow(t *testing.T) { inv, root := clitest.New(t, "schedule", "show", "--all") //nolint:gocritic // Testing that owner user sees all clitest.SetupConfig(t, ownerClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitShort) require.NoError(t, inv.Run()) // Then: they should see all workspaces // 1st workspace: a-owner-ws1 has both autostart and autostop enabled. - pty.ExpectMatch(ws[0].OwnerName + "/" + ws[0].Name) - pty.ExpectMatch(sched.Humanize()) - pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339)) - pty.ExpectMatch("8h") - pty.ExpectMatch(ws[0].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) + stdout.ExpectMatchContext(ctx, ws[0].OwnerName+"/"+ws[0].Name) + stdout.ExpectMatchContext(ctx, sched.Humanize()) + stdout.ExpectMatchContext(ctx, sched.Next(now).In(loc).Format(time.RFC3339)) + stdout.ExpectMatchContext(ctx, "8h") + stdout.ExpectMatchContext(ctx, ws[0].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) // 2nd workspace: b-owner-ws2 has only autostart enabled. - pty.ExpectMatch(ws[1].OwnerName + "/" + ws[1].Name) - pty.ExpectMatch(sched.Humanize()) - pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339)) + stdout.ExpectMatchContext(ctx, ws[1].OwnerName+"/"+ws[1].Name) + stdout.ExpectMatchContext(ctx, sched.Humanize()) + stdout.ExpectMatchContext(ctx, sched.Next(now).In(loc).Format(time.RFC3339)) // 3rd workspace: c-member-ws3 has only autostop enabled. - pty.ExpectMatch(ws[2].OwnerName + "/" + ws[2].Name) - pty.ExpectMatch("8h") - pty.ExpectMatch(ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) + stdout.ExpectMatchContext(ctx, ws[2].OwnerName+"/"+ws[2].Name) + stdout.ExpectMatchContext(ctx, "8h") + stdout.ExpectMatchContext(ctx, ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) // 4th workspace: d-member-ws4 has neither autostart nor autostop enabled. - pty.ExpectMatch(ws[3].OwnerName + "/" + ws[3].Name) + stdout.ExpectMatchContext(ctx, ws[3].OwnerName+"/"+ws[3].Name) }) t.Run("OwnerSearchByName", func(t *testing.T) { @@ -145,14 +147,15 @@ func TestScheduleShow(t *testing.T) { inv, root := clitest.New(t, "schedule", "show", "--search", "name:"+ws[1].Name) //nolint:gocritic // Testing that owner user sees all clitest.SetupConfig(t, ownerClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitShort) require.NoError(t, inv.Run()) // Then: they should see workspaces matching that query // 2nd workspace: b-owner-ws2 has only autostart enabled. - pty.ExpectMatch(ws[1].OwnerName + "/" + ws[1].Name) - pty.ExpectMatch(sched.Humanize()) - pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339)) + stdout.ExpectMatchContext(ctx, ws[1].OwnerName+"/"+ws[1].Name) + stdout.ExpectMatchContext(ctx, sched.Humanize()) + stdout.ExpectMatchContext(ctx, sched.Next(now).In(loc).Format(time.RFC3339)) }) t.Run("OwnerOneArg", func(t *testing.T) { @@ -160,37 +163,39 @@ func TestScheduleShow(t *testing.T) { inv, root := clitest.New(t, "schedule", "show", ws[2].OwnerName+"/"+ws[2].Name) //nolint:gocritic // Testing that owner user sees all clitest.SetupConfig(t, ownerClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitShort) require.NoError(t, inv.Run()) // Then: they should see that workspace // 3rd workspace: c-member-ws3 has only autostop enabled. - pty.ExpectMatch(ws[2].OwnerName + "/" + ws[2].Name) - pty.ExpectMatch("8h") - pty.ExpectMatch(ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) + stdout.ExpectMatchContext(ctx, ws[2].OwnerName+"/"+ws[2].Name) + stdout.ExpectMatchContext(ctx, "8h") + stdout.ExpectMatchContext(ctx, ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) }) t.Run("MemberNoArgs", func(t *testing.T) { // When: a member specifies no args inv, root := clitest.New(t, "schedule", "show") clitest.SetupConfig(t, memberClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitShort) require.NoError(t, inv.Run()) // Then: they should see their own workspaces // 1st workspace: c-member-ws3 has only autostop enabled. - pty.ExpectMatch(ws[2].OwnerName + "/" + ws[2].Name) - pty.ExpectMatch("8h") - pty.ExpectMatch(ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) + stdout.ExpectMatchContext(ctx, ws[2].OwnerName+"/"+ws[2].Name) + stdout.ExpectMatchContext(ctx, "8h") + stdout.ExpectMatchContext(ctx, ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) // 2nd workspace: d-member-ws4 has neither autostart nor autostop enabled. - pty.ExpectMatch(ws[3].OwnerName + "/" + ws[3].Name) + stdout.ExpectMatchContext(ctx, ws[3].OwnerName+"/"+ws[3].Name) }) t.Run("MemberAll", func(t *testing.T) { // When: a member lists all workspaces inv, root := clitest.New(t, "schedule", "show", "--all") clitest.SetupConfig(t, memberClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) ctx := testutil.Context(t, testutil.WaitShort) errC := make(chan error) go func() { @@ -200,11 +205,11 @@ func TestScheduleShow(t *testing.T) { // Then: they should only see their own // 1st workspace: c-member-ws3 has only autostop enabled. - pty.ExpectMatch(ws[2].OwnerName + "/" + ws[2].Name) - pty.ExpectMatch("8h") - pty.ExpectMatch(ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) + stdout.ExpectMatchContext(ctx, ws[2].OwnerName+"/"+ws[2].Name) + stdout.ExpectMatchContext(ctx, "8h") + stdout.ExpectMatchContext(ctx, ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) // 2nd workspace: d-member-ws4 has neither autostart nor autostop enabled. - pty.ExpectMatch(ws[3].OwnerName + "/" + ws[3].Name) + stdout.ExpectMatchContext(ctx, ws[3].OwnerName+"/"+ws[3].Name) }) t.Run("JSON", func(t *testing.T) { @@ -276,13 +281,14 @@ func TestScheduleModify(t *testing.T) { ) //nolint:gocritic // this workspace is not owned by the same user clitest.SetupConfig(t, ownerClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitShort) require.NoError(t, inv.Run()) // Then: the updated schedule should be shown - pty.ExpectMatch(ws[3].OwnerName + "/" + ws[3].Name) - pty.ExpectMatch(sched.Humanize()) - pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339)) + stdout.ExpectMatchContext(ctx, ws[3].OwnerName+"/"+ws[3].Name) + stdout.ExpectMatchContext(ctx, sched.Humanize()) + stdout.ExpectMatchContext(ctx, sched.Next(now).In(loc).Format(time.RFC3339)) }) t.Run("SetStop", func(t *testing.T) { @@ -292,13 +298,14 @@ func TestScheduleModify(t *testing.T) { ) //nolint:gocritic // this workspace is not owned by the same user clitest.SetupConfig(t, ownerClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitShort) require.NoError(t, inv.Run()) // Then: the updated schedule should be shown - pty.ExpectMatch(ws[2].OwnerName + "/" + ws[2].Name) - pty.ExpectMatch("8h30m") - pty.ExpectMatch(ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) + stdout.ExpectMatchContext(ctx, ws[2].OwnerName+"/"+ws[2].Name) + stdout.ExpectMatchContext(ctx, "8h30m") + stdout.ExpectMatchContext(ctx, ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) }) t.Run("UnsetStart", func(t *testing.T) { @@ -308,11 +315,12 @@ func TestScheduleModify(t *testing.T) { ) //nolint:gocritic // this workspace is owned by owner clitest.SetupConfig(t, ownerClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitShort) require.NoError(t, inv.Run()) // Then: the updated schedule should be shown - pty.ExpectMatch(ws[1].OwnerName + "/" + ws[1].Name) + stdout.ExpectMatchContext(ctx, ws[1].OwnerName+"/"+ws[1].Name) }) t.Run("UnsetStop", func(t *testing.T) { @@ -322,11 +330,12 @@ func TestScheduleModify(t *testing.T) { ) //nolint:gocritic // this workspace is owned by owner clitest.SetupConfig(t, ownerClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitShort) require.NoError(t, inv.Run()) // Then: the updated schedule should be shown - pty.ExpectMatch(ws[0].OwnerName + "/" + ws[0].Name) + stdout.ExpectMatchContext(ctx, ws[0].OwnerName+"/"+ws[0].Name) }) } @@ -359,7 +368,8 @@ func TestScheduleOverride(t *testing.T) { ) clitest.SetupConfig(t, ownerClient, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitShort) require.NoError(t, inv.Run()) // Fetch the workspace to get the actual deadline set by the @@ -376,11 +386,11 @@ func TestScheduleOverride(t *testing.T) { expectedDeadline := updated.LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339) // Then: the updated schedule should be shown - pty.ExpectMatch(ws[0].OwnerName + "/" + ws[0].Name) - pty.ExpectMatch(sched.Humanize()) - pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339)) - pty.ExpectMatch("8h") - pty.ExpectMatch(expectedDeadline) + stdout.ExpectMatchContext(ctx, ws[0].OwnerName+"/"+ws[0].Name) + stdout.ExpectMatchContext(ctx, sched.Humanize()) + stdout.ExpectMatchContext(ctx, sched.Next(now).In(loc).Format(time.RFC3339)) + stdout.ExpectMatchContext(ctx, "8h") + stdout.ExpectMatchContext(ctx, expectedDeadline) }) } } @@ -422,13 +432,14 @@ func TestScheduleStart_TemplateAutostartRequirement(t *testing.T) { "schedule", "start", workspace.Name, "9:30AM", "Mon-Fri", ) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitShort) require.NoError(t, inv.Run()) // Then: warning should be shown // In AGPL, this will show all days (enterprise feature defaults to all days allowed) - pty.ExpectMatch("Warning") - pty.ExpectMatch("may only autostart") + stdout.ExpectMatchContext(ctx, "Warning") + stdout.ExpectMatchContext(ctx, "may only autostart") }) t.Run("NoWarningWhenManual", func(t *testing.T) { diff --git a/cli/secret_test.go b/cli/secret_test.go index 3cbb6b89b8..06224d45c6 100644 --- a/cli/secret_test.go +++ b/cli/secret_test.go @@ -14,8 +14,8 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" "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 TestSecretCreate(t *testing.T) { @@ -501,6 +501,7 @@ func TestSecretDelete(t *testing.T) { t.Run("Success", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, client) @@ -516,12 +517,13 @@ func TestSecretDelete(t *testing.T) { 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) waiter := clitest.StartWithWaiter(t, inv) - pty.ExpectMatchContext(ctx, "Delete secret") - pty.ExpectMatchContext(ctx, "service-token") - pty.WriteLine("yes") - pty.ExpectMatchContext(ctx, "Deleted secret") + stdout.ExpectMatchContext(ctx, "Delete secret") + stdout.ExpectMatchContext(ctx, "service-token") + stdin.WriteLine("yes") + stdout.ExpectMatchContext(ctx, "Deleted secret") require.NoError(t, waiter.Wait()) @@ -566,6 +568,7 @@ func TestSecretDelete(t *testing.T) { t.Run("NotFound", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) client := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, client) @@ -574,11 +577,12 @@ func TestSecretDelete(t *testing.T) { 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) waiter := clitest.StartWithWaiter(t, inv) - pty.ExpectMatchContext(ctx, "Delete secret") - pty.ExpectMatchContext(ctx, "missing-secret") - pty.WriteLine("yes") + stdout.ExpectMatchContext(ctx, "Delete secret") + stdout.ExpectMatchContext(ctx, "missing-secret") + stdin.WriteLine("yes") err := waiter.Wait() require.ErrorContains(t, err, `delete secret "missing-secret"`) diff --git a/cli/server_createadminuser_test.go b/cli/server_createadminuser_test.go index c0883a2d27..d0eef5f72d 100644 --- a/cli/server_createadminuser_test.go +++ b/cli/server_createadminuser_test.go @@ -21,6 +21,7 @@ import ( "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" ) //nolint:paralleltest, tparallel @@ -128,19 +129,17 @@ func TestServerCreateAdminUser(t *testing.T) { "--email", email, "--password", password, ) - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.Start(t, inv) - pty.ExpectMatchContext(ctx, "Creating user...") - pty.ExpectMatchContext(ctx, "Generating user SSH key...") - pty.ExpectMatchContext(ctx, fmt.Sprintf("Adding user to organization %q (%s) as admin...", org1Name, org1ID.String())) - pty.ExpectMatchContext(ctx, fmt.Sprintf("Adding user to organization %q (%s) as admin...", org2Name, org2ID.String())) - pty.ExpectMatchContext(ctx, "User created successfully.") - pty.ExpectMatchContext(ctx, username) - pty.ExpectMatchContext(ctx, email) - pty.ExpectMatchContext(ctx, "****") + stdout.ExpectMatchContext(ctx, "Creating user...") + stdout.ExpectMatchContext(ctx, "Generating user SSH key...") + stdout.ExpectMatchContext(ctx, fmt.Sprintf("Adding user to organization %q (%s) as admin...", org1Name, org1ID.String())) + stdout.ExpectMatchContext(ctx, fmt.Sprintf("Adding user to organization %q (%s) as admin...", org2Name, org2ID.String())) + stdout.ExpectMatchContext(ctx, "User created successfully.") + stdout.ExpectMatchContext(ctx, username) + stdout.ExpectMatchContext(ctx, email) + stdout.ExpectMatchContext(ctx, "****") verifyUser(t, connectionURL, username, email, password) }) @@ -184,6 +183,7 @@ func TestServerCreateAdminUser(t *testing.T) { // Skip on non-Linux because it spawns a PostgreSQL instance. t.SkipNow() } + logger := testutil.Logger(t) connectionURL, err := dbtestutil.Open(t) require.NoError(t, err) @@ -195,23 +195,24 @@ func TestServerCreateAdminUser(t *testing.T) { "--postgres-url", connectionURL, "--ssh-keygen-algorithm", "ed25519", ) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) clitest.Start(t, inv) - pty.ExpectMatchContext(ctx, "Username") - pty.WriteLine(username) - pty.ExpectMatchContext(ctx, "Email") - pty.WriteLine(email) - pty.ExpectMatchContext(ctx, "Password") - pty.WriteLine(password) - pty.ExpectMatchContext(ctx, "Confirm password") - pty.WriteLine(password) + stdout.ExpectMatchContext(ctx, "Username") + stdin.WriteLine(username) + stdout.ExpectMatchContext(ctx, "Email") + stdin.WriteLine(email) + stdout.ExpectMatchContext(ctx, "Password") + stdin.WriteLine(password) + stdout.ExpectMatchContext(ctx, "Confirm password") + stdin.WriteLine(password) - pty.ExpectMatchContext(ctx, "User created successfully.") - pty.ExpectMatchContext(ctx, username) - pty.ExpectMatchContext(ctx, email) - pty.ExpectMatchContext(ctx, "****") + stdout.ExpectMatchContext(ctx, "User created successfully.") + stdout.ExpectMatchContext(ctx, username) + stdout.ExpectMatchContext(ctx, email) + stdout.ExpectMatchContext(ctx, "****") verifyUser(t, connectionURL, username, email, password) }) diff --git a/cli/server_test.go b/cli/server_test.go index 89e0ba7048..6776e84424 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -59,6 +59,7 @@ import ( "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/tailnet/tailnettest" "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" "github.com/coder/serpent" ) @@ -229,7 +230,7 @@ func TestServer(t *testing.T) { "--access-url", "http://example.com", "--ephemeral", ) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) // Embedded postgres takes a while to fire up. const superDuperLong = testutil.WaitSuperLong * 3 @@ -240,7 +241,7 @@ func TestServer(t *testing.T) { }() matchCh1 := make(chan string, 1) go func() { - matchCh1 <- pty.ExpectMatchContext(ctx, "Using an ephemeral deployment directory") + matchCh1 <- stdout.ExpectMatchContext(ctx, "Using an ephemeral deployment directory") }() select { case err := <-errCh: @@ -248,7 +249,7 @@ func TestServer(t *testing.T) { case <-matchCh1: // OK! } - rootDirLine := pty.ReadLine(ctx) + rootDirLine := stdout.ReadLine(ctx) rootDir := strings.TrimPrefix(rootDirLine, "Using an ephemeral deployment directory") rootDir = strings.TrimSpace(rootDir) rootDir = strings.TrimPrefix(rootDir, "(") @@ -259,7 +260,7 @@ func TestServer(t *testing.T) { matchCh2 := make(chan string, 1) go func() { // The "View the Web UI" log is a decent indicator that the server was successfully started. - matchCh2 <- pty.ExpectMatchContext(ctx, "View the Web UI") + matchCh2 <- stdout.ExpectMatchContext(ctx, "View the Web UI") }() select { case err := <-errCh: @@ -276,24 +277,23 @@ func TestServer(t *testing.T) { t.Run("BuiltinPostgresURL", func(t *testing.T) { t.Parallel() root, _ := clitest.New(t, "server", "postgres-builtin-url") - pty := ptytest.New(t) - root.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, root) + ctx := testutil.Context(t, testutil.WaitShort) err := root.Run() require.NoError(t, err) - pty.ExpectMatch("psql") + stdout.ExpectMatchContext(ctx, "psql") }) t.Run("BuiltinPostgresURLRaw", func(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitLong) root, _ := clitest.New(t, "server", "postgres-builtin-url", "--raw-url") - pty := ptytest.New(t) - root.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, root) err := root.WithContext(ctx).Run() require.NoError(t, err) - got := pty.ReadLine(ctx) + got := stdout.ReadLine(ctx) if !strings.HasPrefix(got, "postgres://") { t.Fatalf("expected postgres URL to start with \"postgres://\", got %q", got) } @@ -506,6 +506,7 @@ func TestServer(t *testing.T) { // reachable. t.Run("LocalAccessURL", func(t *testing.T) { t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) inv, cfg := clitest.New(t, "server", dbArg(t), @@ -513,7 +514,7 @@ func TestServer(t *testing.T) { "--access-url", "http://localhost:3000/", "--cache-dir", t.TempDir(), ) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) // Since we end the test after seeing the log lines about the access url, we could cancel the test before // our initial interactions with PostgreSQL are complete. So, ignore errors of that type for this test. startIgnoringPostgresQueryCancel(t, inv) @@ -521,9 +522,9 @@ func TestServer(t *testing.T) { // Just wait for startup _ = waitAccessURL(t, cfg) - pty.ExpectMatch("this may cause unexpected problems when creating workspaces") - pty.ExpectMatch("View the Web UI:") - pty.ExpectMatch("http://localhost:3000/") + stdout.ExpectMatchContext(ctx, "this may cause unexpected problems when creating workspaces") + stdout.ExpectMatchContext(ctx, "View the Web UI:") + stdout.ExpectMatchContext(ctx, "http://localhost:3000/") }) // Validate that an https scheme is prepended to a remote access URL @@ -531,6 +532,7 @@ func TestServer(t *testing.T) { t.Run("RemoteAccessURL", func(t *testing.T) { t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) inv, cfg := clitest.New(t, "server", dbArg(t), @@ -538,7 +540,7 @@ func TestServer(t *testing.T) { "--access-url", "https://foobarbaz.mydomain", "--cache-dir", t.TempDir(), ) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) // Since we end the test after seeing the log lines about the access url, we could cancel the test before // our initial interactions with PostgreSQL are complete. So, ignore errors of that type for this test. @@ -547,13 +549,14 @@ func TestServer(t *testing.T) { // Just wait for startup _ = waitAccessURL(t, cfg) - pty.ExpectMatch("this may cause unexpected problems when creating workspaces") - pty.ExpectMatch("View the Web UI:") - pty.ExpectMatch("https://foobarbaz.mydomain") + stdout.ExpectMatchContext(ctx, "this may cause unexpected problems when creating workspaces") + stdout.ExpectMatchContext(ctx, "View the Web UI:") + stdout.ExpectMatchContext(ctx, "https://foobarbaz.mydomain") }) t.Run("NoWarningWithRemoteAccessURL", func(t *testing.T) { t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) inv, cfg := clitest.New(t, "server", dbArg(t), @@ -561,7 +564,7 @@ func TestServer(t *testing.T) { "--access-url", "https://google.com", "--cache-dir", t.TempDir(), ) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) // Since we end the test after seeing the log lines about the access url, we could cancel the test before // our initial interactions with PostgreSQL are complete. So, ignore errors of that type for this test. startIgnoringPostgresQueryCancel(t, inv) @@ -569,8 +572,8 @@ func TestServer(t *testing.T) { // Just wait for startup _ = waitAccessURL(t, cfg) - pty.ExpectMatch("View the Web UI:") - pty.ExpectMatch("https://google.com") + stdout.ExpectMatchContext(ctx, "View the Web UI:") + stdout.ExpectMatchContext(ctx, "https://google.com") }) t.Run("NoSchemeAccessURL", func(t *testing.T) { @@ -735,8 +738,6 @@ func TestServer(t *testing.T) { "--tls-key-file", key2Path, "--cache-dir", t.TempDir(), ) - pty := ptytest.New(t) - root.Stdout = pty.Output() clitest.Start(t, root.WithContext(ctx)) accessURL := waitAccessURL(t, cfg) @@ -814,18 +815,18 @@ func TestServer(t *testing.T) { "--tls-key-file", keyPath, "--cache-dir", t.TempDir(), ) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.Start(t, inv) // We can't use waitAccessURL as it will only return the HTTP URL. const httpLinePrefix = "Started HTTP listener at" - pty.ExpectMatch(httpLinePrefix) - httpLine := pty.ReadLine(ctx) + stdout.ExpectMatchContext(ctx, httpLinePrefix) + httpLine := stdout.ReadLine(ctx) httpAddr := strings.TrimSpace(strings.TrimPrefix(httpLine, httpLinePrefix)) require.NotEmpty(t, httpAddr) const tlsLinePrefix = "Started TLS/HTTPS listener at " - pty.ExpectMatch(tlsLinePrefix) - tlsLine := pty.ReadLine(ctx) + stdout.ExpectMatchContext(ctx, tlsLinePrefix) + tlsLine := stdout.ReadLine(ctx) tlsAddr := strings.TrimSpace(strings.TrimPrefix(tlsLine, tlsLinePrefix)) require.NotEmpty(t, tlsAddr) @@ -951,8 +952,7 @@ func TestServer(t *testing.T) { } inv, _ := clitest.New(t, flags...) - pty := ptytest.New(t) - pty.Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.Start(t, inv) @@ -963,15 +963,15 @@ func TestServer(t *testing.T) { // We can't use waitAccessURL as it will only return the HTTP URL. if c.httpListener { const httpLinePrefix = "Started HTTP listener at" - pty.ExpectMatch(httpLinePrefix) - httpLine := pty.ReadLine(ctx) + stdout.ExpectMatchContext(ctx, httpLinePrefix) + httpLine := stdout.ReadLine(ctx) httpAddr = strings.TrimSpace(strings.TrimPrefix(httpLine, httpLinePrefix)) require.NotEmpty(t, httpAddr) } if c.tlsListener { const tlsLinePrefix = "Started TLS/HTTPS listener at" - pty.ExpectMatch(tlsLinePrefix) - tlsLine := pty.ReadLine(ctx) + stdout.ExpectMatchContext(ctx, tlsLinePrefix) + tlsLine := stdout.ReadLine(ctx) tlsAddr = strings.TrimSpace(strings.TrimPrefix(tlsLine, tlsLinePrefix)) require.NotEmpty(t, tlsAddr) } @@ -1041,6 +1041,7 @@ func TestServer(t *testing.T) { t.Run("CanListenUnspecifiedv4", func(t *testing.T) { t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) inv, _ := clitest.New(t, "server", dbArg(t), @@ -1048,18 +1049,19 @@ func TestServer(t *testing.T) { "--access-url", "http://example.com", ) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) // Since we end the test after seeing the log lines about the HTTP listener, we could cancel the test before // our initial interactions with PostgreSQL are complete. So, ignore errors of that type for this test. startIgnoringPostgresQueryCancel(t, inv) - pty.ExpectMatch("Started HTTP listener") - pty.ExpectMatch("http://0.0.0.0:") + stdout.ExpectMatchContext(ctx, "Started HTTP listener") + stdout.ExpectMatchContext(ctx, "http://0.0.0.0:") }) t.Run("CanListenUnspecifiedv6", func(t *testing.T) { t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) inv, _ := clitest.New(t, "server", dbArg(t), @@ -1067,13 +1069,13 @@ func TestServer(t *testing.T) { "--access-url", "http://example.com", ) - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) // Since we end the test after seeing the log lines about the HTTP listener, we could cancel the test before // our initial interactions with PostgreSQL are complete. So, ignore errors of that type for this test. startIgnoringPostgresQueryCancel(t, inv) - pty.ExpectMatch("Started HTTP listener at") - pty.ExpectMatch("http://[::]:") + stdout.ExpectMatchContext(ctx, "Started HTTP listener at") + stdout.ExpectMatchContext(ctx, "http://[::]:") }) t.Run("NoAddress", func(t *testing.T) { @@ -1128,12 +1130,10 @@ func TestServer(t *testing.T) { "--access-url", "http://example.com", "--cache-dir", t.TempDir(), ) - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.Start(t, inv.WithContext(ctx)) - pty.ExpectMatch("is deprecated") + stdout.ExpectMatchContext(ctx, "is deprecated") accessURL := waitAccessURL(t, cfg) require.Equal(t, "http", accessURL.Scheme) @@ -1158,12 +1158,10 @@ func TestServer(t *testing.T) { "--tls-key-file", keyPath, "--cache-dir", t.TempDir(), ) - pty := ptytest.New(t) - root.Stdout = pty.Output() - root.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, root) clitest.Start(t, root.WithContext(ctx)) - pty.ExpectMatch("is deprecated") + stdout.ExpectMatchContext(ctx, "is deprecated") accessURL := waitAccessURL(t, cfg) require.Equal(t, "https", accessURL.Scheme) @@ -1259,15 +1257,13 @@ func TestServer(t *testing.T) { "--cache-dir", t.TempDir(), ) - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.Start(t, inv) // Wait until we see the prometheus address in the logs. addrMatchExpr := `http server listening\s+addr=(\S+)\s+name=prometheus` - lineMatch := pty.ExpectRegexMatchContext(ctx, addrMatchExpr) + lineMatch := stdout.ExpectRegexMatchContext(ctx, addrMatchExpr) promAddr := regexp.MustCompile(addrMatchExpr).FindStringSubmatch(lineMatch)[1] testutil.Eventually(ctx, t, func(ctx context.Context) bool { @@ -1322,15 +1318,13 @@ func TestServer(t *testing.T) { "--cache-dir", t.TempDir(), ) - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.Start(t, inv) // Wait until we see the prometheus address in the logs. addrMatchExpr := `http server listening\s+addr=(\S+)\s+name=prometheus` - lineMatch := pty.ExpectRegexMatchContext(ctx, addrMatchExpr) + lineMatch := stdout.ExpectRegexMatchContext(ctx, addrMatchExpr) promAddr := regexp.MustCompile(addrMatchExpr).FindStringSubmatch(lineMatch)[1] testutil.Eventually(ctx, t, func(ctx context.Context) bool { @@ -1751,7 +1745,6 @@ func TestServer(t *testing.T) { inv, cfg := clitest.New(t, args..., ) - ptytest.New(t).Attach(inv) inv = inv.WithContext(ctx) w := clitest.StartWithWaiter(t, inv) gotURL := waitAccessURL(t, cfg) @@ -2019,15 +2012,15 @@ func TestServer_Logging_NoParallel(t *testing.T) { "--provisioner-types=echo", "--log-stackdriver", fi, ) - // Attach pty so we get debug output from the command if this test + // Attach expecter so we get debug output from the command if this test // fails. - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) startIgnoringPostgresQueryCancel(t, inv.WithContext(ctx)) // Wait for server to listen on HTTP, this is a good // starting point for expecting logs. - _ = pty.ExpectMatchContext(ctx, "Started HTTP listener at") + _ = stdout.ExpectMatchContext(ctx, "Started HTTP listener at") loggingWaitFile(t, fi, testutil.WaitSuperLong) }) @@ -2056,15 +2049,15 @@ func TestServer_Logging_NoParallel(t *testing.T) { "--log-json", fi2, "--log-stackdriver", fi3, ) - // Attach pty so we get debug output from the command if this test + // Attach expecter so we get debug output from the command if this test // fails. - pty := ptytest.New(t).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) startIgnoringPostgresQueryCancel(t, inv) // Wait for server to listen on HTTP, this is a good // starting point for expecting logs. - _ = pty.ExpectMatchContext(ctx, "Started HTTP listener at") + _ = stdout.ExpectMatchContext(ctx, "Started HTTP listener at") loggingWaitFile(t, fi1, testutil.WaitSuperLong) loggingWaitFile(t, fi2, testutil.WaitSuperLong) @@ -2258,7 +2251,7 @@ func TestServer_GracefulShutdown(t *testing.T) { return ctx, stopFunc }) serverErr := make(chan error, 1) - pty := ptytest.New(t).Attach(root) + stdout := expecter.NewAttachedToInvocation(t, root) go func() { serverErr <- root.WithContext(ctx).Run() }() @@ -2266,7 +2259,7 @@ func TestServer_GracefulShutdown(t *testing.T) { // It's fair to assume `stopFunc` isn't nil here, because the server // has started and access URL is propagated. stopFunc() - pty.ExpectMatch("waiting for provisioner jobs to complete") + stdout.ExpectMatchContext(ctx, "waiting for provisioner jobs to complete") err := <-serverErr require.NoError(t, err) } @@ -2501,19 +2494,19 @@ func TestServer_TelemetryDisabled_FinalReport(t *testing.T) { inv.Logger = inv.Logger.Named(opts.name) errChan := make(chan error, 1) - pty := ptytest.New(t).Named(opts.name).Attach(inv) + stdout := expecter.NewAttachedToInvocation(t, inv) go func() { errChan <- inv.WithContext(ctx).Run() // close the pty here so that we can start tearing down resources. This test creates multiple servers with // associated ptys. There is a `t.Cleanup()` that does this, but it waits until the whole test is complete. - _ = pty.Close() + stdout.Close("invocation complete") }() if opts.waitForSnapshot { - pty.ExpectMatchContext(testutil.Context(t, testutil.WaitLong), "submitted snapshot") + stdout.ExpectMatchContext(testutil.Context(t, testutil.WaitLong), "submitted snapshot") } if opts.waitForTelemetryDisabledCheck { - pty.ExpectMatchContext(testutil.Context(t, testutil.WaitLong), "finished telemetry status check") + stdout.ExpectMatchContext(testutil.Context(t, testutil.WaitLong), "finished telemetry status check") } return errChan, cancelFunc }