Skip to content

Commit cf10151

Browse files
committed
use prng, add --rand-seed argument
1 parent cca6a0b commit cf10151

File tree

6 files changed

+44
-24
lines changed

6 files changed

+44
-24
lines changed

cli/exp_scaletest.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77
"io"
8+
"math/rand"
89
"net/http"
910
"os"
1011
"strconv"
@@ -1049,6 +1050,7 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd {
10491050
minWait time.Duration
10501051
maxWait time.Duration
10511052
headless bool
1053+
randSeed int64
10521054

10531055
client = &codersdk.Client{}
10541056
tracingFlags = &scaletestTracingFlags{}
@@ -1106,6 +1108,8 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd {
11061108
}
11071109

11081110
for _, usr := range users {
1111+
//nolint:gosec // not used for cryptographic purposes
1112+
rndGen := rand.New(rand.NewSource(randSeed))
11091113
name := fmt.Sprintf("dashboard-%s", usr.Username)
11101114
userTokResp, err := client.CreateToken(ctx, usr.ID.String(), codersdk.CreateTokenRequest{
11111115
Lifetime: 30 * 24 * time.Hour,
@@ -1126,6 +1130,7 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd {
11261130
Logger: logger.Named(name),
11271131
Headless: headless,
11281132
ActionFunc: dashboard.ClickRandomElement,
1133+
RandIntn: rndGen.Intn,
11291134
}
11301135
//nolint:gocritic
11311136
logger.Info(ctx, "runner config", slog.F("min_wait", minWait), slog.F("max_wait", maxWait), slog.F("headless", headless), slog.F("trace", tracingEnabled))
@@ -1189,6 +1194,13 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd {
11891194
Description: "Controls headless mode. Setting to false is useful for debugging.",
11901195
Value: clibase.BoolOf(&headless),
11911196
},
1197+
{
1198+
Flag: "rand-seed",
1199+
Env: "CODER_SCALETEST_DASHBOARD_RAND_SEED",
1200+
Default: "0",
1201+
Description: "Seed for the random number generator.",
1202+
Value: clibase.Int64Of(&randSeed),
1203+
},
11921204
}
11931205

11941206
tracingFlags.attach(&cmd.Options)

cli/exp_scaletest_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ func TestScaleTestDashboard(t *testing.T) {
156156
"--timeout", "5s",
157157
"--scaletest-prometheus-address", "127.0.0.1:0",
158158
"--scaletest-prometheus-wait", "0s",
159+
"--rand-seed", "1234567890",
159160
)
160161
clitest.SetupConfig(t, client, root)
161162
pty := ptytest.New(t)

scaletest/dashboard/chromedp.go

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package dashboard
22

33
import (
44
"context"
5-
"math/rand"
65
"net/url"
76
"os"
87
"time"
@@ -91,43 +90,45 @@ var defaultTargets = []Target{
9190
// If no elements are found, an error is returned.
9291
// If more than one element is found, one is chosen at random.
9392
// The label of the clicked element is returned.
94-
func ClickRandomElement(ctx context.Context) (Label, Action, error) {
93+
func ClickRandomElement(ctx context.Context, randIntn func(int) int) (Label, Action, error) {
9594
var xpath Selector
9695
var found bool
9796
var err error
98-
matches := make(map[Label]Selector)
99-
waitFor := make(map[Label]Selector)
97+
matches := make([]Target, 0)
10098
for _, tgt := range defaultTargets {
101-
xpath, found, err = randMatch(ctx, tgt.ClickOn)
99+
xpath, found, err = randMatch(ctx, tgt.ClickOn, randIntn)
102100
if err != nil {
103101
return "", nil, xerrors.Errorf("find matches for %q: %w", tgt.ClickOn, err)
104102
}
105103
if !found {
106104
continue
107105
}
108-
matches[tgt.Label] = xpath
109-
waitFor[tgt.Label] = tgt.WaitFor
106+
matches = append(matches, Target{
107+
Label: tgt.Label,
108+
ClickOn: xpath,
109+
WaitFor: tgt.WaitFor,
110+
})
110111
}
111112

113+
if len(matches) == 0 {
114+
return "", nil, xerrors.Errorf("no matches found")
115+
}
116+
match := pick(matches, randIntn)
112117
// rely on map iteration order being random
113-
for lbl, tgt := range matches {
114-
act := func(actx context.Context) error {
115-
if err := clickAndWait(actx, tgt, waitFor[lbl]); err != nil {
116-
return xerrors.Errorf("click %q: %w", tgt, err)
117-
}
118-
return nil
118+
act := func(actx context.Context) error {
119+
if err := clickAndWait(actx, match.ClickOn, match.WaitFor); err != nil {
120+
return xerrors.Errorf("click %q: %w", match.ClickOn, err)
119121
}
120-
return lbl, act, nil
122+
return nil
121123
}
122-
123-
return "", nil, xerrors.Errorf("no matches found")
124+
return match.Label, act, nil
124125
}
125126

126127
// randMatch returns a random match for the given selector.
127128
// The returned selector is the full XPath of the matched node.
128129
// If no matches are found, an error is returned.
129130
// If multiple matches are found, one is chosen at random.
130-
func randMatch(ctx context.Context, s Selector) (Selector, bool, error) {
131+
func randMatch(ctx context.Context, s Selector, randIntn func(int) int) (Selector, bool, error) {
131132
var nodes []*cdp.Node
132133
err := chromedp.Run(ctx, chromedp.Nodes(s, &nodes, chromedp.NodeVisible, chromedp.AtLeast(0)))
133134
if err != nil {
@@ -136,7 +137,7 @@ func randMatch(ctx context.Context, s Selector) (Selector, bool, error) {
136137
if len(nodes) == 0 {
137138
return "", false, nil
138139
}
139-
n := pick(nodes)
140+
n := pick(nodes, randIntn)
140141
return Selector(n.FullXPath()), true, nil
141142
}
142143

@@ -210,11 +211,11 @@ func visitMainPage(ctx context.Context, u *url.URL) error {
210211

211212
// pick chooses a random element from a slice.
212213
// If the slice is empty, it returns the zero value of the type.
213-
func pick[T any](s []T) T {
214+
func pick[T any](s []T, randIntn func(int) int) T {
214215
if len(s) == 0 {
215216
var zero T
216217
return zero
217218
}
218219
// nolint:gosec
219-
return s[rand.Intn(len(s))]
220+
return s[randIntn(len(s))]
220221
}

scaletest/dashboard/config.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ type Config struct {
2121
// Headless controls headless mode for chromedp.
2222
Headless bool `json:"headless"`
2323
// ActionFunc is a function that returns an action to run.
24-
ActionFunc func(ctx context.Context) (Label, Action, error) `json:"-"`
24+
ActionFunc func(ctx context.Context, randIntn func(int) int) (Label, Action, error) `json:"-"`
25+
// RandIntn is a function that returns a random number between 0 and n-1.
26+
RandIntn func(int) int `json:"-"`
2527
}
2628

2729
func (c Config) Validate() error {

scaletest/dashboard/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error {
6060
return nil
6161
case <-t.C:
6262
t.Reset(r.randWait())
63-
l, act, err := r.cfg.ActionFunc(cdpCtx)
63+
l, act, err := r.cfg.ActionFunc(cdpCtx, r.cfg.RandIntn)
6464
if err != nil {
6565
r.cfg.Logger.Error(ctx, "calling ActionFunc", slog.Error(err))
6666
continue

scaletest/dashboard/run_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ func Test_Run(t *testing.T) {
3636
return assert.AnError
3737
}
3838

39+
//nolint: gosec // just for testing
40+
rg := rand.New(rand.NewSource(0)) // deterministic for testing
41+
3942
client := coderdtest.New(t, nil)
4043
_ = coderdtest.CreateFirstUser(t, client)
4144

@@ -48,12 +51,13 @@ func Test_Run(t *testing.T) {
4851
MaxWait: 500 * time.Millisecond,
4952
Logger: log,
5053
Headless: true,
51-
ActionFunc: func(ctx context.Context) (dashboard.Label, dashboard.Action, error) {
52-
if rand.Intn(2) == 0 { //nolint:gosec // just for testing
54+
ActionFunc: func(_ context.Context, rnd func(int) int) (dashboard.Label, dashboard.Action, error) {
55+
if rnd(2) == 0 {
5356
return "fails", failAction, nil
5457
}
5558
return "succeeds", successAction, nil
5659
},
60+
RandIntn: rg.Intn,
5761
}
5862
r := dashboard.NewRunner(client, m, cfg)
5963
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)

0 commit comments

Comments
 (0)