mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
the campaign tool and the sweep tools that drive it have to read a seed specification the same way, or a sweep records an intent that differs from what ran. parseSeeds becomes seedspec.Parse with no behaviour change.
84 lines
2.4 KiB
Go
84 lines
2.4 KiB
Go
// Package seedspec expands the seed specification a campaign is given into the
|
|
// explicit list of seeds it intends to run. The campaign tool and the tools
|
|
// that drive it have to read a specification the same way, or a sweep records
|
|
// an intent that differs from what ran.
|
|
package seedspec
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Parse expands a seed specification such as "1-10,20,30-32" into the
|
|
// explicit seed list a campaign intends to run.
|
|
func Parse(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
|
|
}
|