diff --git a/c/.gitignore b/c/.gitignore deleted file mode 100644 index 33a9488..0000000 --- a/c/.gitignore +++ /dev/null @@ -1 +0,0 @@ -example diff --git a/c/Makefile b/c/Makefile deleted file mode 100644 index acd63f1..0000000 --- a/c/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -CFLAGS ?= -O2 -Wall -Wextra -JANSSON := $(shell pkg-config --cflags --libs jansson) - -example: example.c mesh_sdk.c mesh_sdk.h - $(CC) $(CFLAGS) -o $@ example.c mesh_sdk.c $(JANSSON) - -clean: - rm -f example diff --git a/c/README.md b/c/README.md deleted file mode 100644 index bad0829..0000000 --- a/c/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# mesh-sdk for C - -The skeleton (novox/hq ADR 0188): `mesh_serve(name, tools, count)` speaks MCP over stdio to the -node's tool runtime. A `mesh_tool` is a name, a description, an argument schema and a function -returning JSON. A tool named `.` is the seat's implementation. JSON is jansson's, the -one dependency. - -What it holds, under ADR 0039's test: the stdio loop and the tool type. Nothing else — no bus, no -client, nothing of any module's. `make` and pipe a `tools/list` request into `./example`. diff --git a/c/example.c b/c/example.c deleted file mode 100644 index 49a5773..0000000 --- a/c/example.c +++ /dev/null @@ -1,27 +0,0 @@ -/* A tools bundle in C: one tool of its own and one seat verb, served over stdio. */ -#include "mesh_sdk.h" -#include - -static json_t *greet(json_t *args, char **error) { - (void)error; - const char *who = json_string_value(json_object_get(args, "who")); - char greeting[128]; - snprintf(greeting, sizeof greeting, "hello %s", who ? who : "world"); - return json_pack("{s:s,s:s}", "greeting", greeting, "language", "c"); -} - -static json_t *on(json_t *args, char **error) { - (void)args; (void)error; - return json_pack("{s:b,s:s}", "on", 1, "language", "c"); -} - -int main(void) { - json_t *who = json_pack("{s:{s:s,s:s}}", "who", "type", "string", "description", "whom"); - const mesh_tool tools[] = { - {"greet", "say hello", who, greet}, - {"node-lamp.on", "the seat's verb", NULL, on}, - }; - int rc = mesh_serve("c-example", tools, 2); - json_decref(who); - return rc; -} diff --git a/c/mesh_sdk.c b/c/mesh_sdk.c deleted file mode 100644 index 7f3e000..0000000 --- a/c/mesh_sdk.c +++ /dev/null @@ -1,83 +0,0 @@ -#include "mesh_sdk.h" -#include -#include -#include - -static void say(json_t *m) { - json_object_set_new(m, "jsonrpc", json_string("2.0")); - char *text = json_dumps(m, JSON_COMPACT); - fprintf(stdout, "%s\n", text); - fflush(stdout); - free(text); - json_decref(m); -} - -static json_t *as_schema(json_t *given) { - if (!given || !json_is_object(given) || json_object_size(given) == 0) - return json_pack("{s:s,s:{}}", "type", "object", "properties"); - if (json_object_get(given, "type") || json_object_get(given, "properties")) - return json_incref(given); - return json_pack("{s:s,s:O}", "type", "object", "properties", given); -} - -int mesh_serve(const char *name, const mesh_tool *tools, size_t count) { - char *line = NULL; - size_t cap = 0; - ssize_t got; - while ((got = getline(&line, &cap, stdin)) >= 0) { - json_error_t err; - json_t *req = json_loads(line, 0, &err); - if (!req) continue; - json_t *id = json_object_get(req, "id"); - const char *method = json_string_value(json_object_get(req, "method")); - json_t *params = json_object_get(req, "params"); - if (!method) { json_decref(req); continue; } - if (strcmp(method, "initialize") == 0) { - say(json_pack("{s:O,s:{s:s,s:{s:{}},s:{s:s,s:s}}}", "id", id ? id : json_null(), "result", - "protocolVersion", MESH_SDK_PROTOCOL, "capabilities", "tools", - "serverInfo", "name", name, "version", "1")); - } else if (strcmp(method, "notifications/initialized") == 0) { - } else if (strcmp(method, "ping") == 0) { - if (id) say(json_pack("{s:O,s:{}}", "id", id, "result")); - } else if (strcmp(method, "tools/list") == 0) { - json_t *listed = json_array(); - for (size_t i = 0; i < count; i++) - json_array_append_new(listed, json_pack("{s:s,s:s,s:o}", "name", tools[i].name, - "description", tools[i].description, "inputSchema", as_schema(tools[i].input))); - say(json_pack("{s:O,s:{s:o}}", "id", id ? id : json_null(), "result", "tools", listed)); - } else if (strcmp(method, "tools/call") == 0) { - const char *wanted = params ? json_string_value(json_object_get(params, "name")) : NULL; - const mesh_tool *tool = NULL; - for (size_t i = 0; wanted && i < count; i++) - if (strcmp(tools[i].name, wanted) == 0) tool = &tools[i]; - if (!tool) { - char message[256]; - snprintf(message, sizeof message, "%s has no tool %s", name, wanted ? wanted : ""); - say(json_pack("{s:O,s:{s:i,s:s}}", "id", id ? id : json_null(), "error", "code", -32602, "message", message)); - } else { - json_t *args = params ? json_object_get(params, "arguments") : NULL; - json_t *empty = args ? NULL : json_object(); - char *error = NULL; - json_t *result = tool->run(args ? args : empty, &error); - if (empty) json_decref(empty); - if (result) { - char *text = json_dumps(result, JSON_COMPACT); - say(json_pack("{s:O,s:{s:[{s:s,s:s}]}}", "id", id ? id : json_null(), "result", "content", "type", "text", "text", text)); - free(text); - json_decref(result); - } else { - say(json_pack("{s:O,s:{s:[{s:s,s:s}],s:b}}", "id", id ? id : json_null(), "result", "content", - "type", "text", "text", error ? error : "the tool failed", "isError", 1)); - free(error); - } - } - } else if (id) { - char message[256]; - snprintf(message, sizeof message, "no %s", method); - say(json_pack("{s:O,s:{s:i,s:s}}", "id", id, "error", "code", -32601, "message", message)); - } - json_decref(req); - } - free(line); - return ferror(stdin) ? -1 : 0; -} diff --git a/c/mesh_sdk.h b/c/mesh_sdk.h deleted file mode 100644 index 47f92ef..0000000 --- a/c/mesh_sdk.h +++ /dev/null @@ -1,29 +0,0 @@ -/* The Novox Mesh SDK for C — the skeleton (novox/hq ADR 0188). - * - * A tools bundle is a process the node's runtime launches and speaks MCP over stdio to: - * `initialize`, `tools/list` once, `tools/call` per call, newline-framed JSON-RPC on stdin and - * stdout. This is the loop, so a bundle writes its tools and nothing else. A tool named - * `.` is the module's implementation of that seat's verb; any other name is the - * module's own. JSON is jansson's. - * - * No transport here: the bus, the subjects and the memberships are the runtime's (ADR 0039, 0175). - * stdout is the protocol; log to stderr. */ -#ifndef MESH_SDK_H -#define MESH_SDK_H -#include - -#define MESH_SDK_PROTOCOL "2025-03-26" - -/* One tool. `run` returns a new JSON value (the answer) or NULL with *error set to a message the - * caller frees. `input` is the schema of the arguments, or NULL for a tool that takes nothing. */ -typedef struct { - const char *name; - const char *description; - json_t *input; - json_t *(*run)(json_t *args, char **error); -} mesh_tool; - -/* Serve these tools on stdin/stdout until stdin closes. Returns 0, or -1 on an I/O fault. */ -int mesh_serve(const char *name, const mesh_tool *tools, size_t count); - -#endif diff --git a/go/README.md b/go/README.md index 58dcfa4..f554fca 100644 --- a/go/README.md +++ b/go/README.md @@ -16,6 +16,6 @@ at the same version as the TypeScript package. What it holds, under ADR 0039's test: the stdio loop and the tool type, and `cli`, the local protocol `mesh-cli` speaks to the node-engine on its machine (novox/hq ADR 0272): the socket's path and one request and one -answer; and `seal`, the mesh's sealed box (novox/hq ADR 0206: X25519, HKDF-SHA256, AES-256-GCM, `{v, eph, iv, tag, ct}`), byte for byte the box the licence manager made before it lived here and the TypeScript `@novox/mesh-sdk/seal` makes. Nothing else — no bus, no client, nothing of any module's. `go build ./example` and pipe a `tools/list` request into it. +answer; and `seal`, the mesh's sealed box (novox/hq ADR 0206: X25519, HKDF-SHA256, AES-256-GCM, `{v, eph, iv, tag, ct}`), byte for byte the box the licence manager made before it lived here. Nothing else — no bus, no client, nothing of any module's. From release 4a of design 47 (novox/hq ADR 0453 clause 3, ADR 0446 clause 4) it also holds the composition of a machine's declaration, over a plain input and with nothing behind it: `declaration` and `validate`, the node-engine's parser and its validator, copied from mesh-host; `catalogue`, the manifests, their resolution and rendering, and `overlay`, the private network's generator, copied from mesh-controller; `compose`, the controller's planFor, renderingFor and declarationWith as functions of an `Input` (the composition never allocates, makes, keeps or writes: what a send would do it names as foreseen or returns); `sdkversion`, which mesh-sdk version a program was built with; and, in `delivery`, the layouts of the keys the builder, the checker and the indexer write (the source fingerprint's new form, the builds, the verdicts and kept verdicts, the manifests by digest). None of them holds a bus, a store or a vault client, which `go list -deps` of `compose` shows. 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_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 06156e1..0000000 --- a/go/buildstep/askrun.go +++ /dev/null @@ -1,481 +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 - } - for _, level := range job.Levels { - la := delivery.LevelAnswer{Level: level} - passed, err := s.level(ctx, job, level, f, runner, &la) - answer.Levels = append(answer.Levels, la) - if err != nil { - return nil, err - } - if !passed { - return answer, nil // the first level that did not pass ends the check (ADR 0484 clause 13) - } - } - 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, and a failure among them ends the level - // there, so a change's own failing test is answered without the rest of the suite (issue 1289; ADR 0444). Past them every - // test step of the level runs, also after one that did not pass, so one check names every failing test of the rest of - // the level and not only the first (issue 1921); 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 - } - if !all { - return false, nil // the change's own tests failed: the others are not run (issue 1289) - } - } - 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 // past the change's own packages the others still run (issue 1921) - if priority && !all { - return false, nil // the change's own tests failed: the others are not run (issue 1289) - } - } - 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 30d4072..0000000 --- a/go/buildstep/askrun_test.go +++ /dev/null @@ -1,328 +0,0 @@ -package buildstep - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "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, when one of them fails, no other (issue 1289), and runs no -// level after one that did not pass (ADR 0484 clause 13). -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"}` - if got != want { - t.Fatalf("calls:\n%s\nwant:\n%s", got, want) - } - a := out.(delivery.CheckAnswer) - if len(a.Levels) != 1 { - t.Fatalf("levels %+v, want the unit level alone", 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) != 1 || 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: a failure in the packages whose tests changed ends the level there, the others are not run, and no later level -// runs. Issue 1921: past the change's own packages every package still runs, so two failing packages none of which the -// change touched are two test results of the one level. A Node check does the same with its module directories. -func TestRunAskStopsAtTheChangesOwnFailureAndRunsTheRestAfterOthers(t *testing.T) { - for _, row := range []struct { - name string - changed []string - tests map[string]bool - want []string // the packages tested, in order - }{ - {"the change's own test fails: nothing else runs", []string{"pkg/b/b_test.go"}, map[string]bool{"x/pkg/a": false, "x/pkg/b": false}, []string{"x/pkg/b"}}, - {"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, " ")) - } - } - // the passing row goes on to the integration level, which tests the same packages again; the first level is what is judged - if n := len(row.want); len(got) < n || fmt.Sprint(got[:n]) != fmt.Sprint(row.want) { - t.Errorf("%s: tested %v, want %v first", row.name, got, row.want) - } - a := out.(delivery.CheckAnswer) - failed := false - for _, raw := range a.Levels[0].Tests { - var res struct{ Passed bool } - _ = json.Unmarshal(raw, &res) - failed = failed || !res.Passed - } - if failed && (len(a.Levels) != 1 || len(got) != len(row.want)) { - t.Errorf("%s: a failed level went on: levels %d, tests %v", row.name, len(a.Levels), got) - } - } - - 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) != 2 || len(a.Levels) != 1 || len(a.Levels[0].Tests) != 1 { - t.Fatalf("calls %v, levels %+v: want the changed directory b alone and no integration level", 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