mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
242 lines
5.5 KiB
Go
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]
|
|
}
|