From 905e6dfab83812638b6b7de495e8667663a0ef63 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Tue, 2 Jun 2026 20:00:22 +0000 Subject: [PATCH] test: batch 07 of refactoring CLI tests not to use PTY --- cli/exp_scaletest_test.go | 37 ------- cli/externalauth_test.go | 21 ++-- cli/gitaskpass_test.go | 30 +++--- cli/gitssh_test.go | 6 -- cli/keyring_test.go | 102 ++++++++++---------- cli/logout_test.go | 63 ++++++------ cli/netcheck_test.go | 6 +- cli/open_test.go | 39 ++------ cli/ping_test.go | 23 ++--- cli/resetpassword_test.go | 12 +-- cli/root_test.go | 9 +- cli/server_createadminuser_test.go | 13 +-- cli/server_regenerate_vapid_keypair_test.go | 30 +++--- cli/speedtest_test.go | 4 - enterprise/cli/groupcreate_test.go | 9 +- enterprise/cli/groupdelete_test.go | 10 +- enterprise/cli/groupedit_test.go | 10 +- enterprise/cli/grouplist_test.go | 18 ++-- enterprise/cli/licenses_test.go | 46 ++++----- enterprise/cli/provisionerkeys_test.go | 29 +++--- enterprise/cli/server_dbcrypt_test.go | 17 ---- enterprise/cli/workspaceproxy_test.go | 16 ++- 22 files changed, 216 insertions(+), 334 deletions(-) diff --git a/cli/exp_scaletest_test.go b/cli/exp_scaletest_test.go index 942b104564..98d2071ad0 100644 --- a/cli/exp_scaletest_test.go +++ b/cli/exp_scaletest_test.go @@ -10,7 +10,6 @@ import ( "cdr.dev/slog/v3/sloggers/slogtest" "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" ) @@ -56,10 +55,6 @@ func TestScaleTestCreateWorkspaces(t *testing.T) { "--max-failures", "1", ) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() - err := inv.WithContext(ctx).Run() require.ErrorContains(t, err, "could not find template \"doesnotexist\" in any organization") } @@ -91,10 +86,6 @@ func TestScaleTestWorkspaceTraffic(t *testing.T) { "--ssh", ) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() - err := inv.WithContext(ctx).Run() require.ErrorContains(t, err, "no scaletest workspaces exist") } @@ -120,10 +111,6 @@ func TestScaleTestWorkspaceTraffic_Template(t *testing.T) { "--template", "doesnotexist", ) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() - err := inv.WithContext(ctx).Run() require.ErrorContains(t, err, "could not find template \"doesnotexist\" in any organization") } @@ -149,10 +136,6 @@ func TestScaleTestWorkspaceTraffic_TargetWorkspaces(t *testing.T) { "--target-workspaces", "0:0", ) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() - err := inv.WithContext(ctx).Run() require.ErrorContains(t, err, "invalid target workspaces \"0:0\": start and end cannot be equal") } @@ -178,10 +161,6 @@ func TestScaleTestCleanup_Template(t *testing.T) { "--template", "doesnotexist", ) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() - err := inv.WithContext(ctx).Run() require.ErrorContains(t, err, "could not find template \"doesnotexist\" in any organization") } @@ -208,10 +187,6 @@ func TestScaleTestDashboard(t *testing.T) { "--interval", "0s", ) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() - err := inv.WithContext(ctx).Run() require.ErrorContains(t, err, "--interval must be greater than zero") }) @@ -232,10 +207,6 @@ func TestScaleTestDashboard(t *testing.T) { "--jitter", "1s", ) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() - err := inv.WithContext(ctx).Run() require.ErrorContains(t, err, "--jitter must be less than --interval") }) @@ -260,10 +231,6 @@ func TestScaleTestDashboard(t *testing.T) { "--rand-seed", "1234567890", ) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() - err := inv.WithContext(ctx).Run() require.NoError(t, err, "") }) @@ -283,10 +250,6 @@ func TestScaleTestDashboard(t *testing.T) { "--target-users", "0:0", ) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() - err := inv.WithContext(ctx).Run() require.ErrorContains(t, err, "invalid target users \"0:0\": start and end cannot be equal") }) diff --git a/cli/externalauth_test.go b/cli/externalauth_test.go index c14b144a2e..65409b55d8 100644 --- a/cli/externalauth_test.go +++ b/cli/externalauth_test.go @@ -10,13 +10,15 @@ import ( "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/coderd/httpapi" "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" ) func TestExternalAuth(t *testing.T) { t.Parallel() t.Run("CanceledWithURL", func(t *testing.T) { t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { httpapi.Write(context.Background(), w, http.StatusOK, agentsdk.ExternalAuthResponse{ URL: "https://github.com", @@ -25,14 +27,14 @@ func TestExternalAuth(t *testing.T) { t.Cleanup(srv.Close) url := srv.URL inv, _ := clitest.New(t, "--agent-url", url, "--agent-token", "foo", "external-auth", "access-token", "github") - pty := ptytest.New(t) - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) waiter := clitest.StartWithWaiter(t, inv) - pty.ExpectMatch("https://github.com") + stdout.ExpectMatchContext(ctx, "https://github.com") waiter.RequireIs(cliui.ErrCanceled) }) t.Run("SuccessWithToken", func(t *testing.T) { t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { httpapi.Write(context.Background(), w, http.StatusOK, agentsdk.ExternalAuthResponse{ AccessToken: "bananas", @@ -41,10 +43,9 @@ func TestExternalAuth(t *testing.T) { t.Cleanup(srv.Close) url := srv.URL inv, _ := clitest.New(t, "--agent-url", url, "--agent-token", "foo", "external-auth", "access-token", "github") - pty := ptytest.New(t) - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.Start(t, inv) - pty.ExpectMatch("bananas") + stdout.ExpectMatchContext(ctx, "bananas") }) t.Run("NoArgs", func(t *testing.T) { t.Parallel() @@ -61,6 +62,7 @@ func TestExternalAuth(t *testing.T) { }) t.Run("SuccessWithExtra", func(t *testing.T) { t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { httpapi.Write(context.Background(), w, http.StatusOK, agentsdk.ExternalAuthResponse{ AccessToken: "bananas", @@ -72,9 +74,8 @@ func TestExternalAuth(t *testing.T) { t.Cleanup(srv.Close) url := srv.URL inv, _ := clitest.New(t, "--agent-url", url, "--agent-token", "foo", "external-auth", "access-token", "github", "--extra", "hey") - pty := ptytest.New(t) - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.Start(t, inv) - pty.ExpectMatch("there") + stdout.ExpectMatchContext(ctx, "there") }) } diff --git a/cli/gitaskpass_test.go b/cli/gitaskpass_test.go index 584e003427..f903194a6b 100644 --- a/cli/gitaskpass_test.go +++ b/cli/gitaskpass_test.go @@ -15,14 +15,15 @@ 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" ) func TestGitAskpass(t *testing.T) { t.Parallel() t.Run("UsernameAndPassword", func(t *testing.T) { t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { httpapi.Write(context.Background(), w, http.StatusOK, agentsdk.ExternalAuthResponse{ Username: "something", @@ -34,22 +35,21 @@ func TestGitAskpass(t *testing.T) { inv, _ := clitest.New(t, "--agent-url", url, "Username for 'https://github.com':") inv.Environ.Set("GIT_PREFIX", "/") inv.Environ.Set("CODER_AGENT_TOKEN", "fake-token") - pty := ptytest.New(t) - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.Start(t, inv) - pty.ExpectMatch("something") + stdout.ExpectMatchContext(ctx, "something") inv, _ = clitest.New(t, "--agent-url", url, "Password for 'https://potato@github.com':") inv.Environ.Set("GIT_PREFIX", "/") inv.Environ.Set("CODER_AGENT_TOKEN", "fake-token") - pty = ptytest.New(t) - inv.Stdout = pty.Output() + stdout = expecter.NewAttachedToInvocation(t, inv) clitest.Start(t, inv) - pty.ExpectMatch("bananas") + stdout.ExpectMatchContext(ctx, "bananas") }) t.Run("NoHost", func(t *testing.T) { t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { httpapi.Write(context.Background(), w, http.StatusNotFound, codersdk.Response{ Message: "Nope!", @@ -60,11 +60,10 @@ func TestGitAskpass(t *testing.T) { inv, _ := clitest.New(t, "--agent-url", url, "--no-open", "Username for 'https://github.com':") inv.Environ.Set("GIT_PREFIX", "/") inv.Environ.Set("CODER_AGENT_TOKEN", "fake-token") - pty := ptytest.New(t) - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) err := inv.Run() require.ErrorIs(t, err, cliui.ErrCanceled) - pty.ExpectMatch("Nope!") + stdout.ExpectMatchContext(ctx, "Nope!") }) t.Run("Poll", func(t *testing.T) { @@ -92,20 +91,19 @@ func TestGitAskpass(t *testing.T) { inv, _ := clitest.New(t, "--agent-url", url, "--no-open", "Username for 'https://github.com':") inv.Environ.Set("GIT_PREFIX", "/") inv.Environ.Set("CODER_AGENT_TOKEN", "fake-token") - stdout := ptytest.New(t) - inv.Stdout = stdout.Output() - stderr := ptytest.New(t) - inv.Stderr = stderr.Output() + var stdout, stderr *expecter.Expecter + stdout, inv.Stdout = expecter.NewPiped(t) + stderr, inv.Stderr = expecter.NewPiped(t) go func() { err := inv.Run() assert.NoError(t, err) }() testutil.RequireReceive(ctx, t, poll) - stderr.ExpectMatch("Open the following URL to authenticate") + stderr.ExpectMatchContext(ctx, "Open the following URL to authenticate") resp.Store(&agentsdk.ExternalAuthResponse{ Username: "username", Password: "password", }) - stdout.ExpectMatch("username") + stdout.ExpectMatchContext(ctx, "username") }) } diff --git a/cli/gitssh_test.go b/cli/gitssh_test.go index 0dd375b92d..7b6bb0206b 100644 --- a/cli/gitssh_test.go +++ b/cli/gitssh_test.go @@ -27,7 +27,6 @@ import ( "github.com/coder/coder/v2/coderd/database/dbfake" "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" ) @@ -194,7 +193,6 @@ func TestGitSSH(t *testing.T) { }, "\n")), 0o600) require.NoError(t, err) - pty := ptytest.New(t) cmdArgs := []string{ "gitssh", "--agent-url", client.SDK.URL.String(), @@ -205,8 +203,6 @@ func TestGitSSH(t *testing.T) { } // Test authentication via local private key. inv, _ := clitest.New(t, cmdArgs...) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() // This occasionally times out at 15s on Windows CI runners. Use a // longer timeout to reduce flakes. ctx := testutil.Context(t, testutil.WaitSuperLong) @@ -225,8 +221,6 @@ func TestGitSSH(t *testing.T) { // With the local file deleted, the coder key should be used. inv, _ = clitest.New(t, cmdArgs...) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() // This occasionally times out at 15s on Windows CI runners. Use a // longer timeout to reduce flakes. ctx = testutil.Context(t, testutil.WaitSuperLong) // Reset context for second cmd test. diff --git a/cli/keyring_test.go b/cli/keyring_test.go index 08f5db7c8d..b05d46ed81 100644 --- a/cli/keyring_test.go +++ b/cli/keyring_test.go @@ -17,7 +17,8 @@ import ( "github.com/coder/coder/v2/cli/sessionstore" "github.com/coder/coder/v2/cli/sessionstore/testhelpers" "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" "github.com/coder/serpent" ) @@ -66,13 +67,12 @@ func TestUseKeyring(t *testing.T) { t.Skip("keyring is not supported on this OS") } + logger := testutil.Logger(t) + ctx := testutil.Context(t, testutil.WaitMedium) // Create a test server client := coderdtest.New(t, nil) coderdtest.CreateFirstUser(t, client) - // Create a pty for interactive prompts - pty := ptytest.New(t) - // Create CLI invocation which defaults to using the keyring env := setupKeyringTestEnv(t, client.URL.String(), "login", @@ -80,8 +80,8 @@ func TestUseKeyring(t *testing.T) { "--no-open", client.URL.String()) inv := env.inv - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) // Run login in background doneChan := make(chan struct{}) @@ -92,9 +92,9 @@ func TestUseKeyring(t *testing.T) { }() // Provide the token when prompted - pty.ExpectMatch("Paste your token here:") - pty.WriteLine(client.SessionToken()) - pty.ExpectMatch("Welcome to Coder") + stdout.ExpectMatchContext(ctx, "Paste your token here:") + stdin.WriteLine(client.SessionToken()) + stdout.ExpectMatchContext(ctx, "Welcome to Coder") <-doneChan // Verify that session file was NOT created (using keyring instead) @@ -115,13 +115,12 @@ func TestUseKeyring(t *testing.T) { t.Skip("keyring is not supported on this OS") } + logger := testutil.Logger(t) + ctx := testutil.Context(t, testutil.WaitMedium) // Create a test server client := coderdtest.New(t, nil) coderdtest.CreateFirstUser(t, client) - // Create a pty for interactive prompts - pty := ptytest.New(t) - // First, login with the keyring (default) env := setupKeyringTestEnv(t, client.URL.String(), "login", @@ -130,8 +129,8 @@ func TestUseKeyring(t *testing.T) { client.URL.String(), ) loginInv := env.inv - loginInv.Stdin = pty.Input() - loginInv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, loginInv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), loginInv) doneChan := make(chan struct{}) go func() { @@ -140,9 +139,9 @@ func TestUseKeyring(t *testing.T) { assert.NoError(t, err) }() - pty.ExpectMatch("Paste your token here:") - pty.WriteLine(client.SessionToken()) - pty.ExpectMatch("Welcome to Coder") + stdout.ExpectMatchContext(ctx, "Paste your token here:") + stdin.WriteLine(client.SessionToken()) + stdout.ExpectMatchContext(ctx, "Welcome to Coder") <-doneChan // Verify credential exists in OS keyring @@ -181,13 +180,12 @@ func TestUseKeyring(t *testing.T) { t.Skip("file storage is the default for Linux") } + logger := testutil.Logger(t) + ctx := testutil.Context(t, testutil.WaitMedium) // Create a test server client := coderdtest.New(t, nil) coderdtest.CreateFirstUser(t, client) - // Create a pty for interactive prompts - pty := ptytest.New(t) - env := setupKeyringTestEnv(t, client.URL.String(), "login", "--force-tty", @@ -195,8 +193,8 @@ func TestUseKeyring(t *testing.T) { client.URL.String(), ) inv := env.inv - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) doneChan := make(chan struct{}) go func() { @@ -205,9 +203,9 @@ func TestUseKeyring(t *testing.T) { assert.NoError(t, err) }() - pty.ExpectMatch("Paste your token here:") - pty.WriteLine(client.SessionToken()) - pty.ExpectMatch("Welcome to Coder") + stdout.ExpectMatchContext(ctx, "Paste your token here:") + stdin.WriteLine(client.SessionToken()) + stdout.ExpectMatchContext(ctx, "Welcome to Coder") <-doneChan // Verify that session file WAS created (not using keyring) @@ -224,13 +222,12 @@ func TestUseKeyring(t *testing.T) { t.Run("EnvironmentVariable", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) + ctx := testutil.Context(t, testutil.WaitMedium) // Create a test server client := coderdtest.New(t, nil) coderdtest.CreateFirstUser(t, client) - // Create a pty for interactive prompts - pty := ptytest.New(t) - // Login using CODER_USE_KEYRING environment variable set to disable keyring usage, // which should have the same behavior on all platforms. env := setupKeyringTestEnv(t, client.URL.String(), @@ -240,8 +237,8 @@ func TestUseKeyring(t *testing.T) { client.URL.String(), ) inv := env.inv - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) inv.Environ.Set("CODER_USE_KEYRING", "false") doneChan := make(chan struct{}) @@ -251,9 +248,9 @@ func TestUseKeyring(t *testing.T) { assert.NoError(t, err) }() - pty.ExpectMatch("Paste your token here:") - pty.WriteLine(client.SessionToken()) - pty.ExpectMatch("Welcome to Coder") + stdout.ExpectMatchContext(ctx, "Paste your token here:") + stdin.WriteLine(client.SessionToken()) + stdout.ExpectMatchContext(ctx, "Welcome to Coder") <-doneChan // Verify that session file WAS created (not using keyring) @@ -270,9 +267,10 @@ func TestUseKeyring(t *testing.T) { t.Run("DisableKeyringWithFlag", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) + ctx := testutil.Context(t, testutil.WaitMedium) client := coderdtest.New(t, nil) coderdtest.CreateFirstUser(t, client) - pty := ptytest.New(t) // Login with --use-keyring=false to explicitly disable keyring usage, which // should have the same behavior on all platforms. @@ -284,8 +282,8 @@ func TestUseKeyring(t *testing.T) { client.URL.String(), ) inv := env.inv - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) doneChan := make(chan struct{}) go func() { @@ -294,9 +292,9 @@ func TestUseKeyring(t *testing.T) { assert.NoError(t, err) }() - pty.ExpectMatch("Paste your token here:") - pty.WriteLine(client.SessionToken()) - pty.ExpectMatch("Welcome to Coder") + stdout.ExpectMatchContext(ctx, "Paste your token here:") + stdin.WriteLine(client.SessionToken()) + stdout.ExpectMatchContext(ctx, "Welcome to Coder") <-doneChan // Verify that session file WAS created (not using keyring) @@ -324,9 +322,10 @@ func TestUseKeyringUnsupportedOS(t *testing.T) { t.Run("LoginWithDefaultKeyring", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) + ctx := testutil.Context(t, testutil.WaitMedium) client := coderdtest.New(t, nil) coderdtest.CreateFirstUser(t, client) - pty := ptytest.New(t) env := setupKeyringTestEnv(t, client.URL.String(), "login", @@ -335,8 +334,8 @@ func TestUseKeyringUnsupportedOS(t *testing.T) { client.URL.String(), ) inv := env.inv - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) doneChan := make(chan struct{}) go func() { @@ -345,9 +344,9 @@ func TestUseKeyringUnsupportedOS(t *testing.T) { assert.NoError(t, err) }() - pty.ExpectMatch("Paste your token here:") - pty.WriteLine(client.SessionToken()) - pty.ExpectMatch("Welcome to Coder") + stdout.ExpectMatchContext(ctx, "Paste your token here:") + stdin.WriteLine(client.SessionToken()) + stdout.ExpectMatchContext(ctx, "Welcome to Coder") <-doneChan // Verify that session file WAS created (automatic fallback to file storage) @@ -363,9 +362,10 @@ func TestUseKeyringUnsupportedOS(t *testing.T) { t.Run("LogoutWithDefaultKeyring", func(t *testing.T) { t.Parallel() + logger := testutil.Logger(t) + ctx := testutil.Context(t, testutil.WaitMedium) client := coderdtest.New(t, nil) coderdtest.CreateFirstUser(t, client) - pty := ptytest.New(t) // First login to create a session (will use file storage due to automatic fallback) env := setupKeyringTestEnv(t, client.URL.String(), @@ -375,8 +375,8 @@ func TestUseKeyringUnsupportedOS(t *testing.T) { client.URL.String(), ) loginInv := env.inv - loginInv.Stdin = pty.Input() - loginInv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, loginInv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), loginInv) doneChan := make(chan struct{}) go func() { @@ -385,9 +385,9 @@ func TestUseKeyringUnsupportedOS(t *testing.T) { assert.NoError(t, err) }() - pty.ExpectMatch("Paste your token here:") - pty.WriteLine(client.SessionToken()) - pty.ExpectMatch("Welcome to Coder") + stdout.ExpectMatchContext(ctx, "Paste your token here:") + stdin.WriteLine(client.SessionToken()) + stdout.ExpectMatchContext(ctx, "Welcome to Coder") <-doneChan // Verify session file exists diff --git a/cli/logout_test.go b/cli/logout_test.go index 9e7e95c68f..01d0fcd2cb 100644 --- a/cli/logout_test.go +++ b/cli/logout_test.go @@ -1,6 +1,7 @@ package cli_test import ( + "context" "fmt" "os" "runtime" @@ -12,7 +13,8 @@ import ( "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/cli/config" "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 TestLogout(t *testing.T) { @@ -20,8 +22,9 @@ func TestLogout(t *testing.T) { t.Run("Logout", func(t *testing.T) { t.Parallel() - pty := ptytest.New(t) - config := login(t, pty) + ctx := testutil.Context(t, testutil.WaitMedium) + logger := testutil.Logger(t) + config := login(ctx, t) // Ensure session files exist. require.FileExists(t, string(config.URL())) @@ -29,8 +32,8 @@ func TestLogout(t *testing.T) { logoutChan := make(chan struct{}) logout, _ := clitest.New(t, "logout", "--global-config", string(config)) - logout.Stdin = pty.Input() - logout.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, logout) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), logout) go func() { defer close(logoutChan) @@ -40,16 +43,16 @@ func TestLogout(t *testing.T) { assert.NoFileExists(t, string(config.Session())) }() - pty.ExpectMatch("Are you sure you want to log out?") - pty.WriteLine("yes") - pty.ExpectMatch("You are no longer logged in. You can log in using 'coder login '.") + stdout.ExpectMatchContext(ctx, "Are you sure you want to log out?") + stdin.WriteLine("yes") + stdout.ExpectMatchContext(ctx, "You are no longer logged in. You can log in using 'coder login '.") <-logoutChan }) t.Run("SkipPrompt", func(t *testing.T) { t.Parallel() - pty := ptytest.New(t) - config := login(t, pty) + ctx := testutil.Context(t, testutil.WaitMedium) + config := login(ctx, t) // Ensure session files exist. require.FileExists(t, string(config.URL())) @@ -57,8 +60,7 @@ func TestLogout(t *testing.T) { logoutChan := make(chan struct{}) logout, _ := clitest.New(t, "logout", "--global-config", string(config), "-y") - logout.Stdin = pty.Input() - logout.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, logout) go func() { defer close(logoutChan) @@ -68,14 +70,14 @@ func TestLogout(t *testing.T) { assert.NoFileExists(t, string(config.Session())) }() - pty.ExpectMatch("You are no longer logged in. You can log in using 'coder login '.") + stdout.ExpectMatchContext(ctx, "You are no longer logged in. You can log in using 'coder login '.") <-logoutChan }) t.Run("NoURLFile", func(t *testing.T) { t.Parallel() - pty := ptytest.New(t) - config := login(t, pty) + ctx := testutil.Context(t, testutil.WaitMedium) + config := login(ctx, t) // Ensure session files exist. require.FileExists(t, string(config.URL())) @@ -87,9 +89,6 @@ func TestLogout(t *testing.T) { logoutChan := make(chan struct{}) logout, _ := clitest.New(t, "logout", "--global-config", string(config)) - logout.Stdin = pty.Input() - logout.Stdout = pty.Output() - executable, err := os.Executable() require.NoError(t, err) require.NotEqual(t, "", executable) @@ -105,8 +104,9 @@ func TestLogout(t *testing.T) { t.Run("CannotDeleteFiles", func(t *testing.T) { t.Parallel() - pty := ptytest.New(t) - config := login(t, pty) + ctx := testutil.Context(t, testutil.WaitMedium) + logger := testutil.Logger(t) + config := login(ctx, t) // Ensure session files exist. require.FileExists(t, string(config.URL())) @@ -144,12 +144,12 @@ func TestLogout(t *testing.T) { logout, _ := clitest.New(t, "logout", "--global-config", string(config)) - logout.Stdin = pty.Input() - logout.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, logout) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), logout) go func() { - pty.ExpectMatch("Are you sure you want to log out?") - pty.WriteLine("yes") + stdout.ExpectMatchContext(ctx, "Are you sure you want to log out?") + stdin.WriteLine("yes") }() err = logout.Run() require.Error(t, err) @@ -166,26 +166,27 @@ func TestLogout(t *testing.T) { }) } -func login(t *testing.T, pty *ptytest.PTY) config.Root { +func login(ctx context.Context, t *testing.T) config.Root { t.Helper() + logger := testutil.Logger(t) client := coderdtest.New(t, nil) coderdtest.CreateFirstUser(t, client) doneChan := make(chan struct{}) root, cfg := clitest.New(t, "login", "--force-tty", client.URL.String(), "--no-open") - root.Stdin = pty.Input() - root.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, root) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), root) go func() { defer close(doneChan) err := root.Run() assert.NoError(t, err) }() - pty.ExpectMatch("Paste your token here:") - pty.WriteLine(client.SessionToken()) - pty.ExpectMatch("Welcome to Coder") - <-doneChan + stdout.ExpectMatchContext(ctx, "Paste your token here:") + stdin.WriteLine(client.SessionToken()) + stdout.ExpectMatchContext(ctx, "Welcome to Coder") + testutil.TryReceive(ctx, t, doneChan) return cfg } diff --git a/cli/netcheck_test.go b/cli/netcheck_test.go index bf124fc778..cf8e5a5499 100644 --- a/cli/netcheck_test.go +++ b/cli/netcheck_test.go @@ -9,14 +9,14 @@ import ( "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/codersdk/healthsdk" - "github.com/coder/coder/v2/pty/ptytest" + "github.com/coder/coder/v2/testutil" ) func TestNetcheck(t *testing.T) { t.Parallel() - pty := ptytest.New(t) - config := login(t, pty) + ctx := testutil.Context(t, testutil.WaitMedium) + config := login(ctx, t) var out bytes.Buffer inv, _ := clitest.New(t, "netcheck", "--global-config", string(config)) diff --git a/cli/open_test.go b/cli/open_test.go index 564fbe657a..60cfc27f44 100644 --- a/cli/open_test.go +++ b/cli/open_test.go @@ -24,8 +24,8 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/codersdk" "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 TestOpenVSCode(t *testing.T) { @@ -120,9 +120,8 @@ func TestOpenVSCode(t *testing.T) { inv, root := clitest.New(t, append([]string{"open", "vscode"}, tt.args...)...) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() + var stdout *expecter.Expecter + stdout, inv.Stdout = expecter.NewPiped(t) ctx := testutil.Context(t, testutil.WaitLong) inv = inv.WithContext(ctx) @@ -140,7 +139,7 @@ func TestOpenVSCode(t *testing.T) { me, err := client.User(ctx, codersdk.Me) require.NoError(t, err) - line := pty.ReadLine(ctx) + line := stdout.ReadLine(ctx) u, err := url.ParseRequestURI(line) require.NoError(t, err, "line: %q", line) @@ -246,9 +245,8 @@ func TestOpenVSCode_NoAgentDirectory(t *testing.T) { inv, root := clitest.New(t, append([]string{"open", "vscode"}, tt.args...)...) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() + var stdout *expecter.Expecter + stdout, inv.Stdout = expecter.NewPiped(t) ctx := testutil.Context(t, testutil.WaitLong) inv = inv.WithContext(ctx) @@ -266,7 +264,7 @@ func TestOpenVSCode_NoAgentDirectory(t *testing.T) { me, err := client.User(ctx, codersdk.Me) require.NoError(t, err) - line := pty.ReadLine(ctx) + line := stdout.ReadLine(ctx) u, err := url.ParseRequestURI(line) require.NoError(t, err, "line: %q", line) @@ -570,10 +568,8 @@ func TestOpenVSCodeDevContainer(t *testing.T) { inv, root := clitest.New(t, append([]string{"open", "vscode"}, tt.args...)...) clitest.SetupConfig(t, client, root) - - pty := ptytest.New(t) - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() + var stdout *expecter.Expecter + stdout, inv.Stdout = expecter.NewPiped(t) ctx := testutil.Context(t, testutil.WaitLong) inv = inv.WithContext(ctx) @@ -592,7 +588,7 @@ func TestOpenVSCodeDevContainer(t *testing.T) { me, err := client.User(ctx, codersdk.Me) require.NoError(t, err) - line := pty.ReadLine(ctx) + line := stdout.ReadLine(ctx) u, err := url.ParseRequestURI(line) require.NoError(t, err, "line: %q", line) @@ -640,9 +636,6 @@ func TestOpenApp(t *testing.T) { inv, root := clitest.New(t, "open", "app", ws.Name, "app1", "--test.open-error") clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() w := clitest.StartWithWaiter(t, inv) w.RequireError() @@ -671,9 +664,6 @@ func TestOpenApp(t *testing.T) { client, _, _ := setupWorkspaceForAgent(t) inv, root := clitest.New(t, "open", "app", "not-a-workspace", "app1") clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() w := clitest.StartWithWaiter(t, inv) w.RequireError() w.RequireContains("Resource not found or you do not have access to this resource") @@ -686,9 +676,6 @@ func TestOpenApp(t *testing.T) { inv, root := clitest.New(t, "open", "app", ws.Name, "app1") clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() w := clitest.StartWithWaiter(t, inv) w.RequireError() @@ -710,9 +697,6 @@ func TestOpenApp(t *testing.T) { inv, root := clitest.New(t, "open", "app", ws.Name, "app1", "--region", "bad-region") clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() w := clitest.StartWithWaiter(t, inv) w.RequireError() @@ -734,9 +718,6 @@ func TestOpenApp(t *testing.T) { }) inv, root := clitest.New(t, "open", "app", ws.Name, "app1", "--test.open-error") clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() w := clitest.StartWithWaiter(t, inv) w.RequireError() diff --git a/cli/ping_test.go b/cli/ping_test.go index ffdcee07f0..df532bda57 100644 --- a/cli/ping_test.go +++ b/cli/ping_test.go @@ -9,8 +9,8 @@ import ( "github.com/coder/coder/v2/agent/agenttest" "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 TestPing(t *testing.T) { @@ -22,10 +22,7 @@ func TestPing(t *testing.T) { client, workspace, agentToken := setupWorkspaceForAgent(t) inv, root := clitest.New(t, "ping", 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) _ = agenttest.New(t, client.URL, agentToken) _ = coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) @@ -38,7 +35,7 @@ func TestPing(t *testing.T) { assert.NoError(t, err) }) - pty.ExpectMatch("pong from " + workspace.Name) + stdout.ExpectMatchContext(ctx, "pong from "+workspace.Name) cancel() <-cmdDone }) @@ -49,10 +46,7 @@ func TestPing(t *testing.T) { client, workspace, agentToken := setupWorkspaceForAgent(t) inv, root := clitest.New(t, "ping", "-n", "1", 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) _ = agenttest.New(t, client.URL, agentToken) _ = coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) @@ -65,7 +59,7 @@ func TestPing(t *testing.T) { assert.NoError(t, err) }) - pty.ExpectMatch("pong from " + workspace.Name) + stdout.ExpectMatchContext(ctx, "pong from "+workspace.Name) cancel() <-cmdDone }) @@ -93,10 +87,7 @@ func TestPing(t *testing.T) { inv, root := clitest.New(t, args...) 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) _ = agenttest.New(t, client.URL, agentToken) _ = coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) @@ -119,7 +110,7 @@ func TestPing(t *testing.T) { rfc3339 += `(?:Z|[+-]\d{2}:\d{2})` } - pty.ExpectRegexMatch(`\[` + rfc3339 + `\] pong from ` + workspace.Name) + stdout.ExpectRegexMatchContext(ctx, `\[`+rfc3339+`\] pong from `+workspace.Name) cancel() <-cmdDone }) diff --git a/cli/resetpassword_test.go b/cli/resetpassword_test.go index de712874f3..e71b26248f 100644 --- a/cli/resetpassword_test.go +++ b/cli/resetpassword_test.go @@ -12,8 +12,8 @@ import ( "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/coderd/database/dbtestutil" "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 @@ -31,6 +31,7 @@ func TestResetPassword(t *testing.T) { const oldPassword = "MyOldPassword!" const newPassword = "MyNewPassword!" + logger := testutil.Logger(t) // start postgres and coder server processes connectionURL, err := dbtestutil.Open(t) require.NoError(t, err) @@ -69,9 +70,8 @@ func TestResetPassword(t *testing.T) { resetinv, cmdCfg := clitest.New(t, "reset-password", "--postgres-url", connectionURL, username) clitest.SetupConfig(t, client, cmdCfg) cmdDone := make(chan struct{}) - pty := ptytest.New(t) - resetinv.Stdin = pty.Input() - resetinv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, resetinv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), resetinv) go func() { defer close(cmdDone) err = resetinv.Run() @@ -86,8 +86,8 @@ func TestResetPassword(t *testing.T) { {"Confirm", newPassword}, } for _, match := range matches { - pty.ExpectMatch(match.output) - pty.WriteLine(match.input) + stdout.ExpectMatchContext(ctx, match.output) + stdin.WriteLine(match.input) } <-cmdDone diff --git a/cli/root_test.go b/cli/root_test.go index fefb87382c..5850a77da0 100644 --- a/cli/root_test.go +++ b/cli/root_test.go @@ -22,8 +22,8 @@ import ( "github.com/coder/coder/v2/coderd/coderdtest" "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/serpent" ) @@ -275,10 +275,7 @@ func TestDERPHeaders(t *testing.T) { } inv, root := clitest.New(t, args...) clitest.SetupConfig(t, member, root) - pty := ptytest.New(t) - inv.Stdin = pty.Input() - inv.Stderr = pty.Output() - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) ctx := testutil.Context(t, testutil.WaitLong) cmdDone := tGo(t, func() { @@ -286,7 +283,7 @@ func TestDERPHeaders(t *testing.T) { assert.NoError(t, err) }) - pty.ExpectMatch("pong from " + workspace.Name) + stdout.ExpectMatchContext(ctx, "pong from "+workspace.Name) <-cmdDone require.Greater(t, derpCalled.Load(), int64(0), "expected /derp to be called at least once") diff --git a/cli/server_createadminuser_test.go b/cli/server_createadminuser_test.go index d0eef5f72d..c458ab5dda 100644 --- a/cli/server_createadminuser_test.go +++ b/cli/server_createadminuser_test.go @@ -19,7 +19,6 @@ import ( "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/userpassword" "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" ) @@ -163,15 +162,13 @@ func TestServerCreateAdminUser(t *testing.T) { inv.Environ.Set("CODER_EMAIL", email) inv.Environ.Set("CODER_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, "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_regenerate_vapid_keypair_test.go b/cli/server_regenerate_vapid_keypair_test.go index 6c9603e009..a52978173a 100644 --- a/cli/server_regenerate_vapid_keypair_test.go +++ b/cli/server_regenerate_vapid_keypair_test.go @@ -11,8 +11,8 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbgen" "github.com/coder/coder/v2/coderd/database/dbtestutil" - "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" ) func TestRegenerateVapidKeypair(t *testing.T) { @@ -39,16 +39,14 @@ func TestRegenerateVapidKeypair(t *testing.T) { inv, _ := clitest.New(t, "server", "regenerate-vapid-keypair", "--postgres-url", connectionURL, "--yes") - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.Start(t, inv) - pty.ExpectMatchContext(ctx, "Regenerating VAPID keypair...") - pty.ExpectMatchContext(ctx, "This will delete all existing webpush subscriptions.") - pty.ExpectMatchContext(ctx, "Are you sure you want to continue? (y/N)") - pty.WriteLine("y") - pty.ExpectMatchContext(ctx, "VAPID keypair regenerated successfully.") + stdout.ExpectMatchContext(ctx, "Regenerating VAPID keypair...") + stdout.ExpectMatchContext(ctx, "This will delete all existing webpush subscriptions.") + stdout.ExpectMatchContext(ctx, "Are you sure you want to continue? (y/N)") + // don't need to write to stdin because we passed --yes + stdout.ExpectMatchContext(ctx, "VAPID keypair regenerated successfully.") // Ensure the VAPID keypair was created. keys, err := db.GetWebpushVAPIDKeys(ctx) @@ -84,16 +82,14 @@ func TestRegenerateVapidKeypair(t *testing.T) { inv, _ := clitest.New(t, "server", "regenerate-vapid-keypair", "--postgres-url", connectionURL, "--yes") - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.Start(t, inv) - pty.ExpectMatchContext(ctx, "Regenerating VAPID keypair...") - pty.ExpectMatchContext(ctx, "This will delete all existing webpush subscriptions.") - pty.ExpectMatchContext(ctx, "Are you sure you want to continue? (y/N)") - pty.WriteLine("y") - pty.ExpectMatchContext(ctx, "VAPID keypair regenerated successfully.") + stdout.ExpectMatchContext(ctx, "Regenerating VAPID keypair...") + stdout.ExpectMatchContext(ctx, "This will delete all existing webpush subscriptions.") + stdout.ExpectMatchContext(ctx, "Are you sure you want to continue? (y/N)") + // don't need to write to stdin because we passed --yes + stdout.ExpectMatchContext(ctx, "VAPID keypair regenerated successfully.") // Ensure the VAPID keypair was created. keys, err := db.GetWebpushVAPIDKeys(ctx) diff --git a/cli/speedtest_test.go b/cli/speedtest_test.go index 71e9d0c508..cc0689d4b5 100644 --- a/cli/speedtest_test.go +++ b/cli/speedtest_test.go @@ -14,7 +14,6 @@ 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" ) @@ -43,9 +42,6 @@ func TestSpeedtest(t *testing.T) { inv, root := clitest.New(t, "speedtest", workspace.Name) clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() diff --git a/enterprise/cli/groupcreate_test.go b/enterprise/cli/groupcreate_test.go index 95807a3663..84adaca77a 100644 --- a/enterprise/cli/groupcreate_test.go +++ b/enterprise/cli/groupcreate_test.go @@ -13,7 +13,8 @@ import ( "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" "github.com/coder/coder/v2/enterprise/coderd/license" - "github.com/coder/coder/v2/pty/ptytest" + "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" "github.com/coder/pretty" ) @@ -40,13 +41,13 @@ func TestCreateGroup(t *testing.T) { "--avatar-url", avatarURL, ) - pty := ptytest.New(t) - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.SetupConfig(t, anotherClient, conf) + ctx := testutil.Context(t, testutil.WaitMedium) err := inv.Run() require.NoError(t, err) - pty.ExpectMatch(fmt.Sprintf("Successfully created group %s!", pretty.Sprint(cliui.DefaultStyles.Keyword, groupName))) + stdout.ExpectMatchContext(ctx, fmt.Sprintf("Successfully created group %s!", pretty.Sprint(cliui.DefaultStyles.Keyword, groupName))) }) } diff --git a/enterprise/cli/groupdelete_test.go b/enterprise/cli/groupdelete_test.go index c812751315..ef5670bea8 100644 --- a/enterprise/cli/groupdelete_test.go +++ b/enterprise/cli/groupdelete_test.go @@ -13,7 +13,8 @@ import ( "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" "github.com/coder/coder/v2/enterprise/coderd/license" - "github.com/coder/coder/v2/pty/ptytest" + "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" "github.com/coder/pretty" ) @@ -36,15 +37,14 @@ func TestGroupDelete(t *testing.T) { "groups", "delete", group.Name, ) - pty := ptytest.New(t) - - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitMedium) clitest.SetupConfig(t, anotherClient, conf) err := inv.Run() require.NoError(t, err) - pty.ExpectMatch(fmt.Sprintf("Successfully deleted group %s", pretty.Sprint(cliui.DefaultStyles.Keyword, group.Name))) + stdout.ExpectMatchContext(ctx, fmt.Sprintf("Successfully deleted group %s", pretty.Sprint(cliui.DefaultStyles.Keyword, group.Name))) }) t.Run("NoArg", func(t *testing.T) { diff --git a/enterprise/cli/groupedit_test.go b/enterprise/cli/groupedit_test.go index 2d5c2b3673..defcd0f5b9 100644 --- a/enterprise/cli/groupedit_test.go +++ b/enterprise/cli/groupedit_test.go @@ -13,7 +13,8 @@ import ( "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" "github.com/coder/coder/v2/enterprise/coderd/license" - "github.com/coder/coder/v2/pty/ptytest" + "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" "github.com/coder/pretty" ) @@ -48,15 +49,14 @@ func TestGroupEdit(t *testing.T) { "-r", user3.ID.String(), ) - pty := ptytest.New(t) - - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.SetupConfig(t, anotherClient, conf) + ctx := testutil.Context(t, testutil.WaitMedium) err := inv.Run() require.NoError(t, err) - pty.ExpectMatch(fmt.Sprintf("Successfully patched group %s", pretty.Sprint(cliui.DefaultStyles.Keyword, expectedName))) + stdout.ExpectMatchContext(ctx, fmt.Sprintf("Successfully patched group %s", pretty.Sprint(cliui.DefaultStyles.Keyword, expectedName))) }) t.Run("InvalidUserInput", func(t *testing.T) { diff --git a/enterprise/cli/grouplist_test.go b/enterprise/cli/grouplist_test.go index 87cf80c6c2..a92f9e99d2 100644 --- a/enterprise/cli/grouplist_test.go +++ b/enterprise/cli/grouplist_test.go @@ -14,7 +14,8 @@ import ( "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" "github.com/coder/coder/v2/enterprise/coderd/license" - "github.com/coder/coder/v2/pty/ptytest" + "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" ) func TestGroupList(t *testing.T) { @@ -41,11 +42,9 @@ func TestGroupList(t *testing.T) { inv, conf := newCLI(t, "groups", "list") - pty := ptytest.New(t) - - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.SetupConfig(t, anotherClient, conf) - + ctx := testutil.Context(t, testutil.WaitMedium) err := inv.Run() require.NoError(t, err) @@ -56,7 +55,7 @@ func TestGroupList(t *testing.T) { } for _, match := range matches { - pty.ExpectMatch(match) + stdout.ExpectMatchContext(ctx, match) } }) @@ -72,9 +71,8 @@ func TestGroupList(t *testing.T) { inv, conf := newCLI(t, "groups", "list") - pty := ptytest.New(t) - - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) + ctx := testutil.Context(t, testutil.WaitMedium) clitest.SetupConfig(t, anotherClient, conf) err := inv.Run() @@ -86,7 +84,7 @@ func TestGroupList(t *testing.T) { } for _, match := range matches { - pty.ExpectMatch(match) + stdout.ExpectMatchContext(ctx, match) } }) diff --git a/enterprise/cli/licenses_test.go b/enterprise/cli/licenses_test.go index bc726c55d5..b3b2199dd0 100644 --- a/enterprise/cli/licenses_test.go +++ b/enterprise/cli/licenses_test.go @@ -20,8 +20,8 @@ import ( "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" - "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" "github.com/coder/serpent" ) @@ -37,41 +37,42 @@ func TestLicensesAddFake(t *testing.T) { t.Run("LFlag", func(t *testing.T) { t.Parallel() inv := setupFakeLicenseServerTest(t, "licenses", "add", "-l", fakeLicenseJWT) - pty := attachPty(t, inv) + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.Start(t, inv) - pty.ExpectMatch("License with ID 1 added") + ctx := testutil.Context(t, testutil.WaitMedium) + stdout.ExpectMatchContext(ctx, "License with ID 1 added") }) t.Run("Prompt", func(t *testing.T) { t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() + logger := testutil.Logger(t) + ctx := testutil.Context(t, testutil.WaitLong) inv := setupFakeLicenseServerTest(t, "license", "add") - pty := attachPty(t, inv) + stdout := expecter.NewAttachedToInvocation(t, inv) + stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv) errC := make(chan error) go func() { errC <- inv.WithContext(ctx).Run() }() - pty.ExpectMatch("Paste license:") - pty.WriteLine(fakeLicenseJWT) + stdout.ExpectMatchContext(ctx, "Paste license:") + stdin.WriteLine(fakeLicenseJWT) require.NoError(t, <-errC) - pty.ExpectMatch("License with ID 1 added") + stdout.ExpectMatchContext(ctx, "License with ID 1 added") }) t.Run("File", func(t *testing.T) { t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() + ctx := testutil.Context(t, testutil.WaitLong) dir := t.TempDir() filename := filepath.Join(dir, "license.jwt") err := os.WriteFile(filename, []byte(fakeLicenseJWT), 0o600) require.NoError(t, err) inv := setupFakeLicenseServerTest(t, "license", "add", "-f", filename) - pty := attachPty(t, inv) + stdout := expecter.NewAttachedToInvocation(t, inv) errC := make(chan error) go func() { errC <- inv.WithContext(ctx).Run() }() require.NoError(t, <-errC) - pty.ExpectMatch("License with ID 1 added") + stdout.ExpectMatchContext(ctx, "License with ID 1 added") }) t.Run("StdIn", func(t *testing.T) { t.Parallel() @@ -100,16 +101,15 @@ func TestLicensesAddFake(t *testing.T) { }) t.Run("DebugOutput", func(t *testing.T) { t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() + ctx := testutil.Context(t, testutil.WaitLong) inv := setupFakeLicenseServerTest(t, "licenses", "add", "-l", fakeLicenseJWT, "--debug") - pty := attachPty(t, inv) + stdout := expecter.NewAttachedToInvocation(t, inv) errC := make(chan error) go func() { errC <- inv.WithContext(ctx).Run() }() require.NoError(t, <-errC) - pty.ExpectMatch("\"f2\": 2") + stdout.ExpectMatchContext(ctx, "\"f2\": 2") }) } @@ -201,10 +201,11 @@ func TestLicensesDeleteFake(t *testing.T) { t.Parallel() inv := setupFakeLicenseServerTest(t, "licenses", "delete", "55") - pty := attachPty(t, inv) + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.Start(t, inv) - pty.ExpectMatch("License with ID 55 deleted") + ctx := testutil.Context(t, testutil.WaitMedium) + stdout.ExpectMatchContext(ctx, "License with ID 55 deleted") }) } @@ -240,13 +241,6 @@ func setupFakeLicenseServerTest(t *testing.T, args ...string) *serpent.Invocatio return inv } -func attachPty(t *testing.T, inv *serpent.Invocation) *ptytest.PTY { - pty := ptytest.New(t) - inv.Stdin = pty.Input() - inv.Stdout = pty.Output() - return pty -} - func newFakeLicenseAPI(t *testing.T) http.Handler { r := chi.NewRouter() a := &fakeLicenseAPI{t: t, r: r} diff --git a/enterprise/cli/provisionerkeys_test.go b/enterprise/cli/provisionerkeys_test.go index 53ee012fea..c2d120a5c4 100644 --- a/enterprise/cli/provisionerkeys_test.go +++ b/enterprise/cli/provisionerkeys_test.go @@ -13,8 +13,8 @@ import ( "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" "github.com/coder/coder/v2/enterprise/coderd/license" - "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" ) func TestProvisionerKeys(t *testing.T) { @@ -39,19 +39,18 @@ func TestProvisionerKeys(t *testing.T) { "provisioner", "keys", "create", name, "--tag", "foo=bar", "--tag", "my=way", ) - pty := ptytest.New(t) - inv.Stdout = pty.Output() + stdout := expecter.NewAttachedToInvocation(t, inv) clitest.SetupConfig(t, orgAdminClient, conf) err := inv.WithContext(ctx).Run() require.NoError(t, err) - line := pty.ReadLine(ctx) + line := stdout.ReadLine(ctx) require.Contains(t, line, "Successfully created provisioner key") require.Contains(t, line, strings.ToLower(name)) // empty line - _ = pty.ReadLine(ctx) - key := pty.ReadLine(ctx) + _ = stdout.ReadLine(ctx) + key := stdout.ReadLine(ctx) require.NotEmpty(t, key) require.NoError(t, provisionerkey.Validate(key)) @@ -59,17 +58,16 @@ func TestProvisionerKeys(t *testing.T) { t, "provisioner", "keys", "ls", ) - pty = ptytest.New(t) - inv.Stdout = pty.Output() + stdout = expecter.NewAttachedToInvocation(t, inv) clitest.SetupConfig(t, orgAdminClient, conf) err = inv.WithContext(ctx).Run() require.NoError(t, err) - line = pty.ReadLine(ctx) + line = stdout.ReadLine(ctx) require.Contains(t, line, "NAME") require.Contains(t, line, "CREATED AT") require.Contains(t, line, "TAGS") - line = pty.ReadLine(ctx) + line = stdout.ReadLine(ctx) require.Contains(t, line, strings.ToLower(name)) require.Contains(t, line, "foo=bar my=way") @@ -78,13 +76,12 @@ func TestProvisionerKeys(t *testing.T) { "provisioner", "keys", "delete", "-y", name, ) - pty = ptytest.New(t) - inv.Stdout = pty.Output() + stdout = expecter.NewAttachedToInvocation(t, inv) clitest.SetupConfig(t, orgAdminClient, conf) err = inv.WithContext(ctx).Run() require.NoError(t, err) - line = pty.ReadLine(ctx) + line = stdout.ReadLine(ctx) require.Contains(t, line, "Successfully deleted provisioner key") require.Contains(t, line, strings.ToLower(name)) @@ -92,14 +89,12 @@ func TestProvisionerKeys(t *testing.T) { t, "provisioner", "keys", "ls", ) - pty = ptytest.New(t) - inv.Stdout = pty.Output() - inv.Stderr = pty.Output() + stdout = expecter.NewAttachedToInvocation(t, inv) clitest.SetupConfig(t, orgAdminClient, conf) err = inv.WithContext(ctx).Run() require.NoError(t, err) - line = pty.ReadLine(ctx) + line = stdout.ReadLine(ctx) require.Contains(t, line, "No provisioner keys found") }) } diff --git a/enterprise/cli/server_dbcrypt_test.go b/enterprise/cli/server_dbcrypt_test.go index 3cafb4ce38..d13e1a877f 100644 --- a/enterprise/cli/server_dbcrypt_test.go +++ b/enterprise/cli/server_dbcrypt_test.go @@ -18,7 +18,6 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/enterprise/cli" "github.com/coder/coder/v2/enterprise/dbcrypt" - "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" ) @@ -72,11 +71,8 @@ func TestServerDBCrypt(t *testing.T) { "--new-key", base64.StdEncoding.EncodeToString([]byte(keyA)), "--yes", ) - pty := ptytest.New(t) - inv.Stdout = pty.Output() err = inv.Run() require.NoError(t, err) - require.NoError(t, pty.Close()) // Validate that all existing data has been encrypted with cipher A. for _, usr := range users { @@ -95,11 +91,8 @@ func TestServerDBCrypt(t *testing.T) { "--old-keys", base64.StdEncoding.EncodeToString([]byte(keyA)), "--yes", ) - pty = ptytest.New(t) - inv.Stdout = pty.Output() err = inv.Run() require.NoError(t, err) - require.NoError(t, pty.Close()) // Validate that all data has been re-encrypted with cipher B. for _, usr := range users { @@ -137,11 +130,8 @@ func TestServerDBCrypt(t *testing.T) { "--keys", base64.StdEncoding.EncodeToString([]byte(keyB)), "--yes", ) - pty = ptytest.New(t) - inv.Stdout = pty.Output() err = inv.Run() require.NoError(t, err) - require.NoError(t, pty.Close()) // Validate that both keys have been revoked. keys, err = db.GetDBCryptKeys(ctx) @@ -167,12 +157,8 @@ func TestServerDBCrypt(t *testing.T) { "--new-key", base64.StdEncoding.EncodeToString([]byte(keyC)), "--yes", ) - - pty = ptytest.New(t) - inv.Stdout = pty.Output() err = inv.Run() require.NoError(t, err) - require.NoError(t, pty.Close()) // Validate that all data has been re-encrypted with cipher C. for _, usr := range users { @@ -186,11 +172,8 @@ func TestServerDBCrypt(t *testing.T) { "--external-token-encryption-keys", base64.StdEncoding.EncodeToString([]byte(keyC)), "--yes", ) - pty = ptytest.New(t) - inv.Stdout = pty.Output() err = inv.Run() require.NoError(t, err) - require.NoError(t, pty.Close()) // Assert that no user links remain. for _, usr := range users { diff --git a/enterprise/cli/workspaceproxy_test.go b/enterprise/cli/workspaceproxy_test.go index cc0155356e..ea84ab6224 100644 --- a/enterprise/cli/workspaceproxy_test.go +++ b/enterprise/cli/workspaceproxy_test.go @@ -11,8 +11,8 @@ import ( "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" "github.com/coder/coder/v2/enterprise/coderd/license" - "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" + "github.com/coder/coder/v2/testutil/expecter" ) func Test_ProxyCRUD(t *testing.T) { @@ -40,14 +40,14 @@ func Test_ProxyCRUD(t *testing.T) { "--only-token", ) - pty := ptytest.New(t) - inv.Stdout = pty.Output() + var stdout *expecter.Expecter + stdout, inv.Stdout = expecter.NewPiped(t) clitest.SetupConfig(t, client, conf) //nolint:gocritic // create wsproxy requires owner err := inv.WithContext(ctx).Run() require.NoError(t, err) - line := pty.ReadLine(ctx) + line := stdout.ReadLine(ctx) parts := strings.Split(line, ":") require.Len(t, parts, 2, "expected 2 parts") _, err = uuid.Parse(parts[0]) @@ -59,13 +59,12 @@ func Test_ProxyCRUD(t *testing.T) { "wsproxy", "ls", ) - pty = ptytest.New(t) - inv.Stdout = pty.Output() + stdout, inv.Stdout = expecter.NewPiped(t) clitest.SetupConfig(t, client, conf) //nolint:gocritic // requires owner err = inv.WithContext(ctx).Run() require.NoError(t, err) - pty.ExpectMatch(expectedName) + stdout.ExpectMatchContext(ctx, expectedName) // Also check via the api proxies, err := client.WorkspaceProxies(ctx) //nolint:gocritic // requires owner @@ -104,9 +103,6 @@ func Test_ProxyCRUD(t *testing.T) { t, "wsproxy", "delete", "-y", expectedName, ) - - pty := ptytest.New(t) - inv.Stdout = pty.Output() clitest.SetupConfig(t, client, conf) //nolint:gocritic // requires owner err = inv.WithContext(ctx).Run()