mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17: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.
175 lines
5.7 KiB
Go
175 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
// The corpus is tastejs/todomvc. Its examples/ directory holds 48
|
|
// implementations of one requirement; five are excluded and the 43 that remain
|
|
// are this experiment's population.
|
|
//
|
|
// The population is named here rather than read from whatever directories
|
|
// happen to exist, so a corpus at the wrong commit fails verifyCorpus instead
|
|
// of quietly sweeping a different sample and reporting it as this one.
|
|
var includedImplementations = []string{
|
|
"angular-dart", "angular2", "angular2_es2015", "angularjs", "angularjs_require",
|
|
"aurelia", "backbone", "backbone_marionette", "backbone_require", "binding-scala",
|
|
"canjs", "canjs_require", "closure", "dijon", "dojo",
|
|
"duel", "elm", "emberjs", "enyo_backbone", "exoskeleton",
|
|
"jquery", "js_of_ocaml", "jsblocks", "knockback", "knockoutjs",
|
|
"knockoutjs_require", "kotlin-react", "lavaca_require", "mithril", "polymer",
|
|
"ractive", "react", "react-alt", "react-backbone", "reagent",
|
|
"riotjs", "scalajs-react", "typescript-angular", "typescript-backbone", "typescript-react",
|
|
"vanilla-es6", "vanillajs", "vue",
|
|
}
|
|
|
|
// excludedImplementations are the five directories under examples/ that the
|
|
// corpus survey dropped. They are listed rather than merely omitted so
|
|
// verifyCorpus can insist the corpus holds exactly these 48 names: an example
|
|
// added or renamed upstream then stops the sweep rather than silently shrinking
|
|
// or growing the sample.
|
|
var excludedImplementations = []string{
|
|
"cujo", "emberjs_require", "firebase-angular", "gwt", "react-hooks",
|
|
}
|
|
|
|
const examplesDirectory = "examples"
|
|
|
|
// documentPath names the served document for the implementations that do not
|
|
// keep an index.html at the root of their example directory.
|
|
var documentPath = map[string]string{
|
|
"angular-dart": "examples/angular-dart/web/index.html",
|
|
"duel": "examples/duel/www/index.html",
|
|
}
|
|
|
|
func documentFor(name string) string {
|
|
if override, ok := documentPath[name]; ok {
|
|
return override
|
|
}
|
|
return examplesDirectory + "/" + name + "/index.html"
|
|
}
|
|
|
|
// implementation is one member of the population, with the port it owns for the
|
|
// whole sweep. The port is what keeps implementations apart: every one is
|
|
// served on its own, so every one is its own web origin and localStorage keeps
|
|
// their records in separate partitions. Four pairs in this corpus write the
|
|
// same key, and one origin between them is one record between them.
|
|
type implementation struct {
|
|
Name string
|
|
Document string
|
|
Port int
|
|
}
|
|
|
|
func (i implementation) Origin() string {
|
|
return fmt.Sprintf("http://127.0.0.1:%d", i.Port)
|
|
}
|
|
|
|
func (i implementation) URL() string {
|
|
return i.Origin() + "/" + i.Document
|
|
}
|
|
|
|
// verifyCorpus insists the corpus holds exactly the 48 example directories this
|
|
// population was drawn from. A sweep against a different checkout would still
|
|
// run, and its 43 arms would still be labelled, which is why the check is here
|
|
// and not left to whoever reads the results.
|
|
func verifyCorpus(corpusRoot string) error {
|
|
entries, err := os.ReadDir(filepath.Join(corpusRoot, examplesDirectory))
|
|
if err != nil {
|
|
return fmt.Errorf("--corpus: %w", err)
|
|
}
|
|
var found []string
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
found = append(found, entry.Name())
|
|
}
|
|
}
|
|
expected := slices.Concat(includedImplementations, excludedImplementations)
|
|
slices.Sort(expected)
|
|
slices.Sort(found)
|
|
if slices.Equal(expected, found) {
|
|
return nil
|
|
}
|
|
var missing, unexpected []string
|
|
for _, name := range expected {
|
|
if !slices.Contains(found, name) {
|
|
missing = append(missing, name)
|
|
}
|
|
}
|
|
for _, name := range found {
|
|
if !slices.Contains(expected, name) {
|
|
unexpected = append(unexpected, name)
|
|
}
|
|
}
|
|
return fmt.Errorf(
|
|
"%s/%s is not the corpus this population was drawn from: missing %v, unexpected %v",
|
|
corpusRoot,
|
|
examplesDirectory,
|
|
missing,
|
|
unexpected,
|
|
)
|
|
}
|
|
|
|
// selectImplementations resolves --implementations against the population. An
|
|
// empty selection is the whole population, which is what a real sweep runs; a
|
|
// named subset is for smoke runs and is recorded in the manifest like any other
|
|
// intent.
|
|
func selectImplementations(selection string) ([]string, error) {
|
|
if strings.TrimSpace(selection) == "" {
|
|
return slices.Clone(includedImplementations), nil
|
|
}
|
|
var names []string
|
|
seen := map[string]bool{}
|
|
for _, part := range strings.Split(selection, ",") {
|
|
name := strings.TrimSpace(part)
|
|
if name == "" {
|
|
return nil, fmt.Errorf("empty implementation in %q", selection)
|
|
}
|
|
if !slices.Contains(includedImplementations, name) {
|
|
return nil, fmt.Errorf(
|
|
"%q is not one of the %d implementations in this population",
|
|
name,
|
|
len(includedImplementations),
|
|
)
|
|
}
|
|
if seen[name] {
|
|
return nil, fmt.Errorf("duplicate implementation %q", name)
|
|
}
|
|
seen[name] = true
|
|
names = append(names, name)
|
|
}
|
|
return names, nil
|
|
}
|
|
|
|
// planImplementations gives every selected implementation its document and its
|
|
// own port, in population order so the manifest can name the URL each arm was
|
|
// served from before anything has been served.
|
|
func planImplementations(
|
|
corpusRoot string,
|
|
names []string,
|
|
basePort int,
|
|
) ([]implementation, error) {
|
|
if basePort+len(names)-1 > 65535 {
|
|
return nil, fmt.Errorf(
|
|
"--base-port %d leaves no room for %d implementations",
|
|
basePort,
|
|
len(names),
|
|
)
|
|
}
|
|
planned := make([]implementation, 0, len(names))
|
|
for index, name := range names {
|
|
document := documentFor(name)
|
|
if _, err := os.Stat(filepath.Join(corpusRoot, filepath.FromSlash(document))); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", name, err)
|
|
}
|
|
planned = append(planned, implementation{
|
|
Name: name,
|
|
Document: document,
|
|
Port: basePort + index,
|
|
})
|
|
}
|
|
return planned, nil
|
|
}
|