mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
same fixed campaign as implementation-sweep, over a corpus that needs no build. each implementation gets its own port: the corpus holds pairs that write the same localStorage key, and one shared origin is one stored record shared between them.
133 lines
4.4 KiB
Go
133 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
manifestFileName = "sweep.json"
|
|
recordsFileName = "implementations.jsonl"
|
|
)
|
|
|
|
// plannedImplementation is one implementation the sweep intends to run, with
|
|
// the origin that keeps its stored state its own and the URL every seed is
|
|
// driven at.
|
|
type plannedImplementation struct {
|
|
Name string `json:"name"`
|
|
Document string `json:"document"`
|
|
Port int `json:"port"`
|
|
Origin string `json:"origin"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// manifest is sweep.json: what the sweep INTENDED to run, written before the
|
|
// first run so a host that dropped an implementation or a seed shows up as a
|
|
// missing run rather than as a smaller sample.
|
|
type manifest struct {
|
|
Generator string `json:"generator"`
|
|
Platform string `json:"platform"`
|
|
SpecPath string `json:"spec_path"`
|
|
CorpusRoot string `json:"corpus_root"`
|
|
CorpusCommit string `json:"corpus_commit"`
|
|
MaxSteps int `json:"max_steps"`
|
|
DurationMillis int64 `json:"duration_millis"`
|
|
Seeds []int64 `json:"seeds"`
|
|
Implementations []plannedImplementation `json:"implementations"`
|
|
Concurrency int `json:"concurrency"`
|
|
Host string `json:"host"`
|
|
CampaignPath string `json:"campaign_path"`
|
|
SanderlingPath string `json:"sanderling_path"`
|
|
StartedAt time.Time `json:"started_at"`
|
|
}
|
|
|
|
func buildManifest(
|
|
configuration config,
|
|
implementations []implementation,
|
|
host string,
|
|
startedAt time.Time,
|
|
) manifest {
|
|
planned := make([]plannedImplementation, 0, len(implementations))
|
|
for _, target := range implementations {
|
|
planned = append(planned, plannedImplementation{
|
|
Name: target.Name,
|
|
Document: target.Document,
|
|
Port: target.Port,
|
|
Origin: target.Origin(),
|
|
URL: target.URL(),
|
|
})
|
|
}
|
|
return manifest{
|
|
Generator: generator,
|
|
Platform: platform,
|
|
SpecPath: configuration.specPath,
|
|
CorpusRoot: configuration.corpusRoot,
|
|
CorpusCommit: corpusCommit(configuration.corpusRoot),
|
|
MaxSteps: configuration.maxSteps,
|
|
DurationMillis: configuration.duration.Milliseconds(),
|
|
Seeds: configuration.seeds,
|
|
Implementations: planned,
|
|
Concurrency: configuration.concurrency,
|
|
Host: host,
|
|
CampaignPath: configuration.campaignPath,
|
|
SanderlingPath: configuration.sanderlingPath,
|
|
StartedAt: startedAt,
|
|
}
|
|
}
|
|
|
|
// corpusCommit records which checkout was swept. It is empty rather than fatal
|
|
// for a corpus that is not a git working tree, because the population check has
|
|
// already established the corpus holds the right examples.
|
|
func corpusCommit(corpusRoot string) string {
|
|
command := exec.Command("git", "-C", corpusRoot, "rev-parse", "HEAD")
|
|
output, err := command.Output()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(string(output))
|
|
}
|
|
|
|
func writeManifest(directory string, value manifest) error {
|
|
body, err := json.MarshalIndent(value, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("marshal manifest: %w", err)
|
|
}
|
|
return os.WriteFile(
|
|
filepath.Join(directory, manifestFileName),
|
|
append(body, '\n'),
|
|
0o644,
|
|
)
|
|
}
|
|
|
|
// runRecord is one campaign, which is one implementation at one seed.
|
|
type runRecord struct {
|
|
Seed int64 `json:"seed"`
|
|
ExitCode int `json:"exit_code"`
|
|
LaunchError string `json:"launch_error,omitempty"`
|
|
CampaignDirectory string `json:"campaign_directory"`
|
|
MonotonicMillis int64 `json:"monotonic_millis"`
|
|
}
|
|
|
|
// implementationRecord is one line of implementations.jsonl. FailedStage names
|
|
// the step that stopped this implementation, and one that never got served
|
|
// carries no runs at all.
|
|
type implementationRecord struct {
|
|
Name string `json:"implementation"`
|
|
Document string `json:"document"`
|
|
Port int `json:"port"`
|
|
Origin string `json:"origin"`
|
|
URL string `json:"url"`
|
|
FailedStage string `json:"failed_stage,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
StartedAt time.Time `json:"started_at"`
|
|
MonotonicMillis int64 `json:"monotonic_millis"`
|
|
Runs []runRecord `json:"runs"`
|
|
}
|
|
|
|
const stageServe = "serve"
|