Files
sanderling/cmd/internal-tools/implementation-sweep/main_test.go
T

242 lines
5.5 KiB
Go

package main
import (
"io"
"net/url"
"path/filepath"
"slices"
"strconv"
"strings"
"testing"
"time"
)
func baseArguments() []string {
return []string{
"--implementations", "/e4/implementations",
"--spec", "/e4/relay.ts",
"--seeds", "1-3",
"--max-steps", "400",
"--output", "/campaigns/e4",
}
}
func TestParseArguments_DefaultsAndSeeds(t *testing.T) {
configuration, err := parseArguments(baseArguments(), io.Discard)
if err != nil {
t.Fatal(err)
}
if !slices.Equal(configuration.seeds, []int64{1, 2, 3}) {
t.Errorf("seeds: got %v", configuration.seeds)
}
if configuration.concurrency != defaultConcurrency {
t.Errorf(
"concurrency default: got %d, want %d",
configuration.concurrency,
defaultConcurrency,
)
}
if configuration.basePort != defaultBasePort {
t.Errorf(
"base port default: got %d, want %d",
configuration.basePort,
defaultBasePort,
)
}
if configuration.duration != 5*time.Minute {
t.Errorf("duration default: got %s", configuration.duration)
}
for name, got := range map[string]string{
"bun": configuration.bunPath,
"campaign": configuration.campaignPath,
"sanderling": configuration.sanderlingPath,
} {
if got != name {
t.Errorf("%s path default: got %q", name, got)
}
}
}
func TestParseArguments_Rejections(t *testing.T) {
cases := []struct {
name string
arguments []string
want string
}{
{
"missing implementations",
[]string{
"--spec",
"s",
"--seeds",
"1",
"--max-steps",
"10",
"--output",
"o",
},
"--implementations is required",
},
{
"missing spec",
[]string{
"--implementations",
"i",
"--seeds",
"1",
"--max-steps",
"10",
"--output",
"o",
},
"--spec is required",
},
{
"missing output",
[]string{
"--implementations",
"i",
"--spec",
"s",
"--seeds",
"1",
"--max-steps",
"10",
},
"--output is required",
},
{
"zero max steps",
append(baseArguments(), "--max-steps", "0"),
"--max-steps must be positive",
},
{
"zero concurrency",
append(baseArguments(), "--concurrency", "0"),
"--concurrency must be positive",
},
{
"privileged base port",
append(baseArguments(), "--base-port", "80"),
"outside 1024-65535",
},
{
"seed zero",
append(baseArguments(), "--seeds", "0,1"),
"not reproducible",
},
}
for _, testCase := range cases {
_, err := parseArguments(testCase.arguments, io.Discard)
if err == nil {
t.Errorf("%s: expected error", testCase.name)
continue
}
if !strings.Contains(err.Error(), testCase.want) {
t.Errorf(
"%s: got %q, want it to contain %q",
testCase.name,
err,
testCase.want,
)
}
}
}
// Three flags missing is one rerun, not three: the operator is told about all
// of them at once, in flag order, whatever order the check happened to walk.
func TestParseArguments_NamesEveryMissingRequiredFlagInFlagOrder(t *testing.T) {
_, err := parseArguments(
[]string{"--spec", "s", "--max-steps", "10"},
io.Discard,
)
if err == nil {
t.Fatal("got no error, want every missing flag named")
}
message := err.Error()
previous := -1
for _, name := range []string{"--implementations", "--seeds", "--output"} {
at := strings.Index(message, name)
if at < 0 {
t.Fatalf("got %q, want %s named", message, name)
}
if at < previous {
t.Errorf("got %q, want the flags named in flag order", message)
}
previous = at
}
if strings.Contains(message, "--spec") {
t.Errorf("got %q, want the supplied --spec left out", message)
}
}
// The seed reaches two independent things, the campaign's own seed and the
// scaffold's failure stream, and a replay reproduces neither unless they carry
// the same number.
func TestCampaignArguments_OneSeedReachesBothTheCampaignAndTheURL(
t *testing.T,
) {
configuration, err := parseArguments(
append(baseArguments(), "--", "--clear-data=false"),
io.Discard,
)
if err != nil {
t.Fatal(err)
}
target := implementation{
Name: "impl-07",
Directory: "/e4/implementations/impl-07",
Port: 5306,
}
for _, seed := range []string{"1", "42"} {
arguments := campaignArguments(configuration, target, seed)
if got := argumentValue(arguments, "--seeds"); got != seed {
t.Errorf("--seeds: got %q, want %q", got, seed)
}
bundle := argumentValue(arguments, "--bundle-id")
parsed, err := url.Parse(bundle)
if err != nil {
t.Fatalf("--bundle-id %q: %v", bundle, err)
}
if got := parsed.Query().Get("seed"); got != seed {
t.Errorf(
"served URL seed: got %q, want %q (from %q)",
got,
seed,
bundle,
)
}
if parsed.Host != "localhost:"+strconv.Itoa(target.Port) {
t.Errorf(
"served host: got %q, want the implementation's own port %d",
parsed.Host,
target.Port,
)
}
for flagName, want := range map[string]string{
"--arm": "impl-07",
"--platform": "web",
"--generator": "seeded",
"--max-steps": "400",
"--spec": "/e4/relay.ts",
"--output": filepath.Join("/campaigns/e4", "impl-07", "seed-"+seed),
} {
if got := argumentValue(arguments, flagName); got != want {
t.Errorf("%s: got %q, want %q", flagName, got, want)
}
}
if arguments[len(arguments)-2] != "--" ||
arguments[len(arguments)-1] != "--clear-data=false" {
t.Errorf("passthrough flags lost: %v", arguments)
}
}
}
func argumentValue(arguments []string, name string) string {
index := slices.Index(arguments, name)
if index < 0 || index+1 >= len(arguments) {
return ""
}
return arguments[index+1]
}