diff --git a/cli/exp_scaletest_createusers.go b/cli/exp_scaletest_createusers.go index 2f5468bf46d..1f3268dda31 100644 --- a/cli/exp_scaletest_createusers.go +++ b/cli/exp_scaletest_createusers.go @@ -23,6 +23,7 @@ func (r *RootCmd) scaletestCreateUsers() *serpent.Command { var ( count int64 templateAdminPercentage float64 + usernameInfix string noCleanup bool tracingFlags = &scaletestTracingFlags{} @@ -93,6 +94,15 @@ func (r *RootCmd) scaletestCreateUsers() *serpent.Command { th := harness.NewTestHarness(strategy.toStrategy(), cleanupStrategy.toStrategy()) + // The scaletest- root is always kept so the cleanup command still + // finds these users; --username-infix is inserted between it and the + // random suffix, e.g. --username-infix asdf produces usernames like + // scaletest-asdf--. + userPrefix := loadtestutil.ScaleTestPrefix + "-" + if usernameInfix != "" { + userPrefix += usernameInfix + "-" + } + _, _ = fmt.Fprintln(inv.Stderr, "Creating users...") for i := range count { id := strconv.FormatInt(i, 10) @@ -110,9 +120,15 @@ func (r *RootCmd) scaletestCreateUsers() *serpent.Command { return xerrors.Errorf("create runner client: %w", err) } - // Username and email are generated by createusers when left empty. + username, email, err := loadtestutil.GenerateUserIdentifierWithPrefix(userPrefix, id) + if err != nil { + return xerrors.Errorf("generate user identifier: %w", err) + } + config := createusers.Config{ OrganizationID: me.OrganizationIDs[0], + Username: username, + Email: email, } if err := config.Validate(); err != nil { return xerrors.Errorf("validate config: %w", err) @@ -191,6 +207,12 @@ func (r *RootCmd) scaletestCreateUsers() *serpent.Command { Description: "Percentage of users to assign the Template Admin role to (0-100).", Value: serpent.Float64Of(&templateAdminPercentage), }, + { + Flag: "username-infix", + Env: "CODER_SCALETEST_CREATE_USERS_USERNAME_INFIX", + Description: "Optional infix inserted between the mandatory \"scaletest-\" root and the rest of each generated username. For example --username-infix asdf produces usernames like scaletest-asdf--; leave empty for the default scaletest-- names. Use it to partition users into disjoint pools that a matching --reuse-users run can select. The scaletest- root is always kept so the cleanup command still discovers the users.", + Value: serpent.StringOf(&usernameInfix), + }, { Flag: "no-cleanup", Env: "CODER_SCALETEST_NO_CLEANUP", diff --git a/scaletest/loadtestutil/names.go b/scaletest/loadtestutil/names.go index 68d528b1562..44f7158d027 100644 --- a/scaletest/loadtestutil/names.go +++ b/scaletest/loadtestutil/names.go @@ -21,12 +21,24 @@ const ( // The username follows the pattern: scaletest-- // The email follows the pattern: -@scaletest.local func GenerateUserIdentifier(id string) (username, email string, err error) { + return GenerateUserIdentifierWithPrefix(ScaleTestPrefix+"-", id) +} + +// GenerateUserIdentifierWithPrefix generates a username and email for scale +// testing using a caller-supplied username prefix. The username follows the +// pattern: -. Callers are expected to keep the "scaletest-" +// root in prefix so users created with a custom prefix are still discovered by +// IsScaleTestUser and the scaletest cleanup command; a caller-chosen infix +// is inserted between the root and the random suffix (for example prefix +// "scaletest-asdf-" yields scaletest-asdf--). The email keeps the +// scaletest domain regardless (-@scaletest.local). +func GenerateUserIdentifierWithPrefix(prefix, id string) (username, email string, err error) { randStr, err := cryptorand.String(DefaultRandLength) if err != nil { return "", "", err } - username = fmt.Sprintf("%s-%s-%s", ScaleTestPrefix, randStr, id) + username = fmt.Sprintf("%s%s-%s", prefix, randStr, id) email = fmt.Sprintf("%s-%s%s", randStr, id, EmailDomain) return username, email, nil } diff --git a/scaletest/loadtestutil/names_test.go b/scaletest/loadtestutil/names_test.go new file mode 100644 index 00000000000..c89f2089edc --- /dev/null +++ b/scaletest/loadtestutil/names_test.go @@ -0,0 +1,72 @@ +package loadtestutil_test + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/scaletest/loadtestutil" +) + +func TestGenerateUserIdentifierWithPrefix(t *testing.T) { + t.Parallel() + + const id = "7" + + cases := []struct { + name string + prefix string + wantPrefix string + }{ + { + name: "default scaletest prefix", + prefix: loadtestutil.ScaleTestPrefix + "-", + wantPrefix: "scaletest-", + }, + { + name: "infix inserted after scaletest root", + prefix: loadtestutil.ScaleTestPrefix + "-notif-", + wantPrefix: "scaletest-notif-", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + username, email, err := loadtestutil.GenerateUserIdentifierWithPrefix(tc.prefix, id) + require.NoError(t, err) + + // username == -. + require.True(t, strings.HasPrefix(username, tc.wantPrefix), + "username %q should start with %q", username, tc.wantPrefix) + require.True(t, strings.HasSuffix(username, "-"+id), + "username %q should end with %q", username, "-"+id) + + randPart := strings.TrimSuffix(strings.TrimPrefix(username, tc.prefix), "-"+id) + require.Len(t, randPart, loadtestutil.DefaultRandLength) + + // The email reuses the same random-id and keeps the scaletest domain. + require.Equal(t, randPart+"-"+id+loadtestutil.EmailDomain, email) + + // Custom prefixes that keep the scaletest- root stay discoverable. + require.True(t, loadtestutil.IsScaleTestUser(username, email)) + }) + } +} + +func TestGenerateUserIdentifierUsesDefaultPrefix(t *testing.T) { + t.Parallel() + + username, email, err := loadtestutil.GenerateUserIdentifier("3") + require.NoError(t, err) + + require.True(t, strings.HasPrefix(username, loadtestutil.ScaleTestPrefix+"-"), + "username %q should start with the scaletest- root", username) + require.True(t, strings.HasSuffix(username, "-3"), + "username %q should end with the id", username) + require.True(t, strings.HasSuffix(email, "-3"+loadtestutil.EmailDomain), + "email %q should end with the id and scaletest domain", email) + require.True(t, loadtestutil.IsScaleTestUser(username, email)) +}