diff --git a/go/buildcpu/buildcpu.go b/go/buildcpu/buildcpu.go deleted file mode 100644 index dcd2f36..0000000 --- a/go/buildcpu/buildcpu.go +++ /dev/null @@ -1,215 +0,0 @@ -// Package buildcpu is how much of a build node's processors its builds may take, and how they yield to everything else -// there (novox/hq ADR 0553, issue 1806; the operator, 2026-10-26: "limit CPU usage on our build nodes so the total cpu -// usage never impacts the normal workings of our nodes ... We have 3 build agents so they should share the cpu power"). -// -// Three parts, each with one owner: -// -// - **Priority.** A build seat's holder (catalogue.Seat.Yields) runs at the lowest priority there is: the composer -// gives its runtime process declaration.Yields, and the node-engine places every yielding process of the node in -// one slice (declaration.YieldSlice) whose CPU weight is idle, with Nice=19 for every process it starts. -// Interactive and service work always comes first; builds take the cycles nothing else wants. -// - **One cap per node, shared.** The node's share of its processors is one value, the node-engine's own config -// `build-cpu-share` (ShareSetting), set per node through the node's settings layer. The node-engine writes it as the -// slice's CPUQuota: one bound the kernel holds for all the node's build modules together, never one per module. -// - **Threads.** The quota bounds the total; threads only size each step. A step gets the share divided by its own -// module's capacity (Threads), from the share the composer hands as a word (ShareWord): GOMAXPROCS sets every go -// command's -p, the compiler's concurrency, a test binary's processors and -parallel. A module given no word sets -// no thread limit at all: without the slice there is nothing to size a step to, and a limit then would only slow -// builds. -package buildcpu - -import ( - "os" - "runtime" - "strconv" - "strings" -) - -// ShareSetting is the node-engine's config value of the node's processor share for its builds, in percent of the -// machine's processors, 1 to 100: one value per node, set through the node's settings layer (novox/hq ADR 0553 §3). -const ShareSetting = "build-cpu-share" - -// DefaultPercent is the share where nothing sets one: half the machine, so a desktop keeps half its processors for -// itself whatever builds. -const DefaultPercent = 50 - -// ShareWord is the word the composer gives every yielding module (novox/hq ADR 0553 §4): the node's share, in percent. -const ShareWord = "MESH_BUILD_CPU_SHARE" - -// Quota is a CPUQuota for a share of a machine with cores processors: systemd counts a quota in percent of one -// processor, so half of 24 is 1200%. At least 100%, one processor, so a share on a small machine never stops a step. -func Quota(percent, cores int) int { - q := percent * cores - if q < 100 { - q = 100 - } - return q -} - -// Threads is how many threads one step may run at once: its part of the share when its own module runs slots steps at -// once, at least one. The slice's quota bounds the steps of all the node's build modules together; this only sizes one. -func Threads(percent, cores, slots int) int { - if slots < 1 { - slots = 1 - } - if percent < 1 || percent > 100 { - percent = DefaultPercent - } - t := cores * percent / 100 / slots - if t < 1 { - t = 1 - } - return t -} - -// Limits is what one step is held to on this node, as a status tool says it. -type Limits struct { - // Percent is the node's share in percent of its processors, and From where it was read: ShareWord, or "none" when - // the composer gave no share and a step runs without a thread limit. - Percent int `json:"share-percent,omitempty"` - From string `json:"share-from"` - // Cores is the machine's processor count. - Cores int `json:"cores"` - // Slots is this module's own capacity on the node: steps at once. - Slots int `json:"slots"` - // Threads is what each step is given (Threads); 0 is no limit. - Threads int `json:"threads-per-step"` -} - -// FromWords is the limits of a step from the share the composer gave this process (ShareWord) and the module's own -// capacity. Without a usable share there is no limit: Threads is 0. -func FromWords(getenv func(string) string, own int) Limits { - if own < 1 { - own = 1 - } - l := Limits{From: "none", Cores: runtime.NumCPU(), Slots: own} - if n, err := strconv.Atoi(strings.TrimSpace(getenv(ShareWord))); err == nil && n >= 1 && n <= 100 { - l.Percent, l.From = n, ShareWord - l.Threads = Threads(n, l.Cores, own) - } - return l -} - -// Env is the environment a step's commands are given so they start no more threads than they may: GOMAXPROCS for every -// Go program (the compiler, a test binary, esbuild, buildah), UV_THREADPOOL_SIZE for Node's worker pool. Returned as -// KEY=value words to append to a command's environment; nothing for 0 threads, no limit. -func Env(threads int) []string { - if threads < 1 { - return nil - } - t := strconv.Itoa(threads) - return []string{"GOMAXPROCS=" + t, "UV_THREADPOOL_SIZE=" + t} -} - -// InForce is what the kernel holds this process to, read rather than assumed (evidence over assertion): its nice -// level, its cgroup (the unit) and the cgroup's parent (the slice the node-engine placed it in), each one's CPU weight, -// idle mark and quota. A field the machine does not answer is left empty and Unread says which. -// -// The IO weight is read but binds only where the disk's scheduler honours it (bfq, or the io.cost controller); under -// the scheduler `none`, the usual one for NVMe, it does nothing, and the CPU bound is what holds. -type InForce struct { - Nice int `json:"nice"` - Cgroup string `json:"cgroup,omitempty"` - Unit Group `json:"unit"` - Slice Group `json:"slice"` - // Quota is the tighter of the two groups' cpu.max as a percent of one processor, 0 with none. - Quota int `json:"quota-percent,omitempty"` - Unread []string `json:"unread,omitempty"` -} - -// Group is one cgroup's controls. -type Group struct { - Path string `json:"path,omitempty"` - CPUIdle string `json:"cpu-idle,omitempty"` - CPUWeight string `json:"cpu-weight,omitempty"` - // CPUMax is the cgroup's cpu.max: " ", or "max" with no quota. - CPUMax string `json:"cpu-max,omitempty"` - IOWeight string `json:"io-weight,omitempty"` -} - -// cgroupRoot is where the unified hierarchy is mounted. A variable so a test can point it elsewhere. -var cgroupRoot = "/sys/fs/cgroup" - -// procSelfCgroup is this process's cgroup file. A variable so a test can point it elsewhere. -var procSelfCgroup = "/proc/self/cgroup" - -// procSelfStat is this process's stat file. A variable so a test can point it elsewhere. -var procSelfStat = "/proc/self/stat" - -// ReadInForce reads what the kernel holds this process to. -func ReadInForce() InForce { - var f InForce - f.Nice = nice() - raw, err := os.ReadFile(procSelfCgroup) - if err != nil { - f.Unread = append(f.Unread, "cgroup: "+err.Error()) - return f - } - for _, line := range strings.Split(strings.TrimSpace(string(raw)), "\n") { - if strings.HasPrefix(line, "0::") { - f.Cgroup = strings.TrimPrefix(line, "0::") - } - } - if f.Cgroup == "" { - f.Unread = append(f.Unread, "cgroup: not on the unified hierarchy") - return f - } - read := func(path string) Group { - g := Group{Path: path} - value := func(name string) string { - b, err := os.ReadFile(cgroupRoot + path + "/" + name) - if err != nil { - f.Unread = append(f.Unread, path+"/"+name+": "+err.Error()) - return "" - } - return strings.TrimSpace(string(b)) - } - g.CPUIdle, g.CPUWeight, g.CPUMax, g.IOWeight = value("cpu.idle"), value("cpu.weight"), value("cpu.max"), value("io.weight") - return g - } - f.Unit = read(f.Cgroup) - if i := strings.LastIndex(f.Cgroup, "/"); i > 0 { - f.Slice = read(f.Cgroup[:i]) - } - for _, q := range []int{quotaOf(f.Unit.CPUMax), quotaOf(f.Slice.CPUMax)} { - if q > 0 && (f.Quota == 0 || q < f.Quota) { - f.Quota = q - } - } - return f -} - -// quotaOf is a cpu.max value as a percent of one processor: "1200000 100000" is 1200; "max ..." or nothing is 0. -func quotaOf(cpuMax string) int { - fields := strings.Fields(cpuMax) - if len(fields) != 2 || fields[0] == "max" { - return 0 - } - q, err1 := strconv.Atoi(fields[0]) - p, err2 := strconv.Atoi(fields[1]) - if err1 != nil || err2 != nil || p <= 0 { - return 0 - } - return q * 100 / p -} - -// nice is this process's nice level from its stat file (the 19th field, counted after the command's closing -// parenthesis, which may itself hold spaces); 0 when it cannot be read. -func nice() int { - raw, err := os.ReadFile(procSelfStat) - if err != nil { - return 0 - } - s := string(raw) - i := strings.LastIndex(s, ")") - if i < 0 { - return 0 - } - fields := strings.Fields(s[i+1:]) - // After the command: state is field 3, so nice (field 19) is index 16. - if len(fields) < 17 { - return 0 - } - n, _ := strconv.Atoi(fields[16]) - return n -} diff --git a/go/buildcpu/buildcpu_agree_test.go b/go/buildcpu/buildcpu_agree_test.go deleted file mode 100644 index bf61988..0000000 --- a/go/buildcpu/buildcpu_agree_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package buildcpu - -import ( - "testing" - - "git.novox.be/novox/mesh-sdk/go/catalogue" -) - -// The share's names the composer gives are the build modules' (catalogue writes them too, so it does not import this). -func TestTheShareNamesAgreeWithTheCatalogues(t *testing.T) { - if ShareSetting != catalogue.BuildCPUShareSetting || ShareWord != catalogue.BuildCPUShareWord || DefaultPercent != catalogue.BuildCPUDefaultPercent { - t.Fatal("buildcpu's share names differ from catalogue's") - } -} diff --git a/go/buildcpu/buildcpu_test.go b/go/buildcpu/buildcpu_test.go deleted file mode 100644 index 6740687..0000000 --- a/go/buildcpu/buildcpu_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package buildcpu - -import ( - "os" - "path/filepath" - "reflect" - "runtime" - "testing" -) - -// The node's share as a quota, the threads one step is given for its own module's capacity, and the word the composer -// hands, in one table (ADR 0553, issue 1806). -func TestShareQuotaAndThreads(t *testing.T) { - for _, c := range []struct { - name string - percent, cores, slots int - quota, threads int - }{ - {"shanks: half of 24, three slots", 50, 24, 3, 1200, 4}, - {"ace: three quarters of 24, two slots", 75, 24, 2, 1800, 9}, - {"g14: half of 16, one slot", 50, 16, 1, 800, 8}, - {"one processor at least", 10, 2, 4, 100, 1}, - {"no slots counts as one", 100, 4, 0, 400, 4}, - {"a share out of range is the default", 0, 8, 1, 0, 4}, - } { - if c.quota != 0 { - if q := Quota(c.percent, c.cores); q != c.quota { - t.Errorf("%s: Quota = %d, want %d", c.name, q, c.quota) - } - } - if th := Threads(c.percent, c.cores, c.slots); th != c.threads { - t.Errorf("%s: Threads = %d, want %d", c.name, th, c.threads) - } - } - l := FromWords(func(k string) string { return map[string]string{ShareWord: "75"}[k] }, 2) - if l.Percent != 75 || l.From != ShareWord || l.Slots != 2 || l.Threads != Threads(75, runtime.NumCPU(), 2) { - t.Errorf("from the word: %+v", l) - } - // Told nothing usable, a step has no thread limit: nothing in its environment. - for _, word := range []string{"", "250", "lots"} { - l := FromWords(func(string) string { return word }, 3) - if l.Threads != 0 || l.From != "none" || Env(l.Threads) != nil { - t.Errorf("share %q: %+v, env %v", word, l, Env(l.Threads)) - } - } - if got := Env(3); !reflect.DeepEqual(got, []string{"GOMAXPROCS=3", "UV_THREADPOOL_SIZE=3"}) { - t.Errorf("Env = %q", got) - } -} - -// What the kernel holds a process to is read from its unit's and its slice's cgroup and its stat file, not assumed. -func TestReadInForce(t *testing.T) { - dir := t.TempDir() - cg := filepath.Join(dir, "cg") - slice := filepath.Join(cg, "meshyield.slice") - unit := filepath.Join(slice, "mesh-build-go-runtime.service") - if err := os.MkdirAll(unit, 0o755); err != nil { - t.Fatal(err) - } - for at, files := range map[string]map[string]string{ - slice: {"cpu.idle": "1\n", "cpu.weight": "0\n", "cpu.max": "1200000 100000\n", "io.weight": "default 1\n"}, - unit: {"cpu.idle": "0\n", "cpu.weight": "100\n", "cpu.max": "max 100000\n", "io.weight": "default 100\n"}, - } { - for name, v := range files { - if err := os.WriteFile(filepath.Join(at, name), []byte(v), 0o644); err != nil { - t.Fatal(err) - } - } - } - self := filepath.Join(dir, "cgroup") - stat := filepath.Join(dir, "stat") - _ = os.WriteFile(self, []byte("0::/meshyield.slice/mesh-build-go-runtime.service\n"), 0o644) - // A command with a space and a parenthesis in it, then nice 19 in field 19. - _ = os.WriteFile(stat, []byte("42 (mesh (build) go) S 1 42 42 0 -1 4194560 1 0 0 0 0 0 0 0 39 19 8 0 1 2 3\n"), 0o644) - cgroupRoot, procSelfCgroup, procSelfStat = cg, self, stat - t.Cleanup(func() { - cgroupRoot, procSelfCgroup, procSelfStat = "/sys/fs/cgroup", "/proc/self/cgroup", "/proc/self/stat" - }) - - got := ReadInForce() - want := InForce{Nice: 19, Cgroup: "/meshyield.slice/mesh-build-go-runtime.service", - Unit: Group{Path: "/meshyield.slice/mesh-build-go-runtime.service", CPUIdle: "0", CPUWeight: "100", - CPUMax: "max 100000", IOWeight: "default 100"}, - Slice: Group{Path: "/meshyield.slice", CPUIdle: "1", CPUWeight: "0", CPUMax: "1200000 100000", - IOWeight: "default 1"}, - Quota: 1200} - if !reflect.DeepEqual(got, want) { - t.Fatalf("ReadInForce = %+v\nwant %+v", got, want) - } -} diff --git a/go/buildstep/askrun.go b/go/buildstep/askrun.go deleted file mode 100644 index 2a0836b..0000000 --- a/go/buildstep/askrun.go +++ /dev/null @@ -1,473 +0,0 @@ -package buildstep - -import ( - "context" - "encoding/json" - "fmt" - "maps" - "path" - "slices" - "sort" - "strings" - - "git.novox.be/novox/mesh-sdk/go/delivery" -) - -// **One ask, one whole job** (novox/hq ADR 0525 §3). A build module runs a pulled ask with its own step tools' bodies, -// the same ones its request and reply tools run (ADR 0525 §8), in the order the job names: a build ask fetches, then -// builds or copies each artifact; a check ask fetches, digests the level, takes the kept verdicts and tests the rest, -// the packages whose tests the change touches first. The answers are the tools' own, whole, so the asker judges them as -// it judged the tools' answers. - -// StepFunc is one step tool's body: its arguments in, its answer out. -type StepFunc func(ctx context.Context, args map[string]any) (any, error) - -// AskSteps are the step tools a build module runs an ask with. Copy is the image module's alone; Digest the Go module's. -type AskSteps struct { - Fetch, Build, Copy, Digest, Test StepFunc - // Toolchain is the toolchain the module builds with now; a build ask naming another is declined (Declining). Nil checks nothing - // (an image or an archive names none). - Toolchain func(ctx context.Context) (delivery.ToolchainUsed, error) - // Kept looks up a kept verdict by level and digest (the checker's kept bucket, delivery.KeptKey): whether one is - // kept and how many tests it counted. Nil takes none. - Kept func(ctx context.Context, level, digest string) (tests int, found bool, err error) - // Release removes a workspace once the ask's steps on it ended (the module's release tool's body); nil leaves it to - // the module's sweep. - Release func(workspace string) error - Logf func(format string, args ...any) - // Has says whether this node has a need a package's tests declare (delivery.AskNeeds, issue 890); a package needing - // what it lacks is not tested here but named in the level's Unmet, for the asker to place. Nil has nothing. - Has func(need string) bool -} - -// RunAsk runs ask's job with steps and answers a delivery.BuildAnswer or a delivery.CheckAnswer. -func RunAsk(ctx context.Context, steps AskSteps, ask delivery.StepAsk) (any, error) { - if ask.Build != nil { - job := *ask.Build - job.Fetch = withPackages(job.Fetch, ask.Packages) - return steps.build(ctx, job) - } - if ask.Check != nil { - job := *ask.Check - job.Fetch = withPackages(job.Fetch, ask.Packages) - return steps.check(ctx, job) - } - return nil, Refuse("the ask %s holds no job", ask.ID) -} - -// withPackages is the fetch's arguments with the ask's package versions (novox/hq ADR 0573 §2), which the build module -// writes into the workspace it fetches; a copy, so the ask is left as it was. An ask with none, or a copy alone that -// fetches nothing, is left as it is. -func withPackages(fetch map[string]any, packages delivery.PackageVersions) map[string]any { - if len(packages) == 0 || fetch == nil { - return fetch - } - out := maps.Clone(fetch) - out["packages"] = maps.Clone(packages) - return out -} - -func raw(v any) (json.RawMessage, error) { - if r, ok := v.(json.RawMessage); ok { - return r, nil - } - return json.Marshal(v) -} - -// fetched is what an ask reads of a fetch's answer. -type fetched struct { - Workspace string `json:"workspace"` - Merge string `json:"merge"` - Conflicts []string `json:"conflicts"` - Changed []string `json:"changed"` - Modules []string `json:"modules"` -} - -func (s AskSteps) fetch(ctx context.Context, args map[string]any) (json.RawMessage, fetched, error) { - if s.Fetch == nil { - return nil, fetched{}, Refuse("this build module fetches nothing") - } - if _, merges := args["base"]; merges { - Phase(ctx, "fetch and merge") - } else { - Phase(ctx, "fetch") - } - out, err := s.Fetch(ctx, args) - if err != nil { - return nil, fetched{}, err - } - r, err := raw(out) - if err != nil { - return nil, fetched{}, err - } - var f fetched - if err := json.Unmarshal(r, &f); err != nil || f.Workspace == "" { - return r, f, fmt.Errorf("the fetch answered no workspace: %s", r) - } - return r, f, nil -} - -// onToolchain is the toolchain this node builds with when the ask names one and this module reports its own, nil when -// either is absent. A node on another toolchain than the ask names answers a Declining: it is not on the agreed one and -// sits out, for a node that is to take the ask (novox/hq ADR 0584 clause 2). -func (s AskSteps) onToolchain(ctx context.Context, want *delivery.ToolchainUsed) (*delivery.ToolchainUsed, error) { - if want == nil || s.Toolchain == nil { - return nil, nil - } - tc, err := s.Toolchain(ctx) - if err != nil { - return nil, err - } - if tc.Version != want.Version || tc.Recipe != want.Recipe { - return nil, &Declining{Lacks: []string{fmt.Sprintf("the agreed toolchain %s (recipe %s)", want.Version, want.Recipe)}, - Text: fmt.Sprintf("the ask is for %s (recipe %s) and this node builds with %s (recipe %s): it sits out, and the ask is left to a node on the agreed toolchain", - want.Version, want.Recipe, tc.Version, tc.Recipe)} - } - return &tc, nil -} - -func (s AskSteps) build(ctx context.Context, job delivery.BuildJob) (any, error) { - answer := delivery.BuildAnswer{} - tc, err := s.onToolchain(ctx, job.Toolchain) - if err != nil { - return nil, err - } - answer.Toolchain = tc - var f fetched - defer func() { s.release(f.Workspace) }() - for _, a := range job.Artifacts { - if a.From == "" && f.Workspace == "" { - r, got, err := s.fetch(ctx, job.Fetch) - if err != nil { - return nil, err - } - if _, candidate := job.Fetch["base"]; candidate { - // A candidate is built from its head merged onto the trunk; a head that does not merge leaves the tree at the - // trunk, and building that would store the trunk's build under the candidate's fingerprint. - if len(got.Conflicts) > 0 { - return nil, fmt.Errorf("the head does not merge into the trunk: files in conflict: %s", strings.Join(got.Conflicts, ", ")) - } - if got.Merge == "" { - return nil, fmt.Errorf("the fetch with a base answered no merge: %s", r) - } - } - f = got - } - var ( - out any - err error - ) - if a.From != "" { - if s.Copy == nil { - return nil, Refuse("this build module copies no artifact (%s)", a.Artifact) - } - Phase(ctx, "copy "+a.Artifact) - out, err = s.Copy(ctx, map[string]any{"module": job.Module, "artifact": a.Artifact, "from": a.From}) - } else { - if s.Build == nil { - return nil, Refuse("this build module builds no artifact (%s)", a.Artifact) - } - args := map[string]any{"workspace": f.Workspace, "module": job.Path, "artifact": a.Artifact} - if job.Publish { - args["publish"] = true - } - Phase(ctx, "build "+a.Artifact) - out, err = s.Build(ctx, args) - } - if err != nil { - return nil, fmt.Errorf("%s: %w", a.Artifact, err) - } - r, err := raw(out) - if err != nil { - return nil, err - } - var built struct { - Digest string `json:"digest"` - Reference string `json:"reference"` - } - if err := json.Unmarshal(r, &built); err != nil || built.Digest == "" { - return nil, fmt.Errorf("the build of %s answered no digest: %s", a.Artifact, r) - } - answer.Artifacts = append(answer.Artifacts, delivery.AskBuilt{Artifact: a.Artifact, Digest: built.Digest, Reference: built.Reference}) - } - return answer, nil -} - -func (s AskSteps) check(ctx context.Context, job delivery.CheckJob) (any, error) { - if _, err := s.onToolchain(ctx, job.Toolchain); err != nil { - return nil, err - } - r, f, err := s.fetch(ctx, job.Fetch) - if err != nil { - return nil, err - } - defer s.release(f.Workspace) - answer := delivery.CheckAnswer{Fetch: r} - if len(f.Conflicts) > 0 { - return answer, nil // the asker reads the conflict from the fetch's answer: a verdict, not an error - } - if s.Test == nil { - return nil, Refuse("this build module tests nothing") - } - runner := "" - if job.Runner != "" && (job.RunnerWhen == "" || changesUnder(f.Changed, job.RunnerWhen)) { - runner = job.Runner - } - // Every level runs, also after one that did not pass: one check names every failure, not only the first (the operator, - // 2026-11-02: "A failed check shows only its first fault"). - for _, level := range job.Levels { - la := delivery.LevelAnswer{Level: level} - _, err := s.level(ctx, job, level, f, runner, &la) - answer.Levels = append(answer.Levels, la) - if err != nil { - return nil, err - } - } - return answer, nil -} - -// level runs one level of a check ask and says whether it passed. -func (s AskSteps) level(ctx context.Context, job delivery.CheckJob, level string, f fetched, runner string, la *delivery.LevelAnswer) (bool, error) { - withRunner := func(args map[string]any) map[string]any { - if runner != "" { - args["runner"] = runner - } - if len(job.NodeNames) > 0 { - args[delivery.NodeNamesArg] = job.NodeNames // the names check's (ADR 0567 §12) - } - return args - } - test := func(args map[string]any) (bool, error) { - what := "the whole level" - if dir, _ := args["module"].(string); dir != "" { - what = dir - } else if pkgs, _ := args["packages"].([]string); len(pkgs) > 0 { - what = fmt.Sprintf("%d packages", len(pkgs)) - } - Phase(ctx, fmt.Sprintf("%s: tests of %s", level, what)) - out, err := s.Test(ctx, args) - if err != nil { - return false, err - } - r, err := raw(out) - if err != nil { - return false, err - } - la.Tests = append(la.Tests, r) - var t struct { - Passed bool `json:"passed"` - } - _ = json.Unmarshal(r, &t) - return t.Passed, nil - } - // The packages (or Node directories) whose tests the change touches run first (issue 1289; ADR 0444), and then every - // other test step of the level, also after one that did not pass, so one check names every failing test of the level - // and not only the first (issue 1921; the operator, 2026-11-02); the level passes only when each of its steps did. - all := true - if s.Digest == nil { - // A module with no digest (Node) tests each module directory of the tree, those whose tests changed first. - for _, priority := range []bool{true, false} { - for _, dir := range f.Modules { - if changesTestIn(f.Changed, dir) != priority { - continue - } - passed, err := test(withRunner(map[string]any{"workspace": f.Workspace, "module": dir, "level": level})) - if err != nil { - return false, fmt.Errorf("the %s level of %s: %w", level, dir, err) - } - all = all && passed - } - } - return all, nil - } - todo, whole, keepRunner := s.plan(ctx, job, level, f, runner, withRunner, la) - if !keepRunner { - runner = "" // an installed module that ignores the runner judges the level as it judges any other (ADR 0535 §2) - } - if whole && len(job.Packages) > 0 { - todo, whole = job.Packages, false // a placed ask tests its packages alone, whatever the digest said (ADR 0552 §4) - } - for _, priority := range []bool{true, false} { - args := withRunner(map[string]any{"workspace": f.Workspace, "level": level}) - if job.Run != "" { - args["run"] = job.Run - } - if whole { - if priority { - continue // nothing to put first: the level runs whole after - } - } else { - var run []string - for _, p := range todo { - if changesTestOf(f.Changed, p) == priority { - run = append(run, p) - } - } - if len(run) == 0 { - continue - } - args["packages"] = run - } - passed, err := test(args) - if err != nil { - return false, fmt.Errorf("the %s level: %w", level, err) - } - all = all && passed // the others still run after a failure (issue 1921) - } - return all, nil -} - -// release removes an ask's workspace, saying a failure: the module's sweep removes it later. -func (s AskSteps) release(workspace string) { - if workspace == "" || s.Release == nil { - return - } - if err := s.Release(workspace); err != nil && s.Logf != nil { - s.Logf("the workspace %s is left to the sweep: %v", workspace, err) - } -} - -// plan digests the level and takes the kept verdicts: the packages left to test, or all when the level runs whole, and -// whether the runner stays. A try of some tests runs whole, digesting nothing (a partial run proves no package). Under the -// candidate's runner the digest is asked as the candidate: when the candidate answers it, every package runs and nothing -// is digested for keeping (ADR 0535 §4); when the installed module answers it (it does not take the runner), the level is -// planned as any other and the runner is dropped. The level runs whole and takes nothing when the digest names no package -// or does not cover the siblings the fetch placed (ADR 0444, L5). -func (s AskSteps) plan(ctx context.Context, job delivery.CheckJob, level string, f fetched, runner string, withRunner func(map[string]any) map[string]any, la *delivery.LevelAnswer) ([]string, bool, bool) { - if job.Run != "" { - return nil, true, true - } - Phase(ctx, level+": digest") - out, err := s.Digest(ctx, withRunner(map[string]any{"workspace": f.Workspace, "level": level})) - if err != nil { - if s.Logf != nil { - s.Logf("no digest of the %s level, so it runs whole: %v", level, err) - } - return nil, true, true - } - r, err := raw(out) - if err != nil { - return nil, true, true - } - var d delivery.LevelDigest // packages kept as a sorted list, never keyed by a package path (issue 1930) - if err := json.Unmarshal(r, &d); err != nil { - return nil, true, true - } - if runner != "" { - if strings.HasPrefix(d.JudgedBy, "candidate ") { - la.Judged = d.JudgedBy - return nil, true, true // the candidate decides which verdicts would be taken: all run, nothing kept - } - runner = "" - } - if len(d.Packages) == 0 || !slices.Equal(d.Beside, besideOf(job.Fetch)) { - return nil, true, runner != "" // a digest that names nothing or not the siblings keys no verdict: the level runs whole - } - la.Digest = &d - only := map[string]bool{} - for _, p := range job.Packages { - only[p] = true - } - var todo []string - for _, pd := range d.Packages { - p := pd.Package - if len(only) > 0 && !only[p] { - continue // a placed ask's other packages were judged by the first ask (ADR 0552 §4) - } - if job.Kept && s.Kept != nil { - tests, found, err := s.Kept(ctx, level, pd.Digest) - if err != nil && s.Logf != nil { - s.Logf("the kept verdict of %s at %s cannot be read, so it runs: %v", p, level, err) - } - if err == nil && found { - la.Kept = append(la.Kept, delivery.KeptTaken{Package: p, Digest: pd.Digest, Tests: tests}) - continue - } - } - if needs := d.NeedsOf(p); len(delivery.Lacks(needs, s.Has)) > 0 { - // Its tests would only skip here (issue 890): it is left for a node that has what they need. - la.Unmet = append(la.Unmet, delivery.PackageNeeds{Package: p, Needs: needs}) - if s.Logf != nil { - s.Logf("the %s level of %s is left to a node with %s: this node lacks %s", level, p, - strings.Join(needs, ", "), strings.Join(delivery.Lacks(needs, s.Has), ", ")) - } - continue - } - todo = append(todo, p) - } - return todo, false, false -} - -// besideOf are the siblings a fetch's arguments place, as a digest echoes them: ` `, sorted. -func besideOf(fetch map[string]any) []string { - raw, err := json.Marshal(fetch["beside"]) - if err != nil { - return nil - } - var refs []struct { - Repository string `json:"repository"` - Commit string `json:"commit"` - } - _ = json.Unmarshal(raw, &refs) - var out []string - for _, r := range refs { - out = append(out, r.Repository+" "+r.Commit) - } - sort.Strings(out) - return out -} - -// changesUnder says whether a path the merge changes is under prefix. -func changesUnder(changed []string, prefix string) bool { - for _, p := range changed { - if strings.HasPrefix(p, prefix) { - return true - } - } - return false -} - -// isTestFile says whether a repository-relative path is a test file: Go's _test.go, and a TypeScript module's .test. -// and .spec. files and test directories. -func isTestFile(p string) bool { - switch { - case strings.HasSuffix(p, "_test.go"): - return true - case strings.Contains(p, ".test.") || strings.Contains(p, ".spec."): - return true - } - for _, seg := range strings.Split(path.Dir(p), "/") { - if seg == "__tests__" || seg == "test" || seg == "tests" { - return true - } - } - return false -} - -// changesTestOf says whether the merge changes a test file of the Go package with the import path pkg: the file's -// directory and the import path agree on their last segments. -func changesTestOf(changed []string, pkg string) bool { - for _, f := range changed { - if !strings.HasSuffix(f, "_test.go") { - continue - } - dir := path.Dir(f) - if dir == pkg || strings.HasSuffix(pkg, "/"+dir) || strings.HasSuffix(dir, "/"+pkg) { - return true - } - } - return false -} - -// changesTestIn says whether the merge changes a test file inside the directory of a Node module. -func changesTestIn(changed []string, dir string) bool { - for _, f := range changed { - if !isTestFile(f) || strings.HasSuffix(f, "_test.go") { - continue - } - if dir == "." || dir == "" || strings.HasPrefix(f, strings.TrimSuffix(dir, "/")+"/") { - return true - } - } - return false -} diff --git a/go/buildstep/askrun_test.go b/go/buildstep/askrun_test.go deleted file mode 100644 index 5616b18..0000000 --- a/go/buildstep/askrun_test.go +++ /dev/null @@ -1,325 +0,0 @@ -package buildstep - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "slices" - "strings" - "testing" - "time" - - "git.novox.be/novox/mesh-sdk/go/delivery" -) - -const runCommit = "0123456789abcdef0123456789abcdef01234567" - -// recorder is a build module's step tools that record each call. -type recorder struct { - calls []string - fetch map[string]any - tests map[string]bool // package or module directory -> passed - digests map[string]string - needs []delivery.PackageNeeds -} - -func (r *recorder) steps() AskSteps { - call := func(name string, answer func(args map[string]any) any) StepFunc { - return func(ctx context.Context, args map[string]any) (any, error) { - b, _ := json.Marshal(args) - r.calls = append(r.calls, name+" "+string(b)) - return answer(args), nil - } - } - return AskSteps{ - Fetch: call("fetch", func(map[string]any) any { return r.fetch }), - Build: call("build", func(args map[string]any) any { - return map[string]any{"digest": "sha256:" + args["artifact"].(string), "reference": "artifact-store://" + args["artifact"].(string)} - }), - Copy: call("copy", func(args map[string]any) any { return map[string]any{"digest": "sha256:copied"} }), - Digest: call("digest", func(map[string]any) any { - return map[string]any{"level": "unit", "packages": r.digests, "needs": r.needs} - }), - Test: call("test", func(args map[string]any) any { - passed := true - if pkgs, ok := args["packages"].([]string); ok { - for _, p := range pkgs { - passed = passed && r.tests[p] - } - } - if dir, ok := args["module"].(string); ok { - passed = r.tests[dir] - } - return map[string]any{"passed": passed, "tests": 1} - }), - Kept: func(ctx context.Context, level, digest string) (int, bool, error) { return 7, digest == "d-kept", nil }, - } -} - -func ask(job any) delivery.StepAsk { - a := delivery.StepAsk{ID: NewID(), Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: "t", At: time.Now()} - switch j := job.(type) { - case delivery.BuildJob: - a.Build = &j - case delivery.CheckJob: - a.Check = &j - } - return a -} - -// 1887/A3: a build ask fetches once, builds each artifact from the workspace with publish, copies a pinned one without a -// fetch, and answers each artifact's digest and reference. -func TestRunAskBuilds(t *testing.T) { - r := &recorder{fetch: map[string]any{"workspace": "/w/1"}} - steps := r.steps() - var released []string - steps.Release = func(ws string) error { released = append(released, ws); return nil } - // Issue 1585: the ask's package versions reach the fetch, and the ask's own fetch arguments stay as they were. - a := ask(delivery.BuildJob{Module: "m", Path: "modules/m", Publish: true, - Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}, - Artifacts: []delivery.AskArtifact{{Artifact: "base", From: "registry/b@sha256:00"}, {Artifact: "bundle"}, {Artifact: "lib"}}}) - a.Packages = delivery.PackageVersions{"git.novox.be/novox/mesh-sdk/go": runCommit} - out, err := RunAsk(context.Background(), steps, a) - if err != nil { - t.Fatal(err) - } - if _, put := a.Build.Fetch["packages"]; put { - t.Error("the ask's fetch arguments were changed") - } - got := strings.Join(r.calls, "\n") - want := `copy {"artifact":"base","from":"registry/b@sha256:00","module":"m"} -fetch {"commit":"` + runCommit + `","packages":{"git.novox.be/novox/mesh-sdk/go":"` + runCommit + `"},"repository":"novox/x"} -build {"artifact":"bundle","module":"modules/m","publish":true,"workspace":"/w/1"} -build {"artifact":"lib","module":"modules/m","publish":true,"workspace":"/w/1"}` - if got != want { - t.Fatalf("calls:\n%s\nwant:\n%s", got, want) - } - if len(released) != 1 || released[0] != "/w/1" { - t.Errorf("released %v, want the one workspace", released) - } - answer := out.(delivery.BuildAnswer) - if len(answer.Artifacts) != 3 || answer.Artifacts[1].Reference != "artifact-store://bundle" { - t.Fatalf("answer %+v", answer) - } -} - -// A build ask for another toolchain is declined (ADR 0584 clause 2), so the node sits out and the asker puts it again. -func TestRunAskRefusesAnotherToolchain(t *testing.T) { - r := &recorder{fetch: map[string]any{"workspace": "/w"}} - s := r.steps() - s.Toolchain = func(context.Context) (delivery.ToolchainUsed, error) { - return delivery.ToolchainUsed{Version: "go1.27", Recipe: "3"}, nil - } - _, err := RunAsk(context.Background(), s, ask(delivery.BuildJob{Module: "m", Path: ".", Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}, - Artifacts: []delivery.AskArtifact{{Artifact: "bundle"}}, Toolchain: &delivery.ToolchainUsed{Version: "go1.26", Recipe: "3"}})) - var dec *Declining - if !errors.As(err, &dec) || len(dec.Lacks) != 1 || len(r.calls) != 0 { - t.Fatalf("err %v, calls %v", err, r.calls) - } - // A check ask names the toolchain too (ADR 0584), and a node on another declines it before it fetches. - _, err = RunAsk(context.Background(), s, ask(delivery.CheckJob{Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}, Levels: []string{delivery.LevelUnit}, - Toolchain: &delivery.ToolchainUsed{Version: "go1.26", Recipe: "3"}})) - if !errors.As(err, &dec) || len(r.calls) != 0 { - t.Fatalf("a check ask: err %v, calls %v", err, r.calls) - } -} - -// 1887/A3, A5a: a check ask runs the levels of one check after one fetch: it digests the unit level, takes the kept -// verdicts, tests the packages whose tests changed first and then the others, also after one of them failed, and runs -// every level, also after one that did not pass, so one check names every failure (the operator, 2026-11-02). -func TestRunAskChecksTheLevelsOfOneCheck(t *testing.T) { - r := &recorder{ - fetch: map[string]any{"workspace": "/w", "changed": []string{"pkg/b/b_test.go"}}, - digests: map[string]string{"x/pkg/a": "d-a", "x/pkg/b": "d-b", "x/pkg/k": "d-kept"}, - tests: map[string]bool{"x/pkg/a": true, "x/pkg/b": false}, - } - out, err := RunAsk(context.Background(), r.steps(), ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit, delivery.LevelIntegration}, Kept: true, - Runner: "workspace", RunnerWhen: "modules/mesh-build-go/", Fetch: map[string]any{"repository": "novox/x", "commit": runCommit, "base": runCommit}})) - if err != nil { - t.Fatal(err) - } - got := strings.Join(r.calls, "\n") - want := `fetch {"base":"` + runCommit + `","commit":"` + runCommit + `","repository":"novox/x"} -digest {"level":"unit","workspace":"/w"} -test {"level":"unit","packages":["x/pkg/b"],"workspace":"/w"} -test {"level":"unit","packages":["x/pkg/a"],"workspace":"/w"} -digest {"level":"integration","workspace":"/w"} -test {"level":"integration","packages":["x/pkg/b"],"workspace":"/w"} -test {"level":"integration","packages":["x/pkg/a"],"workspace":"/w"}` - if got != want { - t.Fatalf("calls:\n%s\nwant:\n%s", got, want) - } - a := out.(delivery.CheckAnswer) - if len(a.Levels) != 2 { - t.Fatalf("levels %+v, want both levels", a.Levels) - } - u := a.Levels[0] - if u.Level != delivery.LevelUnit || len(u.Kept) != 1 || u.Kept[0].Package != "x/pkg/k" || u.Kept[0].Tests != 7 || len(u.Tests) != 2 || u.Digest == nil { - t.Fatalf("the unit level %+v", u) - } - - // ADR 0567 §12: a check's node names reach the digest and test tools, for the names check (a kept verdict misses when - // they change). - r.calls = nil - if _, err := RunAsk(context.Background(), r.steps(), ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, NodeNames: []string{"anchor"}, - Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}})); err != nil || !strings.Contains(strings.Join(r.calls, "\n"), `digest {"level":"unit","node-names":["anchor"]`) || - !strings.Contains(strings.Join(r.calls, "\n"), `test {"level":"unit","node-names":["anchor"]`) { - t.Fatalf("the node names are not handed to the digest and test tools: %v, %v", r.calls, err) - } - - // Issue 890/AC2: a package declaring node-build-node is not tested on a node that lacks it but named unmet, and is - // tested on a node that has it. - for _, has := range []bool{false, true} { - r.calls, r.tests["x/pkg/b"] = nil, true - r.needs = []delivery.PackageNeeds{{Package: "x/pkg/a", Needs: []string{delivery.NeedNodeBuildNode}}} - steps := r.steps() - if has { - steps.Has = func(n string) bool { return n == delivery.NeedNodeBuildNode } - } - out, err := RunAsk(context.Background(), steps, ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, Kept: true, - Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}})) - if err != nil { - t.Fatal(err) - } - u := out.(delivery.CheckAnswer).Levels[0] - tested := strings.Contains(strings.Join(r.calls, "\n"), `"x/pkg/a"`) - if tested != has || (len(u.Unmet) == 0) == !has || (!has && (u.Unmet[0].Package != "x/pkg/a" || delivery.UnmetNeeds([]delivery.LevelAnswer{u})[0] != delivery.NeedNodeBuildNode)) { - t.Fatalf("has %v: tested x/pkg/a %v, unmet %+v, calls %v", has, tested, u.Unmet, r.calls) - } - } - // A placed ask naming its packages tests those alone and takes no other's kept verdict, also when the level would - // run whole (ADR 0552 §4). - for _, digests := range []map[string]string{r.digests, nil} { - r.calls, r.digests = nil, digests - steps := r.steps() - steps.Has = func(string) bool { return true } - out, err := RunAsk(context.Background(), steps, ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, Kept: true, Packages: []string{"x/pkg/a"}, - Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}})) - if err != nil { - t.Fatal(err) - } - u := out.(delivery.CheckAnswer).Levels[0] - if last := r.calls[len(r.calls)-1]; !strings.HasSuffix(last, `"packages":["x/pkg/a"],"workspace":"/w"}`) || len(u.Kept) != 0 || len(u.Tests) != 1 { - t.Fatalf("digests %v: calls %v, kept %+v: want x/pkg/a alone tested, nothing taken kept", digests != nil, r.calls, u.Kept) - } - } -} - -// ADR 0535 §2, §4: a merge that changes the build module's own code is judged by the candidate's runner: its digest says -// the candidate ran, and every package runs with nothing kept; an installed module that ignores the runner is planned as -// any other, the runner dropped. A digest that does not cover the fetch's siblings runs the level whole too. -func TestRunAskRunsWholeUnderTheRunnerOrWithoutTheSiblings(t *testing.T) { - for _, tc := range []struct{ judged, want string }{ - {"candidate 999", `digest {"level":"unit","runner":"workspace","workspace":"/w"} -test {"level":"unit","runner":"workspace","workspace":"/w"}`}, - {"installed", `digest {"level":"unit","runner":"workspace","workspace":"/w"}`}, - } { - r := &recorder{fetch: map[string]any{"workspace": "/w", "changed": []string{"modules/mesh-build-go/x.go"}}, - digests: map[string]string{"x/pkg/a": "d-kept"}, tests: map[string]bool{}} - steps := r.steps() - digest := steps.Digest - steps.Digest = func(ctx context.Context, args map[string]any) (any, error) { - _, _ = digest(ctx, args) - return map[string]any{"level": "unit", "packages": r.digests, "judged_by": tc.judged}, nil - } - out, err := RunAsk(context.Background(), steps, ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, Kept: true, - Runner: "workspace", RunnerWhen: "modules/mesh-build-go/", Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}})) - if err != nil { - t.Fatal(err) - } - if got := strings.Join(r.calls[1:], "\n"); got != tc.want { - t.Errorf("judged by %s: calls\n%s\nwant\n%s", tc.judged, got, tc.want) - } - l := out.(delivery.CheckAnswer).Levels[0] - if tc.judged == "installed" && (len(l.Kept) != 1 || l.Judged != "") { - t.Errorf("an installed module's level %+v, want the kept verdict taken", l) - } - if tc.judged != "installed" && (len(l.Kept) != 0 || l.Digest != nil || l.Judged != tc.judged) { - t.Errorf("the candidate's level %+v, want nothing kept and the program named", l) - } - } - r := &recorder{fetch: map[string]any{"workspace": "/w"}, digests: map[string]string{"x/pkg/a": "d-kept"}, tests: map[string]bool{}} - out, err := RunAsk(context.Background(), r.steps(), ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, Kept: true, - Fetch: map[string]any{"repository": "novox/x", "commit": runCommit, "beside": []any{map[string]any{"repository": "novox/mesh-sdk", "commit": runCommit}}}})) - if err != nil { - t.Fatal(err) - } - if l := out.(delivery.CheckAnswer).Levels[0]; len(l.Kept) != 0 || l.Digest != nil { - t.Fatalf("a digest without the siblings kept %+v", l) - } -} - -// A Node check tests each module directory, the one whose tests changed first; a conflict ends the ask with the fetch -// alone, a verdict for the asker. -func TestRunAskChecksNodeModulesAndConflicts(t *testing.T) { - r := &recorder{fetch: map[string]any{"workspace": "/w", "modules": []string{"a", "b"}, "changed": []string{"b/test/x.test.ts"}}, - tests: map[string]bool{"a": true, "b": true}} - s := r.steps() - s.Digest = nil - if _, err := RunAsk(context.Background(), s, ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}})); err != nil { - t.Fatal(err) - } - if fmt.Sprint(r.calls[1:]) != `[test {"level":"unit","module":"b","workspace":"/w"} test {"level":"unit","module":"a","workspace":"/w"}]` { - t.Fatalf("calls %v", r.calls) - } - r = &recorder{fetch: map[string]any{"workspace": "/w", "conflicts": []string{"go.mod"}}} - out, err := RunAsk(context.Background(), r.steps(), ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}})) - if err != nil || len(r.calls) != 1 || len(out.(delivery.CheckAnswer).Levels) != 0 { - t.Fatalf("a conflict: %v %v", err, r.calls) - } -} - -// Issue 1289: the packages whose tests changed run first. Since 2026-11-02 (the operator: "A failed check shows only its -// first fault") a failure among them ends nothing: every package of the level and every level still runs, so one check -// names every failing test (issue 1921). A Node check does the same with its module directories. -func TestRunAskRunsEveryPackageAndLevelAfterAFailure(t *testing.T) { - for _, row := range []struct { - name string - changed []string - tests map[string]bool - want []string // the packages tested at each level, in order - }{ - {"the change's own test fails: the rest still runs", []string{"pkg/b/b_test.go"}, map[string]bool{"x/pkg/a": false, "x/pkg/b": false}, []string{"x/pkg/b", "x/pkg/a"}}, - {"the change's own test passes: the rest runs, also after a failure", []string{"pkg/b/b_test.go"}, map[string]bool{"x/pkg/a": false, "x/pkg/b": true, "x/pkg/c": false}, []string{"x/pkg/b", "x/pkg/a x/pkg/c"}}, - {"no test changed: every failure is named", nil, map[string]bool{"x/pkg/a": false, "x/pkg/b": false}, []string{"x/pkg/a x/pkg/b"}}, - } { - r := &recorder{fetch: map[string]any{"workspace": "/w", "changed": row.changed}, tests: row.tests, - digests: map[string]string{"x/pkg/a": "d-a", "x/pkg/b": "d-b"}} - if _, ok := row.tests["x/pkg/c"]; ok { - r.digests["x/pkg/c"] = "d-c" - } - out, err := RunAsk(context.Background(), r.steps(), ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit, delivery.LevelIntegration}, - Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}})) - if err != nil { - t.Fatal(err) - } - var got []string - for _, c := range r.calls[1:] { - if strings.HasPrefix(c, "test ") { - var a struct{ Packages []string } - _ = json.Unmarshal([]byte(strings.TrimPrefix(c, "test ")), &a) - got = append(got, strings.Join(a.Packages, " ")) - } - } - if want := append(slices.Clone(row.want), row.want...); fmt.Sprint(got) != fmt.Sprint(want) { - t.Errorf("%s: tested %v, want %v", row.name, got, want) - } - if a := out.(delivery.CheckAnswer); len(a.Levels) != 2 || len(a.Levels[0].Tests) != len(row.want) { - t.Errorf("%s: levels %+v, want both, each with every test result", row.name, a.Levels) - } - } - - r := &recorder{fetch: map[string]any{"workspace": "/w", "modules": []string{"a", "b"}, "changed": []string{"b/test/x.test.ts"}}, - tests: map[string]bool{"a": true, "b": false}} - s := r.steps() - s.Digest = nil - out, err := RunAsk(context.Background(), s, ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit, delivery.LevelIntegration}, - Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}})) - if err != nil { - t.Fatal(err) - } - if a := out.(delivery.CheckAnswer); len(r.calls) != 5 || len(a.Levels) != 2 || len(a.Levels[0].Tests) != 2 { - t.Fatalf("calls %v, levels %+v: want b then a at each of the two levels", r.calls, a.Levels) - } -} diff --git a/go/buildstep/blob.go b/go/buildstep/blob.go deleted file mode 100644 index 127c6ac..0000000 --- a/go/buildstep/blob.go +++ /dev/null @@ -1,170 +0,0 @@ -package buildstep - -import ( - "bytes" - "context" - "crypto/sha256" - "encoding/hex" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "strings" - "syscall" - "time" -) - -// RegistryWait is how long a push waits for an artifact store that refuses connections, longer than its nightly -// collection holds it still (about a minute and a half, issue 457); RegistryFirstPause is the first pause, doubling -// to RegistryMostPause. Variables so a test shortens them. -var ( - RegistryWait = 5 * time.Minute - RegistryFirstPause = time.Second - RegistryMostPause = 10 * time.Second -) - -// BlobReference is where a pushed bundle or archive is, as a done key names it and the controller delivers from -// (novox/hq ADR 0498 clause 1): artifact-store:////blobs/, the store's address left out -// (issue 102). -func BlobReference(module, artifact, digest string) string { - return "artifact-store://" + module + "/" + artifact + "/blobs/" + digest -} - -// PushBlob puts body into the artifact store at registry (host:port) as a blob of repository (/) by -// its digest (`sha256:` and hex), from the build module's own process, after the build step ended (novox/hq ADR 0498 -// clauses 1 and 2): a blob the store already holds is done without an upload; otherwise it is uploaded (POST -// blobs/uploads/, then PUT with the digest). A store refusing connections is waited out up to RegistryWait; any other -// refusal fails, naming the store's status and its own words, for the caller's `not stored:` error. Bytes that are not -// their digest are refused before anything is sent. -func PushBlob(ctx context.Context, client *http.Client, registry, repository, digest string, body []byte) error { - sum := sha256.Sum256(body) - if got := "sha256:" + hex.EncodeToString(sum[:]); got != digest { - return fmt.Errorf("the blob is %s, not the digest %s it was to be pushed as", got, digest) - } - if client == nil { - client = http.DefaultClient - } - base := "http://" + registry + "/v2/" + repository + "/blobs/" - var present bool - if err := waitForStore(ctx, registry, func() error { - var err error - present, err = blobPresent(ctx, client, base+digest) - return err - }); err != nil { - return err - } - if present { - return nil - } - var location string - if err := waitForStore(ctx, registry, func() error { - var err error - location, err = startUpload(ctx, client, base+"uploads/") - return err - }); err != nil { - return err - } - target, err := url.Parse(base) - if err != nil { - return err - } - loc, err := target.Parse(location) - if err != nil { - return fmt.Errorf("the store answered the upload's location %q, which is not a URL: %w", location, err) - } - q := loc.Query() - q.Set("digest", digest) - loc.RawQuery = q.Encode() - req, err := http.NewRequestWithContext(ctx, http.MethodPut, loc.String(), bytes.NewReader(body)) - if err != nil { - return err - } - req.Header.Set("Content-Type", "application/octet-stream") - resp, err := client.Do(req) - if err != nil { - return fmt.Errorf("putting %s of %s: %w", digest, repository, err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusCreated { - return storeSaid(resp, "putting "+digest+" of "+repository) - } - return nil -} - -func blobPresent(ctx context.Context, client *http.Client, at string) (bool, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodHead, at, nil) - if err != nil { - return false, err - } - resp, err := client.Do(req) - if err != nil { - return false, err - } - resp.Body.Close() - switch resp.StatusCode { - case http.StatusOK: - return true, nil - case http.StatusNotFound: - return false, nil - } - return false, fmt.Errorf("asking the store for %s: it answered %s", at, resp.Status) -} - -func startUpload(ctx context.Context, client *http.Client, at string) (string, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodPost, at, nil) - if err != nil { - return "", err - } - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusAccepted { - return "", storeSaid(resp, "starting an upload at "+at) - } - location := resp.Header.Get("Location") - if location == "" { - return "", fmt.Errorf("starting an upload at %s: the store named no location", at) - } - return location, nil -} - -// storeSaid is the store's refusal: its status and the first of its own words. -func storeSaid(resp *http.Response, what string) error { - words, _ := io.ReadAll(io.LimitReader(resp.Body, 2048)) - return fmt.Errorf("%s: the store answered %s: %s", what, resp.Status, strings.TrimSpace(string(words))) -} - -// refused is whether an error is a connection refused. -func refused(err error) bool { - return err != nil && (errors.Is(err, syscall.ECONNREFUSED) || strings.Contains(err.Error(), "connection refused")) -} - -// waitForStore runs try, and while it fails because the store refuses connections, tries again with a growing pause -// until RegistryWait has passed. A refusal is the one error waited for: nothing was sent, so trying again does nothing -// twice. -func waitForStore(ctx context.Context, registry string, try func() error) error { - err := try() - if !refused(err) { - return err - } - started, pause := time.Now(), RegistryFirstPause - for { - left := RegistryWait - time.Since(started) - if left <= 0 { - return fmt.Errorf("the artifact store at %s refused every connection for %s, longer than its nightly collection holds it still, so it is down, not paused: %w", - registry, time.Since(started).Round(time.Millisecond), err) - } - select { - case <-ctx.Done(): - return fmt.Errorf("stopped while waiting for the artifact store at %s: %w (last: %v)", registry, ctx.Err(), err) - case <-time.After(min(pause, left)): - } - pause = min(pause*2, RegistryMostPause) - if err = try(); !refused(err) { - return err - } - } -} diff --git a/go/buildstep/blob_test.go b/go/buildstep/blob_test.go deleted file mode 100644 index b77afb0..0000000 --- a/go/buildstep/blob_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package buildstep - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "io" - "net" - "net/http" - "net/http/httptest" - "strings" - "sync" - "testing" - "time" - - "git.novox.be/novox/mesh-sdk/go/delivery" -) - -func digestOfBytes(b []byte) string { - h := sha256.Sum256(b) - return "sha256:" + hex.EncodeToString(h[:]) -} - -// registry is an artifact store as far as a blob push needs one: HEAD a blob, start an upload, PUT it with its digest. -type registry struct { - mu sync.Mutex - blobs map[string][]byte - calls []string - put int // the status a PUT answers, 201 when 0 -} - -func (r *registry) ServeHTTP(w http.ResponseWriter, req *http.Request) { - r.mu.Lock() - defer r.mu.Unlock() - r.calls = append(r.calls, req.Method+" "+req.URL.Path) - switch { - case req.Method == http.MethodHead && strings.Contains(req.URL.Path, "/blobs/sha256:"): - if _, ok := r.blobs[req.URL.Path[strings.LastIndex(req.URL.Path, "/")+1:]]; ok { - w.WriteHeader(http.StatusOK) - return - } - w.WriteHeader(http.StatusNotFound) - case req.Method == http.MethodPost && strings.HasSuffix(req.URL.Path, "/blobs/uploads/"): - w.Header().Set("Location", strings.TrimSuffix(req.URL.Path, "/")+"/u1?state=x") - w.WriteHeader(http.StatusAccepted) - case req.Method == http.MethodPut && strings.Contains(req.URL.Path, "/blobs/uploads/u1"): - if r.put != 0 { - w.WriteHeader(r.put) - _, _ = io.WriteString(w, `{"errors":[{"code":"DIGEST_INVALID","message":"provided digest did not match uploaded content"}]}`) - return - } - body, _ := io.ReadAll(req.Body) - d := req.URL.Query().Get("digest") - if req.URL.Query().Get("state") != "x" || d != digestOfBytes(body) { - w.WriteHeader(http.StatusBadRequest) - return - } - r.blobs[d] = body - w.WriteHeader(http.StatusCreated) - default: - w.WriteHeader(http.StatusMethodNotAllowed) - } -} - -// A done build's bundle or archive is pushed to the artifact store by its build module (novox/hq ADR 0498 clauses 1 -// and 2): a blob the store already holds is done with no upload; one it lacks is uploaded by digest; its reference is -// the artifact-store form the builder's key and the controller check. -func TestABlobIsPushedOnceByItsDigest(t *testing.T) { - reg := ®istry{blobs: map[string][]byte{}} - srv := httptest.NewServer(reg) - defer srv.Close() - addr := strings.TrimPrefix(srv.URL, "http://") - body := []byte("the packed bundle") - d := digestOfBytes(body) - if err := PushBlob(context.Background(), srv.Client(), addr, "mail/code", d, body); err != nil { - t.Fatal(err) - } - if string(reg.blobs[d]) != string(body) { - t.Fatalf("the store holds %q", reg.blobs[d]) - } - reg.calls = nil - if err := PushBlob(context.Background(), srv.Client(), addr, "mail/code", d, body); err != nil { - t.Fatal(err) - } - if len(reg.calls) != 1 || !strings.HasPrefix(reg.calls[0], "HEAD ") { - t.Fatalf("a blob the store holds was uploaded again: %v", reg.calls) - } - ref := BlobReference("mail", "code", d) - if ref != "artifact-store://mail/code/blobs/"+d { - t.Fatalf("BlobReference = %q", ref) - } - b := delivery.BuilderBuild{State: delivery.BuildDone, Manifest: strings.Repeat("4", 64), - Artifacts: []delivery.BuiltArtifact{{Name: "code", Kind: "bundle", Digest: d, Reference: ref}}} - if _, err := delivery.EncodeBuilderBuild(b); err != nil { - t.Fatalf("the key refuses the reference BlobReference gives: %v", err) - } -} - -// A store that refuses the upload fails the push naming the store's own words; bytes that are not the digest are -// refused before anything is sent. -func TestAPushTheStoreRefusesSaysTheStoresWords(t *testing.T) { - reg := ®istry{blobs: map[string][]byte{}, put: http.StatusBadRequest} - srv := httptest.NewServer(reg) - defer srv.Close() - addr := strings.TrimPrefix(srv.URL, "http://") - body := []byte("x") - err := PushBlob(context.Background(), srv.Client(), addr, "mail/code", digestOfBytes(body), body) - if err == nil || !strings.Contains(err.Error(), "provided digest did not match") || !strings.Contains(err.Error(), "400") { - t.Fatalf("a refused upload said %v", err) - } - reg.calls = nil - err = PushBlob(context.Background(), srv.Client(), addr, "mail/code", digestOfBytes([]byte("other")), body) - if err == nil || len(reg.calls) != 0 { - t.Fatalf("bytes that are not their digest: %v, calls %v", err, reg.calls) - } -} - -// A store refusing connections (its nightly collection holds it still) is waited out, up to RegistryWait (issue 457). -func TestAPushWaitsOutAStoreRefusingConnections(t *testing.T) { - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - addr := l.Addr().String() - l.Close() // refusing now - reg := ®istry{blobs: map[string][]byte{}} - go func() { - time.Sleep(300 * time.Millisecond) - l2, err := net.Listen("tcp", addr) - if err != nil { - return - } - _ = http.Serve(l2, reg) - }() - beforeWait, beforePause := RegistryWait, RegistryFirstPause - RegistryWait, RegistryFirstPause = 3*time.Second, 100*time.Millisecond - defer func() { RegistryWait, RegistryFirstPause = beforeWait, beforePause }() - body := []byte("late") - if err := PushBlob(context.Background(), http.DefaultClient, addr, "mail/code", digestOfBytes(body), body); err != nil { - t.Fatalf("a store that came back was not waited for: %v", err) - } - - l3, _ := net.Listen("tcp", "127.0.0.1:0") - gone := l3.Addr().String() - l3.Close() - RegistryWait = 300 * time.Millisecond - err = PushBlob(context.Background(), http.DefaultClient, gone, "mail/code", digestOfBytes(body), body) - if err == nil || !strings.Contains(err.Error(), "refused every connection") { - t.Fatalf("a store that stayed down said %v", err) - } -} diff --git a/go/buildstep/buildhours.go b/go/buildstep/buildhours.go deleted file mode 100644 index 6e3dee4..0000000 --- a/go/buildstep/buildhours.go +++ /dev/null @@ -1,129 +0,0 @@ -package buildstep - -import ( - "encoding/json" - "fmt" - "os" - "strings" - "time" -) - -// **A node may build only within its build hours** (the operator, 2026-10-26 21:18: the laptop builds only outside -// office hours, "from 18 o'clock until 6 in the morning"). A build module's `build-hours` setting is the node's window -// in its local time, a config value set on each node's layer of mesh-config (ADR 0567 §12: no repository names a node); -// outside it the capacity judged by is 0: steps running finish, no new one is taken. An empty value builds at any hour. -// The puller judges it at every judgement and wakes on one timer to the window's next edge (a clock edge, not a poll of -// another part). - -// BuildHoursSetting is the build modules' setting of the node's build window: HH:MM-HH:MM in the node's local time; an -// end before its start crosses midnight (18:00-06:00). -const BuildHoursSetting = "build-hours" - -// BuildWindow is one node's build hours: minutes after local midnight, Start inclusive, End exclusive. -type BuildWindow struct { - Start, End int -} - -// String is the window as the setting writes it. -func (w BuildWindow) String() string { - return fmt.Sprintf("%02d:%02d-%02d:%02d", w.Start/60, w.Start%60, w.End/60, w.End%60) -} - -// Open says whether t, in its own location, falls in the window. -func (w BuildWindow) Open(t time.Time) bool { - m := t.Hour()*60 + t.Minute() - if w.Start < w.End { - return m >= w.Start && m < w.End - } - return m >= w.Start || m < w.End -} - -// NextEdge is the first time after t, in t's location, at which the window opens or closes. -func (w BuildWindow) NextEdge(t time.Time) time.Time { - var next time.Time - for day := 0; day <= 1; day++ { - for _, m := range []int{w.Start, w.End} { - e := time.Date(t.Year(), t.Month(), t.Day()+day, m/60, m%60, 0, 0, t.Location()) - if e.After(t) && (next.IsZero() || e.Before(next)) { - next = e - } - } - } - return next -} - -// ParseBuildWindow reads a build-hours value, HH:MM-HH:MM: nil when it is empty. -func ParseBuildWindow(setting string) (*BuildWindow, error) { - setting = strings.TrimSpace(setting) - if setting == "" { - return nil, nil - } - from, to, ok := strings.Cut(setting, "-") - if !ok { - return nil, fmt.Errorf("the build hours %q are not HH:MM-HH:MM", setting) - } - start, err := minuteOf(from) - if err != nil { - return nil, fmt.Errorf("the build hours %q: %v", setting, err) - } - end, err := minuteOf(to) - if err != nil { - return nil, fmt.Errorf("the build hours %q: %v", setting, err) - } - if start == end { - return nil, fmt.Errorf("the build hours %q open and close at the same minute", setting) - } - return &BuildWindow{Start: start, End: end}, nil -} - -// minuteOf reads HH:MM as minutes after midnight. -func minuteOf(s string) (int, error) { - var h, m int - s = strings.TrimSpace(s) - if n, err := fmt.Sscanf(s, "%d:%d", &h, &m); err != nil || n != 2 || h < 0 || h > 23 || m < 0 || m > 59 || len(s) != 5 { - return 0, fmt.Errorf("%q is not a time HH:MM", s) - } - return h*60 + m, nil -} - -// NodeBuildHours is the node's build window, read from the module's settings file at path at every call, and why it is -// none when the value cannot be read. No path, no file value or an empty one is no window and no fault. A value that -// cannot be read is no window too, so the node builds at any hour; the setting is a preference of hours, not a safety -// bound (a node that must never build has capacity 0). It is loud: the fault is said once per distinct fault through -// logf, and the puller writes it in the capacity record that build_usage shows. -func NodeBuildHours(path, node string, logf func(string, ...any)) func() (*BuildWindow, string) { - said := "" - say := func(why string) { - if logf != nil && why != said { - said = why - logf("%s, so %s builds at any hour", why, node) - } - } - fault := func(why string) (*BuildWindow, string) { - say(why) - return nil, why - } - return func() (*BuildWindow, string) { - if path == "" { - return nil, "" - } - raw, err := os.ReadFile(path) - if err != nil { - return fault(fmt.Sprintf("the setting %s cannot be read: %v", BuildHoursSetting, err)) - } - var s map[string]json.RawMessage - if err := json.Unmarshal(raw, &s); err != nil { - return fault(fmt.Sprintf("the settings file %s is not JSON: %v", path, err)) - } - setting := "" - if v, ok := s[BuildHoursSetting]; ok && json.Unmarshal(v, &setting) != nil { - return fault(fmt.Sprintf("the setting %s %s is not a string", BuildHoursSetting, v)) - } - w, err := ParseBuildWindow(setting) - if err != nil { - return fault(err.Error()) - } - said = "" - return w, "" - } -} diff --git a/go/buildstep/capacity.go b/go/buildstep/capacity.go index 5b4fad5..098f657 100644 --- a/go/buildstep/capacity.go +++ b/go/buildstep/capacity.go @@ -1,61 +1,7 @@ package buildstep -import ( - "encoding/json" - "fmt" - "os" -) - // CapacitySetting is the build modules' setting of the node's build capacity (novox/hq ADR 0525 §5, ADR 0567 §12): how // many asks the node takes at once, an integer config value set on each node's layer of mesh-config. No repository names // a node: each build module declares it with the default 0, which pulls nothing, and the controller (or mesh-config) // lays the node's value into the module's settings file. const CapacitySetting = "capacity" - -// NodeCapacity is the node's build capacity, read from the module's settings file at path at every call, so a change -// takes effect at the next pull. No path, no file value, or a value that is not a whole number of at least 0 gives 0 and -// a value that cannot be read says why: pulling on a setting nobody can read could build on a node that must not. -func NodeCapacity(path, node string, logf func(string, ...any)) func() int { - say := func(format string, args ...any) { - if logf != nil { - logf(format, args...) - } - } - return func() int { - n, err := ReadCapacity(path) - if err != nil { - say("%v, so %s pulls nothing", err, node) - return 0 - } - return n - } -} - -// ReadCapacity is the capacity the settings file at path holds, and why it cannot be read when it cannot (then 0). -func ReadCapacity(path string) (int, error) { - if path == "" { - return 0, nil - } - raw, err := os.ReadFile(path) - if err != nil { - return 0, fmt.Errorf("the build capacity cannot be read: %w", err) - } - var s map[string]json.RawMessage - if err := json.Unmarshal(raw, &s); err != nil { - return 0, fmt.Errorf("the settings file %s is not JSON: %w", path, err) - } - return capacityOf(s[CapacitySetting]) -} - -// capacityOf is the capacity a config value holds: absent or null is 0; anything but a whole number of at least 0 is -// refused, naming it. -func capacityOf(value json.RawMessage) (int, error) { - if len(value) == 0 || string(value) == "null" { - return 0, nil - } - var n int - if err := json.Unmarshal(value, &n); err != nil || n < 0 { - return 0, fmt.Errorf("the build capacity %s is not a whole number of asks at once, 0 or more", value) - } - return n, nil -} diff --git a/go/buildstep/capacity_cpu.go b/go/buildstep/capacity_cpu.go deleted file mode 100644 index 413bfb9..0000000 --- a/go/buildstep/capacity_cpu.go +++ /dev/null @@ -1,141 +0,0 @@ -package buildstep - -import ( - "encoding/json" - "fmt" - "math" - "os" - "runtime" - "strconv" - "strings" - - "git.novox.be/novox/mesh-sdk/go/buildcpu" - "git.novox.be/novox/mesh-sdk/go/delivery" -) - -// **A build seat's capacity follows the CPU its node gives builds** (issue 1806's follow-up; issue 1890, 2026-10-26 -// 19:41: under the 50 % cap the workstation's mesh-build-go kept capacity 3, three full mesh-catalog checks shared 12 cores, and -// each step ran past the 30-minute step bound with no test run). The configured capacity is an upper bound; the node's -// build slice bounds it again by the cores a step needs: floor(the slice's quota in cores / cores-per-step), one at -// least. The quota is read live from the cgroup at every judgement, so a changed cap is followed at the next take -// without a restart, and the result is said (the seat's current verb, the status tools, `capacity.` in the pauses -// state that the delivery view's build_usage reads). - -// CoresPerStepSetting is a build module's setting of the processors one of its steps needs: a number above zero, the -// same on every node (a step's need is the module's, not the node's). Each build module declares it with its own default -// (DefaultCoresPerStep). -const CoresPerStepSetting = "cores-per-step" - -// DefaultCoresPerStep are the build modules' defaults by language, from measured steps (2026-10-26): a full mesh-catalog -// check on mesh-build-go takes what it is given (three at once took 965 % of the workstation's 1200 % slice, about 4 cores each, -// and ran past the 30-minute step bound), so a Go step is given 6, half again what starved; a Node step (tsc, esbuild, a -// test runner: median 15 s) and an image build (buildah, its RUN lines mostly fetches) need 2. -var DefaultCoresPerStep = map[string]float64{delivery.AskGo: 6, delivery.AskNode: 2, delivery.AskImage: 2} - -// Quota is the build slice's CPU quota as this process's cgroup holds it, in percent of one processor (1200 is 12 -// cores), and where it was read: the cgroup's cpu.max (buildcpu.ReadInForce, the tighter of its unit's and its slice's), -// else the share the composer gave (buildcpu.ShareWord) over the machine's processors, else 0 and why none is known. -func Quota() (percent int, from string) { - f := buildcpu.ReadInForce() - if f.Quota > 0 { - path := f.Slice.Path - if path == "" { - path = f.Cgroup - } - return f.Quota, "the cpu.max of " + path - } - if n, err := strconv.Atoi(strings.TrimSpace(os.Getenv(buildcpu.ShareWord))); err == nil && n >= 1 && n <= 100 { - return buildcpu.Quota(n, runtime.NumCPU()), fmt.Sprintf("the share %d%% the composer gave (%s), of %d processors; no cpu.max is read", n, buildcpu.ShareWord, runtime.NumCPU()) - } - return 0, "no CPU quota: neither a cpu.max on its cgroup nor a share from the composer" -} - -// EffectiveCapacity is the capacity a seat's holder judges by, from its configured capacity, its node's quota (percent of -// one processor, 0 when none is known) and the cores one of its steps needs. Without a quota it is the configured one; -// with one, min(configured, floor(quota cores / coresPerStep)), and one at least while the configured one is above -// zero. A coresPerStep of zero or less is taken as 1. -func EffectiveCapacity(configured, quotaPercent int, quotaFrom string, coresPerStep float64) delivery.Capacity { - if coresPerStep <= 0 || math.IsNaN(coresPerStep) || math.IsInf(coresPerStep, 0) { - coresPerStep = 1 - } - c := delivery.Capacity{Configured: configured, Effective: configured, QuotaFrom: quotaFrom, CoresPerStep: coresPerStep} - switch { - case configured <= 0: - c.Effective = 0 - c.Why = fmt.Sprintf("its configured capacity is %d, so it pulls nothing", configured) - return c - case quotaPercent <= 0: - c.Why = fmt.Sprintf("its configured capacity %d; %s, so the CPU bounds nothing", configured, quotaFrom) - return c - } - c.QuotaCores = float64(quotaPercent) / 100 - c.ByCPU = int(math.Floor(c.QuotaCores/coresPerStep + 1e-9)) - if c.ByCPU < 1 { - c.ByCPU = 1 - } - byCPU := fmt.Sprintf("%g cores of the build slice (%s) / %g cores a step = %d", c.QuotaCores, quotaFrom, coresPerStep, c.ByCPU) - if c.ByCPU < configured { - c.Effective = c.ByCPU - c.Why = fmt.Sprintf("%d, bounded by the CPU: %s, under its configured %d", c.Effective, byCPU, configured) - } else { - c.Why = fmt.Sprintf("%d, its configured capacity; the CPU allows %s", configured, byCPU) - } - return c -} - -// NodeCoresPerStep is the module's cores-per-step, read from its settings file at path at every call so a change is -// followed at the next judgement; fallback when there is no path, no value or one that is not a number above zero, said -// once per distinct fault through logf. -func NodeCoresPerStep(path string, fallback float64, logf func(string, ...any)) func() float64 { - said := "" - say := func(why string) { - if logf != nil && why != said { - said = why - logf("%s, so a step is taken to need %g cores", why, fallback) - } - } - return func() float64 { - if path == "" { - return fallback - } - raw, err := os.ReadFile(path) - if err != nil { - say(fmt.Sprintf("the setting %s cannot be read: %v", CoresPerStepSetting, err)) - return fallback - } - var s map[string]json.RawMessage - if err := json.Unmarshal(raw, &s); err != nil { - say(fmt.Sprintf("the settings file %s is not JSON: %v", path, err)) - return fallback - } - v, ok := s[CoresPerStepSetting] - if !ok { - return fallback // not declared yet (a module before its manifest names it): the default, nothing to say - } - n, err := coresOf(v) - if err != nil { - say(fmt.Sprintf("the setting %s %s: %v", CoresPerStepSetting, v, err)) - return fallback - } - said = "" - return n - } -} - -// coresOf reads a cores-per-step value: a JSON number or a string holding one, above zero. -func coresOf(v json.RawMessage) (float64, error) { - var n float64 - if err := json.Unmarshal(v, &n); err != nil { - var s string - if err := json.Unmarshal(v, &s); err != nil { - return 0, fmt.Errorf("is neither a number nor a string") - } - if n, err = strconv.ParseFloat(strings.TrimSpace(s), 64); err != nil { - return 0, fmt.Errorf("is not a number") - } - } - if n <= 0 || math.IsNaN(n) || math.IsInf(n, 0) { - return 0, fmt.Errorf("is not above zero") - } - return n, nil -} diff --git a/go/buildstep/capacity_test.go b/go/buildstep/capacity_test.go deleted file mode 100644 index aef58c6..0000000 --- a/go/buildstep/capacity_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package buildstep - -import ( - "os" - "path/filepath" - "strings" - "testing" - "time" -) - -// 1887/A7: the node's build capacity is the build module's setting, read at every pull: the control-node at 0 pulls -// nothing, a change takes effect at the next read, and a setting that cannot be read pulls nothing. -// ADR 0553 §4: a step's threads are the composer's share over the module's own capacity; without the word, none. And -// issue 1806's follow-up: the capacity judged by is the configured one bounded by the build slice's cores over the cores -// a step needs (the three build nodes as they stand on 2026-10-26), and cores-per-step is read from the settings file. -func TestNodeCapacityAndLimits(t *testing.T) { - // ADR 0567 §12: the capacity is the node's integer, its default 0, and no default names a node. - if got := NodeCapacity("", "laptop", nil)(); got != 0 { - t.Errorf("no settings file = %d, want 0", got) - } - file := filepath.Join(t.TempDir(), "settings.json") - capacity := NodeCapacity(file, "anchor", nil) - for value, want := range map[string]int{`{"capacity":1}`: 1, `{"capacity":4}`: 4, `{}`: 0, `{"capacity":null}`: 0, - `{"capacity":"anchor=3"}`: 0, `{"capacity":-1}`: 0, `{"capacity":1.5}`: 0} { - os.WriteFile(file, []byte(value), 0o600) - if got := capacity(); got != want { - t.Errorf("%s = %d, want %d", value, got, want) - } - } - if got := NodeCapacity(filepath.Join(t.TempDir(), "none"), "anchor", nil)(); got != 0 { - t.Errorf("a missing file = %d, want 0", got) - } - t.Setenv("MESH_BUILD_CPU_SHARE", "") - if l := NodeLimits(3); l.Threads != 0 || l.Slots != 3 { - t.Errorf("without the word: %+v", l) - } - t.Setenv("MESH_BUILD_CPU_SHARE", "75") - if l := NodeLimits(3); l.Percent != 75 || l.Slots != 3 || l.Threads < 1 { - t.Errorf("from the word: %+v", l) - } - for _, c := range []struct { - name string - configured, quota int - cores float64 - effective, byCPU int - }{ - {"shanks: 12 cores, configured 3", 3, 1200, 6, 2, 2}, - {"ace: 18 cores, configured 2", 2, 1800, 6, 2, 3}, - {"g14: 8 cores, configured 2", 2, 800, 6, 1, 1}, - {"node on shanks: 12 cores at 2", 3, 1200, 2, 3, 6}, - {"no quota: the configured one", 3, 0, 6, 3, 0}, - {"configured 0 pulls nothing", 0, 1200, 6, 0, 0}, - {"a quota under one step still builds one", 2, 100, 6, 1, 1}, - {"no cores a step is one", 2, 150, 0, 1, 1}, - } { - got := EffectiveCapacity(c.configured, c.quota, "test", c.cores) - if got.Effective != c.effective || got.ByCPU != c.byCPU || got.Why == "" { - t.Errorf("%s: %+v, want effective %d, by CPU %d", c.name, got, c.effective, c.byCPU) - } - } - file = filepath.Join(t.TempDir(), "settings.json") - var said []string - cores := NodeCoresPerStep(file, 6, func(f string, a ...any) { said = append(said, f) }) - for _, c := range []struct { - file string - want float64 - }{{`{"capacity":2}`, 6}, {`{"cores-per-step":4}`, 4}, {`{"cores-per-step":"3.5"}`, 3.5}, {`{"cores-per-step":0}`, 6}, {`{"cores-per-step":0}`, 6}} { - os.WriteFile(file, []byte(c.file), 0o600) - if got := cores(); got != c.want { - t.Errorf("cores-per-step from %s = %g, want %g", c.file, got, c.want) - } - } - if len(said) != 1 { - t.Errorf("a value not above zero is said once, said %d times", len(said)) - } - - // The operator, 2026-10-26 21:18: g14 builds only from 18:00 to 06:00, its local time. Outside its build hours the - // capacity judged by is 0, said with the window and when it opens; a node not named, or a bad value, builds at any hour. - // And a node its module proves cannot build (ace without /dev/net/tun, 2026-10-28) judges by 0 at every hour, saying - // why, so its asks go to a node that can. - cet := time.FixedZone("CET", 3600) - day := func(hh, mm int) time.Time { return time.Date(2026, 10, 26, hh, mm, 0, 0, cet) } - was := now - defer func() { now = was }() - for _, c := range []struct { - name, setting, node string - at time.Time - effective int - edge string // the window's next edge, HH:MM; "" for no window - bad bool - ineligible string // what the module's Eligible says; "" for eligible - }{ - {"the laptop in the office", "18:00-06:00", "laptop", day(10, 0), 0, "18:00", false, ""}, - {"the laptop in the evening", "18:00-06:00", "laptop", day(18, 0), 2, "06:00", false, ""}, - {"the laptop past midnight", "18:00-06:00", "laptop", day(5, 59), 2, "06:00", false, ""}, - {"the laptop at the close", "18:00-06:00", "laptop", day(6, 0), 0, "18:00", false, ""}, - {"a day window", "09:00-17:30", "anchor", day(17, 29), 2, "17:30", false, ""}, - {"no window", "", "anchor", day(10, 0), 2, "", false, ""}, - {"a bad time", "18:00-6", "laptop", day(10, 0), 2, "", true, ""}, - {"no span", "18:00-18:00", "laptop", day(10, 0), 2, "", true, ""}, - {"the old form naming a node", "laptop=18:00-06:00", "laptop", day(10, 0), 2, "", true, ""}, - {"a node without /dev/net/tun", "", "anchor", day(10, 0), 0, "", false, "/dev/net/tun cannot be opened: no such device"}, - {"not eligible within its hours", "18:00-06:00", "laptop", day(19, 0), 0, "06:00", false, "/dev/net/tun cannot be opened: no such device"}, - } { - w, err := ParseBuildWindow(c.setting) - if (err != nil) != c.bad { - t.Errorf("%s: parse %v", c.name, err) - } - if edge := ""; w != nil { - edge = w.NextEdge(c.at).Format("15:04") - if edge != c.edge { - t.Errorf("%s: next edge %s, want %s", c.name, edge, c.edge) - } - } else if c.edge != "" { - t.Errorf("%s: no window, want one", c.name) - } - file := filepath.Join(t.TempDir(), "settings.json") - os.WriteFile(file, []byte(`{"build-hours":"`+c.setting+`"}`), 0o600) - now = func() time.Time { return c.at } - p := &Puller{Language: "go", Node: c.node, Capacity: func() int { return 2 }, BuildHours: NodeBuildHours(file, c.node, nil), - Eligible: func() string { return c.ineligible }} - got := p.CapacityDetail() - outside := c.effective == 0 && c.ineligible == "" - if got.Effective != c.effective || got.OutsideHours != outside || (outside && !strings.Contains(got.Why, "outside its build hours 18:00-06:00 CET until 18:00")) { - t.Errorf("%s: %+v, want capacity %d", c.name, got, c.effective) - } - if got.Ineligible != c.ineligible || (c.ineligible != "" && !strings.HasPrefix(got.Why, "0, not eligible: "+c.ineligible+" (otherwise ")) { - t.Errorf("%s: not eligible is not said: %+v", c.name, got) - } - // A value that cannot be read builds at any hour, loudly: the record names the value and says why. - if c.bad && (!strings.Contains(got.BuildHoursFault, c.setting) || !strings.Contains(got.Why, "at any hour, because its build hours cannot be read")) { - t.Errorf("%s: a bad value is not said in the record: %+v", c.name, got) - } - } -} diff --git a/go/buildstep/conformance/captured/README.md b/go/buildstep/conformance/captured/README.md deleted file mode 100644 index 3c882c9..0000000 --- a/go/buildstep/conformance/captured/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# The conformance kit's captured runner output - -Every file here is the real output of a test runner, never written by hand (issue 1901: a fixture that agrees with the -mistake proves the mistake). The toolchains are the build nodes' own, as `mesh-build-go.build_go_toolchains` and -`mesh-build-node.build_node_toolchains` reported them on 2026-10-26: go1.27.3-X:nodwarf5 on every build node, node -v26.12.1 (npm 12.2.1) on shanks, novox and g14, and typescript 5.9.3 from mesh-build-node's toolchain/package.json. -To capture a case again, make its tiny project in a fresh directory as below and run the command shown; the kit's case -table (conformance.go) says what each failure must say. - -## go/ — `go test -json` - -- `passfirst.jsonl` and `subtests.jsonl` (2026-10-26, go1.27.3-X:nodwarf5, for issue 1901's thorough fix): in - `/tmp/1901k/go/`, `GOTOOLCHAIN=local GOFLAGS=-mod=mod go test -json -count=1 ./... > ../.jsonl 2>&1`. - - passfirst: module `example.com/passfirst`, `go 1.26`. Package `a` has `TestTheFirstPackagePasses` (empty). Package `b` - has `TestTheSecondPackagePassesFirst` (empty) and `TestTheSecondPackageFails`, which calls - `t.Logf("judged against the seat as it stands")` then `t.Fatalf("the seat has 2 holders, want 1")`: a passing package - and a passing test before the failing one. - - subtests: module `example.com/subtests`; `TestTheTable` runs `t.Run("passes", func(t *testing.T) {})` and - `t.Run("fails", func(t *testing.T) { t.Errorf("got 3 machines, want 4") })`: the parent fails only by its subtest. -- `subpanic.jsonl` (2026-10-26, go1.27.3-X:nodwarf5, review round 1 of issue 1901's fix), the same way: module - `example.com/subpanic`; `TestTable` runs `t.Run("passes", func(t *testing.T) {})` and `t.Run("panics", ...)`, which - writes to a nil map. go test -json files the subtest's panic under the parent `TestTable`. -- `logthenfatal.jsonl`, `panicking`, `timeout`, `deeptimeout`, `buildfail`, `exiting`, `killed`, `passthenfail`, - `mainexit`, `race`, `racemain` were captured for issue 1901's first and third meetings (2026-10-23 and 2026-10-25, - go1.27.3) in mesh-catalog's mesh-build-go (`testdata/captured/go-test-json-*.jsonl`, named there `go-test-json-pkg-` - and `go-test-json-log-then-fatal`) and moved here unchanged. From that README: each is the standard output and error of - `go test -json -count=1 ./...` (the timeouts with `-timeout 2s`; race and racemain with `-race`, CGO_ENABLED=1) in a - throwaway one-file module that fails without a test's own assertion: panicking (a nil map write in TestPanics), timeout - (TestWaitsForever sleeps past the timeout), deeptimeout (two parallel tests and sixty blocked goroutines past the - timeout, so the goroutine dump pushes the panic line out of the last 4 KB), buildfail (a type error in a test file), - exiting (os.Exit(3) in TestExits), killed (TestKilled sends itself SIGKILL), passthenfail (TestMain panics in a - goroutine after m.Run passed), mainexit (TestMain exits 2 after m.Run passed: go test prints "PASS" and then - "FAIL "), race (TestRacesOnTheSeat writes a package variable from itself and a goroutine), racemain (TestMain does - so after m.Run passed). logthenfatal is a test `TestThree` in package `x` that logs a line with t.Logf and then fails - with a multi-line t.Fatalf naming three subscriptions. - -## node/ — `npm test` running `node --test` under the TAP reporter - -Captured 2026-10-26 with node v26.12.1 and npm 12.2.1 (`/usr/bin/node`, `/usr/bin/npm`), in `/tmp/1901k/node/`, the -way mesh-build-node runs a test script: - - PATH=/usr/bin:$PATH NODE_OPTIONS="--test-reporter=tap --test-reporter-destination=/tmp/1901k/node/.tap" \ - npm test > ../.stdout 2> ../.stderr - -`.tap` is the report file, `.console` the stdout followed by the stderr. Each project's package.json is -`{"name":"","private":true,"type":"module","scripts":{"test":"node --test --experimental-strip-types 'test/*.test.ts'"}}` -(the script every TypeScript package of the catalogue and the SDK runs), with these test files: - -- two-files: `test/a.test.ts` holds `test('passes in the first file', () => {})`; `test/b.test.ts` holds - `test('passes in the second file first', () => {})` and `test('fails in the second file', ...)`, which logs - `judged against the seat as it stands` and asserts `assert.strictEqual(1 + 1, 3, 'one plus one is not three')`. -- suite: `test/a.test.ts` has `describe('the seat', ...)` with `it('passes inside', () => {})` and `it('fails inside', ...)` - asserting `assert.deepStrictEqual({ holders: 2 }, { holders: 1 })`. -- crash: `test/a.test.ts` holds `test('passes before the crash', () => {})`; `test/b.test.ts` holds - `test('throws after it returned', ...)`, which throws `new Error('the bus closed under the test')` in a `setTimeout` of - 1 ms, and `test('waits after the throw', ...)`, which waits 50 ms: node fails the file with an uncaught exception. -- logcrash (review round 1): `test/a.test.ts` holds `test('passes and logs', ...)`, which logs - `retrying after Error: the store was not ready yet`; `test/b.test.ts` is crash's. A passing test's console line - holding "Error" comes before the crash's own. -- timeout: `test/a.test.ts` holds `test('passes before the wait', () => {})` and - `test('waits for an answer that never comes', { timeout: 200 }, async () => { await new Promise(() => {}); })`. -- syntax: `test/a.test.ts` holds `test('passes in the first file', () => {})`; `test/b.test.ts` is - `import test from 'node:test';` and `test('never loads', () => {` (cut off): the file does not load. - -## tsc/ — the type-check - -`typeerror.console` (2026-10-26, typescript 5.9.3 under node v26.12.1): in `/tmp/1901k/tsc/typeerror`, `ok.ts` is -`export const holders: number = 1;` and `seat.ts` is `import { holders } from './ok.js'; export const name: string = holders;`; -the command is mesh-build-node's type-check line without a tsconfig.json: - - node /bin/tsc --noEmit --module NodeNext --moduleResolution NodeNext --target ES2022 --rootDir . ok.ts seat.ts - -and the file is its stdout followed by its stderr. diff --git a/go/buildstep/conformance/captured/go/buildfail.jsonl b/go/buildstep/conformance/captured/go/buildfail.jsonl deleted file mode 100644 index 2b7967d..0000000 --- a/go/buildstep/conformance/captured/go/buildfail.jsonl +++ /dev/null @@ -1,6 +0,0 @@ -{"ImportPath":"example.com/buildfail [example.com/buildfail.test]","Action":"build-output","Output":"# example.com/buildfail [example.com/buildfail.test]\n"} -{"ImportPath":"example.com/buildfail [example.com/buildfail.test]","Action":"build-output","Output":"./p_test.go:5:40: cannot use \"seat\" (untyped string constant) as int value in variable declaration\n"} -{"ImportPath":"example.com/buildfail [example.com/buildfail.test]","Action":"build-fail"} -{"Time":"2026-10-25T15:34:33.261137737+01:00","Action":"start","Package":"example.com/buildfail"} -{"Time":"2026-10-25T15:34:33.261255019+01:00","Action":"output","Package":"example.com/buildfail","Output":"FAIL\texample.com/buildfail [build failed]\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:33.261268099+01:00","Action":"fail","Package":"example.com/buildfail","Elapsed":0,"FailedBuild":"example.com/buildfail [example.com/buildfail.test]"} diff --git a/go/buildstep/conformance/captured/go/deeptimeout.jsonl b/go/buildstep/conformance/captured/go/deeptimeout.jsonl deleted file mode 100644 index 4018250..0000000 --- a/go/buildstep/conformance/captured/go/deeptimeout.jsonl +++ /dev/null @@ -1,417 +0,0 @@ -{"Time":"2026-10-25T15:35:18.991630993+01:00","Action":"start","Package":"example.com/deeptimeout"} -{"Time":"2026-10-25T15:35:18.993895188+01:00","Action":"run","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat"} -{"Time":"2026-10-25T15:35:18.993963909+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat","Output":"=== RUN TestHoldsTheSeat\n","OutputType":"frame"} -{"Time":"2026-10-25T15:35:18.99399484+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat","Output":"=== PAUSE TestHoldsTheSeat\n","OutputType":"frame"} -{"Time":"2026-10-25T15:35:18.9939982+01:00","Action":"pause","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat"} -{"Time":"2026-10-25T15:35:18.99400173+01:00","Action":"run","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus"} -{"Time":"2026-10-25T15:35:18.99400456+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"=== RUN TestWaitsForTheBus\n","OutputType":"frame"} -{"Time":"2026-10-25T15:35:18.99400838+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"=== PAUSE TestWaitsForTheBus\n","OutputType":"frame"} -{"Time":"2026-10-25T15:35:18.99401133+01:00","Action":"pause","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus"} -{"Time":"2026-10-25T15:35:18.99401441+01:00","Action":"cont","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat"} -{"Time":"2026-10-25T15:35:18.99401704+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat","Output":"=== CONT TestHoldsTheSeat\n","OutputType":"frame"} -{"Time":"2026-10-25T15:35:18.994074141+01:00","Action":"cont","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus"} -{"Time":"2026-10-25T15:35:18.994102612+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"=== CONT TestWaitsForTheBus\n","OutputType":"frame"} -{"Time":"2026-10-25T15:35:21.00935195+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"panic: test timed out after 2s\n"} -{"Time":"2026-10-25T15:35:21.00938383+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\trunning tests:\n"} -{"Time":"2026-10-25T15:35:21.009394391+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t\tTestHoldsTheSeat (2s)\n"} -{"Time":"2026-10-25T15:35:21.009398021+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t\tTestWaitsForTheBus (2s)\n"} -{"Time":"2026-10-25T15:35:21.009401801+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009406481+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 81 [running]:\n"} -{"Time":"2026-10-25T15:35:21.009409911+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"testing.(*M).startAlarm.func1()\n"} -{"Time":"2026-10-25T15:35:21.009413361+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2961 +0x34a\n"} -{"Time":"2026-10-25T15:35:21.009418341+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by time.goFunc\n"} -{"Time":"2026-10-25T15:35:21.009421781+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/time/sleep.go:182 +0x2d\n"} -{"Time":"2026-10-25T15:35:21.009424991+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009428341+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 1 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009431721+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"testing.tRunner.func1()\n"} -{"Time":"2026-10-25T15:35:21.009441811+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2144 +0x425\n"} -{"Time":"2026-10-25T15:35:21.009445511+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"testing.tRunner(0x282e9702008, 0x282e9646bc8)\n"} -{"Time":"2026-10-25T15:35:21.009449641+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2201 +0x123\n"} -{"Time":"2026-10-25T15:35:21.009453471+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"testing.runTests({0x55a45d, 0x17}, {0x55a45d, 0x17}, 0x282e95940f0, {0x6f19f0, 0x2, 0x2}, {0xc2afa2ca3b3c0c8d, 0x7737f168, ...})\n"} -{"Time":"2026-10-25T15:35:21.009462092+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2742 +0x510\n"} -{"Time":"2026-10-25T15:35:21.009467222+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"testing.(*M).Run(0x282e96581e0)\n"} -{"Time":"2026-10-25T15:35:21.009470662+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2602 +0x6af\n"} -{"Time":"2026-10-25T15:35:21.009473692+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"main.main()\n"} -{"Time":"2026-10-25T15:35:21.009476942+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t_testmain.go:48 +0x9b\n"} -{"Time":"2026-10-25T15:35:21.009480092+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009483242+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 7 [sleep]:\n"} -{"Time":"2026-10-25T15:35:21.009486502+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"time.Sleep(0xdf8475800)\n"} -{"Time":"2026-10-25T15:35:21.009490682+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/runtime/time.go:368 +0x165\n"} -{"Time":"2026-10-25T15:35:21.009493812+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat(0x282e9702248?)\n"} -{"Time":"2026-10-25T15:35:21.009498842+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:14 +0x9e\n"} -{"Time":"2026-10-25T15:35:21.009502242+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"testing.tRunner(0x282e9702248, 0x6d5e78)\n"} -{"Time":"2026-10-25T15:35:21.009505742+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2195 +0xea\n"} -{"Time":"2026-10-25T15:35:21.009509292+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by testing.(*T).Run in goroutine 1\n"} -{"Time":"2026-10-25T15:35:21.009512752+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2260 +0x4d4\n"} -{"Time":"2026-10-25T15:35:21.009515792+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009518623+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 8 [sleep]:\n"} -{"Time":"2026-10-25T15:35:21.009521303+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"time.Sleep(0xdf8475800)\n"} -{"Time":"2026-10-25T15:35:21.009524423+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/runtime/time.go:368 +0x165\n"} -{"Time":"2026-10-25T15:35:21.009531403+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestWaitsForTheBus(0x282e9702488?)\n"} -{"Time":"2026-10-25T15:35:21.009534943+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:19 +0x25\n"} -{"Time":"2026-10-25T15:35:21.009538403+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"testing.tRunner(0x282e9702488, 0x6d5e80)\n"} -{"Time":"2026-10-25T15:35:21.009541953+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2195 +0xea\n"} -{"Time":"2026-10-25T15:35:21.009545233+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by testing.(*T).Run in goroutine 1\n"} -{"Time":"2026-10-25T15:35:21.009548323+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/usr/lib/go/src/testing/testing.go:2260 +0x4d4\n"} -{"Time":"2026-10-25T15:35:21.009551343+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009554263+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 9 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009557523+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009561063+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009564513+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009571113+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009574233+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009577433+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 10 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009580823+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009584174+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009587504+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009590974+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009594004+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009597184+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 11 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009600394+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009603874+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009610954+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009614714+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009618104+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009621364+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 12 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009624704+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009628324+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009631954+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009635404+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009638734+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009641904+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 13 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009645325+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009648645+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009652065+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009655625+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009658885+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009663385+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 14 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009666735+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009670235+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009676385+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009680205+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009683315+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009686475+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 15 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009689785+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009697235+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009700795+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009704985+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009708085+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009711346+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 16 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009714586+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009717946+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009721676+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009725626+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009728866+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009732106+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 17 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009735376+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009738956+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009742326+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009746436+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009750666+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009754126+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 18 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009758286+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009761846+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009765776+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009769816+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009773167+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009780507+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 19 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009784207+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009788007+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009791627+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009796347+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009799427+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009802937+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 20 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009806667+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009809987+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009813827+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009818087+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009821397+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009824227+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 21 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009827607+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009831307+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009835167+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009839528+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009842728+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009845888+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 22 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009849168+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009852548+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009855958+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009863648+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009866938+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009870088+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 23 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009879828+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009883728+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009887608+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009891548+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009895238+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009898748+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 24 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009902349+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009907109+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009911519+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009915609+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009918939+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009922329+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 25 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009928329+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009931909+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.009935489+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.009939429+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.009942699+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.009946019+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 26 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.009949559+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.009953489+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.00996244+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.00996647+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.00996987+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.00997314+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 27 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.00997664+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.00998035+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.00998418+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.00998793+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.00999131+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.00999534+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 28 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.00999894+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.01000268+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.0100087+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.01001258+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.01001605+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.01002007+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 29 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.01002393+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010027531+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010031351+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010035521+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010038781+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010042281+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 30 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010046091+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010054261+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010057881+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010061781+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010064931+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010068261+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 31 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010072151+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010075981+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010079841+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010098802+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010103202+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010106382+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 32 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010109852+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010113242+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010116952+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010120672+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010124112+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010127162+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 33 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010130662+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010134442+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010138142+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010141972+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010145362+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010152262+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 34 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010156353+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010159913+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010163533+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010167403+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010170533+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010173953+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 35 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010177453+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010181033+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010184693+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010188583+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010191753+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010195033+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 36 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010198603+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010202393+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010206123+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010210583+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010213503+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010216914+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 37 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010220134+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010223644+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010227154+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010235154+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010238354+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010241754+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 38 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010270284+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010275144+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010278714+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010282825+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010285795+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010288935+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 39 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010292405+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010295915+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010299545+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010311835+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010315565+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010318975+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 40 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010322655+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010326225+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010329815+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010333745+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010337095+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010340515+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 41 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010344066+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010347706+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010355596+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010359046+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010362036+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010365076+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 42 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010367956+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010370906+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010375366+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010378386+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010381006+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010383666+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 43 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010386676+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010389786+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010392636+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010395746+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010398466+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010401166+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 44 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010404226+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010407186+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010410117+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010413237+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010416157+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010418897+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 45 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010423157+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010430107+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010433197+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010451207+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010454217+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010456967+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 46 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010459877+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010463967+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010467067+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010470597+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010473418+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010476108+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 47 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010478998+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010481958+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010484978+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010488258+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010490888+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010493628+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 48 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010496588+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010499498+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010502498+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010505768+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010508558+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010514148+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 49 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010517178+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010520238+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010523478+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010526818+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010529718+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010532508+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 50 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010535639+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010538749+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010541939+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010546689+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010549869+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010552989+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 51 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010556299+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010560079+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010563479+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010567189+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010570299+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010573079+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 52 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010576319+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010579599+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010583099+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010590519+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010594029+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010596989+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 53 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.01059986+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.01060433+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.01060789+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.01061136+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.01061393+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.01061667+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 54 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.01061984+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.0106227+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.01062566+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.01062876+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.01063131+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.01063428+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 55 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.01063719+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.01063997+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.01064278+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.01064582+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.01064836+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.01065114+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 56 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.01065437+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.01065724+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010671481+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010675671+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010678431+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010691171+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 57 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010694531+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010697641+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010700781+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010704001+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010706611+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010709421+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 58 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010712331+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010715351+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010718431+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010721691+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010724371+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010727362+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 59 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010730592+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010733662+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010736712+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010740162+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010742922+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010745772+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 60 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010752222+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010755402+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010758542+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010761822+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010764592+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010767992+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 61 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010771062+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010774282+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010779382+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010782942+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010785722+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010788573+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 62 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010791893+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010794943+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010798083+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010801683+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010804293+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010807283+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 63 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010810443+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010813553+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010816793+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010820233+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010823063+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010829493+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 64 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010832683+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010835953+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010839223+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010842463+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010845333+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010848423+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 65 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010851663+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010854964+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010858364+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010861714+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010864914+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010867984+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 66 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010870944+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010874114+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010877464+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010880504+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010883144+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010886194+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 67 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010889154+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010892234+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010895524+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010902154+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.010904984+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\n"} -{"Time":"2026-10-25T15:35:21.010907984+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"goroutine 68 [chan receive]:\n"} -{"Time":"2026-10-25T15:35:21.010911064+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"example.com/deeptimeout.TestHoldsTheSeat.func1()\n"} -{"Time":"2026-10-25T15:35:21.010914074+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x19\n"} -{"Time":"2026-10-25T15:35:21.010917365+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"created by example.com/deeptimeout.TestHoldsTheSeat in goroutine 7\n"} -{"Time":"2026-10-25T15:35:21.010920505+01:00","Action":"output","Package":"example.com/deeptimeout","Test":"TestWaitsForTheBus","Output":"\t/tmp/fix-1901b/deeptimeout/p_test.go:12 +0x37\n"} -{"Time":"2026-10-25T15:35:21.013209581+01:00","Action":"output","Package":"example.com/deeptimeout","Output":"FAIL\texample.com/deeptimeout\t2.021s\n","OutputType":"frame"} -{"Time":"2026-10-25T15:35:21.013258851+01:00","Action":"fail","Package":"example.com/deeptimeout","Elapsed":2.022} diff --git a/go/buildstep/conformance/captured/go/exiting.jsonl b/go/buildstep/conformance/captured/go/exiting.jsonl deleted file mode 100644 index ec4ce02..0000000 --- a/go/buildstep/conformance/captured/go/exiting.jsonl +++ /dev/null @@ -1,10 +0,0 @@ -{"Time":"2026-10-25T15:34:33.768742837+01:00","Action":"start","Package":"example.com/exiting"} -{"Time":"2026-10-25T15:34:33.773822287+01:00","Action":"run","Package":"example.com/exiting","Test":"TestFine"} -{"Time":"2026-10-25T15:34:33.773896608+01:00","Action":"output","Package":"example.com/exiting","Test":"TestFine","Output":"=== RUN TestFine\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:33.773940319+01:00","Action":"output","Package":"example.com/exiting","Test":"TestFine","Output":"--- PASS: TestFine (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:33.773945359+01:00","Action":"pass","Package":"example.com/exiting","Test":"TestFine","Elapsed":0} -{"Time":"2026-10-25T15:34:33.773952529+01:00","Action":"run","Package":"example.com/exiting","Test":"TestExits"} -{"Time":"2026-10-25T15:34:33.773956439+01:00","Action":"output","Package":"example.com/exiting","Test":"TestExits","Output":"=== RUN TestExits\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:33.773959829+01:00","Action":"output","Package":"example.com/exiting","Test":"TestExits","Output":" p_test.go:11: closing the store\n"} -{"Time":"2026-10-25T15:34:33.775158538+01:00","Action":"output","Package":"example.com/exiting","Output":"FAIL\texample.com/exiting\t0.006s\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:33.775186509+01:00","Action":"fail","Package":"example.com/exiting","Elapsed":0.006} diff --git a/go/buildstep/conformance/captured/go/killed.jsonl b/go/buildstep/conformance/captured/go/killed.jsonl deleted file mode 100644 index f4ba525..0000000 --- a/go/buildstep/conformance/captured/go/killed.jsonl +++ /dev/null @@ -1,7 +0,0 @@ -{"Time":"2026-10-25T15:34:34.328258934+01:00","Action":"start","Package":"example.com/killed"} -{"Time":"2026-10-25T15:34:34.331465435+01:00","Action":"run","Package":"example.com/killed","Test":"TestKilled"} -{"Time":"2026-10-25T15:34:34.331699248+01:00","Action":"output","Package":"example.com/killed","Test":"TestKilled","Output":"=== RUN TestKilled\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:34.331853111+01:00","Action":"output","Package":"example.com/killed","Test":"TestKilled","Output":" p_test.go:10: the kernel is about to kill this\n"} -{"Time":"2026-10-25T15:34:34.334563213+01:00","Action":"output","Package":"example.com/killed","Test":"TestKilled","Output":"signal: killed\n"} -{"Time":"2026-10-25T15:34:34.334611114+01:00","Action":"output","Package":"example.com/killed","Output":"FAIL\texample.com/killed\t0.006s\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:34.334627134+01:00","Action":"fail","Package":"example.com/killed","Elapsed":0.006} diff --git a/go/buildstep/conformance/captured/go/logthenfatal.jsonl b/go/buildstep/conformance/captured/go/logthenfatal.jsonl deleted file mode 100644 index c59fba2..0000000 --- a/go/buildstep/conformance/captured/go/logthenfatal.jsonl +++ /dev/null @@ -1,13 +0,0 @@ -{"Action":"start","Package":"x"} -{"Action":"run","Package":"x","Test":"TestThree"} -{"Action":"output","Package":"x","Test":"TestThree","Output":"=== RUN TestThree\n","OutputType":"frame"} -{"Action":"output","Package":"x","Test":"TestThree","Output":" a_test.go:4: judged against /var/lib/mesh-build-go-work/id/beside/mesh-catalog as cloned\n"} -{"Action":"output","Package":"x","Test":"TestThree","Output":" a_test.go:5: 3 subscription(s) derive to a subject no emitter owns:\n","OutputType":"error"} -{"Action":"output","Package":"x","Test":"TestThree","Output":" a.b.c\n","OutputType":"error-continue"} -{"Action":"output","Package":"x","Test":"TestThree","Output":" d.e.f\n","OutputType":"error-continue"} -{"Action":"output","Package":"x","Test":"TestThree","Output":" g.h.i\n","OutputType":"error-continue"} -{"Action":"output","Package":"x","Test":"TestThree","Output":"--- FAIL: TestThree (0.00s)\n","OutputType":"frame"} -{"Action":"fail","Package":"x","Test":"TestThree","Elapsed":0} -{"Action":"output","Package":"x","Output":"FAIL\n","OutputType":"frame"} -{"Action":"output","Package":"x","Output":"FAIL\tx\t0.002s\n","OutputType":"frame"} -{"Action":"fail","Package":"x","Elapsed":0.002} diff --git a/go/buildstep/conformance/captured/go/mainexit.jsonl b/go/buildstep/conformance/captured/go/mainexit.jsonl deleted file mode 100644 index 5f106ba..0000000 --- a/go/buildstep/conformance/captured/go/mainexit.jsonl +++ /dev/null @@ -1,8 +0,0 @@ -{"Time":"2026-10-25T15:34:35.274953775+01:00","Action":"start","Package":"example.com/mainexit"} -{"Time":"2026-10-25T15:34:35.278129565+01:00","Action":"run","Package":"example.com/mainexit","Test":"TestFine"} -{"Time":"2026-10-25T15:34:35.278191516+01:00","Action":"output","Package":"example.com/mainexit","Test":"TestFine","Output":"=== RUN TestFine\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:35.278341979+01:00","Action":"output","Package":"example.com/mainexit","Test":"TestFine","Output":"--- PASS: TestFine (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:35.278351739+01:00","Action":"pass","Package":"example.com/mainexit","Test":"TestFine","Elapsed":0} -{"Time":"2026-10-25T15:34:35.278359039+01:00","Action":"output","Package":"example.com/mainexit","Output":"PASS\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:35.279516487+01:00","Action":"output","Package":"example.com/mainexit","Output":"FAIL\texample.com/mainexit\t0.004s\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:35.279539298+01:00","Action":"fail","Package":"example.com/mainexit","Elapsed":0.005} diff --git a/go/buildstep/conformance/captured/go/panicking.jsonl b/go/buildstep/conformance/captured/go/panicking.jsonl deleted file mode 100644 index 7609d5d..0000000 --- a/go/buildstep/conformance/captured/go/panicking.jsonl +++ /dev/null @@ -1,27 +0,0 @@ -{"Time":"2026-10-25T15:34:33.031091436+01:00","Action":"start","Package":"example.com/panicking"} -{"Time":"2026-10-25T15:34:33.036353279+01:00","Action":"run","Package":"example.com/panicking","Test":"TestBefore"} -{"Time":"2026-10-25T15:34:33.036441781+01:00","Action":"output","Package":"example.com/panicking","Test":"TestBefore","Output":"=== RUN TestBefore\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:33.036568163+01:00","Action":"output","Package":"example.com/panicking","Test":"TestBefore","Output":"--- PASS: TestBefore (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:33.036609863+01:00","Action":"pass","Package":"example.com/panicking","Test":"TestBefore","Elapsed":0} -{"Time":"2026-10-25T15:34:33.036641304+01:00","Action":"run","Package":"example.com/panicking","Test":"TestPanics"} -{"Time":"2026-10-25T15:34:33.036645914+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"=== RUN TestPanics\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:33.036698995+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":" p_test.go:8: about to look up the seat\n"} -{"Time":"2026-10-25T15:34:33.036773996+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"--- FAIL: TestPanics (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:33.03897418+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"panic: assignment to entry in nil map [recovered, repanicked]\n"} -{"Time":"2026-10-25T15:34:33.040332172+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"\n"} -{"Time":"2026-10-25T15:34:33.040414493+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"goroutine 8 [running]:\n"} -{"Time":"2026-10-25T15:34:33.040521475+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"testing.tRunner.func1.2({0x6b8120, 0x6eff80})\n"} -{"Time":"2026-10-25T15:34:33.040601826+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"\t/usr/lib/go/src/testing/testing.go:2125 +0x232\n"} -{"Time":"2026-10-25T15:34:33.040641947+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"testing.tRunner.func1()\n"} -{"Time":"2026-10-25T15:34:33.040720648+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"\t/usr/lib/go/src/testing/testing.go:2128 +0x329\n"} -{"Time":"2026-10-25T15:34:33.04083286+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"panic({0x6b8120?, 0x6eff80?})\n"} -{"Time":"2026-10-25T15:34:33.040912921+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"\t/usr/lib/go/src/runtime/panic.go:859 +0x125\n"} -{"Time":"2026-10-25T15:34:33.040981652+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"example.com/panicking.TestPanics(0x228226cd0488?)\n"} -{"Time":"2026-10-25T15:34:33.041059903+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"\t/tmp/fix-1901b/panicking/p_test.go:10 +0x53\n"} -{"Time":"2026-10-25T15:34:33.041132704+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"testing.tRunner(0x228226cd0488, 0x6d5bc8)\n"} -{"Time":"2026-10-25T15:34:33.041215586+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"\t/usr/lib/go/src/testing/testing.go:2195 +0xea\n"} -{"Time":"2026-10-25T15:34:33.041221966+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"created by testing.(*T).Run in goroutine 1\n"} -{"Time":"2026-10-25T15:34:33.041225556+01:00","Action":"output","Package":"example.com/panicking","Test":"TestPanics","Output":"\t/usr/lib/go/src/testing/testing.go:2260 +0x4d4\n"} -{"Time":"2026-10-25T15:34:33.041984088+01:00","Action":"fail","Package":"example.com/panicking","Test":"TestPanics","Elapsed":0} -{"Time":"2026-10-25T15:34:33.041996638+01:00","Action":"output","Package":"example.com/panicking","Output":"FAIL\texample.com/panicking\t0.010s\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:33.042044079+01:00","Action":"fail","Package":"example.com/panicking","Elapsed":0.011} diff --git a/go/buildstep/conformance/captured/go/passfirst.jsonl b/go/buildstep/conformance/captured/go/passfirst.jsonl deleted file mode 100644 index a8286d9..0000000 --- a/go/buildstep/conformance/captured/go/passfirst.jsonl +++ /dev/null @@ -1,22 +0,0 @@ -{"Time":"2026-10-26T19:40:29.574068887+01:00","Action":"start","Package":"example.com/passfirst/a"} -{"Time":"2026-10-26T19:40:29.574347185+01:00","Action":"start","Package":"example.com/passfirst/b"} -{"Time":"2026-10-26T19:40:29.576503518+01:00","Action":"run","Package":"example.com/passfirst/a","Test":"TestTheFirstPackagePasses"} -{"Time":"2026-10-26T19:40:29.576557657+01:00","Action":"output","Package":"example.com/passfirst/a","Test":"TestTheFirstPackagePasses","Output":"=== RUN TestTheFirstPackagePasses\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.576589367+01:00","Action":"output","Package":"example.com/passfirst/a","Test":"TestTheFirstPackagePasses","Output":"--- PASS: TestTheFirstPackagePasses (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.576594637+01:00","Action":"pass","Package":"example.com/passfirst/a","Test":"TestTheFirstPackagePasses","Elapsed":0} -{"Time":"2026-10-26T19:40:29.576602487+01:00","Action":"output","Package":"example.com/passfirst/a","Output":"PASS\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.576614477+01:00","Action":"run","Package":"example.com/passfirst/b","Test":"TestTheSecondPackagePassesFirst"} -{"Time":"2026-10-26T19:40:29.576626897+01:00","Action":"output","Package":"example.com/passfirst/b","Test":"TestTheSecondPackagePassesFirst","Output":"=== RUN TestTheSecondPackagePassesFirst\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.576631187+01:00","Action":"output","Package":"example.com/passfirst/b","Test":"TestTheSecondPackagePassesFirst","Output":"--- PASS: TestTheSecondPackagePassesFirst (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.576635567+01:00","Action":"pass","Package":"example.com/passfirst/b","Test":"TestTheSecondPackagePassesFirst","Elapsed":0} -{"Time":"2026-10-26T19:40:29.576638427+01:00","Action":"run","Package":"example.com/passfirst/b","Test":"TestTheSecondPackageFails"} -{"Time":"2026-10-26T19:40:29.576640647+01:00","Action":"output","Package":"example.com/passfirst/b","Test":"TestTheSecondPackageFails","Output":"=== RUN TestTheSecondPackageFails\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.576643477+01:00","Action":"output","Package":"example.com/passfirst/b","Test":"TestTheSecondPackageFails","Output":" b_test.go:8: judged against the seat as it stands\n"} -{"Time":"2026-10-26T19:40:29.576646497+01:00","Action":"output","Package":"example.com/passfirst/b","Test":"TestTheSecondPackageFails","Output":" b_test.go:9: the seat has 2 holders, want 1\n","OutputType":"error"} -{"Time":"2026-10-26T19:40:29.576650087+01:00","Action":"output","Package":"example.com/passfirst/b","Test":"TestTheSecondPackageFails","Output":"--- FAIL: TestTheSecondPackageFails (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.576652797+01:00","Action":"fail","Package":"example.com/passfirst/b","Test":"TestTheSecondPackageFails","Elapsed":0} -{"Time":"2026-10-26T19:40:29.576655177+01:00","Action":"output","Package":"example.com/passfirst/b","Output":"FAIL\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.577365541+01:00","Action":"output","Package":"example.com/passfirst/a","Output":"ok \texample.com/passfirst/a\t0.003s\n"} -{"Time":"2026-10-26T19:40:29.577382561+01:00","Action":"pass","Package":"example.com/passfirst/a","Elapsed":0.003} -{"Time":"2026-10-26T19:40:29.577380641+01:00","Action":"output","Package":"example.com/passfirst/b","Output":"FAIL\texample.com/passfirst/b\t0.003s\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.5774131+01:00","Action":"fail","Package":"example.com/passfirst/b","Elapsed":0.003} diff --git a/go/buildstep/conformance/captured/go/passthenfail.jsonl b/go/buildstep/conformance/captured/go/passthenfail.jsonl deleted file mode 100644 index 55fa930..0000000 --- a/go/buildstep/conformance/captured/go/passthenfail.jsonl +++ /dev/null @@ -1,15 +0,0 @@ -{"Time":"2026-10-25T15:34:34.919624192+01:00","Action":"start","Package":"example.com/passthenfail"} -{"Time":"2026-10-25T15:34:34.922861224+01:00","Action":"run","Package":"example.com/passthenfail","Test":"TestFine"} -{"Time":"2026-10-25T15:34:34.922932965+01:00","Action":"output","Package":"example.com/passthenfail","Test":"TestFine","Output":"=== RUN TestFine\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:34.922969205+01:00","Action":"output","Package":"example.com/passthenfail","Test":"TestFine","Output":"--- PASS: TestFine (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:34.922979956+01:00","Action":"pass","Package":"example.com/passthenfail","Test":"TestFine","Elapsed":0} -{"Time":"2026-10-25T15:34:34.922989416+01:00","Action":"output","Package":"example.com/passthenfail","Output":"PASS\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:34.925022528+01:00","Action":"output","Package":"example.com/passthenfail","Output":"panic: the bus closed after the tests: nats: connection closed\n"} -{"Time":"2026-10-25T15:34:34.925043388+01:00","Action":"output","Package":"example.com/passthenfail","Output":"\n"} -{"Time":"2026-10-25T15:34:34.925046848+01:00","Action":"output","Package":"example.com/passthenfail","Output":"goroutine 20 [running]:\n"} -{"Time":"2026-10-25T15:34:34.925051338+01:00","Action":"output","Package":"example.com/passthenfail","Output":"example.com/passthenfail.TestMain.func1()\n"} -{"Time":"2026-10-25T15:34:34.925055878+01:00","Action":"output","Package":"example.com/passthenfail","Output":"\t/tmp/fix-1901b/passthenfail/p_test.go:13 +0x25\n"} -{"Time":"2026-10-25T15:34:34.925061428+01:00","Action":"output","Package":"example.com/passthenfail","Output":"created by example.com/passthenfail.TestMain in goroutine 1\n"} -{"Time":"2026-10-25T15:34:34.925065028+01:00","Action":"output","Package":"example.com/passthenfail","Output":"\t/tmp/fix-1901b/passthenfail/p_test.go:13 +0x25\n"} -{"Time":"2026-10-25T15:34:34.925390203+01:00","Action":"output","Package":"example.com/passthenfail","Output":"FAIL\texample.com/passthenfail\t0.005s\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:34.925402904+01:00","Action":"fail","Package":"example.com/passthenfail","Elapsed":0.006} diff --git a/go/buildstep/conformance/captured/go/race.jsonl b/go/buildstep/conformance/captured/go/race.jsonl deleted file mode 100644 index 93e9ac1..0000000 --- a/go/buildstep/conformance/captured/go/race.jsonl +++ /dev/null @@ -1,53 +0,0 @@ -{"Time":"2026-10-25T15:50:42.908557309+01:00","Action":"start","Package":"example.com/race"} -{"Time":"2026-10-25T15:50:42.924298679+01:00","Action":"run","Package":"example.com/race","Test":"TestBefore"} -{"Time":"2026-10-25T15:50:42.92438195+01:00","Action":"output","Package":"example.com/race","Test":"TestBefore","Output":"=== RUN TestBefore\n","OutputType":"frame"} -{"Time":"2026-10-25T15:50:42.924648842+01:00","Action":"output","Package":"example.com/race","Test":"TestBefore","Output":"--- PASS: TestBefore (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-25T15:50:42.924685133+01:00","Action":"pass","Package":"example.com/race","Test":"TestBefore","Elapsed":0} -{"Time":"2026-10-25T15:50:42.924728573+01:00","Action":"run","Package":"example.com/race","Test":"TestRacesOnTheSeat"} -{"Time":"2026-10-25T15:50:42.924733573+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"=== RUN TestRacesOnTheSeat\n","OutputType":"frame"} -{"Time":"2026-10-25T15:50:42.92637298+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"==================\n"} -{"Time":"2026-10-25T15:50:42.92641567+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"WARNING: DATA RACE\n"} -{"Time":"2026-10-25T15:50:42.926444061+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"Write at 0x000000833468 by goroutine 10:\n"} -{"Time":"2026-10-25T15:50:42.926462621+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" example.com/race.TestRacesOnTheSeat.func1()\n"} -{"Time":"2026-10-25T15:50:42.926467121+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /tmp/fix-1901b/race/p_test.go:13 +0x24\n"} -{"Time":"2026-10-25T15:50:42.926483061+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"\n"} -{"Time":"2026-10-25T15:50:42.926509741+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"Previous write at 0x000000833468 by goroutine 9:\n"} -{"Time":"2026-10-25T15:50:42.926530142+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" example.com/race.TestRacesOnTheSeat()\n"} -{"Time":"2026-10-25T15:50:42.926534952+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /tmp/fix-1901b/race/p_test.go:14 +0x30\n"} -{"Time":"2026-10-25T15:50:42.926550462+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.tRunner()\n"} -{"Time":"2026-10-25T15:50:42.926554612+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2195 +0x21c\n"} -{"Time":"2026-10-25T15:50:42.926571142+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.(*T).Run.gowrap1()\n"} -{"Time":"2026-10-25T15:50:42.926575812+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2260 +0x38\n"} -{"Time":"2026-10-25T15:50:42.926592202+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"\n"} -{"Time":"2026-10-25T15:50:42.926607792+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"Goroutine 10 (running) created at:\n"} -{"Time":"2026-10-25T15:50:42.926624292+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" example.com/race.TestRacesOnTheSeat()\n"} -{"Time":"2026-10-25T15:50:42.926628923+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /tmp/fix-1901b/race/p_test.go:13 +0x24\n"} -{"Time":"2026-10-25T15:50:42.926643853+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.tRunner()\n"} -{"Time":"2026-10-25T15:50:42.926648103+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2195 +0x21c\n"} -{"Time":"2026-10-25T15:50:42.926674843+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.(*T).Run.gowrap1()\n"} -{"Time":"2026-10-25T15:50:42.926680673+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2260 +0x38\n"} -{"Time":"2026-10-25T15:50:42.926695433+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"\n"} -{"Time":"2026-10-25T15:50:42.926711063+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"Goroutine 9 (running) created at:\n"} -{"Time":"2026-10-25T15:50:42.926726614+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.(*T).Run()\n"} -{"Time":"2026-10-25T15:50:42.926730814+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2260 +0xb12\n"} -{"Time":"2026-10-25T15:50:42.926746484+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.runTests.func1()\n"} -{"Time":"2026-10-25T15:50:42.926750764+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2744 +0x84\n"} -{"Time":"2026-10-25T15:50:42.926765354+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.tRunner()\n"} -{"Time":"2026-10-25T15:50:42.926769844+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2195 +0x21c\n"} -{"Time":"2026-10-25T15:50:42.926791794+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.runTests()\n"} -{"Time":"2026-10-25T15:50:42.926796704+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2742 +0x9e9\n"} -{"Time":"2026-10-25T15:50:42.926811544+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.(*M).Run()\n"} -{"Time":"2026-10-25T15:50:42.926816694+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" /usr/lib/go/src/testing/testing.go:2602 +0xf44\n"} -{"Time":"2026-10-25T15:50:42.926831695+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" main.main()\n"} -{"Time":"2026-10-25T15:50:42.926835935+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" _testmain.go:50 +0x164\n"} -{"Time":"2026-10-25T15:50:42.926850905+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"==================\n"} -{"Time":"2026-10-25T15:50:42.935320241+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":" testing.go:1867: race detected during execution of test\n","OutputType":"error"} -{"Time":"2026-10-25T15:50:42.935520113+01:00","Action":"output","Package":"example.com/race","Test":"TestRacesOnTheSeat","Output":"--- FAIL: TestRacesOnTheSeat (0.01s)\n","OutputType":"frame"} -{"Time":"2026-10-25T15:50:42.935592973+01:00","Action":"fail","Package":"example.com/race","Test":"TestRacesOnTheSeat","Elapsed":0.01} -{"Time":"2026-10-25T15:50:42.935722225+01:00","Action":"run","Package":"example.com/race","Test":"TestAfter"} -{"Time":"2026-10-25T15:50:42.935728845+01:00","Action":"output","Package":"example.com/race","Test":"TestAfter","Output":"=== RUN TestAfter\n","OutputType":"frame"} -{"Time":"2026-10-25T15:50:42.935995928+01:00","Action":"output","Package":"example.com/race","Test":"TestAfter","Output":"--- PASS: TestAfter (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-25T15:50:42.936006328+01:00","Action":"pass","Package":"example.com/race","Test":"TestAfter","Elapsed":0} -{"Time":"2026-10-25T15:50:42.936009978+01:00","Action":"output","Package":"example.com/race","Output":"FAIL\n","OutputType":"frame"} -{"Time":"2026-10-25T15:50:42.937730745+01:00","Action":"output","Package":"example.com/race","Output":"FAIL\texample.com/race\t0.028s\n","OutputType":"frame"} -{"Time":"2026-10-25T15:50:42.937802066+01:00","Action":"fail","Package":"example.com/race","Elapsed":0.029} diff --git a/go/buildstep/conformance/captured/go/racemain.jsonl b/go/buildstep/conformance/captured/go/racemain.jsonl deleted file mode 100644 index 8a2a5c0..0000000 --- a/go/buildstep/conformance/captured/go/racemain.jsonl +++ /dev/null @@ -1,27 +0,0 @@ -{"Time":"2026-10-25T15:50:43.551701745+01:00","Action":"start","Package":"example.com/racemain"} -{"Time":"2026-10-25T15:50:43.570709708+01:00","Action":"run","Package":"example.com/racemain","Test":"TestFine"} -{"Time":"2026-10-25T15:50:43.570806059+01:00","Action":"output","Package":"example.com/racemain","Test":"TestFine","Output":"=== RUN TestFine\n","OutputType":"frame"} -{"Time":"2026-10-25T15:50:43.57085525+01:00","Action":"output","Package":"example.com/racemain","Test":"TestFine","Output":"--- PASS: TestFine (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-25T15:50:43.57086149+01:00","Action":"pass","Package":"example.com/racemain","Test":"TestFine","Elapsed":0} -{"Time":"2026-10-25T15:50:43.57086974+01:00","Action":"output","Package":"example.com/racemain","Output":"PASS\n","OutputType":"frame"} -{"Time":"2026-10-25T15:50:43.572155623+01:00","Action":"output","Package":"example.com/racemain","Output":"==================\n"} -{"Time":"2026-10-25T15:50:43.572168083+01:00","Action":"output","Package":"example.com/racemain","Output":"WARNING: DATA RACE\n"} -{"Time":"2026-10-25T15:50:43.572171933+01:00","Action":"output","Package":"example.com/racemain","Output":"Write at 0x000000836428 by goroutine 9:\n"} -{"Time":"2026-10-25T15:50:43.572176703+01:00","Action":"output","Package":"example.com/racemain","Output":" example.com/racemain.TestMain.func1()\n"} -{"Time":"2026-10-25T15:50:43.572180053+01:00","Action":"output","Package":"example.com/racemain","Output":" /tmp/fix-1901b/racemain/p_test.go:15 +0x24\n"} -{"Time":"2026-10-25T15:50:43.572184923+01:00","Action":"output","Package":"example.com/racemain","Output":"\n"} -{"Time":"2026-10-25T15:50:43.572189613+01:00","Action":"output","Package":"example.com/racemain","Output":"Previous write at 0x000000836428 by main goroutine:\n"} -{"Time":"2026-10-25T15:50:43.572192993+01:00","Action":"output","Package":"example.com/racemain","Output":" example.com/racemain.TestMain()\n"} -{"Time":"2026-10-25T15:50:43.572198573+01:00","Action":"output","Package":"example.com/racemain","Output":" /tmp/fix-1901b/racemain/p_test.go:16 +0x44\n"} -{"Time":"2026-10-25T15:50:43.572202433+01:00","Action":"output","Package":"example.com/racemain","Output":" main.main()\n"} -{"Time":"2026-10-25T15:50:43.572205623+01:00","Action":"output","Package":"example.com/racemain","Output":" _testmain.go:48 +0x171\n"} -{"Time":"2026-10-25T15:50:43.572208443+01:00","Action":"output","Package":"example.com/racemain","Output":"\n"} -{"Time":"2026-10-25T15:50:43.572211573+01:00","Action":"output","Package":"example.com/racemain","Output":"Goroutine 9 (running) created at:\n"} -{"Time":"2026-10-25T15:50:43.572214803+01:00","Action":"output","Package":"example.com/racemain","Output":" example.com/racemain.TestMain()\n"} -{"Time":"2026-10-25T15:50:43.572218043+01:00","Action":"output","Package":"example.com/racemain","Output":" /tmp/fix-1901b/racemain/p_test.go:15 +0x37\n"} -{"Time":"2026-10-25T15:50:43.572225414+01:00","Action":"output","Package":"example.com/racemain","Output":" main.main()\n"} -{"Time":"2026-10-25T15:50:43.572228654+01:00","Action":"output","Package":"example.com/racemain","Output":" _testmain.go:48 +0x171\n"} -{"Time":"2026-10-25T15:50:43.572231664+01:00","Action":"output","Package":"example.com/racemain","Output":"==================\n"} -{"Time":"2026-10-25T15:50:44.581262249+01:00","Action":"output","Package":"example.com/racemain","Output":"Found 1 data race(s)\n"} -{"Time":"2026-10-25T15:50:44.584693204+01:00","Action":"output","Package":"example.com/racemain","Output":"FAIL\texample.com/racemain\t1.031s\n","OutputType":"frame"} -{"Time":"2026-10-25T15:50:44.584737425+01:00","Action":"fail","Package":"example.com/racemain","Elapsed":1.033} diff --git a/go/buildstep/conformance/captured/go/subpanic.jsonl b/go/buildstep/conformance/captured/go/subpanic.jsonl deleted file mode 100644 index ec85af7..0000000 --- a/go/buildstep/conformance/captured/go/subpanic.jsonl +++ /dev/null @@ -1,30 +0,0 @@ -{"Time":"2026-10-26T20:15:37.791161045+01:00","Action":"start","Package":"example.com/subpanic"} -{"Time":"2026-10-26T20:15:37.793205666+01:00","Action":"run","Package":"example.com/subpanic","Test":"TestTable"} -{"Time":"2026-10-26T20:15:37.793257158+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"=== RUN TestTable\n","OutputType":"frame"} -{"Time":"2026-10-26T20:15:37.793288149+01:00","Action":"run","Package":"example.com/subpanic","Test":"TestTable/passes"} -{"Time":"2026-10-26T20:15:37.793290669+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable/passes","Output":"=== RUN TestTable/passes\n","OutputType":"frame"} -{"Time":"2026-10-26T20:15:37.793298009+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable/passes","Output":"--- PASS: TestTable/passes (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-26T20:15:37.793311679+01:00","Action":"pass","Package":"example.com/subpanic","Test":"TestTable/passes","Elapsed":0} -{"Time":"2026-10-26T20:15:37.79331977+01:00","Action":"run","Package":"example.com/subpanic","Test":"TestTable/panics"} -{"Time":"2026-10-26T20:15:37.79332377+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable/panics","Output":"=== RUN TestTable/panics\n","OutputType":"frame"} -{"Time":"2026-10-26T20:15:37.79332773+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable/panics","Output":"--- FAIL: TestTable/panics (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-26T20:15:37.79333111+01:00","Action":"fail","Package":"example.com/subpanic","Test":"TestTable/panics","Elapsed":0} -{"Time":"2026-10-26T20:15:37.79333383+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"--- FAIL: TestTable (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-26T20:15:37.795322529+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"panic: assignment to entry in nil map [recovered, repanicked]\n"} -{"Time":"2026-10-26T20:15:37.795331489+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"\n"} -{"Time":"2026-10-26T20:15:37.795340579+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"goroutine 9 [running]:\n"} -{"Time":"2026-10-26T20:15:37.79534387+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"testing.tRunner.func1.2({0x6b7010, 0x6eef80})\n"} -{"Time":"2026-10-26T20:15:37.79534657+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"\t/usr/lib/go/src/testing/testing.go:2125 +0x232\n"} -{"Time":"2026-10-26T20:15:37.79534907+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"testing.tRunner.func1()\n"} -{"Time":"2026-10-26T20:15:37.79535172+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"\t/usr/lib/go/src/testing/testing.go:2128 +0x329\n"} -{"Time":"2026-10-26T20:15:37.79535625+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"panic({0x6b7010?, 0x6eef80?})\n"} -{"Time":"2026-10-26T20:15:37.79535879+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"\t/usr/lib/go/src/runtime/panic.go:859 +0x125\n"} -{"Time":"2026-10-26T20:15:37.79536129+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"example.com/subpanic.TestTable.func2(0x3b39436666c8?)\n"} -{"Time":"2026-10-26T20:15:37.79536452+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"\t/tmp/1901k/go/subpanic/p_test.go:7 +0x28\n"} -{"Time":"2026-10-26T20:15:37.79536782+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"testing.tRunner(0x3b39436666c8, 0x6d4b58)\n"} -{"Time":"2026-10-26T20:15:37.7953704+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"\t/usr/lib/go/src/testing/testing.go:2195 +0xea\n"} -{"Time":"2026-10-26T20:15:37.79537289+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"created by testing.(*T).Run in goroutine 7\n"} -{"Time":"2026-10-26T20:15:37.795381681+01:00","Action":"output","Package":"example.com/subpanic","Test":"TestTable","Output":"\t/usr/lib/go/src/testing/testing.go:2260 +0x4d4\n"} -{"Time":"2026-10-26T20:15:37.795731441+01:00","Action":"fail","Package":"example.com/subpanic","Test":"TestTable","Elapsed":0} -{"Time":"2026-10-26T20:15:37.795759332+01:00","Action":"output","Package":"example.com/subpanic","Output":"FAIL\texample.com/subpanic\t0.004s\n","OutputType":"frame"} -{"Time":"2026-10-26T20:15:37.795783503+01:00","Action":"fail","Package":"example.com/subpanic","Elapsed":0.005} diff --git a/go/buildstep/conformance/captured/go/subtests.jsonl b/go/buildstep/conformance/captured/go/subtests.jsonl deleted file mode 100644 index b12cb5a..0000000 --- a/go/buildstep/conformance/captured/go/subtests.jsonl +++ /dev/null @@ -1,17 +0,0 @@ -{"Time":"2026-10-26T19:40:29.768101399+01:00","Action":"start","Package":"example.com/subtests"} -{"Time":"2026-10-26T19:40:29.770109733+01:00","Action":"run","Package":"example.com/subtests","Test":"TestTheTable"} -{"Time":"2026-10-26T19:40:29.770152272+01:00","Action":"output","Package":"example.com/subtests","Test":"TestTheTable","Output":"=== RUN TestTheTable\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.770189372+01:00","Action":"run","Package":"example.com/subtests","Test":"TestTheTable/passes"} -{"Time":"2026-10-26T19:40:29.770191882+01:00","Action":"output","Package":"example.com/subtests","Test":"TestTheTable/passes","Output":"=== RUN TestTheTable/passes\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.770197442+01:00","Action":"output","Package":"example.com/subtests","Test":"TestTheTable/passes","Output":"--- PASS: TestTheTable/passes (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.770204352+01:00","Action":"pass","Package":"example.com/subtests","Test":"TestTheTable/passes","Elapsed":0} -{"Time":"2026-10-26T19:40:29.770209622+01:00","Action":"run","Package":"example.com/subtests","Test":"TestTheTable/fails"} -{"Time":"2026-10-26T19:40:29.770211832+01:00","Action":"output","Package":"example.com/subtests","Test":"TestTheTable/fails","Output":"=== RUN TestTheTable/fails\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.770214722+01:00","Action":"output","Package":"example.com/subtests","Test":"TestTheTable/fails","Output":" s_test.go:7: got 3 machines, want 4\n","OutputType":"error"} -{"Time":"2026-10-26T19:40:29.770218032+01:00","Action":"output","Package":"example.com/subtests","Test":"TestTheTable/fails","Output":"--- FAIL: TestTheTable/fails (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.770220732+01:00","Action":"fail","Package":"example.com/subtests","Test":"TestTheTable/fails","Elapsed":0} -{"Time":"2026-10-26T19:40:29.770223522+01:00","Action":"output","Package":"example.com/subtests","Test":"TestTheTable","Output":"--- FAIL: TestTheTable (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.770226292+01:00","Action":"fail","Package":"example.com/subtests","Test":"TestTheTable","Elapsed":0} -{"Time":"2026-10-26T19:40:29.770228582+01:00","Action":"output","Package":"example.com/subtests","Output":"FAIL\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.770561979+01:00","Action":"output","Package":"example.com/subtests","Output":"FAIL\texample.com/subtests\t0.002s\n","OutputType":"frame"} -{"Time":"2026-10-26T19:40:29.770581629+01:00","Action":"fail","Package":"example.com/subtests","Elapsed":0.002} diff --git a/go/buildstep/conformance/captured/go/timeout.jsonl b/go/buildstep/conformance/captured/go/timeout.jsonl deleted file mode 100644 index 12a2c12..0000000 --- a/go/buildstep/conformance/captured/go/timeout.jsonl +++ /dev/null @@ -1,43 +0,0 @@ -{"Time":"2026-10-25T15:34:36.049580618+01:00","Action":"start","Package":"example.com/timeout"} -{"Time":"2026-10-25T15:34:36.053250986+01:00","Action":"run","Package":"example.com/timeout","Test":"TestQuick"} -{"Time":"2026-10-25T15:34:36.053354018+01:00","Action":"output","Package":"example.com/timeout","Test":"TestQuick","Output":"=== RUN TestQuick\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:36.053459789+01:00","Action":"output","Package":"example.com/timeout","Test":"TestQuick","Output":"--- PASS: TestQuick (0.00s)\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:36.05348499+01:00","Action":"pass","Package":"example.com/timeout","Test":"TestQuick","Elapsed":0} -{"Time":"2026-10-25T15:34:36.0535114+01:00","Action":"run","Package":"example.com/timeout","Test":"TestWaitsForever"} -{"Time":"2026-10-25T15:34:36.05351549+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"=== RUN TestWaitsForever\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:36.053567451+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":" p_test.go:11: waiting for the bus\n"} -{"Time":"2026-10-25T15:34:38.064693307+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"panic: test timed out after 2s\n"} -{"Time":"2026-10-25T15:34:38.06487122+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\trunning tests:\n"} -{"Time":"2026-10-25T15:34:38.06491684+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t\tTestWaitsForever (2s)\n"} -{"Time":"2026-10-25T15:34:38.065034382+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\n"} -{"Time":"2026-10-25T15:34:38.065108573+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"goroutine 33 [running]:\n"} -{"Time":"2026-10-25T15:34:38.065154754+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"testing.(*M).startAlarm.func1()\n"} -{"Time":"2026-10-25T15:34:38.065386208+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2961 +0x34a\n"} -{"Time":"2026-10-25T15:34:38.065431878+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"created by time.goFunc\n"} -{"Time":"2026-10-25T15:34:38.0655151+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/time/sleep.go:182 +0x2d\n"} -{"Time":"2026-10-25T15:34:38.06553471+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\n"} -{"Time":"2026-10-25T15:34:38.065847805+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"goroutine 1 [chan receive]:\n"} -{"Time":"2026-10-25T15:34:38.066038298+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"testing.(*T).Run(0x3a0209bc8008, {0x5580aa?, 0x3a0209a9caa0?}, 0x6d5b10)\n"} -{"Time":"2026-10-25T15:34:38.066047488+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2268 +0x4f2\n"} -{"Time":"2026-10-25T15:34:38.066051368+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"testing.runTests.func1(0x3a0209bc8008)\n"} -{"Time":"2026-10-25T15:34:38.066056128+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2744 +0x37\n"} -{"Time":"2026-10-25T15:34:38.066059478+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"testing.tRunner(0x3a0209bc8008, 0x3a0209a9cbc8)\n"} -{"Time":"2026-10-25T15:34:38.066064958+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2195 +0xea\n"} -{"Time":"2026-10-25T15:34:38.066069599+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"testing.runTests({0x559017, 0x13}, {0x559017, 0x13}, 0x3a0209b8e0d8, {0x6f19f0, 0x2, 0x2}, {0xc2afa2bf832a3f56, 0x773908b5, ...})\n"} -{"Time":"2026-10-25T15:34:38.066083309+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2742 +0x510\n"} -{"Time":"2026-10-25T15:34:38.066086879+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"testing.(*M).Run(0x3a0209b8c1e0)\n"} -{"Time":"2026-10-25T15:34:38.066090269+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2602 +0x6af\n"} -{"Time":"2026-10-25T15:34:38.066093429+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"main.main()\n"} -{"Time":"2026-10-25T15:34:38.066097569+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t_testmain.go:48 +0x9b\n"} -{"Time":"2026-10-25T15:34:38.066100639+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\n"} -{"Time":"2026-10-25T15:34:38.066103769+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"goroutine 20 [sleep]:\n"} -{"Time":"2026-10-25T15:34:38.066106969+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"time.Sleep(0xdf8475800)\n"} -{"Time":"2026-10-25T15:34:38.066110279+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/runtime/time.go:368 +0x165\n"} -{"Time":"2026-10-25T15:34:38.066113639+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"example.com/timeout.TestWaitsForever(0x3a0209bc8488?)\n"} -{"Time":"2026-10-25T15:34:38.066117109+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/tmp/fix-1901b/timeout/p_test.go:12 +0x48\n"} -{"Time":"2026-10-25T15:34:38.066120239+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"testing.tRunner(0x3a0209bc8488, 0x6d5b10)\n"} -{"Time":"2026-10-25T15:34:38.066123509+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2195 +0xea\n"} -{"Time":"2026-10-25T15:34:38.066126839+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"created by testing.(*T).Run in goroutine 1\n"} -{"Time":"2026-10-25T15:34:38.066129989+01:00","Action":"output","Package":"example.com/timeout","Test":"TestWaitsForever","Output":"\t/usr/lib/go/src/testing/testing.go:2260 +0x4d4\n"} -{"Time":"2026-10-25T15:34:38.066986453+01:00","Action":"output","Package":"example.com/timeout","Output":"FAIL\texample.com/timeout\t2.017s\n","OutputType":"frame"} -{"Time":"2026-10-25T15:34:38.067002113+01:00","Action":"fail","Package":"example.com/timeout","Elapsed":2.017} diff --git a/go/buildstep/conformance/captured/node/crash.console b/go/buildstep/conformance/captured/node/crash.console deleted file mode 100644 index 90a8ef6..0000000 --- a/go/buildstep/conformance/captured/node/crash.console +++ /dev/null @@ -1,2 +0,0 @@ -npm notice run test -npm notice run node --test --experimental-strip-types 'test/*.test.ts' diff --git a/go/buildstep/conformance/captured/node/crash.tap b/go/buildstep/conformance/captured/node/crash.tap deleted file mode 100644 index bea3c65..0000000 --- a/go/buildstep/conformance/captured/node/crash.tap +++ /dev/null @@ -1,41 +0,0 @@ -TAP version 13 -# Subtest: passes before the crash -ok 1 - passes before the crash - --- - duration_ms: 0.746694 - type: 'test' - ... -# Subtest: throws after it returned -ok 2 - throws after it returned - --- - duration_ms: 0.763364 - type: 'test' - ... -# Subtest: waits after the throw -ok 3 - waits after the throw - --- - duration_ms: 49.953893 - type: 'test' - ... -# Error: Test "throws after it returned" at test/b.test.ts:2:1 generated asynchronous activity after the test ended. This activity created the error "Error: the bus closed under the test" and would have caused the test to fail, but instead triggered an uncaughtException event. -# Subtest: test/b.test.ts -not ok 2 - test/b.test.ts - --- - duration_ms: 155.864774 - type: 'test' - location: '/tmp/1901k/node/crash/test/b.test.ts:1:1' - failureType: 'testCodeFailure' - exitCode: 1 - signal: ~ - error: 'test failed' - code: 'ERR_TEST_FAILURE' - ... -1..4 -# tests 4 -# suites 0 -# pass 3 -# fail 1 -# cancelled 0 -# skipped 0 -# todo 0 -# duration_ms 168.479173 diff --git a/go/buildstep/conformance/captured/node/logcrash.console b/go/buildstep/conformance/captured/node/logcrash.console deleted file mode 100644 index 90a8ef6..0000000 --- a/go/buildstep/conformance/captured/node/logcrash.console +++ /dev/null @@ -1,2 +0,0 @@ -npm notice run test -npm notice run node --test --experimental-strip-types 'test/*.test.ts' diff --git a/go/buildstep/conformance/captured/node/logcrash.tap b/go/buildstep/conformance/captured/node/logcrash.tap deleted file mode 100644 index 89dbfb8..0000000 --- a/go/buildstep/conformance/captured/node/logcrash.tap +++ /dev/null @@ -1,42 +0,0 @@ -TAP version 13 -# retrying after Error: the store was not ready yet -# Subtest: passes and logs -ok 1 - passes and logs - --- - duration_ms: 1.184595 - type: 'test' - ... -# Subtest: throws after it returned -ok 2 - throws after it returned - --- - duration_ms: 0.748862 - type: 'test' - ... -# Subtest: waits after the throw -ok 3 - waits after the throw - --- - duration_ms: 51.019875 - type: 'test' - ... -# Error: Test "throws after it returned" at test/b.test.ts:2:1 generated asynchronous activity after the test ended. This activity created the error "Error: the bus closed under the test" and would have caused the test to fail, but instead triggered an uncaughtException event. -# Subtest: test/b.test.ts -not ok 2 - test/b.test.ts - --- - duration_ms: 172.91715 - type: 'test' - location: '/tmp/1901k/node/logcrash/test/b.test.ts:1:1' - failureType: 'testCodeFailure' - exitCode: 1 - signal: ~ - error: 'test failed' - code: 'ERR_TEST_FAILURE' - ... -1..4 -# tests 4 -# suites 0 -# pass 3 -# fail 1 -# cancelled 0 -# skipped 0 -# todo 0 -# duration_ms 185.744334 diff --git a/go/buildstep/conformance/captured/node/suite.console b/go/buildstep/conformance/captured/node/suite.console deleted file mode 100644 index 90a8ef6..0000000 --- a/go/buildstep/conformance/captured/node/suite.console +++ /dev/null @@ -1,2 +0,0 @@ -npm notice run test -npm notice run node --test --experimental-strip-types 'test/*.test.ts' diff --git a/go/buildstep/conformance/captured/node/suite.tap b/go/buildstep/conformance/captured/node/suite.tap deleted file mode 100644 index 06e9761..0000000 --- a/go/buildstep/conformance/captured/node/suite.tap +++ /dev/null @@ -1,61 +0,0 @@ -TAP version 13 -# Subtest: the seat - # Subtest: passes inside - ok 1 - passes inside - --- - duration_ms: 0.577396 - type: 'test' - ... - # Subtest: fails inside - not ok 2 - fails inside - --- - duration_ms: 1.621198 - type: 'test' - location: '/tmp/1901k/node/suite/test/a.test.ts:5:3' - failureType: 'testCodeFailure' - error: |- - Expected values to be strictly deep-equal: - + actual - expected - -  { - + holders: 2 - - holders: 1 -  } - - code: 'ERR_ASSERTION' - name: 'AssertionError' - expected: - holders: 1 - actual: - holders: 2 - operator: 'deepStrictEqual' - stack: |- - TestContext. (file:///tmp/1901k/node/suite/test/a.test.ts:5:37) - Test.runInAsyncScope (node:async_hooks:226:14) - Test.run (node:internal/test_runner/test:1402:25) - Suite.processPendingSubtests (node:internal/test_runner/test:974:18) - Test.postRun (node:internal/test_runner/test:1542:19) - Test.run (node:internal/test_runner/test:1467:12) - async Promise.all (index 0) - async Suite.run (node:internal/test_runner/test:1905:7) - async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) - ... - 1..2 -not ok 1 - the seat - --- - duration_ms: 3.295664 - type: 'suite' - location: '/tmp/1901k/node/suite/test/a.test.ts:3:1' - failureType: 'subtestsFailed' - error: '1 subtest failed' - code: 'ERR_TEST_FAILURE' - ... -1..1 -# tests 2 -# suites 1 -# pass 1 -# fail 1 -# cancelled 0 -# skipped 0 -# todo 0 -# duration_ms 115.948112 diff --git a/go/buildstep/conformance/captured/node/syntax.console b/go/buildstep/conformance/captured/node/syntax.console deleted file mode 100644 index 90a8ef6..0000000 --- a/go/buildstep/conformance/captured/node/syntax.console +++ /dev/null @@ -1,2 +0,0 @@ -npm notice run test -npm notice run node --test --experimental-strip-types 'test/*.test.ts' diff --git a/go/buildstep/conformance/captured/node/syntax.tap b/go/buildstep/conformance/captured/node/syntax.tap deleted file mode 100644 index ceadd12..0000000 --- a/go/buildstep/conformance/captured/node/syntax.tap +++ /dev/null @@ -1,47 +0,0 @@ -TAP version 13 -# Subtest: passes in the first file -ok 1 - passes in the first file - --- - duration_ms: 0.637465 - type: 'test' - ... -# node:internal/modules/run_main:111 -# triggerUncaughtException( -# ^ -# file:///tmp/1901k/node/syntax/test/b.test.ts:2 -# test('never loads', () => { -# SyntaxError [ERR_INVALID_TYPESCRIPT_SYNTAX]: Expected ',', got '' -#  at parseTypeScript (node:internal/modules/typescript:61:36) -#  at processTypeScriptCode (node:internal/modules/typescript:129:37) -#  at Object.stripTypeScriptModuleTypes (node:internal/modules/typescript:172:22) -#  at stripTypeScriptModuleTypes (node:internal/modules/esm/translators:35:49) -#  at ModuleLoader. (node:internal/modules/esm/translators:688:29) -#  at \#translate (node:internal/modules/esm/loader:441:20) -#  at afterLoad (node:internal/modules/esm/loader:509:29) -#  at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:514:12) -#  at \#getOrCreateModuleJobAfterResolve (node:internal/modules/esm/loader:567:36) -#  at afterResolve (node:internal/modules/esm/loader:614:52) { -# code: 'ERR_INVALID_TYPESCRIPT_SYNTAX' -# } -# Node.js v26.12.1 -# Subtest: test/b.test.ts -not ok 2 - test/b.test.ts - --- - duration_ms: 87.629815 - type: 'test' - location: '/tmp/1901k/node/syntax/test/b.test.ts:1:1' - failureType: 'testCodeFailure' - exitCode: 1 - signal: ~ - error: 'test failed' - code: 'ERR_TEST_FAILURE' - ... -1..2 -# tests 2 -# suites 0 -# pass 1 -# fail 1 -# cancelled 0 -# skipped 0 -# todo 0 -# duration_ms 106.551435 diff --git a/go/buildstep/conformance/captured/node/timeout.console b/go/buildstep/conformance/captured/node/timeout.console deleted file mode 100644 index 90a8ef6..0000000 --- a/go/buildstep/conformance/captured/node/timeout.console +++ /dev/null @@ -1,2 +0,0 @@ -npm notice run test -npm notice run node --test --experimental-strip-types 'test/*.test.ts' diff --git a/go/buildstep/conformance/captured/node/timeout.tap b/go/buildstep/conformance/captured/node/timeout.tap deleted file mode 100644 index dd87e17..0000000 --- a/go/buildstep/conformance/captured/node/timeout.tap +++ /dev/null @@ -1,26 +0,0 @@ -TAP version 13 -# Subtest: passes before the wait -ok 1 - passes before the wait - --- - duration_ms: 0.683015 - type: 'test' - ... -# Subtest: waits for an answer that never comes -not ok 2 - waits for an answer that never comes - --- - duration_ms: 201.103775 - type: 'test' - location: '/tmp/1901k/node/timeout/test/a.test.ts:3:1' - failureType: 'testTimeoutFailure' - error: 'test timed out after 200ms' - code: 'ERR_TEST_FAILURE' - ... -1..2 -# tests 2 -# suites 0 -# pass 1 -# fail 0 -# cancelled 1 -# skipped 0 -# todo 0 -# duration_ms 308.920061 diff --git a/go/buildstep/conformance/captured/node/two-files.console b/go/buildstep/conformance/captured/node/two-files.console deleted file mode 100644 index 90a8ef6..0000000 --- a/go/buildstep/conformance/captured/node/two-files.console +++ /dev/null @@ -1,2 +0,0 @@ -npm notice run test -npm notice run node --test --experimental-strip-types 'test/*.test.ts' diff --git a/go/buildstep/conformance/captured/node/two-files.tap b/go/buildstep/conformance/captured/node/two-files.tap deleted file mode 100644 index 8e0a85b..0000000 --- a/go/buildstep/conformance/captured/node/two-files.tap +++ /dev/null @@ -1,49 +0,0 @@ -TAP version 13 -# Subtest: passes in the first file -ok 1 - passes in the first file - --- - duration_ms: 0.740124 - type: 'test' - ... -# judged against the seat as it stands -# Subtest: passes in the second file first -ok 2 - passes in the second file first - --- - duration_ms: 0.916463 - type: 'test' - ... -# Subtest: fails in the second file -not ok 3 - fails in the second file - --- - duration_ms: 1.719236 - type: 'test' - location: '/tmp/1901k/node/two-files/test/b.test.ts:4:1' - failureType: 'testCodeFailure' - error: |- - one plus one is not three - - 2 !== 3 - - code: 'ERR_ASSERTION' - name: 'AssertionError' - expected: 3 - actual: 2 - operator: 'strictEqual' - stack: |- - TestContext. (file:///tmp/1901k/node/two-files/test/b.test.ts:6:10) - Test.runInAsyncScope (node:async_hooks:226:14) - Test.run (node:internal/test_runner/test:1402:25) - Test.processPendingSubtests (node:internal/test_runner/test:974:18) - Test.postRun (node:internal/test_runner/test:1542:19) - Test.run (node:internal/test_runner/test:1467:12) - async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) - ... -1..3 -# tests 3 -# suites 0 -# pass 2 -# fail 1 -# cancelled 0 -# skipped 0 -# todo 0 -# duration_ms 135.931121 diff --git a/go/buildstep/conformance/captured/tsc/typeerror.console b/go/buildstep/conformance/captured/tsc/typeerror.console deleted file mode 100644 index ac7713e..0000000 --- a/go/buildstep/conformance/captured/tsc/typeerror.console +++ /dev/null @@ -1 +0,0 @@ -seat.ts(2,14): error TS2322: Type 'number' is not assignable to type 'string'. diff --git a/go/buildstep/conformance/conformance.go b/go/buildstep/conformance/conformance.go deleted file mode 100644 index 78596ff..0000000 --- a/go/buildstep/conformance/conformance.go +++ /dev/null @@ -1,201 +0,0 @@ -// Package conformance is the kit every build module's tests run (novox/hq issue 1901, its thorough fix): real output of -// the test runners, captured with the build nodes' own toolchains (captured/README.md), and what a build module must say -// of each failure in it under the failure contract (buildstep.Failure). A build module's test calls Run with its own parse -// path, the code that turns a runner's output into the failures of its test result; the kit fails the test when a -// failure is not named, its cause is missing or wrong, or a passing line stands in its place. -// -// A catalogue check (mesh-catalog checks/failurekit) refuses a build module with a test step whose tests do not call Run, -// and any build module whose tests do not call LeftRunning, the kit's case of steps a previous process left running -// (leftrunning.go, issue 1890). -package conformance - -import ( - "embed" - "fmt" - "strings" - "testing" - - "git.novox.be/novox/mesh-sdk/go/buildstep" -) - -// The runners the kit holds output of. -const ( - // GoTest is `go test -json -count=1 ./...`: Output.Stdout is its output (go1.27.3). - GoTest = "go test -json" - // NodeTest is a package's `npm test` running `node --test --experimental-strip-types` with - // NODE_OPTIONS=buildstep.NodeTestOptions(): Output.Report is the TAP file, Output.Stdout the script's own output - // and stderr, Output.Dir the package's directory as it was captured (node v26.12.1, npm 12.2.1). - NodeTest = "node --test" - // TypeCheck is tsc's type-check (`tsc --noEmit`, typescript 5.9.3): Output.Stdout is what it wrote; its failure - // names the test "type-check". - TypeCheck = "tsc" -) - -// Output is one captured run of a runner, as a build module's parse path takes it. -type Output struct { - Stdout, Stderr string - // Report is what a runner wrote to a report file of its own (node --test's TAP), "" for the others. - Report string - // Dir is the directory the run was captured in: a node test's file is said relative to it. - Dir string -} - -// Parse is a build module's own parse path: the failures its test result would carry for one run that failed. -type Parse func(Output) []buildstep.Failure - -// Case is one captured run and what its failure must say. -type Case struct { - Name string - Runner string - // Test is the failing test the failure names (for a package that stopped, the tests that were running; "" for none). - Test string - // File, when set, is where the failing test is. - File string - // Cause holds what the failure's cause must contain. - Cause []string - // Passing are the names of tests that passed in the run, and what they logged: no failure's words may hold them. - Passing []string -} - -//go:embed captured -var captured embed.FS - -// Cases are the kit's cases of a runner. -func Cases(runner string) []Case { - var out []Case - for _, c := range cases { - if c.Runner == runner { - out = append(out, c) - } - } - return out -} - -var cases = []Case{ - {Name: "passfirst", Runner: GoTest, Test: "TestTheSecondPackageFails", Cause: []string{"the seat has 2 holders, want 1"}, - Passing: []string{"TestTheFirstPackagePasses", "TestTheSecondPackagePassesFirst"}}, - {Name: "subtests", Runner: GoTest, Test: "TestTheTable/fails", Cause: []string{"got 3 machines, want 4"}, Passing: []string{"TestTheTable/passes"}}, - {Name: "logthenfatal", Runner: GoTest, Test: "TestThree", Cause: []string{"3 subscription(s) derive to a subject no emitter owns:"}}, - {Name: "panicking", Runner: GoTest, Test: "TestPanics", Cause: []string{"panic: assignment to entry in nil map"}}, - {Name: "timeout", Runner: GoTest, Test: "TestWaitsForever", Cause: []string{"panic: test timed out after 2s", "TestWaitsForever"}}, - {Name: "deeptimeout", Runner: GoTest, Test: "TestHoldsTheSeat, TestWaitsForTheBus", Cause: []string{"panic: test timed out after 2s", "TestHoldsTheSeat", "TestWaitsForTheBus"}}, - {Name: "buildfail", Runner: GoTest, Cause: []string{`cannot use "seat" (untyped string constant) as int value`}}, - {Name: "exiting", Runner: GoTest, Test: "TestExits", Cause: []string{"exited", "TestExits"}}, - {Name: "killed", Runner: GoTest, Test: "TestKilled", Cause: []string{"signal: killed"}}, - {Name: "passthenfail", Runner: GoTest, Cause: []string{"panic: the bus closed after the tests: nats: connection closed"}}, - {Name: "mainexit", Runner: GoTest, Cause: []string{"passed", "exited"}}, - {Name: "race", Runner: GoTest, Test: "TestRacesOnTheSeat", Cause: []string{"DATA RACE", "Write at", "example.com/race.TestRacesOnTheSeat.func1()", "p_test.go:13", "created at example.com/race.TestRacesOnTheSeat()"}}, - {Name: "subpanic", Runner: GoTest, Test: "TestTable/panics", Cause: []string{"panic: assignment to entry in nil map"}, Passing: []string{"TestTable/passes"}}, - {Name: "racemain", Runner: GoTest, Cause: []string{"DATA RACE", "Write at", "example.com/racemain.TestMain.func1()", "p_test.go:15", "created at example.com/racemain.TestMain()"}}, - - {Name: "two-files", Runner: NodeTest, Test: "fails in the second file", File: "test/b.test.ts:4:1", Cause: []string{"one plus one is not three"}, - Passing: []string{"passes in the first file", "passes in the second file first"}}, - {Name: "suite", Runner: NodeTest, Test: "fails inside", File: "test/a.test.ts:5:3", Cause: []string{"Expected values to be strictly deep-equal:"}, - Passing: []string{"passes inside"}}, - {Name: "crash", Runner: NodeTest, Test: "test/b.test.ts", File: "test/b.test.ts:1:1", Cause: []string{"the bus closed under the test"}, - Passing: []string{"passes before the crash"}}, - {Name: "logcrash", Runner: NodeTest, Test: "test/b.test.ts", File: "test/b.test.ts:1:1", Cause: []string{"the bus closed under the test"}, - Passing: []string{"passes and logs", "retrying after Error: the store was not ready yet"}}, - {Name: "timeout", Runner: NodeTest, Test: "waits for an answer that never comes", File: "test/a.test.ts:3:1", Cause: []string{"test timed out after 200ms"}, - Passing: []string{"passes before the wait"}}, - {Name: "syntax", Runner: NodeTest, Test: "test/b.test.ts", File: "test/b.test.ts:1:1", Cause: []string{"SyntaxError", "Expected ',', got ''"}, - Passing: []string{"passes in the first file"}}, - - {Name: "typeerror", Runner: TypeCheck, Test: "type-check", Cause: []string{"seat.ts(2,14): error TS2322: Type 'number' is not assignable to type 'string'."}}, -} - -// Load is the captured output of a case. -func Load(c Case) (Output, error) { - switch c.Runner { - case GoTest: - b, err := captured.ReadFile("captured/go/" + c.Name + ".jsonl") - return Output{Stdout: string(b)}, err - case NodeTest: - report, err := captured.ReadFile("captured/node/" + c.Name + ".tap") - if err != nil { - return Output{}, err - } - console, err := captured.ReadFile("captured/node/" + c.Name + ".console") - return Output{Report: string(report), Stdout: string(console), Dir: "/tmp/1901k/node/" + c.Name}, err - default: - b, err := captured.ReadFile("captured/tsc/" + c.Name + ".console") - return Output{Stdout: string(b), Dir: "/tmp/1901k/tsc/" + c.Name}, err - } -} - -// Run runs every case of runner through parse, each as a subtest, and fails a case whose failure does not keep the -// contract: exactly one failure, naming the failing test (and its file, where the runner says it), with a cause that -// holds what the case says and is no passing line, an output that starts with the cause, stays within its bound and -// holds no passing line and no passing test's name. -func Run(t *testing.T, runner string, parse Parse) { - t.Helper() - cs := Cases(runner) - if len(cs) == 0 { - t.Fatalf("the kit holds no case of the runner %q", runner) - } - for _, c := range cs { - t.Run(c.Name, func(t *testing.T) { - out, err := Load(c) - if err != nil { - t.Fatal(err) - } - for _, problem := range Judge(c, parse(out)) { - t.Error(problem) - } - }) - } -} - -// Judge says what is wrong with the failures a parse path made of a case; nothing when they keep the contract. -func Judge(c Case, failed []buildstep.Failure) []string { - var problems []string - say := func(s string) { problems = append(problems, c.Runner+" "+c.Name+": "+s) } - if len(failed) != 1 { - say(fmt.Sprintf("want one failure, got %d: %s", len(failed), describe(failed))) - if len(failed) == 0 { - return problems - } - } - f := failed[0] - if f.Test != c.Test { - say("the failure names the test " + quote(f.Test) + ", want " + quote(c.Test)) - } - if c.File != "" && f.File != c.File { - say("the failure's file is " + quote(f.File) + ", want " + quote(c.File)) - } - if why := buildstep.Hidden(f); why != "" { - say("the failure hides its cause: " + why) - } - for _, want := range c.Cause { - if !strings.Contains(f.Cause, want) { - say("the cause " + quote(f.Cause) + " does not hold " + quote(want)) - } - } - if first := strings.SplitN(f.Output, "\n", 2)[0]; strings.TrimSpace(first) != strings.TrimSpace(f.Cause) { - say("the output starts with " + quote(first) + ", not with the cause") - } - if len(f.Output) > buildstep.OutputBytes+buildstep.LineBytes+64 { - say(fmt.Sprintf("the output holds %d bytes, over its bound", len(f.Output))) - } - for _, l := range strings.Split(f.Output, "\n") { - if buildstep.PassingLine(l) { - say("the output keeps the passing line " + quote(l)) - } - } - for _, p := range c.Passing { - if strings.Contains(f.Output, p+"\n") || strings.HasSuffix(f.Output, p) || strings.Contains(f.Cause, p) || strings.Contains(f.Output, "- "+p) { - say("the failure's words hold the passing test " + quote(p)) - } - } - return problems -} - -func describe(failed []buildstep.Failure) string { - var s []string - for _, f := range failed { - s = append(s, quote(f.Package+" "+f.Test)+" ("+quote(f.Cause)+")") - } - return strings.Join(s, ", ") -} - -func quote(s string) string { return fmt.Sprintf("%q", s) } diff --git a/go/buildstep/conformance/conformance_test.go b/go/buildstep/conformance/conformance_test.go deleted file mode 100644 index e6380e4..0000000 --- a/go/buildstep/conformance/conformance_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package conformance - -import ( - "context" - "encoding/json" - "strings" - "testing" - "time" - - "git.novox.be/novox/mesh-sdk/go/buildstep" -) - -// The failure contract (issue 1901): the SDK's own parsers keep it on every captured case of the kit (1901/AC6); the -// kit refuses what the build modules sent before it; the contract's own rules; and the Go reader's older guarantees. -func TestTheFailureContract(t *testing.T) { - t.Run("the SDK parsers keep it", func(t *testing.T) { - Run(t, GoTest, func(o Output) []buildstep.Failure { return buildstep.ReadGoTest(o.Stdout, ".").Failed }) - Run(t, NodeTest, func(o Output) []buildstep.Failure { return buildstep.ReadTAP(".", "test", o.Dir, o.Report, o.Stdout) }) - Run(t, TypeCheck, func(o Output) []buildstep.Failure { - return []buildstep.Failure{buildstep.ConsoleFailure(".", "type-check", o.Stdout+o.Stderr)} - }) - }) - - // 1890/AC7: the SDK's start sweep keeps the left-running case, and the kit refuses a sweep that ends nothing or ends - // everything. - t.Run("the left-running case", func(t *testing.T) { - LeftRunning(t, func(ctx context.Context, scan buildstep.Scan, node string, before time.Time, logf func(string, ...any)) { - buildstep.EndStaleAtStart(ctx, scan, node, before, logf) - }) - if p := JudgeLeftRunning(func(context.Context, buildstep.Scan, string, time.Time, func(string, ...any)) {}); len(p) != 11 { - t.Errorf("a sweep that ends nothing: %d problems, want 11 (three steps not ended and unsaid, one owner unnamed, two asks not deleted and unsaid):\n%s", len(p), strings.Join(p, "\n")) - } - wrongNode := func(ctx context.Context, scan buildstep.Scan, _ string, before time.Time, logf func(string, ...any)) { - buildstep.EndStaleAtStart(ctx, scan, "kitother", before, logf) - } - if p := JudgeLeftRunning(wrongNode); len(p) == 0 { - t.Error("a sweep of another node's steps is not refused") - } - }) - - // node's output cut to its first fifteen lines with no cause, whose first line was a passing test of the first file - // (mesh-build-node's lines() before the contract), and a Go failure keeping its first line. - t.Run("the kit refuses a failure that hides its cause", func(t *testing.T) { - firstLines := func(s string) string { - var keep []string - for _, l := range strings.Split(s, "\n") { - if l = strings.TrimSpace(l); l != "" && len(keep) < 15 { - keep = append(keep, l) - } - } - return strings.Join(keep, "\n") - } - for _, c := range []struct { - runner, name string - old func(Output) []buildstep.Failure - }{ - {NodeTest, "two-files", func(o Output) []buildstep.Failure { - return []buildstep.Failure{{Package: ".", Test: "test", Output: firstLines(o.Report)}} - }}, - {GoTest, "passfirst", func(o Output) []buildstep.Failure { - return []buildstep.Failure{{Package: "example.com/passfirst/b", Test: "TestTheSecondPackageFails", Cause: "ok \texample.com/passfirst/a", Output: "--- PASS: TestTheSecondPackagePassesFirst"}} - }}, - } { - for _, k := range Cases(c.runner) { - if k.Name != c.name { - continue - } - out, err := Load(k) - if err != nil { - t.Fatal(err) - } - if problems := Judge(k, c.old(out)); len(problems) == 0 { - t.Errorf("%s %s: the kit passed a failure that hides its cause", c.runner, c.name) - } - } - } - }) - - // What a passing line is (a test's own line starting "ok " is none), when a failure hides its cause, and that a long - // failure keeps its last lines within the bound and starts with a real line for an older checker, which reads only - // the first line of the output cut to 120 characters (1901/AC1, AC3). - t.Run("its rules", func(t *testing.T) { - for _, c := range []struct { - line string - passing bool - }{ - {"✔ the same merge has the same id (8.0ms)", true}, {"ok 1 - passes", true}, {" ok 3 - inside", true}, - {"ok \texample.com/a\t0.01s", true}, {"PASS", true}, {"--- PASS: TestA (0.00s)", true}, - {" --- PASS: TestA/sub (0.00s)", true}, {"✖ fails (1ms)", false}, {"not ok 2 - fails", false}, - {"okay, the seat moved", false}, {"ok then: the store refused the write", false}, {"ok the seat", false}, {"FAIL", false}, - } { - if buildstep.PassingLine(c.line) != c.passing { - t.Errorf("PassingLine(%q) = %v, want %v", c.line, !c.passing, c.passing) - } - } - if buildstep.Hidden(buildstep.Failure{Cause: "the seat has 2 holders"}) != "" || buildstep.Hidden(buildstep.Failure{}) == "" || - buildstep.Hidden(buildstep.Failure{Cause: "✔ passes (1ms)"}) == "" { - t.Error("Hidden does not tell a real cause from none and from a passing line") - } - var lines []string - for i := 0; i < 400; i++ { - lines = append(lines, " x_test.go:1: log line "+strings.Repeat("y", 40)) - } - lines = append(lines, " x_test.go:9: the real cause", "--- FAIL: TestX (0.00s)") - oldFirst := func(out string) string { // the older checker's rule (judge.go firstLine before issue 1901) - for _, l := range strings.Split(out, "\n") { - if l = strings.TrimSpace(l); l != "" { - return l[:min(len(l), 120)] - } - } - return "" - } - for _, cause := range []string{"the real cause", ""} { - out := buildstep.Clip(lines, cause) - if len(out) > buildstep.OutputBytes+buildstep.LineBytes { - t.Errorf("1901/AC1: kept %d bytes, bound is %d", len(out), buildstep.OutputBytes) - } - if !strings.HasSuffix(out, "the real cause\n--- FAIL: TestX (0.00s)") || !strings.Contains(out, "\n... (") { - t.Errorf("1901/AC1: the last lines or the marker are lost: %q...%q", out[:40], out[len(out)-60:]) - } - if got := oldFirst(out); strings.HasPrefix(got, "... (") || got == "" || (cause != "" && got != cause) { - t.Errorf("1901/AC3: cause %q: an older checker would read %q", cause, got) - } - } - }) - - // The Go reader (moved from mesh-build-go): an older checker, which knows no "cause" field, still reads a failure and - // gets at least the lines it kept (1901/AC3); a test's indented log line saying "panic:" is not the cause, go's own at - // column 0 is (1901/AC4); and each package counts its own skipped tests (issue 890). - t.Run("the Go reader", func(t *testing.T) { - read := func(name string) string { - raw, err := captured.ReadFile("captured/go/" + name + ".jsonl") - if err != nil { - t.Fatal(err) - } - return string(raw) - } - b, _ := json.Marshal(buildstep.ReadGoTest(read("logthenfatal"), ".").Failed) - var old []struct { - Output string `json:"output,omitempty"` - } - if err := json.Unmarshal(b, &old); err != nil || len(old) != 1 || !strings.Contains(old[0].Output, "judged against") || !strings.Contains(old[0].Output, "3 subscription(s)") { - t.Errorf("1901/AC3: an older checker reads %+v, %v", old, err) - } - lines := strings.SplitAfter(read("deeptimeout"), "\n") - added := `{"Action":"output","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat","Output":" p_test.go:9: the child said:\n"}` + "\n" + - `{"Action":"output","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat","Output":" panic: child retried and recovered\n"}` + "\n" + - `{"Action":"output","Package":"example.com/deeptimeout","Test":"TestHoldsTheSeat","Output":" WARNING: DATA RACE\n"}` + "\n" - if got := buildstep.ReadGoTest(strings.Join(lines[:10], "")+added+strings.Join(lines[10:], ""), ".").Failed; len(got) != 1 || !strings.HasPrefix(got[0].Cause, "panic: test timed out after 2s") { - t.Errorf("1901/AC4: failures = %+v, want the timeout's panic as the cause", got) - } - res := buildstep.ReadGoTest(`{"Action":"run","Package":"m/a","Test":"TestA"} -{"Action":"skip","Package":"m/a","Test":"TestA","Elapsed":0} -{"Action":"run","Package":"m/b","Test":"TestB"} -{"Action":"pass","Package":"m/b","Test":"TestB","Elapsed":0} -{"Action":"pass","Package":"m/a","Elapsed":0} -{"Action":"pass","Package":"m/b","Elapsed":0} -`, ".") - got := map[string]int{} - for _, p := range res.Packages { - got[p.Package] = p.Skipped - } - if res.Skipped != 1 || got["m/a"] != 1 || got["m/b"] != 0 { - t.Errorf("skipped = %d, per package %v, want 1 with m/a 1 and m/b 0", res.Skipped, got) - } - }) -} diff --git a/go/buildstep/conformance/leftrunning.go b/go/buildstep/conformance/leftrunning.go deleted file mode 100644 index a8812c4..0000000 --- a/go/buildstep/conformance/leftrunning.go +++ /dev/null @@ -1,212 +0,0 @@ -package conformance - -import ( - "context" - "encoding/json" - "fmt" - "sort" - "strings" - "sync" - "testing" - "time" - - stdio "git.novox.be/novox/mesh-sdk/go" - "git.novox.be/novox/mesh-sdk/go/buildstep" - "git.novox.be/novox/mesh-sdk/go/delivery" -) - -// **A step left running by a previous process** (novox/hq issue 1890, note 21): when a delivery replaced mesh-build-go -// on a node while a check ask ran, the new process's start sweep let the ask's key stand as running, and it held one -// of the node's build slots until a hand end. Every build module runs this case (LeftRunning) through the start sweep -// its main runs: the hand-started steps a previous process left are ended at once and loud, its asks' keys deleted (the -// stream redelivers the ask, novox/hq ADR 0525 §6, and its asker keeps waiting for that), what this process runs kept. A -// catalogue check (mesh-catalog checks/failurekit) refuses a build module whose tests do not call LeftRunning. - -// Sweep is a build module's own start sweep, as its main runs it once at its start: it ends node's steps in scan that -// no process of the module runs any more (buildstep.EndStaleAtStart, here with the runtime already serving), before -// being the start of the process, saying each end with logf. -type Sweep func(ctx context.Context, scan buildstep.Scan, node string, before time.Time, logf func(string, ...any)) - -// LeftNode is the node the case's steps are of. -const LeftNode = "kitnode" - -// leftCase is one step in the steps state before the sweep, and whether the sweep must end it. -type leftCase struct { - id, tool, owner, state string - node string - age time.Duration // started this long before the process began (negative: after it) - ended bool - deleted bool // a gone process's ask: its key is deleted, not ended -} - -// gone is a previous process's owner on the same node. -var gone = "b00t-of-the-kit.4242.mvp0000" - -func leftCases() []leftCase { - return []leftCase{ - {id: "mvpwlkzi7e6ca8", tool: "check ask", owner: gone, age: 5 * time.Minute, deleted: true}, // note 21's ghost - {id: "mvpwlkzi7e6ca9", tool: "delivery ask", owner: gone, age: -time.Second, deleted: true}, - {id: "gonehand1", tool: "build_x_test", owner: gone, age: time.Minute, ended: true}, - {id: "oldhand1", tool: "build_x_fetch", age: time.Hour, ended: true}, // no owner: an older build's - {id: "mvpwlkzi7e6caa", tool: "check ask", age: time.Hour, ended: true}, // no owner, past the bound - {id: "mvpwlkzi7e6cab", tool: "check ask", age: 5 * time.Minute}, // no owner, the stream's still - {id: "mvpwlkzi7e6cac", tool: "check ask", owner: buildstep.Owner, age: time.Minute}, // this process's - {id: "minehand1", tool: "build_x_test", owner: buildstep.Owner, age: -time.Second}, - {id: "donehand1", tool: "build_x_test", owner: gone, age: time.Hour, state: delivery.StepPassed}, - {id: "otherhand1", tool: "build_x_test", owner: gone, age: time.Hour, node: "kitother"}, - } -} - -// LeftRunning runs the case through sweep and fails the test for each step the sweep ended or kept wrongly, or ended -// without saying it. -func LeftRunning(t *testing.T, sweep Sweep) { - t.Helper() - for _, problem := range JudgeLeftRunning(sweep) { - t.Error(problem) - } -} - -// JudgeLeftRunning says what is wrong with what sweep did to the case; nothing when it keeps the rule. -func JudgeLeftRunning(sweep Sweep) []string { - st := &steps{vals: map[string][]byte{}, revs: map[string]uint64{}} - before := time.Now() - for _, c := range leftCases() { - node, state := c.node, c.state - if node == "" { - node = LeftNode - } - if state == "" { - state = delivery.StepRunning - } - key, _ := delivery.StepKey(node, c.id) - raw, err := delivery.EncodeStep(delivery.Step{ID: c.id, Tool: c.tool, Node: node, State: state, Started: before.Add(-c.age).UTC(), Owner: c.owner}) - if err != nil { - return []string{"the kit's step " + c.id + ": " + err.Error()} - } - st.set(key, raw) - } - served := make(chan struct{}) - close(served) - was := buildstep.StaleServed - buildstep.StaleServed = func() <-chan struct{} { return served } - defer func() { buildstep.StaleServed = was }() - var mu sync.Mutex - var said []string - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - sweep(ctx, st.scan(), LeftNode, before, func(f string, a ...any) { - mu.Lock() - defer mu.Unlock() - said = append(said, fmt.Sprintf(f, a...)) - }) - mu.Lock() - log := strings.Join(said, "\n") - mu.Unlock() - var problems []string - for _, c := range leftCases() { - node := c.node - if node == "" { - node = LeftNode - } - key, _ := delivery.StepKey(node, c.id) - raw := st.get(key) - if c.deleted { - if raw != nil { - problems = append(problems, fmt.Sprintf("left running: the ask %s of the gone owner %q is still held after the sweep (%s); "+ - "its key is deleted, never ended: the stream redelivers it (ADR 0525 §6)", key, c.owner, raw)) - } - if !strings.Contains(log, key) || !strings.Contains(log, c.owner) { - problems = append(problems, "left running: the delete of "+key+" is not said with its owner "+c.owner) - } - continue - } - if raw == nil { - problems = append(problems, "left running: the step "+key+" is gone after the sweep, want it kept or ended") - continue - } - s, err := delivery.DecodeStep(raw) - if err != nil { - problems = append(problems, key+": "+err.Error()) - continue - } - want := c.state - if want == "" { - want = delivery.StepRunning - } - if c.ended { - want = delivery.StepFailed - } - if s.State != want { - problems = append(problems, fmt.Sprintf("left running: the step %s (%s, owner %q, %s before the start) is %s after the sweep, want %s", key, c.tool, c.owner, c.age, s.State, want)) - } - if c.ended && !strings.Contains(log, key) { - problems = append(problems, "left running: the end of "+key+" is not said") - } - if c.ended && c.owner != "" && !strings.Contains(log, c.owner) { - problems = append(problems, "left running: the end of "+key+" does not name its owner "+c.owner) - } - } - sort.Strings(problems) - return problems -} - -// steps is a steps state with revisions, whose Update lands only at the revision read (stdio.ErrChanged otherwise). -type steps struct { - mu sync.Mutex - vals map[string][]byte - revs map[string]uint64 -} - -func (s *steps) set(key string, raw []byte) { - s.mu.Lock() - defer s.mu.Unlock() - s.vals[key], s.revs[key] = raw, s.revs[key]+1 -} - -func (s *steps) get(key string) []byte { - s.mu.Lock() - defer s.mu.Unlock() - return s.vals[key] -} - -func (s *steps) scan() buildstep.Scan { - return buildstep.Scan{ - Keys: func() ([]string, error) { - s.mu.Lock() - defer s.mu.Unlock() - var out []string - for k := range s.vals { - out = append(out, k) - } - return out, nil - }, - Get: func(key string) (json.RawMessage, uint64, error) { - s.mu.Lock() - defer s.mu.Unlock() - return s.vals[key], s.revs[key], nil - }, - Delete: func(key string, revision uint64) error { - s.mu.Lock() - defer s.mu.Unlock() - if s.revs[key] != revision { - return stdio.ErrChanged - } - delete(s.vals, key) - s.revs[key]++ - return nil - }, - Update: func(key string, value any, revision uint64) (uint64, error) { - raw, err := json.Marshal(value) - if err != nil { - return 0, err - } - s.mu.Lock() - defer s.mu.Unlock() - if s.revs[key] != revision { - return 0, stdio.ErrChanged - } - s.vals[key], s.revs[key] = raw, revision+1 - return revision + 1, nil - }, - } -} diff --git a/go/buildstep/cpu.go b/go/buildstep/cpu.go deleted file mode 100644 index 39b5e3e..0000000 --- a/go/buildstep/cpu.go +++ /dev/null @@ -1,14 +0,0 @@ -package buildstep - -import ( - "os" - - "git.novox.be/novox/mesh-sdk/go/buildcpu" -) - -// NodeLimits is what one step of a build module is held to (novox/hq ADR 0553 §4): the node's processor share from the -// word the composer gives the module (buildcpu.ShareWord), divided by the module's own capacity, own, over the machine's -// processors. Without the word, no thread limit: Threads is 0. -func NodeLimits(own int) buildcpu.Limits { - return buildcpu.FromWords(os.Getenv, own) -} diff --git a/go/buildstep/digestshape_test.go b/go/buildstep/digestshape_test.go deleted file mode 100644 index aa2b757..0000000 --- a/go/buildstep/digestshape_test.go +++ /dev/null @@ -1,234 +0,0 @@ -package buildstep - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "sort" - "strings" - "sync" - "testing" - "time" - - "git.novox.be/novox/mesh-sdk/go/delivery" -) - -// credentialField is the node runtime's rule for a state value (mesh-tools node-tools/internal/bus/state.go, novox/hq -// ADR 0201), copied whole: the first field anywhere in the value whose name, lower-cased and without - _ and spaces, ends -// in a credential word, or "". A step's end is refused when it answers a field. -func credentialField(value json.RawMessage) string { - endings := []string{"password", "passwd", "secret", "token", "credential", "credentials", - "authorization", "apikey", "privatekey", "accesskey", "cookie"} - var v any - if json.Unmarshal(value, &v) != nil { - return "" - } - var walk func(any) string - walk = func(v any) string { - switch t := v.(type) { - case map[string]any: - keys := make([]string, 0, len(t)) - for k := range t { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - norm := strings.NewReplacer("-", "", "_", "", " ", "").Replace(strings.ToLower(k)) - for _, end := range endings { - if strings.HasSuffix(norm, end) { - return k - } - } - if found := walk(t[k]); found != "" { - return found - } - } - case []any: - for _, x := range t { - if found := walk(x); found != "" { - return found - } - } - } - return "" - } - return walk(v) -} - -// runtimeState is a steps state that refuses a value as the node runtime does, and keeps every value it took. -type runtimeState struct { - mu sync.Mutex - took map[string][]delivery.Step - refused int -} - -func newRuntimeState() *runtimeState { return &runtimeState{took: map[string][]delivery.Step{}} } - -func (s *runtimeState) put(key string, value any) (uint64, error) { - raw, _ := value.(json.RawMessage) - if f := credentialField(raw); f != "" { - s.mu.Lock() - s.refused++ - s.mu.Unlock() - return 0, fmt.Errorf("steps.%s carries a field %q, which names a credential", key, f) - } - st, err := delivery.DecodeStep(raw) - if err != nil { - return 0, err - } - s.mu.Lock() - defer s.mu.Unlock() - s.took[key] = append(s.took[key], st) - return uint64(len(s.took[key])), nil -} - -func (s *runtimeState) last(key string) (delivery.Step, bool) { - s.mu.Lock() - defer s.mu.Unlock() - t := s.took[key] - if len(t) == 0 { - return delivery.Step{}, false - } - return t[len(t)-1], true -} - -func fastEnds(t *testing.T) { - attempts, retry := EndAttempts, EndRetry - EndAttempts, EndRetry = 2, time.Millisecond - t.Cleanup(func() { EndAttempts, EndRetry = attempts, retry }) -} - -// 1930/AC1: given a check of a repository with the package .../internal/token, when its ask's answer is stored as the -// step's end, then no field is keyed by a package path: the runtime's credential rule takes the value, and the package -// and its digest read back from the stored end. -func TestADigestOfAPackageNamedTokenIsStoredAsAList(t *testing.T) { - fastEnds(t) - r := &recorder{ - fetch: map[string]any{"workspace": "/w"}, - digests: map[string]string{"x/internal/token": "d-token", "x/pkg/a": "d-a"}, - tests: map[string]bool{"x/internal/token": true, "x/pkg/a": true}, - } - out, err := RunAsk(context.Background(), r.steps(), ask(delivery.CheckJob{Levels: []string{delivery.LevelUnit}, - Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}})) - if err != nil { - t.Fatal(err) - } - old, _ := json.Marshal(map[string]any{"levels": []any{map[string]any{"level": "unit", "digest": map[string]any{"packages": r.digests}}}}) - if credentialField(old) != "x/internal/token" { - t.Fatalf("the rule does not refuse the form before issue 1930 (%s): the test proves nothing", old) - } - st := newRuntimeState() - if err := finish(st.put, "shanks.a1", delivery.Step{ID: "a1", Tool: "check ask", Node: "shanks", State: delivery.StepRunning, Started: time.Now()}, out, nil, t.Logf); err != nil { - t.Fatal(err) - } - s, ok := st.last("shanks.a1") - if !ok || s.State != delivery.StepPassed || st.refused != 0 { - t.Fatalf("the end %+v (written %v, refused %d), want it passed and taken at once", s, ok, st.refused) - } - var a delivery.CheckAnswer - if err := json.Unmarshal(s.Result, &a); err != nil { - t.Fatal(err) - } - if len(a.Levels) != 1 || a.Levels[0].Digest == nil { - t.Fatalf("levels %+v, want the unit level with its digest", a.Levels) - } - want := []delivery.PackageDigest{{Package: "x/internal/token", Digest: "d-token"}, {Package: "x/pkg/a", Digest: "d-a"}} - if got := a.Levels[0].Digest.Packages; fmt.Sprint(got) != fmt.Sprint(want) { - t.Errorf("packages %v, want %v", got, want) - } - if !strings.Contains(string(s.Result), `"package":"x/internal/token"`) { - t.Errorf("the stored end does not name the package as a value: %s", s.Result) - } -} - -// 1930/AC2: given an end the state refuses every time, when finish writes it, then it logs the refusal and writes a small -// failed end without the result that names the write's error; and when no end can be written at all it answers an error. -func TestARefusedEndIsSaidAndEndsTheStepFailed(t *testing.T) { - fastEnds(t) - big := errors.New("the value carries a field \"x/internal/token\", which names a credential") - var refused int - var stored []delivery.Step - put := func(key string, value any) (uint64, error) { - st, _ := delivery.DecodeStep(value.(json.RawMessage)) - if len(st.Result) > 0 { - refused++ - return 0, big - } - stored = append(stored, st) - return 1, nil - } - var logged []string - logf := func(format string, args ...any) { logged = append(logged, fmt.Sprintf(format, args...)) } - err := finish(put, "shanks.a2", delivery.Step{ID: "a2", Tool: "check ask", Node: "shanks", State: delivery.StepRunning, Started: time.Now()}, map[string]any{"levels": 1}, nil, logf) - if err != nil { - t.Fatalf("finish answered %v, want the small end written", err) - } - if refused != EndAttempts || len(stored) != 1 { - t.Fatalf("refused %d, stored %d, want %d refusals and one small end", refused, len(stored), EndAttempts) - } - s := stored[0] - if s.State != delivery.StepFailed || len(s.Result) != 0 || !strings.Contains(s.Reason, "could not be written") || - !strings.Contains(s.Reason, big.Error()) || !strings.Contains(s.Reason, "passed") { - t.Errorf("the small end %+v, want failed, no result, naming the write's error and that it passed", s) - } - if len(logged) == 0 || !strings.Contains(logged[0], big.Error()) { - t.Errorf("logged %q, want the refusal said", logged) - } - none := func(string, any) (uint64, error) { return 0, errors.New("the bus is gone") } - if err := finish(none, "shanks.a3", delivery.Step{ID: "a3", Tool: "check ask", Node: "shanks", Started: time.Now()}, nil, nil, logf); err == nil || !strings.Contains(err.Error(), "the bus is gone") { - t.Errorf("finish with no write possible answered %v, want the error", err) - } -} - -// 1930/AC2: given an ask whose end the state refuses, when it runs, then the ask is acknowledged only once its small -// failed end is written; when no end can be written at all, it is given back, never acknowledged as done. -func TestAnAskWhoseEndCannotBeWrittenIsNotAcked(t *testing.T) { - fastEnds(t) - for _, tc := range []struct { - name string - smallOK bool - wantAcked bool - }{{"the small end written", true, true}, {"no end written", false, false}} { - t.Run(tc.name, func(t *testing.T) { - src := NewFakeAsks() - a := testAsk(delivery.AskCheck) - _ = src.PublishAsk(a) - var mu sync.Mutex - var ends []delivery.Step - put := func(key string, value any) (uint64, error) { - st, err := delivery.DecodeStep(value.(json.RawMessage)) - if err != nil { - return 0, err - } - if st.State != delivery.StepRunning && (len(st.Result) > 0 || !tc.smallOK) { - return 0, errors.New("refused") - } - mu.Lock() - ends = append(ends, st) - mu.Unlock() - return 1, nil - } - p := &Puller{Source: src, Language: delivery.AskGo, Node: "shanks", Put: put, Capacity: func() int { return 1 }, - PullWait: 10 * time.Millisecond, Logf: t.Logf, - Run: func(ctx context.Context, a delivery.StepAsk) (any, error) { return map[string]any{"ran": true}, nil }} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - go p.Pull(ctx) - waitFor(t, "the ask's fate", func() bool { ac, tm, nk := src.Fates(); return len(ac)+len(tm)+len(nk) > 0 }) - cancel() - acked, termed, naked := src.Fates() - if tc.wantAcked && (len(acked) != 1 || len(naked) != 0) || !tc.wantAcked && (len(acked) != 0 || len(naked) == 0) || len(termed) != 0 { - t.Fatalf("acked %v naked %v termed %v", acked, naked, termed) - } - mu.Lock() - defer mu.Unlock() - if tc.smallOK { - last := ends[len(ends)-1] - if last.State != delivery.StepFailed || !strings.Contains(last.Reason, "refused") { - t.Errorf("the end %+v, want the small failed end", last) - } - } - }) - } -} diff --git a/go/buildstep/doc.go b/go/buildstep/doc.go new file mode 100644 index 0000000..93c1feb --- /dev/null +++ b/go/buildstep/doc.go @@ -0,0 +1,5 @@ +// Package buildstep is the contract of a build step every delivery part reads (novox/hq ADR 0593 §1): a step's id, the +// fetch request, a step a previous process left running and its end, a failure's words, a module tree's shape, the +// capacity setting and the launchers' names. The runner the build modules share is the Go build module's own package +// (mesh-catalog modules/mesh-build-go/buildstep), which imports this one. +package buildstep diff --git a/go/buildstep/failure.go b/go/buildstep/failure.go index 78c27af..bdea558 100644 --- a/go/buildstep/failure.go +++ b/go/buildstep/failure.go @@ -1,11 +1,5 @@ package buildstep -// The failure contract of a build step's test level (novox/hq issue 1901, its thorough fix): what a build module says of -// each test that failed, the same for every language. Each build module used to parse its own test runner's output, and -// a fix in one never reached the others: a failed Go test kept a t.Logf line, a failed node --test run kept a passing -// '✔' line of its first file. The parsers that fill the contract live here (gotest.go, tap.go) and the conformance kit -// (package conformance) holds every build module to them with captured runner output. - import ( "fmt" "strings" diff --git a/go/buildstep/fetch.go b/go/buildstep/fetch.go deleted file mode 100644 index 4135bdb..0000000 --- a/go/buildstep/fetch.go +++ /dev/null @@ -1,196 +0,0 @@ -package buildstep - -import ( - "context" - "fmt" - "net/url" - "os" - "path/filepath" - "regexp" - "strings" - - "git.novox.be/novox/mesh-sdk/go/forgekey" -) - -// Option changes FetchCommit. -type Option func(*fetchOptions) - -type fetchOptions struct { - allowFile bool - mirror string -} - -// AllowFileRemote lets FetchCommit take a file:// remote. It is for a test's local repository; production -// code does not pass it, and FetchCommit then refuses anything but ssh://. -func AllowFileRemote() Option { return func(o *fetchOptions) { o.allowFile = true } } - -// WithMirror makes FetchCommit (and MergeInto) borrow objects from mirror, a bare repository that UpdateMirror keeps -// in the build module's own cache (novox/hq ADR 0536 §2; issue 890): the workspace's .git/objects/info/alternates -// names the mirror's objects directory, so git transfers only the objects the mirror lacks. It is a plain file, never -// a symlink. mirror must be an absolute path of an existing directory; the workspace never writes to it. -func WithMirror(mirror string) Option { return func(o *fetchOptions) { o.mirror = mirror } } - -// useMirror writes the alternates file of the repository in dir, when a mirror is set. -func useMirror(o fetchOptions, dir string) error { - if o.mirror == "" { - return nil - } - info := filepath.Join(dir, ".git", "objects", "info") - if err := os.MkdirAll(info, 0o755); err != nil { - return fmt.Errorf("buildstep: the mirror cannot be used: %w", err) - } - line := filepath.Join(o.mirror, "objects") + "\n" - if err := os.WriteFile(filepath.Join(info, "alternates"), []byte(line), 0o644); err != nil { - return fmt.Errorf("buildstep: the mirror cannot be used: %w", err) - } - return nil -} - -var ( - commitOK = regexp.MustCompile(`^[0-9a-f]{40}$`) - refOK = regexp.MustCompile(`^refs/[A-Za-z0-9._/-]+$`) -) - -// FetchCommit fetches one commit of a forge repository into dir, an empty directory, and checks it out detached -// (novox/hq ADR 0466 §4, §5; issue 1474). remote is ssh:////.git, the ssh name being the -// one the build module's templates set up (ADR 0409 §1); commit is a full 40-hex sha; ref is optional, and names -// the pull request's head (refs/pull//head) when the commit is only there. Every git command runs through run -// as `git -C dir ...` with GIT_TERMINAL_PROMPT=0 and no credential helper, so nothing is asked for and no -// credential is used. A remote that is not ssh://, or one with user information, and a commit that is not 40 -// hex digits are refused before anything runs. -func FetchCommit(ctx context.Context, run forgekey.Runner, dir, remote, commit, ref string, opts ...Option) error { - var o fetchOptions - for _, opt := range opts { - opt(&o) - } - if err := checkFetch(o, dir, remote, commit, ref); err != nil { - return err - } - env := []string{"GIT_TERMINAL_PROMPT=0"} - git := func(args ...string) (string, error) { - full := append([]string{"-c", "credential.helper=", "-C", dir}, args...) - r := run(ctx, env, "git", full...) - if r.Status != 0 || r.Err != "" { - return "", fmt.Errorf("git %s: %s", args[0], forgekey.Said(r.Err+"\n"+r.Stderr)) - } - return r.Stdout, nil - } - if _, err := git("init", "-q"); err != nil { - return err - } - if err := useMirror(o, dir); err != nil { - return err - } - if _, err := git("remote", "add", "origin", remote); err != nil { - return err - } - if _, err := git("fetch", "--no-tags", "--depth", "1", "origin", commit); err != nil { - if ref == "" { - return err - } - if _, rerr := git("fetch", "--no-tags", "origin", ref); rerr != nil { - return fmt.Errorf("%v; then the ref %s: %w", err, ref, rerr) - } - if _, verr := git("cat-file", "-e", commit+"^{commit}"); verr != nil { - return fmt.Errorf("the commit %s is not in %s: %w", commit, ref, verr) - } - } - if _, err := git("checkout", "-q", "--detach", commit); err != nil { - return err - } - out, err := git("rev-parse", "HEAD") - if err != nil { - return err - } - if head := strings.TrimSpace(out); head != commit { - return fmt.Errorf("the checkout is at %s, not at the commit %s", head, commit) - } - return nil -} - -// UpdateMirror brings the bare repository mirror to commit: it makes the mirror when it is absent (git init --bare, -// origin set to remote), then fetches the commit, or the ref when only the ref holds it, with the same rules as -// FetchCommit (no prompt, no credential helper, ssh:// or an allowed file:// remote, a 40-hex commit). The mirror -// keeps every commit it was brought to, so later fetches through WithMirror transfer only what is new. Callers -// serialise updates of one mirror. -func UpdateMirror(ctx context.Context, run forgekey.Runner, mirror, remote, commit, ref string, opts ...Option) error { - var o fetchOptions - for _, opt := range opts { - opt(&o) - } - if !filepath.IsAbs(mirror) { - return fmt.Errorf("buildstep: the mirror %q is not an absolute path", mirror) - } - if err := checkFetch(o, mirror, remote, commit, ref); err != nil { - return err - } - env := []string{"GIT_TERMINAL_PROMPT=0"} - git := func(args ...string) error { - r := run(ctx, env, "git", append([]string{"-c", "credential.helper="}, args...)...) - if r.Status != 0 || r.Err != "" { - verb := args[0] - if verb == "-C" && len(args) > 2 { // the mirror's commands are named by what they do, not by -C - verb = args[2] - } - return fmt.Errorf("git %s: %s", verb, forgekey.Said(r.Err+"\n"+r.Stderr)) - } - return nil - } - in := func(args ...string) error { return git(append([]string{"-C", mirror}, args...)...) } - if _, err := os.Stat(filepath.Join(mirror, "HEAD")); err != nil { - if err := git("init", "-q", "--bare", mirror); err != nil { - return err - } - if err := in("remote", "add", "origin", remote); err != nil { - return err - } - } else if err := in("remote", "set-url", "origin", remote); err != nil { - return err - } - if err := in("fetch", "--no-tags", "origin", commit); err != nil { - if ref == "" { - return err - } - if rerr := in("fetch", "--no-tags", "origin", ref); rerr != nil { - return fmt.Errorf("%v; then the ref %s: %w", err, ref, rerr) - } - } - if err := in("cat-file", "-e", commit+"^{commit}"); err != nil { - return fmt.Errorf("the commit %s is not in the mirror: %w", commit, err) - } - return nil -} - -func checkFetch(o fetchOptions, dir, remote, commit, ref string) error { - if dir == "" { - return fmt.Errorf("buildstep: no workspace directory") - } - if o.mirror != "" { - if !filepath.IsAbs(o.mirror) { - return fmt.Errorf("buildstep: the mirror %q is not an absolute path", o.mirror) - } - if fi, err := os.Stat(o.mirror); err != nil || !fi.IsDir() { - return fmt.Errorf("buildstep: the mirror %q is not an existing directory", o.mirror) - } - } - if !commitOK.MatchString(commit) { - return fmt.Errorf("buildstep: %q is not a full commit (40 hex digits)", commit) - } - if ref != "" && !refOK.MatchString(ref) { - return fmt.Errorf("buildstep: %q is not a ref (refs/...)", ref) - } - u, err := url.Parse(remote) - if err != nil { - return fmt.Errorf("buildstep: the remote is not a URL: %w", err) - } - if u.User != nil { - return fmt.Errorf("buildstep: the remote carries user information; the key is the credential") - } - switch { - case u.Scheme == "ssh" && u.Host != "": - case u.Scheme == "file" && o.allowFile: - default: - return fmt.Errorf("buildstep: the remote %q is not ssh://", remote) - } - return nil -} diff --git a/go/buildstep/fetch_integration_test.go b/go/buildstep/fetch_integration_test.go deleted file mode 100644 index 5f782a1..0000000 --- a/go/buildstep/fetch_integration_test.go +++ /dev/null @@ -1,59 +0,0 @@ -//go:build integration - -package buildstep - -import ( - "context" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - - "git.novox.be/novox/mesh-sdk/go/forgekey" -) - -func gitIn(t *testing.T, dir string, args ...string) string { - t.Helper() - cmd := exec.Command("git", append([]string{"-C", dir, "-c", "user.name=t", "-c", "user.email=t@example.invalid"}, args...)...) - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("git %v: %v\n%s", args, err, out) - } - return strings.TrimSpace(string(out)) -} - -// 1474/AC5: a local bare repository with a commit only under refs/pull/1/head (a file:// remote, allowed by the -// test-only option): the workspace's HEAD is the commit. The forge itself is proven live after the build -// modules' delivery (ADR 0466 §5). -func TestFetchCommitFromAPullRefOnly(t *testing.T) { - root := t.TempDir() - bare := filepath.Join(root, "r.git") - work := filepath.Join(root, "work") - for _, d := range []string{bare, work} { - if err := os.MkdirAll(d, 0o755); err != nil { - t.Fatal(err) - } - } - gitIn(t, bare, "init", "-q", "--bare") - gitIn(t, work, "init", "-q") - if err := os.WriteFile(filepath.Join(work, "f"), []byte("x"), 0o644); err != nil { - t.Fatal(err) - } - gitIn(t, work, "add", "f") - gitIn(t, work, "commit", "-q", "-m", "pull request head") - commit := gitIn(t, work, "rev-parse", "HEAD") - gitIn(t, work, "push", "-q", bare, "HEAD:refs/pull/1/head") - - ws := filepath.Join(root, "ws") - if err := os.MkdirAll(ws, 0o755); err != nil { - t.Fatal(err) - } - err := FetchCommit(context.Background(), forgekey.ExecRunner, ws, "file://"+bare, commit, "refs/pull/1/head", AllowFileRemote()) - if err != nil { - t.Fatal(err) - } - if head := gitIn(t, ws, "rev-parse", "HEAD"); head != commit { - t.Errorf("HEAD = %s, want %s", head, commit) - } -} diff --git a/go/buildstep/fetch_test.go b/go/buildstep/fetch_test.go deleted file mode 100644 index b989ab6..0000000 --- a/go/buildstep/fetch_test.go +++ /dev/null @@ -1,137 +0,0 @@ -package buildstep - -import ( - "context" - "strings" - "testing" - - "git.novox.be/novox/mesh-sdk/go/forgekey" -) - -const sha = "0123456789abcdef0123456789abcdef01234567" - -// fakeGit records every command and answers by a rule. -type fakeGit struct { - calls []string - envs [][]string - rule func(args string) forgekey.Ran -} - -func (f *fakeGit) run(ctx context.Context, env []string, name string, args ...string) forgekey.Ran { - line := name + " " + strings.Join(args, " ") - f.calls = append(f.calls, line) - f.envs = append(f.envs, env) - if f.rule != nil { - if r := f.rule(line); r.Status != 0 || r.Stdout != "" || r.Err != "" { - return r - } - } - if len(args) > 4 && args[4] == "rev-parse" { - return forgekey.Ran{Stdout: sha + "\n"} - } - return forgekey.Ran{} -} - -const pre = "git -c credential.helper= -C /w " - -// 1474/AC1: the git commands are exactly the sequence, none with a credential or an https remote. -func TestFetchCommitRunsTheSequence(t *testing.T) { - f := &fakeGit{} - if err := FetchCommit(context.Background(), f.run, "/w", "ssh://mesh-forge/novox/mesh-sdk.git", sha, ""); err != nil { - t.Fatal(err) - } - want := []string{ - pre + "init -q", - pre + "remote add origin ssh://mesh-forge/novox/mesh-sdk.git", - pre + "fetch --no-tags --depth 1 origin " + sha, - pre + "checkout -q --detach " + sha, - pre + "rev-parse HEAD", - } - if strings.Join(f.calls, "\n") != strings.Join(want, "\n") { - t.Errorf("commands:\n%s\nwant:\n%s", strings.Join(f.calls, "\n"), strings.Join(want, "\n")) - } - for i, c := range f.calls { - if strings.Contains(c, "https://") || strings.Contains(c, "@") || !strings.Contains(c, "credential.helper= ") { - t.Errorf("command %q carries a credential or https", c) - } - if len(f.envs[i]) != 1 || f.envs[i][0] != "GIT_TERMINAL_PROMPT=0" { - t.Errorf("env %v", f.envs[i]) - } - } -} - -// 1474/AC2: the depth-1 fetch fails and a pull ref is given: the ref is fetched and the commit verified; with no -// ref, the fetch's failure is the answer. -func TestFetchCommitFallsBackToThePullRef(t *testing.T) { - f := &fakeGit{rule: func(l string) forgekey.Ran { - if strings.Contains(l, "--depth 1") { - return forgekey.Ran{Status: 128, Stderr: "fatal: couldn't find remote ref " + sha + "\n"} - } - return forgekey.Ran{} - }} - if err := FetchCommit(context.Background(), f.run, "/w", "ssh://mesh-forge/o/r.git", sha, "refs/pull/1/head"); err != nil { - t.Fatal(err) - } - want := []string{pre + "fetch --no-tags origin refs/pull/1/head", pre + "cat-file -e " + sha + "^{commit}", pre + "checkout -q --detach " + sha} - got := strings.Join(f.calls[3:6], "\n") - if got != strings.Join(want, "\n") { - t.Errorf("after the failed fetch:\n%s\nwant:\n%s", got, strings.Join(want, "\n")) - } - - g := &fakeGit{rule: f.rule} - err := FetchCommit(context.Background(), g.run, "/w", "ssh://mesh-forge/o/r.git", sha, "") - if err == nil || !strings.Contains(err.Error(), "couldn't find remote ref") || len(g.calls) != 3 { - t.Errorf("without a ref: %v after %d commands", err, len(g.calls)) - } - - h := &fakeGit{rule: func(l string) forgekey.Ran { - if strings.Contains(l, "--depth 1") || strings.Contains(l, "cat-file") { - return forgekey.Ran{Status: 1, Stderr: "no"} - } - return forgekey.Ran{} - }} - if err := FetchCommit(context.Background(), h.run, "/w", "ssh://mesh-forge/o/r.git", sha, "refs/pull/1/head"); err == nil { - t.Error("a commit that is not in the ref was accepted") - } -} - -// 1474/AC3: HEAD differs from the commit after the checkout: an error naming both. -func TestFetchCommitAnswersAHeadThatIsNotTheCommit(t *testing.T) { - other := strings.Repeat("a", 40) - f := &fakeGit{rule: func(l string) forgekey.Ran { - if strings.HasSuffix(l, "rev-parse HEAD") { - return forgekey.Ran{Stdout: other + "\n"} - } - return forgekey.Ran{} - }} - err := FetchCommit(context.Background(), f.run, "/w", "ssh://mesh-forge/o/r.git", sha, "") - if err == nil || !strings.Contains(err.Error(), other) || !strings.Contains(err.Error(), sha) { - t.Errorf("error = %v, want both shas", err) - } -} - -// 1474/AC4: an https remote, user information, a file remote without the option, or a commit that is not 40 -// hex digits is refused before anything runs. -func TestFetchCommitRefusesBeforeRunningAnything(t *testing.T) { - for _, c := range []struct{ remote, commit, ref string }{ - {"https://forge.example/o/r.git", sha, ""}, - {"http://forge.example/o/r.git", sha, ""}, - {"ssh://git@mesh-forge/o/r.git", sha, ""}, - {"ssh://user:pw@mesh-forge/o/r.git", sha, ""}, - {"file:///tmp/r.git", sha, ""}, - {"/tmp/r.git", sha, ""}, - {"ssh://mesh-forge/o/r.git", "abc123", ""}, - {"ssh://mesh-forge/o/r.git", strings.ToUpper(sha), ""}, - {"ssh://mesh-forge/o/r.git", "main", ""}, - {"ssh://mesh-forge/o/r.git", sha, "--upload-pack=x"}, - } { - f := &fakeGit{} - if err := FetchCommit(context.Background(), f.run, "/w", c.remote, c.commit, c.ref); err == nil || len(f.calls) != 0 { - t.Errorf("%+v: err %v, %d commands", c, err, len(f.calls)) - } - } - f := &fakeGit{} - if err := FetchCommit(context.Background(), f.run, "/w", "file:///tmp/r.git", sha, "", AllowFileRemote()); err != nil { - t.Errorf("a file remote with the option: %v", err) - } -} diff --git a/go/buildstep/gotest.go b/go/buildstep/gotest.go deleted file mode 100644 index 7db4c8c..0000000 --- a/go/buildstep/gotest.go +++ /dev/null @@ -1,409 +0,0 @@ -package buildstep - -// The reader of `go test -json` (moved whole from mesh-catalog's mesh-build-go, issue 1901's thorough fix): per package -// what ran and for how long, and each failure under the contract (failure.go), its real cause too when the failure is no -// test's assertion (a panic, a timeout, a build error, an exit, a kill, a race). - -import ( - "bufio" - "encoding/json" - "fmt" - "regexp" - "slices" - "strings" -) - -// GoPackage is one package's part of a Go test level. -type GoPackage struct { - Dir string `json:"dir"` - Package string `json:"package"` - Passed bool `json:"passed"` - Elapsed float64 `json:"elapsed"` - Tests int `json:"tests"` - // Skipped is how many of the package's tests skipped. - Skipped int `json:"skipped,omitempty"` - // NoTests is a package with no test files (at this level: a file with the other level's build tag is none). - NoTests bool `json:"no_tests,omitempty"` -} - -// TestTime is one test and the seconds it took. -type TestTime struct { - Package string `json:"package"` - Test string `json:"test"` - Elapsed float64 `json:"elapsed"` -} - -// GoTest is what one go.mod directory's `go test -json` said. -type GoTest struct { - Packages []GoPackage - Tests int - Skipped int - Times []TestTime - Failed []Failure - // Elapsed is the slowest package's running time in seconds. - Elapsed float64 - // Noise is the lines that were no event: what go said when it could not run the tests at all. - Noise []string -} - -// ReadGoTest reads the output of `go test -json` run in the go.mod directory dir. -func ReadGoTest(out, dir string) GoTest { - r := &goReader{dir: dir, pkgs: map[string]*GoPackage{}, builds: map[string][]string{}, pkgOut: map[string][]string{}, - testOut: map[string][]string{}, causes: map[string]string{}, failedIn: map[string]bool{}, - running: map[string][]string{}, passed: map[string]bool{}, built: map[string]bool{}} - sc := bufio.NewScanner(strings.NewReader(out)) - sc.Buffer(make([]byte, 1<<20), 1<<26) - for sc.Scan() { - var ev goEvent - if err := json.Unmarshal(sc.Bytes(), &ev); err != nil { - if l := strings.TrimSpace(sc.Text()); l != "" { - r.res.Noise = append(r.res.Noise, l) - } - continue - } - r.event(ev) - } - r.finish() - return r.res -} - -// goEvent is one line of `go test -json` (test2json, and the build events go test adds). -type goEvent struct { - Action string - Package string - Test string - Output string - OutputType string // "error" for a t.Error or t.Fatal message, "error-continue" for its further lines - ImportPath string - FailedBuild string - Elapsed float64 -} - -type goReader struct { - res GoTest - dir string - pkgs map[string]*GoPackage - order []string - builds map[string][]string // build output by import path - pkgOut map[string][]string - testOut map[string][]string // by package + "\x00" + test - causes map[string]string // the first error line of a test, by the same key - failedIn map[string]bool // packages with a failed test - // running is the tests that started and have not ended, by package, in the order they started: when a package - // fails with no failed test, these were running when its test program stopped. - running map[string][]string - // passed says a package's test program printed "PASS" before it failed; built says it failed to build. - passed, built map[string]bool -} - -func (r *goReader) pkg(name string) *GoPackage { - p := r.pkgs[name] - if p == nil { - p = &GoPackage{Dir: r.dir, Package: name} - r.pkgs[name] = p - r.order = append(r.order, name) - } - return p -} - -func keepLine(lines []string, out string) []string { - if t := strings.TrimRight(out, "\r\n"); t != "" && !strings.HasPrefix(t, "=== ") { - lines = append(lines, t) - } - return lines -} - -func (r *goReader) event(ev goEvent) { - switch { - case ev.Package == "" && ev.ImportPath != "": - if ev.Action == "build-output" { - r.builds[ev.ImportPath] = keepLine(r.builds[ev.ImportPath], ev.Output) - } - case ev.Package == "": - case ev.Test == "": - p := r.pkg(ev.Package) - switch ev.Action { - case "output": - // go test's own summary of a package is never a failure's words: a "PASS" before a "FAIL " says only - // that the tests passed and the program failed after them. - switch l := strings.TrimRight(ev.Output, "\r\n"); { - case l == "PASS": - r.passed[ev.Package] = true - case goSummaryLine(l): - default: - r.pkgOut[ev.Package] = keepLine(r.pkgOut[ev.Package], ev.Output) - } - case "pass": - p.Passed, p.Elapsed = true, ev.Elapsed - case "fail": - p.Passed, p.Elapsed = false, ev.Elapsed - if ev.FailedBuild != "" { - r.built[ev.Package] = true - r.pkgOut[ev.Package] = append(r.builds[ev.FailedBuild], r.pkgOut[ev.Package]...) - } - case "skip": - p.Passed, p.NoTests = true, true - } - default: - key := ev.Package + "\x00" + ev.Test - switch ev.Action { - case "output": - r.testOut[key] = keepLine(r.testOut[key], ev.Output) - if ev.OutputType == "error" && r.causes[key] == "" { - r.causes[key] = goErrorWords(ev.Output) - } - case "run": - r.running[ev.Package] = append(r.running[ev.Package], ev.Test) - case "pass", "fail", "skip": - r.running[ev.Package] = slices.DeleteFunc(r.running[ev.Package], func(t string) bool { return t == ev.Test }) - p := r.pkg(ev.Package) - p.Tests++ - r.res.Tests++ - if ev.Action == "skip" { - r.res.Skipped++ - p.Skipped++ - } - r.res.Times = append(r.res.Times, TestTime{Package: ev.Package, Test: ev.Test, Elapsed: ev.Elapsed}) - if ev.Action == "fail" { - r.failedIn[ev.Package] = true - if r.subtestFailed(ev.Package, ev.Test) { - return // a parent failed only by its subtest is that subtest's failure, said once - } - if r.subtestTakesTheCause(ev.Package, ev.Test) { - return // a subtest's panic, which go files under its parent, is said on the subtest - } - r.res.Failed = append(r.res.Failed, r.testFailure(ev.Package, ev.Test)) - } - } - } -} - -// subtestFailed says a failure of test under pkg was already reported for one of its subtests (they end first), each with -// a cause of its own, and the test reported no error and no runtime cause (a subtest's panic) of its own. -func (r *goReader) subtestFailed(pkg, test string) bool { - key := pkg + "\x00" + test - if r.causes[key] != "" || goRuntimeCause(r.testOut[key]) != "" { - return false - } - for _, f := range r.res.Failed { - if f.Package == pkg && strings.HasPrefix(f.Test, test+"/") { - return true - } - } - return false -} - -// noMessage is the cause of a test that failed and said nothing of why. -const noMessage = "the test failed without an error message (t.Fail or t.FailNow)" - -// subtestTakesTheCause gives a parent's runtime cause (a subtest's panic: go test -json files its lines under the -// parent) to the subtests of that parent that failed with no message of their own, and says it did. A parent with an -// error line of its own, or whose failed subtests each said why, is reported as itself. -func (r *goReader) subtestTakesTheCause(pkg, test string) bool { - key := pkg + "\x00" + test - if r.causes[key] != "" { - return false - } - cause := goRuntimeCause(r.testOut[key]) - if cause == "" { - return false - } - took := false - for i, f := range r.res.Failed { - if f.Package == pkg && strings.HasPrefix(f.Test, test+"/") && f.Cause == noMessage { - r.res.Failed[i].Cause, r.res.Failed[i].Output = cause, Clip(r.testOut[key], cause) - took = true - } - } - return took -} - -// testFailure is a test that failed: its cause is its first error line, the race it ran into, the runtime's line, or -// words saying it failed without a message. -func (r *goReader) testFailure(pkg, test string) Failure { - key := pkg + "\x00" + test - cause := r.causes[key] - if race := goRaceCause(r.testOut[key]); race != "" && (cause == "" || strings.HasPrefix(cause, "race detected")) { - cause = race // the race itself, not testing's "race detected during execution of test" - } else if cause == "" { - cause = goRuntimeCause(r.testOut[key]) - } - if cause == "" { - cause = noMessage - } - return Failure{Package: pkg, Test: test, Output: Clip(r.testOut[key], cause), Cause: cause} -} - -// goSummaryLine says a line is go test's summary of a package, in go's exact forms only: "PASS" or "FAIL" alone, -// "ok \t\t