mirror of https://github.com/coder/coder.git
fix(cli)!: protect client Logger and refactor cli scaletest tests (#8317)
- (breaking) Protects Logger and LogBodies fields of codersdk.Client with its mutex. This addresses a data race in cli/scaletest. - Fillets the existing cli/createworkspaces unit test and moves the testing logic there into the tests under scaletest/createworkspaces. - Adds testutil.RaceEnabled bool const and conditionaly skips previously-skipped tests under scaletest/ if the race detector is enabled. This is unfortunate and sad, but I would prefer to have these tests at least running without the race detector than not running at all. - Adds IgnoreErrors option to fake in-memory agent loggers; having the agents fail the test immediately when they encounter any sort of error isn't really helpful.
This commit is contained in:
parent
1d746b901b
commit
7fcf319e01
|
@ -167,7 +167,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
|
||||||
slog.F("version", version),
|
slog.F("version", version),
|
||||||
)
|
)
|
||||||
client := agentsdk.New(r.agentURL)
|
client := agentsdk.New(r.agentURL)
|
||||||
client.SDK.Logger = logger
|
client.SDK.SetLogger(logger)
|
||||||
// Set a reasonable timeout so requests can't hang forever!
|
// Set a reasonable timeout so requests can't hang forever!
|
||||||
// The timeout needs to be reasonably long, because requests
|
// The timeout needs to be reasonably long, because requests
|
||||||
// with large payloads can take a bit. e.g. startup scripts
|
// with large payloads can take a bit. e.g. startup scripts
|
||||||
|
|
|
@ -543,7 +543,7 @@ func (r *RootCmd) InitClient(client *codersdk.Client) clibase.MiddlewareFunc {
|
||||||
|
|
||||||
if r.debugHTTP {
|
if r.debugHTTP {
|
||||||
client.PlainLogger = os.Stderr
|
client.PlainLogger = os.Stderr
|
||||||
client.LogBodies = true
|
client.SetLogBodies(true)
|
||||||
}
|
}
|
||||||
client.DisableDirectConnections = r.disableDirect
|
client.DisableDirectConnections = r.disableDirect
|
||||||
|
|
||||||
|
|
|
@ -3,201 +3,53 @@ package cli_test
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/coder/coder/cli/clitest"
|
"github.com/coder/coder/cli/clitest"
|
||||||
"github.com/coder/coder/coderd/coderdtest"
|
"github.com/coder/coder/coderd/coderdtest"
|
||||||
"github.com/coder/coder/codersdk"
|
|
||||||
"github.com/coder/coder/pty/ptytest"
|
"github.com/coder/coder/pty/ptytest"
|
||||||
"github.com/coder/coder/scaletest/harness"
|
|
||||||
"github.com/coder/coder/testutil"
|
"github.com/coder/coder/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestScaleTestCreateWorkspaces(t *testing.T) {
|
func TestScaleTestCreateWorkspaces(t *testing.T) {
|
||||||
t.Skipf("This test is flakey. See https://github.com/coder/coder/issues/4942")
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// This test does a create-workspaces scale test with --no-cleanup, checks
|
// This test only validates that the CLI command accepts known arguments.
|
||||||
// that the created resources are OK, and then runs a cleanup.
|
// More thorough testing is done in scaletest/createworkspaces/run_test.go.
|
||||||
t.Run("WorkspaceBuildNoCleanup", func(t *testing.T) {
|
ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
t.Parallel()
|
defer cancelFunc()
|
||||||
|
|
||||||
ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.WaitLong)
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
defer cancelFunc()
|
_ = coderdtest.CreateFirstUser(t, client)
|
||||||
|
|
||||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
// Write a parameters file.
|
||||||
user := coderdtest.CreateFirstUser(t, client)
|
tDir := t.TempDir()
|
||||||
|
outputFile := filepath.Join(tDir, "output.json")
|
||||||
|
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
inv, root := clitest.New(t, "scaletest", "create-workspaces",
|
||||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
"--count", "2",
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
"--template", "doesnotexist",
|
||||||
|
"--no-cleanup",
|
||||||
|
"--no-wait-for-agents",
|
||||||
|
"--concurrency", "2",
|
||||||
|
"--timeout", "30s",
|
||||||
|
"--job-timeout", "15s",
|
||||||
|
"--cleanup-concurrency", "1",
|
||||||
|
"--cleanup-timeout", "30s",
|
||||||
|
"--cleanup-job-timeout", "15s",
|
||||||
|
"--output", "text",
|
||||||
|
"--output", "json:"+outputFile,
|
||||||
|
)
|
||||||
|
clitest.SetupConfig(t, client, root)
|
||||||
|
pty := ptytest.New(t)
|
||||||
|
inv.Stdout = pty.Output()
|
||||||
|
inv.Stderr = pty.Output()
|
||||||
|
|
||||||
// Write a parameters file.
|
err := inv.WithContext(ctx).Run()
|
||||||
tDir := t.TempDir()
|
require.ErrorContains(t, err, "could not find template \"doesnotexist\" in any organization")
|
||||||
paramsFile := filepath.Join(tDir, "params.yaml")
|
|
||||||
outputFile := filepath.Join(tDir, "output.json")
|
|
||||||
|
|
||||||
f, err := os.Create(paramsFile)
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer f.Close()
|
|
||||||
_, err = f.WriteString(`---
|
|
||||||
param1: foo
|
|
||||||
param2: true
|
|
||||||
param3: 1
|
|
||||||
`)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = f.Close()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
inv, root := clitest.New(t, "scaletest", "create-workspaces",
|
|
||||||
"--count", "2",
|
|
||||||
"--template", template.Name,
|
|
||||||
"--parameters-file", paramsFile,
|
|
||||||
"--parameter", "param1=bar",
|
|
||||||
"--parameter", "param4=baz",
|
|
||||||
"--no-cleanup",
|
|
||||||
// This flag is important for tests because agents will never be
|
|
||||||
// started.
|
|
||||||
"--no-wait-for-agents",
|
|
||||||
// Run and connect flags cannot be tested because they require an
|
|
||||||
// agent.
|
|
||||||
"--concurrency", "2",
|
|
||||||
"--timeout", "30s",
|
|
||||||
"--job-timeout", "15s",
|
|
||||||
"--cleanup-concurrency", "1",
|
|
||||||
"--cleanup-timeout", "30s",
|
|
||||||
"--cleanup-job-timeout", "15s",
|
|
||||||
"--output", "text",
|
|
||||||
"--output", "json:"+outputFile,
|
|
||||||
)
|
|
||||||
clitest.SetupConfig(t, client, root)
|
|
||||||
pty := ptytest.New(t)
|
|
||||||
inv.Stdout = pty.Output()
|
|
||||||
inv.Stderr = pty.Output()
|
|
||||||
|
|
||||||
done := make(chan any)
|
|
||||||
go func() {
|
|
||||||
err := inv.WithContext(ctx).Run()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
pty.ExpectMatch("Test results:")
|
|
||||||
pty.ExpectMatch("Pass: 2")
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
case <-ctx.Done():
|
|
||||||
}
|
|
||||||
cancelFunc()
|
|
||||||
<-done
|
|
||||||
|
|
||||||
// Recreate the context.
|
|
||||||
ctx, cancelFunc = context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
||||||
defer cancelFunc()
|
|
||||||
|
|
||||||
// Verify the output file.
|
|
||||||
f, err = os.Open(outputFile)
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer f.Close()
|
|
||||||
var res harness.Results
|
|
||||||
err = json.NewDecoder(f).Decode(&res)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.EqualValues(t, 2, res.TotalRuns)
|
|
||||||
require.EqualValues(t, 2, res.TotalPass)
|
|
||||||
|
|
||||||
// Find the workspaces and users and check that they are what we expect.
|
|
||||||
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
|
|
||||||
Offset: 0,
|
|
||||||
Limit: 100,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, workspaces.Workspaces, 2)
|
|
||||||
|
|
||||||
seenUsers := map[string]struct{}{}
|
|
||||||
for _, w := range workspaces.Workspaces {
|
|
||||||
// Sadly we can't verify params as the API doesn't seem to return
|
|
||||||
// them.
|
|
||||||
|
|
||||||
// Verify that the user is a unique scaletest user.
|
|
||||||
u, err := client.User(ctx, w.OwnerID.String())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, ok := seenUsers[u.ID.String()]
|
|
||||||
require.False(t, ok, "user has more than one workspace")
|
|
||||||
seenUsers[u.ID.String()] = struct{}{}
|
|
||||||
|
|
||||||
require.Contains(t, u.Username, "scaletest-")
|
|
||||||
require.Contains(t, u.Email, "scaletest")
|
|
||||||
}
|
|
||||||
|
|
||||||
require.Len(t, seenUsers, len(workspaces.Workspaces))
|
|
||||||
|
|
||||||
// Check that there are exactly 3 users.
|
|
||||||
users, err := client.Users(ctx, codersdk.UsersRequest{
|
|
||||||
Pagination: codersdk.Pagination{
|
|
||||||
Offset: 0,
|
|
||||||
Limit: 100,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, users.Users, len(seenUsers)+1)
|
|
||||||
|
|
||||||
// Cleanup.
|
|
||||||
inv, root = clitest.New(t, "scaletest", "cleanup",
|
|
||||||
"--cleanup-concurrency", "1",
|
|
||||||
"--cleanup-timeout", "30s",
|
|
||||||
"--cleanup-job-timeout", "15s",
|
|
||||||
)
|
|
||||||
clitest.SetupConfig(t, client, root)
|
|
||||||
pty = ptytest.New(t)
|
|
||||||
inv.Stdout = pty.Output()
|
|
||||||
inv.Stderr = pty.Output()
|
|
||||||
|
|
||||||
done = make(chan any)
|
|
||||||
go func() {
|
|
||||||
err := inv.WithContext(ctx).Run()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
pty.ExpectMatch("Test results:")
|
|
||||||
pty.ExpectMatch("Pass: 2")
|
|
||||||
pty.ExpectMatch("Test results:")
|
|
||||||
pty.ExpectMatch("Pass: 2")
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
case <-ctx.Done():
|
|
||||||
}
|
|
||||||
cancelFunc()
|
|
||||||
<-done
|
|
||||||
|
|
||||||
// Recreate the context (again).
|
|
||||||
ctx, cancelFunc = context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
||||||
defer cancelFunc()
|
|
||||||
|
|
||||||
// Verify that the workspaces are gone.
|
|
||||||
workspaces, err = client.Workspaces(ctx, codersdk.WorkspaceFilter{
|
|
||||||
Offset: 0,
|
|
||||||
Limit: 100,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, workspaces.Workspaces, 0)
|
|
||||||
|
|
||||||
// Verify that the users are gone.
|
|
||||||
users, err = client.Users(ctx, codersdk.UsersRequest{
|
|
||||||
Pagination: codersdk.Pagination{
|
|
||||||
Offset: 0,
|
|
||||||
Limit: 100,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, users.Users, 1)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test just validates that the CLI command accepts its known arguments.
|
// This test just validates that the CLI command accepts its known arguments.
|
||||||
|
|
|
@ -119,7 +119,7 @@ func (r *RootCmd) ssh() *clibase.Cmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
// log HTTP requests
|
// log HTTP requests
|
||||||
client.Logger = logger
|
client.SetLogger(logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
workspace, workspaceAgent, err := getWorkspaceAndAgent(ctx, inv, client, codersdk.Me, inv.Args[0])
|
workspace, workspaceAgent, err := getWorkspaceAndAgent(ctx, inv, client, codersdk.Me, inv.Args[0])
|
||||||
|
|
|
@ -49,7 +49,7 @@ func setupWorkspaceForAgent(t *testing.T, mutate func([]*proto.Agent) []*proto.A
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
client.Logger = slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug)
|
client.SetLogger(slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug))
|
||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
agentToken := uuid.NewString()
|
agentToken := uuid.NewString()
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/coder/coder/coderd/coderdtest"
|
"github.com/coder/coder/coderd/coderdtest"
|
||||||
|
@ -47,10 +48,13 @@ func TestDeploymentValues(t *testing.T) {
|
||||||
|
|
||||||
func TestDeploymentStats(t *testing.T) {
|
func TestDeploymentStats(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
t.Log("This test is time-sensitive. It may fail if the deployment is not ready in time.")
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
client := coderdtest.New(t, &coderdtest.Options{})
|
client := coderdtest.New(t, &coderdtest.Options{})
|
||||||
_ = coderdtest.CreateFirstUser(t, client)
|
_ = coderdtest.CreateFirstUser(t, client)
|
||||||
_, err := client.DeploymentStats(ctx)
|
assert.True(t, testutil.Eventually(ctx, t, func(tctx context.Context) bool {
|
||||||
require.NoError(t, err)
|
_, err := client.DeploymentStats(tctx)
|
||||||
|
return err == nil
|
||||||
|
}, testutil.IntervalMedium), "failed to get deployment stats in time")
|
||||||
}
|
}
|
||||||
|
|
|
@ -194,7 +194,7 @@ func (c *Client) Listen(ctx context.Context) (net.Conn, error) {
|
||||||
ticker := time.NewTicker(tick)
|
ticker := time.NewTicker(tick)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
defer func() {
|
defer func() {
|
||||||
c.SDK.Logger.Debug(ctx, "coordinate pinger exited")
|
c.SDK.Logger().Debug(ctx, "coordinate pinger exited")
|
||||||
}()
|
}()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
@ -205,18 +205,18 @@ func (c *Client) Listen(ctx context.Context) (net.Conn, error) {
|
||||||
|
|
||||||
err := conn.Ping(ctx)
|
err := conn.Ping(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.SDK.Logger.Error(ctx, "workspace agent coordinate ping", slog.Error(err))
|
c.SDK.Logger().Error(ctx, "workspace agent coordinate ping", slog.Error(err))
|
||||||
|
|
||||||
err := conn.Close(websocket.StatusGoingAway, "Ping failed")
|
err := conn.Close(websocket.StatusGoingAway, "Ping failed")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.SDK.Logger.Error(ctx, "close workspace agent coordinate websocket", slog.Error(err))
|
c.SDK.Logger().Error(ctx, "close workspace agent coordinate websocket", slog.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.SDK.Logger.Debug(ctx, "got coordinate pong", slog.F("took", time.Since(start)))
|
c.SDK.Logger().Debug(ctx, "got coordinate pong", slog.F("took", time.Since(start)))
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,8 +93,12 @@ func New(serverURL *url.URL) *Client {
|
||||||
// Client is an HTTP caller for methods to the Coder API.
|
// Client is an HTTP caller for methods to the Coder API.
|
||||||
// @typescript-ignore Client
|
// @typescript-ignore Client
|
||||||
type Client struct {
|
type Client struct {
|
||||||
mu sync.RWMutex // Protects following.
|
// mu protects the fields sessionToken, logger, and logBodies. These
|
||||||
|
// need to be safe for concurrent access.
|
||||||
|
mu sync.RWMutex
|
||||||
sessionToken string
|
sessionToken string
|
||||||
|
logger slog.Logger
|
||||||
|
logBodies bool
|
||||||
|
|
||||||
HTTPClient *http.Client
|
HTTPClient *http.Client
|
||||||
URL *url.URL
|
URL *url.URL
|
||||||
|
@ -103,13 +107,6 @@ type Client struct {
|
||||||
// default 'Coder-Session-Token' is used.
|
// default 'Coder-Session-Token' is used.
|
||||||
SessionTokenHeader string
|
SessionTokenHeader string
|
||||||
|
|
||||||
// Logger is optionally provided to log requests.
|
|
||||||
// Method, URL, and response code will be logged by default.
|
|
||||||
Logger slog.Logger
|
|
||||||
|
|
||||||
// LogBodies can be enabled to print request and response bodies to the logger.
|
|
||||||
LogBodies bool
|
|
||||||
|
|
||||||
// PlainLogger may be set to log HTTP traffic in a human-readable form.
|
// PlainLogger may be set to log HTTP traffic in a human-readable form.
|
||||||
// It uses the LogBodies option.
|
// It uses the LogBodies option.
|
||||||
PlainLogger io.Writer
|
PlainLogger io.Writer
|
||||||
|
@ -124,6 +121,34 @@ type Client struct {
|
||||||
DisableDirectConnections bool
|
DisableDirectConnections bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logger returns the logger for the client.
|
||||||
|
func (c *Client) Logger() slog.Logger {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
return c.logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger sets the logger for the client.
|
||||||
|
func (c *Client) SetLogger(logger slog.Logger) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.logger = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogBodies returns whether requests and response bodies are logged.
|
||||||
|
func (c *Client) LogBodies() bool {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
return c.logBodies
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogBodies sets whether to log request and response bodies.
|
||||||
|
func (c *Client) SetLogBodies(logBodies bool) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.logBodies = logBodies
|
||||||
|
}
|
||||||
|
|
||||||
// SessionToken returns the currently set token for the client.
|
// SessionToken returns the currently set token for the client.
|
||||||
func (c *Client) SessionToken() string {
|
func (c *Client) SessionToken() string {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
|
@ -181,7 +206,10 @@ func (c *Client) Request(ctx context.Context, method, path string, body interfac
|
||||||
|
|
||||||
// Copy the request body so we can log it.
|
// Copy the request body so we can log it.
|
||||||
var reqBody []byte
|
var reqBody []byte
|
||||||
if r != nil && c.LogBodies {
|
c.mu.RLock()
|
||||||
|
logBodies := c.logBodies
|
||||||
|
c.mu.RUnlock()
|
||||||
|
if r != nil && logBodies {
|
||||||
reqBody, err = io.ReadAll(r)
|
reqBody, err = io.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("read request body: %w", err)
|
return nil, xerrors.Errorf("read request body: %w", err)
|
||||||
|
@ -223,7 +251,7 @@ func (c *Client) Request(ctx context.Context, method, path string, body interfac
|
||||||
slog.F("url", req.URL.String()),
|
slog.F("url", req.URL.String()),
|
||||||
)
|
)
|
||||||
tracing.RunWithoutSpan(ctx, func(ctx context.Context) {
|
tracing.RunWithoutSpan(ctx, func(ctx context.Context) {
|
||||||
c.Logger.Debug(ctx, "sdk request", slog.F("body", string(reqBody)))
|
c.Logger().Debug(ctx, "sdk request", slog.F("body", string(reqBody)))
|
||||||
})
|
})
|
||||||
|
|
||||||
resp, err := c.HTTPClient.Do(req)
|
resp, err := c.HTTPClient.Do(req)
|
||||||
|
@ -231,7 +259,7 @@ func (c *Client) Request(ctx context.Context, method, path string, body interfac
|
||||||
// We log after sending the request because the HTTP Transport may modify
|
// We log after sending the request because the HTTP Transport may modify
|
||||||
// the request within Do, e.g. by adding headers.
|
// the request within Do, e.g. by adding headers.
|
||||||
if resp != nil && c.PlainLogger != nil {
|
if resp != nil && c.PlainLogger != nil {
|
||||||
out, err := httputil.DumpRequest(resp.Request, c.LogBodies)
|
out, err := httputil.DumpRequest(resp.Request, logBodies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("dump request: %w", err)
|
return nil, xerrors.Errorf("dump request: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -244,7 +272,7 @@ func (c *Client) Request(ctx context.Context, method, path string, body interfac
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.PlainLogger != nil {
|
if c.PlainLogger != nil {
|
||||||
out, err := httputil.DumpResponse(resp, c.LogBodies)
|
out, err := httputil.DumpResponse(resp, logBodies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("dump response: %w", err)
|
return nil, xerrors.Errorf("dump response: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -257,7 +285,7 @@ func (c *Client) Request(ctx context.Context, method, path string, body interfac
|
||||||
|
|
||||||
// Copy the response body so we can log it if it's a loggable mime type.
|
// Copy the response body so we can log it if it's a loggable mime type.
|
||||||
var respBody []byte
|
var respBody []byte
|
||||||
if resp.Body != nil && c.LogBodies {
|
if resp.Body != nil && logBodies {
|
||||||
mimeType := parseMimeType(resp.Header.Get("Content-Type"))
|
mimeType := parseMimeType(resp.Header.Get("Content-Type"))
|
||||||
if _, ok := loggableMimeTypes[mimeType]; ok {
|
if _, ok := loggableMimeTypes[mimeType]; ok {
|
||||||
respBody, err = io.ReadAll(resp.Body)
|
respBody, err = io.ReadAll(resp.Body)
|
||||||
|
@ -274,7 +302,7 @@ func (c *Client) Request(ctx context.Context, method, path string, body interfac
|
||||||
|
|
||||||
// See above for why this is not logged to the span.
|
// See above for why this is not logged to the span.
|
||||||
tracing.RunWithoutSpan(ctx, func(ctx context.Context) {
|
tracing.RunWithoutSpan(ctx, func(ctx context.Context) {
|
||||||
c.Logger.Debug(ctx, "sdk response",
|
c.Logger().Debug(ctx, "sdk response",
|
||||||
slog.F("status", resp.StatusCode),
|
slog.F("status", resp.StatusCode),
|
||||||
slog.F("body", string(respBody)),
|
slog.F("body", string(respBody)),
|
||||||
slog.F("trace_id", resp.Header.Get("X-Trace-Id")),
|
slog.F("trace_id", resp.Header.Get("X-Trace-Id")),
|
||||||
|
|
|
@ -114,8 +114,8 @@ func Test_Client(t *testing.T) {
|
||||||
client.SetSessionToken(token)
|
client.SetSessionToken(token)
|
||||||
|
|
||||||
logBuf := bytes.NewBuffer(nil)
|
logBuf := bytes.NewBuffer(nil)
|
||||||
client.Logger = slog.Make(sloghuman.Sink(logBuf)).Leveled(slog.LevelDebug)
|
client.SetLogger(slog.Make(sloghuman.Sink(logBuf)).Leveled(slog.LevelDebug))
|
||||||
client.LogBodies = true
|
client.SetLogBodies(true)
|
||||||
|
|
||||||
// Setup tracing.
|
// Setup tracing.
|
||||||
res := resource.NewWithAttributes(
|
res := resource.NewWithAttributes(
|
||||||
|
|
|
@ -8,6 +8,8 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/coder/coder/cli/clibase"
|
"github.com/coder/coder/cli/clibase"
|
||||||
"github.com/coder/coder/coderd/coderdtest"
|
"github.com/coder/coder/coderd/coderdtest"
|
||||||
"github.com/coder/coder/codersdk"
|
"github.com/coder/coder/codersdk"
|
||||||
|
@ -17,7 +19,6 @@ import (
|
||||||
"github.com/coder/coder/enterprise/coderd/license"
|
"github.com/coder/coder/enterprise/coderd/license"
|
||||||
"github.com/coder/coder/provisioner/echo"
|
"github.com/coder/coder/provisioner/echo"
|
||||||
"github.com/coder/coder/testutil"
|
"github.com/coder/coder/testutil"
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServiceBanners(t *testing.T) {
|
func TestServiceBanners(t *testing.T) {
|
||||||
|
|
|
@ -52,8 +52,8 @@ func (r *Runner) Run(ctx context.Context, _ string, w io.Writer) error {
|
||||||
logs := loadtestutil.NewSyncWriter(w)
|
logs := loadtestutil.NewSyncWriter(w)
|
||||||
defer logs.Close()
|
defer logs.Close()
|
||||||
logger := slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug)
|
logger := slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug)
|
||||||
r.client.Logger = logger
|
r.client.SetLogger(logger)
|
||||||
r.client.LogBodies = true
|
r.client.SetLogBodies(true)
|
||||||
|
|
||||||
_, _ = fmt.Fprintln(logs, "Opening connection to workspace agent")
|
_, _ = fmt.Fprintln(logs, "Opening connection to workspace agent")
|
||||||
switch r.cfg.ConnectionMode {
|
switch r.cfg.ConnectionMode {
|
||||||
|
|
|
@ -49,8 +49,8 @@ func (r *Runner) Run(ctx context.Context, id string, logs io.Writer) error {
|
||||||
|
|
||||||
logs = loadtestutil.NewSyncWriter(logs)
|
logs = loadtestutil.NewSyncWriter(logs)
|
||||||
logger := slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug)
|
logger := slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug)
|
||||||
r.client.Logger = logger
|
r.client.SetLogger(logger)
|
||||||
r.client.LogBodies = true
|
r.client.SetLogBodies(true)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
client = r.client
|
client = r.client
|
||||||
|
|
|
@ -3,6 +3,7 @@ package createworkspaces_test
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -28,7 +29,9 @@ import (
|
||||||
|
|
||||||
func Test_Runner(t *testing.T) {
|
func Test_Runner(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
t.Skip("Flake seen here: https://github.com/coder/coder/actions/runs/3436164958/jobs/5729513320")
|
if testutil.RaceEnabled() {
|
||||||
|
t.Skip("Race detector enabled, skipping time-sensitive test.")
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("OK", func(t *testing.T) {
|
t.Run("OK", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -79,43 +82,11 @@ func Test_Runner(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
version = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
||||||
|
|
||||||
// Since the runner creates the workspace on it's own, we have to keep
|
closer := goEventuallyStartFakeAgent(ctx, t, client, authToken)
|
||||||
// listing workspaces until we find it, then wait for the build to
|
t.Cleanup(closer)
|
||||||
// finish, then start the agents.
|
|
||||||
go func() {
|
|
||||||
var workspace codersdk.Workspace
|
|
||||||
for {
|
|
||||||
res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{})
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
workspaces := res.Workspaces
|
|
||||||
|
|
||||||
if len(workspaces) == 1 {
|
|
||||||
workspace = workspaces[0]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
||||||
|
|
||||||
agentClient := agentsdk.New(client.URL)
|
|
||||||
agentClient.SetSessionToken(authToken)
|
|
||||||
agentCloser := agent.New(agent.Options{
|
|
||||||
Client: agentClient,
|
|
||||||
Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelWarn),
|
|
||||||
})
|
|
||||||
t.Cleanup(func() {
|
|
||||||
_ = agentCloser.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
|
||||||
}()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
username = "scaletest-user"
|
username = "scaletest-user"
|
||||||
|
@ -185,7 +156,7 @@ func Test_Runner(t *testing.T) {
|
||||||
require.Len(t, workspaces.Workspaces, 0)
|
require.Len(t, workspaces.Workspaces, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("FailedBuild", func(t *testing.T) {
|
t.Run("NoCleanup", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
|
@ -196,6 +167,132 @@ func Test_Runner(t *testing.T) {
|
||||||
})
|
})
|
||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
|
||||||
|
authToken := uuid.NewString()
|
||||||
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
|
Parse: echo.ParseComplete,
|
||||||
|
ProvisionPlan: echo.ProvisionComplete,
|
||||||
|
ProvisionApply: []*proto.Provision_Response{
|
||||||
|
{
|
||||||
|
Type: &proto.Provision_Response_Log{
|
||||||
|
Log: &proto.Log{
|
||||||
|
Level: proto.LogLevel_INFO,
|
||||||
|
Output: "hello from logs",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: &proto.Provision_Response_Complete{
|
||||||
|
Complete: &proto.Provision_Complete{
|
||||||
|
Resources: []*proto.Resource{
|
||||||
|
{
|
||||||
|
Name: "example",
|
||||||
|
Type: "aws_instance",
|
||||||
|
Agents: []*proto.Agent{
|
||||||
|
{
|
||||||
|
Id: uuid.NewString(),
|
||||||
|
Name: "agent",
|
||||||
|
Auth: &proto.Agent_Token{
|
||||||
|
Token: authToken,
|
||||||
|
},
|
||||||
|
Apps: []*proto.App{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
version = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||||
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
|
|
||||||
|
closer := goEventuallyStartFakeAgent(ctx, t, client, authToken)
|
||||||
|
t.Cleanup(closer)
|
||||||
|
|
||||||
|
const (
|
||||||
|
username = "scaletest-user"
|
||||||
|
email = "scaletest@test.coder.com"
|
||||||
|
)
|
||||||
|
runner := createworkspaces.NewRunner(client, createworkspaces.Config{
|
||||||
|
NoCleanup: true,
|
||||||
|
User: createworkspaces.UserConfig{
|
||||||
|
OrganizationID: user.OrganizationID,
|
||||||
|
Username: username,
|
||||||
|
Email: email,
|
||||||
|
},
|
||||||
|
Workspace: workspacebuild.Config{
|
||||||
|
OrganizationID: user.OrganizationID,
|
||||||
|
Request: codersdk.CreateWorkspaceRequest{
|
||||||
|
TemplateID: template.ID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReconnectingPTY: &reconnectingpty.Config{
|
||||||
|
Init: codersdk.WorkspaceAgentReconnectingPTYInit{
|
||||||
|
Height: 24,
|
||||||
|
Width: 80,
|
||||||
|
Command: "echo hello",
|
||||||
|
},
|
||||||
|
Timeout: httpapi.Duration(testutil.WaitLong),
|
||||||
|
},
|
||||||
|
AgentConn: &agentconn.Config{
|
||||||
|
ConnectionMode: agentconn.ConnectionModeDerp,
|
||||||
|
HoldDuration: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
logs := bytes.NewBuffer(nil)
|
||||||
|
err := runner.Run(ctx, "1", logs)
|
||||||
|
logsStr := logs.String()
|
||||||
|
t.Log("Runner logs:\n\n" + logsStr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Ensure a user and workspace were created.
|
||||||
|
users, err := client.Users(ctx, codersdk.UsersRequest{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, users.Users, 2) // 1 user already exists
|
||||||
|
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, workspaces.Workspaces, 1)
|
||||||
|
|
||||||
|
// Look for strings in the logs.
|
||||||
|
require.Contains(t, logsStr, "Generating user password...")
|
||||||
|
require.Contains(t, logsStr, "Creating user:")
|
||||||
|
require.Contains(t, logsStr, "Org ID: "+user.OrganizationID.String())
|
||||||
|
require.Contains(t, logsStr, "Username: "+username)
|
||||||
|
require.Contains(t, logsStr, "Email: "+email)
|
||||||
|
require.Contains(t, logsStr, "Logging in as new user...")
|
||||||
|
require.Contains(t, logsStr, "Creating workspace...")
|
||||||
|
require.Contains(t, logsStr, `"agent" is connected`)
|
||||||
|
require.Contains(t, logsStr, "Opening reconnecting PTY connection to agent")
|
||||||
|
require.Contains(t, logsStr, "Opening connection to workspace agent")
|
||||||
|
|
||||||
|
err = runner.Cleanup(ctx, "1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Ensure the user and workspace were not deleted.
|
||||||
|
users, err = client.Users(ctx, codersdk.UsersRequest{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, users.Users, 2)
|
||||||
|
workspaces, err = client.Workspaces(ctx, codersdk.WorkspaceFilter{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, workspaces.Workspaces, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("FailedBuild", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
|
||||||
|
client := coderdtest.New(t, &coderdtest.Options{
|
||||||
|
IncludeProvisionerDaemon: true,
|
||||||
|
Logger: &logger,
|
||||||
|
})
|
||||||
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.ProvisionComplete,
|
ProvisionPlan: echo.ProvisionComplete,
|
||||||
|
@ -210,8 +307,8 @@ func Test_Runner(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
version = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
||||||
|
|
||||||
runner := createworkspaces.NewRunner(client, createworkspaces.Config{
|
runner := createworkspaces.NewRunner(client, createworkspaces.Config{
|
||||||
User: createworkspaces.UserConfig{
|
User: createworkspaces.UserConfig{
|
||||||
|
@ -235,3 +332,48 @@ func Test_Runner(t *testing.T) {
|
||||||
require.ErrorContains(t, err, "test error")
|
require.ErrorContains(t, err, "test error")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Since the runner creates the workspace on it's own, we have to keep
|
||||||
|
// listing workspaces until we find it, then wait for the build to
|
||||||
|
// finish, then start the agents. It is the caller's responsibility to
|
||||||
|
// call the returned function to stop the agents.
|
||||||
|
func goEventuallyStartFakeAgent(ctx context.Context, t *testing.T, client *codersdk.Client, agentToken string) func() {
|
||||||
|
t.Helper()
|
||||||
|
ch := make(chan io.Closer, 1) // Don't block.
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
var workspace codersdk.Workspace
|
||||||
|
for {
|
||||||
|
res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{})
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
workspaces := res.Workspaces
|
||||||
|
|
||||||
|
if len(workspaces) == 1 {
|
||||||
|
workspace = workspaces[0]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||||
|
|
||||||
|
agentClient := agentsdk.New(client.URL)
|
||||||
|
agentClient.SetSessionToken(agentToken)
|
||||||
|
agentCloser := agent.New(agent.Options{
|
||||||
|
Client: agentClient,
|
||||||
|
Logger: slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).
|
||||||
|
Named("agent").Leveled(slog.LevelWarn),
|
||||||
|
})
|
||||||
|
coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
||||||
|
ch <- agentCloser
|
||||||
|
}()
|
||||||
|
closeFunc := func() {
|
||||||
|
if closer, ok := <-ch; ok {
|
||||||
|
_ = closer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closeFunc
|
||||||
|
}
|
||||||
|
|
|
@ -40,8 +40,8 @@ func (r *Runner) Run(ctx context.Context, _ string, logs io.Writer) error {
|
||||||
|
|
||||||
logs = loadtestutil.NewSyncWriter(logs)
|
logs = loadtestutil.NewSyncWriter(logs)
|
||||||
logger := slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug)
|
logger := slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug)
|
||||||
r.client.Logger = logger
|
r.client.SetLogger(logger)
|
||||||
r.client.LogBodies = true
|
r.client.SetLogBodies(true)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
id = r.cfg.Init.ID
|
id = r.cfg.Init.ID
|
||||||
|
|
|
@ -45,8 +45,8 @@ func (r *Runner) Run(ctx context.Context, _ string, logs io.Writer) error {
|
||||||
|
|
||||||
logs = loadtestutil.NewSyncWriter(logs)
|
logs = loadtestutil.NewSyncWriter(logs)
|
||||||
logger := slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug)
|
logger := slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug)
|
||||||
r.client.Logger = logger
|
r.client.SetLogger(logger)
|
||||||
r.client.LogBodies = true
|
r.client.SetLogBodies(true)
|
||||||
|
|
||||||
req := r.cfg.Request
|
req := r.cfg.Request
|
||||||
if req.Name == "" {
|
if req.Name == "" {
|
||||||
|
|
|
@ -25,7 +25,9 @@ import (
|
||||||
|
|
||||||
func Test_Runner(t *testing.T) {
|
func Test_Runner(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
t.Skip("Flake seen here: https://github.com/coder/coder/actions/runs/3436164958/jobs/5729513320")
|
if testutil.RaceEnabled() {
|
||||||
|
t.Skip("Race detector enabled, skipping time-sensitive test.")
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("OK", func(t *testing.T) {
|
t.Run("OK", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -135,7 +137,7 @@ func Test_Runner(t *testing.T) {
|
||||||
agentClient.SetSessionToken(authToken)
|
agentClient.SetSessionToken(authToken)
|
||||||
agentCloser := agent.New(agent.Options{
|
agentCloser := agent.New(agent.Options{
|
||||||
Client: agentClient,
|
Client: agentClient,
|
||||||
Logger: slogtest.Make(t, nil).
|
Logger: slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).
|
||||||
Named(fmt.Sprintf("agent%d", i)).
|
Named(fmt.Sprintf("agent%d", i)).
|
||||||
Leveled(slog.LevelWarn),
|
Leveled(slog.LevelWarn),
|
||||||
})
|
})
|
||||||
|
@ -188,8 +190,10 @@ func Test_Runner(t *testing.T) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
|
||||||
client := coderdtest.New(t, &coderdtest.Options{
|
client := coderdtest.New(t, &coderdtest.Options{
|
||||||
IncludeProvisionerDaemon: true,
|
IncludeProvisionerDaemon: true,
|
||||||
|
Logger: &logger,
|
||||||
})
|
})
|
||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,8 @@ func (r *Runner) Run(ctx context.Context, _ string, logs io.Writer) error {
|
||||||
|
|
||||||
logs = loadtestutil.NewSyncWriter(logs)
|
logs = loadtestutil.NewSyncWriter(logs)
|
||||||
logger := slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug)
|
logger := slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug)
|
||||||
r.client.Logger = logger
|
r.client.SetLogger(logger)
|
||||||
r.client.LogBodies = true
|
r.client.SetLogBodies(true)
|
||||||
|
|
||||||
// Initialize our metrics eagerly. This is mainly so that we can test for the
|
// Initialize our metrics eagerly. This is mainly so that we can test for the
|
||||||
// presence of a zero-valued metric as opposed to the absence of a metric.
|
// presence of a zero-valued metric as opposed to the absence of a metric.
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package testutil
|
||||||
|
|
||||||
|
// RaceEnabled returns whether the race detector is enabled.
|
||||||
|
// This is a constant at compile time. It should be used to
|
||||||
|
// conditionally skip tests that are known to be sensitive to
|
||||||
|
// being run with the race detector enabled.
|
||||||
|
// Please use sparingly and as a last resort.
|
||||||
|
func RaceEnabled() bool {
|
||||||
|
return raceEnabled
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
//go:build !race
|
||||||
|
|
||||||
|
package testutil
|
||||||
|
|
||||||
|
const (
|
||||||
|
raceEnabled = false
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
//go:build race
|
||||||
|
|
||||||
|
package testutil
|
||||||
|
|
||||||
|
const (
|
||||||
|
raceEnabled = true
|
||||||
|
)
|
Loading…
Reference in New Issue