mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-03 19:47:10 +00:00
campaign.json lists the seeds a sweep intended to run and is written before the first run, so a host that dropped runs shows up as missing seeds rather than as a smaller sample. Seed 0 is rejected: sanderling test reads it as "derive a seed from the clock", which is why conformance/gates.sh controls nothing today. Each run contributes one runs.jsonl line carrying steps to first violation by origin step, the step that armed the failed obligation, so the survival analysis never reopens a trace. Claude-Session: https://claude.ai/code/session_01A5KmftdEJ49A9z5mF5ESrX
80 lines
2.1 KiB
Go
80 lines
2.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// parseSeeds expands a seed specification such as "1-10,20,30-32" into the
|
|
// explicit seed list a campaign intends to run.
|
|
func parseSeeds(specification string) ([]int64, error) {
|
|
trimmed := strings.TrimSpace(specification)
|
|
if trimmed == "" {
|
|
return nil, fmt.Errorf("empty seed spec")
|
|
}
|
|
var seeds []int64
|
|
seen := map[int64]bool{}
|
|
for _, part := range strings.Split(trimmed, ",") {
|
|
part = strings.TrimSpace(part)
|
|
if part == "" {
|
|
return nil, fmt.Errorf("empty seed in %q", specification)
|
|
}
|
|
expanded, err := expandSeedPart(part)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, seed := range expanded {
|
|
if seen[seed] {
|
|
return nil, fmt.Errorf("duplicate seed %d in %q", seed, specification)
|
|
}
|
|
seen[seed] = true
|
|
seeds = append(seeds, seed)
|
|
}
|
|
}
|
|
return seeds, nil
|
|
}
|
|
|
|
func expandSeedPart(part string) ([]int64, error) {
|
|
start, end, isRange := strings.Cut(part, "-")
|
|
if !isRange {
|
|
seed, err := parseSeed(part)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []int64{seed}, nil
|
|
}
|
|
first, err := parseSeed(strings.TrimSpace(start))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("seed range %q: %w", part, err)
|
|
}
|
|
last, err := parseSeed(strings.TrimSpace(end))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("seed range %q: %w", part, err)
|
|
}
|
|
if first > last {
|
|
return nil, fmt.Errorf("seed range %q: start %d is above end %d", part, first, last)
|
|
}
|
|
seeds := make([]int64, 0, last-first+1)
|
|
for seed := first; seed <= last; seed++ {
|
|
seeds = append(seeds, seed)
|
|
}
|
|
return seeds, nil
|
|
}
|
|
|
|
func parseSeed(text string) (int64, error) {
|
|
seed, err := strconv.ParseInt(text, 10, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid seed %q: want a positive integer", text)
|
|
}
|
|
if seed == 0 {
|
|
// `sanderling test` reads --seed 0 as "derive a seed from the clock",
|
|
// so a campaign listing seed 0 records a run nobody can reproduce.
|
|
return 0, fmt.Errorf("seed 0 is not reproducible: sanderling test derives a random seed when --seed is 0, so list explicit non-zero seeds")
|
|
}
|
|
if seed < 0 {
|
|
return 0, fmt.Errorf("invalid seed %d: want a positive integer", seed)
|
|
}
|
|
return seed, nil
|
|
}
|