Merge branch 'feat/47-G-fix11-c1c' into feat/47-G-join11
This commit is contained in:
@@ -112,9 +112,6 @@ dist/
|
||||
/modules/mailu/cmd/automx/automx
|
||||
/modules/memory-pressure/cmd/memory-pressure/memory-pressure
|
||||
/modules/memory-pressure/memory-pressure
|
||||
/modules/mesh-build-go/buildstep/cmd/mesh-pack/mesh-pack
|
||||
/modules/mesh-build-go/buildstep/cmd/seatgen/seatgen
|
||||
/modules/mesh-build-go/buildstep/mesh-pack
|
||||
/modules/mesh-build-go/cmd/mesh-build-go/mesh-build-go
|
||||
/modules/mesh-build-go/mesh-build-go
|
||||
/modules/mesh-build-image/cmd/mesh-build-image/mesh-build-image
|
||||
|
||||
@@ -145,7 +145,7 @@ func runsTheKit(dir, call string) (bool, error) {
|
||||
return err
|
||||
}
|
||||
found = strings.Contains(string(raw), call) &&
|
||||
strings.Contains(string(raw), `"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/conformance"`)
|
||||
strings.Contains(string(raw), `"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/steprun/conformance"`)
|
||||
return nil
|
||||
})
|
||||
return found, err
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// claims no node-build-* seat is not judged.
|
||||
func TestABuildModuleWithATestStepMustRunTheKit(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
const imp = "package main\n\nimport \"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/conformance\"\n\n"
|
||||
const imp = "package main\n\nimport \"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/steprun/conformance\"\n\n"
|
||||
const sweep = "func TestSweep(t *testing.T) { conformance.LeftRunning(t, nil) }\nfunc TestPassing(t *testing.T) { conformance.Passing(t, nil, nil) }\n"
|
||||
kit := imp + "func TestKit(t *testing.T) { conformance.Run(t, conformance.GoTest, nil) }\n" + sweep
|
||||
for path, body := range map[string]string{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Command removedpackages refuses a Go source of the catalogue that imports what mesh-sdk gave up in the release that
|
||||
// narrowed it (novox/hq ADR 0593 §1): a package that left for its owner, and the build runner's names that left
|
||||
// mesh-sdk's buildstep for mesh-build-go's package.
|
||||
// mesh-sdk's buildstep for the build runner's package (now the delivery SDK's steprun).
|
||||
//
|
||||
// A module compiles against the mesh-sdk version it pins, so a source that still names a removed package builds until the
|
||||
// pin moves, and then breaks in a delivery of its own; and a later change could bring the import back with the old pin.
|
||||
@@ -8,8 +8,8 @@
|
||||
//
|
||||
// - an import of a package in Removed (or one under it);
|
||||
// - in a file importing mesh-sdk's buildstep (under any name), a selector naming anything that is not in
|
||||
// BuildstepContract: what is left of that package is what the delivery parts read, and the runner is
|
||||
// mesh-build-go's package (git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep).
|
||||
// BuildstepContract: what is left of that package is what the delivery parts read, and the runner is the
|
||||
// delivery SDK's package steprun (git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/steprun).
|
||||
//
|
||||
// It reads the non-test and test Go files under modules/ and checks/ (testdata, vendor and node_modules left out).
|
||||
//
|
||||
@@ -35,8 +35,8 @@ const sdk = "git.novox.be/novox/mesh-sdk/go"
|
||||
|
||||
// Removed are the packages of mesh-sdk that left it, each with where its logic is now.
|
||||
var Removed = map[string]string{
|
||||
"buildcpu": "the build runner's package, git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/buildcpu",
|
||||
"buildstep/conformance": "the build runner's package, git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/conformance",
|
||||
"buildcpu": "the build runner's package, git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/steprun/buildcpu",
|
||||
"buildstep/conformance": "the build runner's package, git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/steprun/conformance",
|
||||
"delivery/buildrun": "the builder's package (mesh-delivery-builder), imported by its repository's modules",
|
||||
"compose": "the node-engine, which alone composes (ADR 0593 §6)",
|
||||
"compose/frombus": "the node-engine, which alone composes (ADR 0593 §6)",
|
||||
@@ -140,7 +140,7 @@ func fileFindings(path, rel string) ([]string, error) {
|
||||
return true
|
||||
}
|
||||
seen[sel.Sel.Name] = true
|
||||
out = append(out, fmt.Sprintf("%s: uses %s.%s, which is the build runner's, no longer mesh-sdk's buildstep; import git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep (novox/hq ADR 0593 §1)", rel, buildstep, sel.Sel.Name))
|
||||
out = append(out, fmt.Sprintf("%s: uses %s.%s, which is the build runner's, no longer mesh-sdk's buildstep; import git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/steprun (novox/hq ADR 0593 §1)", rel, buildstep, sel.Sel.Name))
|
||||
return true
|
||||
})
|
||||
return out, nil
|
||||
|
||||
@@ -18,11 +18,11 @@ func TestFindingsRefuseWhatMeshSDKGaveUp(t *testing.T) {
|
||||
{"clean", "modules/a/x.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/delivery\"\n)\n\nvar _ = delivery.StepsState\n", ""},
|
||||
{"a removed package", "modules/a/x.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/compose\"\n)\n\nvar _ = compose.Resolve\n", "imports git.novox.be/novox/mesh-sdk/go/compose, which left mesh-sdk; it is the node-engine"},
|
||||
{"under a removed package", "modules/a/x_test.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/compose/frombus\"\n)\n", "the node-engine"},
|
||||
{"buildcpu", "checks/c/x.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/buildcpu\"\n)\n", "buildstep/buildcpu"},
|
||||
{"buildcpu", "checks/c/x.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/buildcpu\"\n)\n", "steprun/buildcpu"},
|
||||
{"the runner through mesh-sdk's buildstep", "modules/a/x.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/buildstep\"\n)\n\nvar _ = buildstep.RunAsk\n", "uses buildstep.RunAsk, which is the build runner's"},
|
||||
{"the runner through an alias", "modules/a/x.go", head + "\tsdkstep \"git.novox.be/novox/mesh-sdk/go/buildstep\"\n)\n\nvar _ = sdkstep.Puller{}\n", "uses sdkstep.Puller"},
|
||||
{"the contract of buildstep", "modules/a/x.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/buildstep\"\n)\n\nvar _ = buildstep.NewID\nvar _ buildstep.Failure\n", ""},
|
||||
{"the runner's own package", "modules/a/x.go", head + "\t\"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep\"\n)\n\nvar _ = buildstep.RunAsk\n", ""},
|
||||
{"the runner's own package", "modules/a/x.go", head + "\t\"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/steprun\"\n)\n\nvar _ = steprun.RunAsk\n", ""},
|
||||
{"a copy in testdata", "modules/a/testdata/x.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/compose\"\n)\n", ""},
|
||||
} {
|
||||
t.Run(row.name, func(t *testing.T) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
|
||||
sweep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshartifactsweep"
|
||||
)
|
||||
|
||||
// The holders whose states this module reads, one constant each (a reader names its holder once; ADR 0634 §4).
|
||||
|
||||
@@ -20,10 +20,10 @@ import (
|
||||
"fmt"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
meshwants "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
catalogclient "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
catalog "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
meshwants "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||
meshconditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
|
||||
artifactref "git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient/reference"
|
||||
artifactref "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/containerimageregistryclient/reference"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
meshwants "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
catalogclient "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
catalog "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
meshwants "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
|
||||
artifactref "git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient/reference"
|
||||
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
|
||||
ociseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry"
|
||||
artifactref "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/containerimageregistryclient/reference"
|
||||
reg "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry"
|
||||
ociseat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/ociartifactregistry"
|
||||
)
|
||||
|
||||
// memBus is the holder's bus in memory: states by name, each key's history, and what was published.
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
ociseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry"
|
||||
ociseat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/ociartifactregistry"
|
||||
|
||||
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
|
||||
reg "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry"
|
||||
)
|
||||
|
||||
// Repo is one repository as the seats' list answers it.
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
|
||||
sweep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshartifactsweep"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
|
||||
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
|
||||
reg "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry"
|
||||
sweep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshartifactsweep"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatlist/seatlisttest"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
|
||||
@@ -16,12 +16,12 @@ import (
|
||||
"strings"
|
||||
|
||||
meshconditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
|
||||
artifactref "git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient/reference"
|
||||
ociseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry"
|
||||
artifactref "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/containerimageregistryclient/reference"
|
||||
ociseat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/ociartifactregistry"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
|
||||
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
|
||||
reg "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry"
|
||||
)
|
||||
|
||||
// SweepRefused is the sweep's finding: a seat refused a hold or a remove the sweep asked.
|
||||
|
||||
@@ -8,13 +8,13 @@ require (
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshartifactsweep v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
@@ -34,4 +34,4 @@ require (
|
||||
golang.org/x/time v0.16.0 // indirect
|
||||
)
|
||||
|
||||
require git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000 // indirect
|
||||
require git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk v0.0.0-00010101000000-000000000000 // indirect
|
||||
|
||||
@@ -6,7 +6,7 @@ package main
|
||||
// `manifests`.
|
||||
|
||||
import (
|
||||
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
catalog "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
)
|
||||
|
||||
// CatalogHalf is what this module reads of the catalog half.
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
delivery "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
catalog "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
delivery "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshdelivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||
mesh "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
catalog "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatlist"
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
condseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
|
||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
catalogclient "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatlist"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
|
||||
@@ -10,8 +10,8 @@ require (
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
@@ -29,4 +29,4 @@ require (
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts v0.0.0-00010101000000-000000000000 // indirect
|
||||
)
|
||||
|
||||
require git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery v0.0.0-00010101000000-000000000000
|
||||
require git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshdelivery v0.0.0-00010101000000-000000000000
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
|
||||
reg "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry"
|
||||
|
||||
"distribution/internal/holder"
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
|
||||
reg "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry"
|
||||
|
||||
"distribution/internal/holder"
|
||||
)
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||
meshconditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
|
||||
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/parts"
|
||||
reg "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/parts"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ go 1.26.0
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshbuilds v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
)
|
||||
@@ -29,7 +29,7 @@ require (
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||
artifactref "git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient/reference"
|
||||
artifactref "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/containerimageregistryclient/reference"
|
||||
)
|
||||
|
||||
type memObjects map[string][]byte
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
|
||||
reg "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry"
|
||||
)
|
||||
|
||||
// VerbList is the seats' verb list.
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
|
||||
reg "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry"
|
||||
)
|
||||
|
||||
// dg is a digest made from a short name, so a test reads as names.
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshbuilds"
|
||||
)
|
||||
|
||||
// Reserved is the holder's check of a publish against the builder's reservation (novox/hq design 47 §381, §478, §504): it asks the
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/parts"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/parts"
|
||||
)
|
||||
|
||||
// The bounds of incoming (design 51 *Publishing*, ADR 0611): a part is at most 1 MiB, a transfer with no part for 24
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
|
||||
reg "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry"
|
||||
)
|
||||
|
||||
// Catalog is what the store answers of what it holds.
|
||||
|
||||
@@ -3,7 +3,7 @@ package holder
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/window"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/window"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/parts"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/parts"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
|
||||
"gitea/internal/packageseats"
|
||||
|
||||
@@ -26,9 +26,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||
goreg "git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry"
|
||||
npmreg "git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/parts"
|
||||
goreg "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/gopackageregistry"
|
||||
npmreg "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/npmpackageregistry"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/parts"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
|
||||
"gitea/internal/packageseats"
|
||||
|
||||
@@ -6,10 +6,10 @@ require (
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/gopackageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshbuilds v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/npmpackageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
@@ -30,9 +30,9 @@ require (
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/parts"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/parts"
|
||||
)
|
||||
|
||||
// The seats the forge module holds.
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/parts"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/parts"
|
||||
)
|
||||
|
||||
type memObjects map[string][]byte
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshbuilds"
|
||||
)
|
||||
|
||||
// Reserved is the holder's check of a publish against the builder's reservation (novox/hq design 47 §381, §478, §504): it asks the
|
||||
|
||||
@@ -149,11 +149,11 @@ seat nothing holds, fails the step `not stored: <artifact>: <the holder's words>
|
||||
A build the builder numbers later is published late: the module consumes the builder's `version.numbered` and, for a
|
||||
bundle or an archive with a digest, publishes that held digest under its version, naming its one part by digest and
|
||||
sending nothing (`latepublish.go`); a holder that no longer holds the digest fails the event, which is offered again. Every
|
||||
ask of a seat a build module makes (a publish, a late publish, an image's copy) is narrated by `buildstep/narrate.go`:
|
||||
ask of a seat a build module makes (a publish, a late publish, an image's copy) is narrated by the delivery SDK's `steprun/narrate.go`:
|
||||
asked, waiting on the seat every five minutes while it has not answered, stored or refused, each the ask's phase on its
|
||||
running step key (logged outside an ask).
|
||||
A contracts module (a package artifact naming `generated-from`) has its generated Go files regenerated with the seat
|
||||
generator this module is built with and compared (`buildstep/generated.go`): the unit level of `build_go_test` fails
|
||||
generator this module is built with and compared (the delivery SDK's `steprun/generated.go`): the unit level of `build_go_test` fails
|
||||
naming each file that differs, is missing or was not written by the generator (its `out-of-step`), and a build ask
|
||||
answers the same files in its answer's `out-of-step`, a failed build's too, so a trunk build's end can raise
|
||||
`generated-types-out-of-step` where no test step runs. Its TypeScript files are mesh-build-node's to compare.
|
||||
@@ -174,19 +174,17 @@ and xz, read in Go as the build agent reads it, issue 1590); it runs no command
|
||||
|
||||
## The runner's package, and what a build says at its end (ADR 0593)
|
||||
|
||||
- The build runner (the pull of asks, the step lifecycle, the toolchain guard, the fetch, the parsers of `go test -json` and
|
||||
`node --test`, `Pack`, the conformance kit and the `mesh-pack` command) is this module's package
|
||||
`git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep`, a Go module of its own in `buildstep/`. mesh-build-node
|
||||
and mesh-build-image require it and replace it to the sibling directory, since it is the same repository. It is declared
|
||||
as this module's Go package artifact `buildstep`, series `0.1` (ADR 0606): a user in another repository accepts its
|
||||
series, never a commit. What the delivery
|
||||
parts read of a step (the failure contract, the fetch request, the stale-step sweep, the step id) stays in mesh-sdk's
|
||||
`buildstep`.
|
||||
- The seat generator (design 26 *Contracts modules*) is the package `buildstep/seatgen` and the command `buildstep/cmd/seatgen`,
|
||||
- The build runner (the pull of asks, the step lifecycle, the toolchain guard, the fetch and release, the regenerate-and-compare
|
||||
step, the parsers of `go test -json` and `node --test`, `Pack`) is the delivery SDK's package `steprun`
|
||||
(`git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/steprun`), with the per-node CPU share `steprun/buildcpu` and
|
||||
the conformance kit `steprun/conformance` (design 47 G §464, §680). mesh-build-go, mesh-build-node and mesh-build-image
|
||||
require the delivery SDK at the zero version and keep no copy of it. What every delivery part reads of a step (the
|
||||
failure contract, the fetch request, the stale-step sweep, the step id) is the delivery SDK's `buildstep`.
|
||||
- The seat generator (design 26 *Contracts modules*) is the delivery SDK's package `seatgen` and its command `cmd/seatgen`,
|
||||
one program writing a contracts module's Go and TypeScript (`seatgen <dir>`; `seatgen -check <dir>` names every path out of
|
||||
step and runs the client and artifact checks). Its version is buildstep's, so the build input of a compare is the
|
||||
mesh-build-go (or mesh-build-node) build itself: a new generator is a new build module and a new fingerprint. A contracts
|
||||
repository runs it as `go run git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/cmd/seatgen@<version> -check .`.
|
||||
step and runs the client and artifact checks); `mesh-pack` is the delivery SDK's `cmd/mesh-pack`. The build input of a
|
||||
compare is the build module's build, which takes the delivery SDK it was built against. A contracts repository runs it
|
||||
as `go run git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/cmd/seatgen@<version> -check .`.
|
||||
- `build_go_imports` (a tool, and the last step of a build ask that built an artifact) answers the mesh packages the module's
|
||||
Go programs import: `go list -deps` of each Go bundle's program, tests left out, the packages of the forge outside the
|
||||
module's own directory (a sibling package of its own repository counts), sorted and each once. A build ask carries them as
|
||||
|
||||
@@ -1,565 +0,0 @@
|
||||
package buildstep
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"path"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
trunk "git.novox.be/novox/mesh-delivery/sdk/trunk"
|
||||
)
|
||||
|
||||
// **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
|
||||
// Imports reads the mesh packages the built programs import (the Go module's alone, ADR 0593 §2): it answers
|
||||
// {"imports": [...]}, sorted, from the workspace the build ran in. A build that built an artifact asks it once, at
|
||||
// its end; nil reports none.
|
||||
Imports StepFunc
|
||||
// Compare regenerates a contracts module's generated files (a package artifact naming generated-from) and answers
|
||||
// {"out-of-step": [...]} (CompareStep): a build ask runs it once on the fetched workspace and answers the paths in
|
||||
// its out-of-step, a failed build's too, so a trunk build's end names them where no test step runs. Nil compares none.
|
||||
Compare 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) (sdkstep.ToolchainUsed, error)
|
||||
// Kept looks up a kept verdict by level and digest (the checker's kept bucket, the checker seat's kept key): 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 (sdkstep.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
|
||||
// Lint is the module's lint body for a pipeline step lint (novox/hq ADR 0666 §7): it takes the workspace and the
|
||||
// module's directory and answers a LintAnswer. Nil ends every lint step as nothing to run.
|
||||
Lint StepFunc
|
||||
// Node is the node this holder runs on, which a step's end names; SayStep says a pipeline step's end as the seat's
|
||||
// event step-ended on that node's subject (sdkstep.StepEndedSubject), under the ask's id. Nil runs no step ask.
|
||||
Node string
|
||||
SayStep func(end sdkstep.StepEnded) error
|
||||
}
|
||||
|
||||
// RunAsk runs ask's job with steps and answers a sdkstep.BuildAnswer or a sdkstep.CheckAnswer.
|
||||
func RunAsk(ctx context.Context, steps AskSteps, ask sdkstep.StepAsk) (any, error) {
|
||||
if ask.Build != nil {
|
||||
job := *ask.Build
|
||||
job.Fetch = withPackages(job.Fetch, ask.Packages)
|
||||
return steps.build(ctx, job)
|
||||
}
|
||||
if ask.Step != nil {
|
||||
return steps.runStep(ctx, ask)
|
||||
}
|
||||
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 *sdkstep.Fetch, packages sdkstep.PackageVersions) *sdkstep.Fetch {
|
||||
if len(packages) == 0 || fetch == nil {
|
||||
return fetch
|
||||
}
|
||||
out := *fetch
|
||||
out.Packages = maps.Clone(packages)
|
||||
return &out
|
||||
}
|
||||
|
||||
// FetchArgs is a fetch as the fetch tool takes it (sdkstep.FetchRequest's Args and the build module's own, beside): the
|
||||
// ask's fetch through the one JSON form, without the layout version, which no tool takes.
|
||||
func FetchArgs(f sdkstep.Fetch) (map[string]any, error) {
|
||||
b, err := json.Marshal(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args := map[string]any{}
|
||||
if err := json.Unmarshal(b, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
delete(args, "v")
|
||||
return args, nil
|
||||
}
|
||||
|
||||
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, fetch *sdkstep.Fetch) (json.RawMessage, fetched, error) {
|
||||
if s.Fetch == nil {
|
||||
return nil, fetched{}, Refuse("this build module fetches nothing")
|
||||
}
|
||||
if fetch == nil {
|
||||
return nil, fetched{}, Refuse("the ask names no fetch")
|
||||
}
|
||||
args, err := FetchArgs(*fetch)
|
||||
if err != nil {
|
||||
return nil, fetched{}, err
|
||||
}
|
||||
if fetch.Base != "" {
|
||||
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 *sdkstep.ToolchainUsed) (*sdkstep.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 sdkstep.BuildJob) (any, error) {
|
||||
answer := sdkstep.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 job.Fetch.Base != "" {
|
||||
// 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
|
||||
if s.Compare != nil {
|
||||
Phase(ctx, "compare the generated files")
|
||||
out, err := s.Compare(ctx, map[string]any{"workspace": f.Workspace, "module": job.Path})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the generated files of %s: %w", job.Module, err)
|
||||
}
|
||||
r, err := raw(out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var read struct {
|
||||
OutOfStep []string `json:"out-of-step"`
|
||||
}
|
||||
if err := json.Unmarshal(r, &read); err != nil {
|
||||
return nil, fmt.Errorf("the compare of %s answered what is not a list: %s", job.Module, r)
|
||||
}
|
||||
answer.OutOfStep = read.OutOfStep
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
// The fingerprint a build kept for the artifact store's holder is asked by (kept-artifact, milestone 47/G W12.5).
|
||||
if job.Fp != "" {
|
||||
args["fp"] = job.Fp
|
||||
}
|
||||
// The seat it is published through and the version the builder gave it (design 51 *Publishing*, CAT-BG2).
|
||||
if a.Seat != "" {
|
||||
args["seat"] = a.Seat
|
||||
}
|
||||
if a.Version != "" {
|
||||
args["version"] = a.Version
|
||||
}
|
||||
Phase(ctx, "build "+a.Artifact)
|
||||
out, err = s.Build(ctx, args)
|
||||
}
|
||||
if err != nil {
|
||||
// The compare's out-of-step is answered with the failure, so the build's end still names it.
|
||||
if len(answer.OutOfStep) > 0 {
|
||||
return answer, fmt.Errorf("%s: %w", a.Artifact, err)
|
||||
}
|
||||
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"`
|
||||
Kept string `json:"kept"`
|
||||
}
|
||||
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, sdkstep.AskBuilt{Artifact: a.Artifact, Digest: built.Digest, Reference: built.Reference})
|
||||
if built.Kept != "" {
|
||||
answer.Kept = strings.TrimPrefix(built.Kept, "sha256:")
|
||||
}
|
||||
}
|
||||
if s.Imports != nil && f.Workspace != "" {
|
||||
out, err := s.Imports(ctx, map[string]any{"workspace": f.Workspace, "module": job.Path})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the imports of %s: %w", job.Module, err)
|
||||
}
|
||||
r, err := raw(out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var read struct {
|
||||
Imports []string `json:"imports"`
|
||||
}
|
||||
if err := json.Unmarshal(r, &read); err != nil {
|
||||
return nil, fmt.Errorf("the imports of %s answered what is not a list: %s", job.Module, r)
|
||||
}
|
||||
if err := trunk.CheckImports(read.Imports); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
answer.Imports = read.Imports
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
func (s AskSteps) check(ctx context.Context, job sdkstep.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 := sdkstep.CheckAnswer{}
|
||||
if err := json.Unmarshal(r, &answer.Fetch); err != nil {
|
||||
return nil, fmt.Errorf("the fetch answered what is not a fetch answer: %s: %w", r, err)
|
||||
}
|
||||
if len(f.Conflicts) > 0 {
|
||||
return answer, nil // the asker reads the conflict from the fetch's answer: a verdict, not an error
|
||||
}
|
||||
if s.Test == nil {
|
||||
return nil, Refuse("this build module tests nothing")
|
||||
}
|
||||
runner := ""
|
||||
if job.Runner != "" && (job.RunnerWhen == "" || changesUnder(f.Changed, job.RunnerWhen)) {
|
||||
runner = job.Runner
|
||||
}
|
||||
// Every level runs, also after one that did not pass: one check names every failure, not only the first (the operator,
|
||||
// 2026-11-02: "A failed check shows only its first fault").
|
||||
for _, level := range job.Levels {
|
||||
la := sdkstep.LevelAnswer{Level: level}
|
||||
_, err := s.level(ctx, job, level, f, runner, &la)
|
||||
answer.Levels = append(answer.Levels, la)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
// level runs one level of a check ask and says whether it passed.
|
||||
func (s AskSteps) level(ctx context.Context, job sdkstep.CheckJob, level string, f fetched, runner string, la *sdkstep.LevelAnswer) (bool, error) {
|
||||
withRunner := func(args map[string]any) map[string]any {
|
||||
if runner != "" {
|
||||
args["runner"] = runner
|
||||
}
|
||||
if len(job.NodeNames) > 0 {
|
||||
args[sdkstep.NodeNamesArg] = job.NodeNames // the names check's (ADR 0567 §12)
|
||||
}
|
||||
return args
|
||||
}
|
||||
test := func(args map[string]any) (bool, error) {
|
||||
what := "the whole level"
|
||||
if dir, _ := args["module"].(string); dir != "" {
|
||||
what = dir
|
||||
} else if pkgs, _ := args["packages"].([]string); len(pkgs) > 0 {
|
||||
what = fmt.Sprintf("%d packages", len(pkgs))
|
||||
}
|
||||
Phase(ctx, fmt.Sprintf("%s: tests of %s", level, what))
|
||||
out, err := s.Test(ctx, args)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
r, err := raw(out)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
la.Tests = append(la.Tests, r)
|
||||
var t struct {
|
||||
Passed bool `json:"passed"`
|
||||
}
|
||||
_ = json.Unmarshal(r, &t)
|
||||
return t.Passed, nil
|
||||
}
|
||||
// The packages (or Node directories) whose tests the change touches run first (issue 1289; ADR 0444), and then every
|
||||
// other test step of the level, also after one that did not pass, so one check names every failing test of the level
|
||||
// and not only the first (issue 1921; the operator, 2026-11-02); the level passes only when each of its steps did.
|
||||
all := true
|
||||
if s.Digest == nil {
|
||||
// A module with no digest (Node) tests each module directory of the tree, those whose tests changed first.
|
||||
for _, priority := range []bool{true, false} {
|
||||
for _, dir := range f.Modules {
|
||||
if changesTestIn(f.Changed, dir) != priority {
|
||||
continue
|
||||
}
|
||||
passed, err := test(withRunner(map[string]any{"workspace": f.Workspace, "module": dir, "level": level}))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("the %s level of %s: %w", level, dir, err)
|
||||
}
|
||||
all = all && passed
|
||||
}
|
||||
}
|
||||
return all, nil
|
||||
}
|
||||
todo, whole, keepRunner := s.plan(ctx, job, level, f, runner, withRunner, la)
|
||||
if !keepRunner {
|
||||
runner = "" // an installed module that ignores the runner judges the level as it judges any other (ADR 0535 §2)
|
||||
}
|
||||
if whole && len(job.Packages) > 0 {
|
||||
todo, whole = job.Packages, false // a placed ask tests its packages alone, whatever the digest said (ADR 0552 §4)
|
||||
}
|
||||
for _, priority := range []bool{true, false} {
|
||||
args := withRunner(map[string]any{"workspace": f.Workspace, "level": level})
|
||||
if job.Run != "" {
|
||||
args["run"] = job.Run
|
||||
}
|
||||
if whole {
|
||||
if priority {
|
||||
continue // nothing to put first: the level runs whole after
|
||||
}
|
||||
} else {
|
||||
var run []string
|
||||
for _, p := range todo {
|
||||
if changesTestOf(f.Changed, p) == priority {
|
||||
run = append(run, p)
|
||||
}
|
||||
}
|
||||
if len(run) == 0 {
|
||||
continue
|
||||
}
|
||||
args["packages"] = run
|
||||
}
|
||||
passed, err := test(args)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("the %s level: %w", level, err)
|
||||
}
|
||||
all = all && passed // the others still run after a failure (issue 1921)
|
||||
}
|
||||
return all, nil
|
||||
}
|
||||
|
||||
// release removes an ask's workspace, saying a failure: the module's sweep removes it later.
|
||||
func (s AskSteps) release(workspace string) {
|
||||
if workspace == "" || s.Release == nil {
|
||||
return
|
||||
}
|
||||
if err := s.Release(workspace); err != nil && s.Logf != nil {
|
||||
s.Logf("the workspace %s is left to the sweep: %v", workspace, err)
|
||||
}
|
||||
}
|
||||
|
||||
// plan digests the level and takes the kept verdicts: the packages left to test, or all when the level runs whole, and
|
||||
// whether the runner stays. A try of some tests runs whole, digesting nothing (a partial run proves no package). Under the
|
||||
// candidate's runner the digest is asked as the candidate: when the candidate answers it, every package runs and nothing
|
||||
// is digested for keeping (ADR 0535 §4); when the installed module answers it (it does not take the runner), the level is
|
||||
// planned as any other and the runner is dropped. The level runs whole and takes nothing when the digest names no package
|
||||
// or does not cover the siblings the fetch placed (ADR 0444, L5).
|
||||
func (s AskSteps) plan(ctx context.Context, job sdkstep.CheckJob, level string, f fetched, runner string, withRunner func(map[string]any) map[string]any, la *sdkstep.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 sdkstep.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, sdkstep.KeptTaken{Package: p, Digest: pd.Digest, Tests: tests})
|
||||
continue
|
||||
}
|
||||
}
|
||||
if needs := d.NeedsOf(p); len(sdkstep.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, sdkstep.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(sdkstep.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: `<owner/repo> <commit>`, sorted.
|
||||
func besideOf(fetch sdkstep.Fetch) []string {
|
||||
var out []string
|
||||
for _, r := range fetch.Beside {
|
||||
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
|
||||
}
|
||||
@@ -1,470 +0,0 @@
|
||||
package buildstep
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
nodebuildgo "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo"
|
||||
nodebuildnode "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildnode"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
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 []sdkstep.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) sdkstep.StepAsk {
|
||||
a := sdkstep.StepAsk{ID: sdkstep.NewID(), Kind: sdkstep.AskCheck, Language: sdkstep.AskGo, Asker: "t", At: time.Now()}
|
||||
switch j := job.(type) {
|
||||
case sdkstep.BuildJob:
|
||||
a.Build = &j
|
||||
case sdkstep.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; an artifact's seat and the version the builder gave it reach
|
||||
// its build (CAT-BG2).
|
||||
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 }
|
||||
// ADR 0593 §2: a Go module's build ends by saying which mesh packages its programs import.
|
||||
steps.Imports = func(ctx context.Context, args map[string]any) (any, error) {
|
||||
b, _ := json.Marshal(args)
|
||||
r.calls = append(r.calls, "imports "+string(b))
|
||||
return map[string]any{"imports": []string{"git.novox.be/novox/mesh-sdk/go/announce", "git.novox.be/novox/mesh-sdk/go/health"}}, nil
|
||||
}
|
||||
// Issue 1585: the ask's package versions reach the fetch, and the ask's own fetch arguments stay as they were.
|
||||
a := ask(sdkstep.BuildJob{Module: "m", Path: "modules/m", Publish: true,
|
||||
Fetch: &sdkstep.Fetch{Repository: "novox/x", Commit: runCommit},
|
||||
Artifacts: []sdkstep.AskArtifact{{Artifact: "base", From: "registry/b@sha256:00"}, {Artifact: "bundle"}, {Artifact: "lib", Seat: "go-package-registry", Version: "v0.1.15"}}})
|
||||
a.Packages = sdkstep.PackageVersions{"git.novox.be/novox/mesh-sdk/go": runCommit}
|
||||
out, err := RunAsk(context.Background(), steps, a)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(a.Build.Fetch.Packages) != 0 {
|
||||
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,"seat":"go-package-registry","version":"v0.1.15","workspace":"/w/1"}
|
||||
imports {"module":"modules/m","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.(sdkstep.BuildAnswer)
|
||||
if len(answer.Artifacts) != 3 || answer.Artifacts[1].Reference != "artifact-store://bundle" {
|
||||
t.Fatalf("answer %+v", answer)
|
||||
}
|
||||
if !slices.Equal(answer.Imports, []string{"git.novox.be/novox/mesh-sdk/go/announce", "git.novox.be/novox/mesh-sdk/go/health"}) {
|
||||
t.Errorf("the answer's imports are %v", answer.Imports)
|
||||
}
|
||||
}
|
||||
|
||||
// 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) (sdkstep.ToolchainUsed, error) {
|
||||
return sdkstep.ToolchainUsed{Version: "go1.27", Recipe: "3"}, nil
|
||||
}
|
||||
_, err := RunAsk(context.Background(), s, ask(sdkstep.BuildJob{Module: "m", Path: ".", Fetch: &sdkstep.Fetch{Repository: "novox/x", Commit: runCommit},
|
||||
Artifacts: []sdkstep.AskArtifact{{Artifact: "bundle"}}, Toolchain: &sdkstep.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 clause 2), and a node on another declines it before it fetches.
|
||||
check := ask(sdkstep.CheckJob{Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit}, Levels: []string{sdkstep.LevelUnit},
|
||||
Toolchain: &sdkstep.ToolchainUsed{Version: "go1.26", Recipe: "3"}})
|
||||
_, err = RunAsk(context.Background(), s, check)
|
||||
if !errors.As(err, &dec) || len(r.calls) != 0 {
|
||||
t.Fatalf("a check ask: err %v, calls %v", err, r.calls)
|
||||
}
|
||||
}
|
||||
|
||||
// 1887/A3, A5a: a check ask runs the levels of one check after one fetch: it digests the unit level, takes the kept
|
||||
// verdicts, tests the packages whose tests changed first and then the others, also after one of them failed, and runs
|
||||
// every level, also after one that did not pass, so one check names every failure (the operator, 2026-11-02).
|
||||
func TestRunAskChecksTheLevelsOfOneCheck(t *testing.T) {
|
||||
r := &recorder{
|
||||
fetch: map[string]any{"workspace": "/w", "changed": []string{"pkg/b/b_test.go"}},
|
||||
digests: map[string]string{"x/pkg/a": "d-a", "x/pkg/b": "d-b", "x/pkg/k": "d-kept"},
|
||||
tests: map[string]bool{"x/pkg/a": true, "x/pkg/b": false},
|
||||
}
|
||||
out, err := RunAsk(context.Background(), r.steps(), ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit, sdkstep.LevelIntegration}, Kept: true,
|
||||
Runner: "workspace", RunnerWhen: "modules/mesh-build-go/", Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit, Base: runCommit}}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := strings.Join(r.calls, "\n")
|
||||
want := `fetch {"base":"` + runCommit + `","commit":"` + runCommit + `","repository":"novox/x"}
|
||||
digest {"level":"unit","workspace":"/w"}
|
||||
test {"level":"unit","packages":["x/pkg/b"],"workspace":"/w"}
|
||||
test {"level":"unit","packages":["x/pkg/a"],"workspace":"/w"}
|
||||
digest {"level":"integration","workspace":"/w"}
|
||||
test {"level":"integration","packages":["x/pkg/b"],"workspace":"/w"}
|
||||
test {"level":"integration","packages":["x/pkg/a"],"workspace":"/w"}`
|
||||
if got != want {
|
||||
t.Fatalf("calls:\n%s\nwant:\n%s", got, want)
|
||||
}
|
||||
a := out.(sdkstep.CheckAnswer)
|
||||
if a.Fetch.Workspace != "/w" || !slices.Equal(a.Fetch.Changed, []string{"pkg/b/b_test.go"}) {
|
||||
t.Fatalf("the answer's fetch is the tool's whole answer, typed: %+v", a.Fetch)
|
||||
}
|
||||
if len(a.Levels) != 2 {
|
||||
t.Fatalf("levels %+v, want both levels", a.Levels)
|
||||
}
|
||||
u := a.Levels[0]
|
||||
if u.Level != sdkstep.LevelUnit || len(u.Kept) != 1 || u.Kept[0].Package != "x/pkg/k" || u.Kept[0].Tests != 7 || len(u.Tests) != 2 || u.Digest == nil {
|
||||
t.Fatalf("the unit level %+v", u)
|
||||
}
|
||||
|
||||
// ADR 0567 §12: a check's node names reach the digest and test tools, for the names check (a kept verdict misses when
|
||||
// they change).
|
||||
r.calls = nil
|
||||
if _, err := RunAsk(context.Background(), r.steps(), ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit}, NodeNames: []string{"anchor"},
|
||||
Fetch: sdkstep.Fetch{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 = []sdkstep.PackageNeeds{{Package: "x/pkg/a", Needs: []string{sdkstep.NeedNodeBuildNode}}}
|
||||
steps := r.steps()
|
||||
if has {
|
||||
steps.Has = func(n string) bool { return n == sdkstep.NeedNodeBuildNode }
|
||||
}
|
||||
out, err := RunAsk(context.Background(), steps, ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit}, Kept: true,
|
||||
Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit}}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
u := out.(sdkstep.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" || sdkstep.UnmetNeeds([]sdkstep.LevelAnswer{u})[0] != sdkstep.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(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit}, Kept: true, Packages: []string{"x/pkg/a"},
|
||||
Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit}}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
u := out.(sdkstep.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(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit}, Kept: true,
|
||||
Runner: "workspace", RunnerWhen: "modules/mesh-build-go/", Fetch: sdkstep.Fetch{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.(sdkstep.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(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit}, Kept: true,
|
||||
Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit, Beside: []sdkstep.FetchBeside{{Repository: "novox/mesh-sdk", Commit: runCommit}}}}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if l := out.(sdkstep.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(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit}, Fetch: sdkstep.Fetch{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(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit}, Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit}}))
|
||||
if err != nil || len(r.calls) != 1 || len(out.(sdkstep.CheckAnswer).Levels) != 0 {
|
||||
t.Fatalf("a conflict: %v %v", err, r.calls)
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 1289: the packages whose tests changed run first. Since 2026-11-02 (the operator: "A failed check shows only its
|
||||
// first fault") a failure among them ends nothing: every package of the level and every level still runs, so one check
|
||||
// names every failing test (issue 1921). A Node check does the same with its module directories.
|
||||
func TestRunAskRunsEveryPackageAndLevelAfterAFailure(t *testing.T) {
|
||||
for _, row := range []struct {
|
||||
name string
|
||||
changed []string
|
||||
tests map[string]bool
|
||||
want []string // the packages tested at each level, in order
|
||||
}{
|
||||
{"the change's own test fails: the rest still runs", []string{"pkg/b/b_test.go"}, map[string]bool{"x/pkg/a": false, "x/pkg/b": false}, []string{"x/pkg/b", "x/pkg/a"}},
|
||||
{"the change's own test passes: the rest runs, also after a failure", []string{"pkg/b/b_test.go"}, map[string]bool{"x/pkg/a": false, "x/pkg/b": true, "x/pkg/c": false}, []string{"x/pkg/b", "x/pkg/a x/pkg/c"}},
|
||||
{"no test changed: every failure is named", nil, map[string]bool{"x/pkg/a": false, "x/pkg/b": false}, []string{"x/pkg/a x/pkg/b"}},
|
||||
} {
|
||||
r := &recorder{fetch: map[string]any{"workspace": "/w", "changed": row.changed}, tests: row.tests,
|
||||
digests: map[string]string{"x/pkg/a": "d-a", "x/pkg/b": "d-b"}}
|
||||
if _, ok := row.tests["x/pkg/c"]; ok {
|
||||
r.digests["x/pkg/c"] = "d-c"
|
||||
}
|
||||
out, err := RunAsk(context.Background(), r.steps(), ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit, sdkstep.LevelIntegration},
|
||||
Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit}}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got []string
|
||||
for _, c := range r.calls[1:] {
|
||||
if strings.HasPrefix(c, "test ") {
|
||||
var a struct{ Packages []string }
|
||||
_ = json.Unmarshal([]byte(strings.TrimPrefix(c, "test ")), &a)
|
||||
got = append(got, strings.Join(a.Packages, " "))
|
||||
}
|
||||
}
|
||||
if want := append(slices.Clone(row.want), row.want...); fmt.Sprint(got) != fmt.Sprint(want) {
|
||||
t.Errorf("%s: tested %v, want %v", row.name, got, want)
|
||||
}
|
||||
if a := out.(sdkstep.CheckAnswer); len(a.Levels) != 2 || len(a.Levels[0].Tests) != len(row.want) {
|
||||
t.Errorf("%s: levels %+v, want both, each with every test result", row.name, a.Levels)
|
||||
}
|
||||
}
|
||||
|
||||
r := &recorder{fetch: map[string]any{"workspace": "/w", "modules": []string{"a", "b"}, "changed": []string{"b/test/x.test.ts"}},
|
||||
tests: map[string]bool{"a": true, "b": false}}
|
||||
s := r.steps()
|
||||
s.Digest = nil
|
||||
out, err := RunAsk(context.Background(), s, ask(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit, sdkstep.LevelIntegration},
|
||||
Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit}}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a := out.(sdkstep.CheckAnswer); len(r.calls) != 5 || len(a.Levels) != 2 || len(a.Levels[0].Tests) != 2 {
|
||||
t.Fatalf("calls %v, levels %+v: want b then a at each of the two levels", r.calls, a.Levels)
|
||||
}
|
||||
}
|
||||
|
||||
// A seat's own ask type (the generated one of any of the three build seats) is the neutral ask through the form's one
|
||||
// conversion (sdkstep.AskOf), reads for its step description through the same conversion (askRead), and reaches the
|
||||
// fetch tool as the fetch's arguments alone: no layout version, nothing of the ask's.
|
||||
func TestASeatsAskTypeRunsAsTheNeutralAsk(t *testing.T) {
|
||||
at := time.Now().UTC()
|
||||
fetch := &nodebuildgo.Fetch{Commit: runCommit, Repository: "novox/x", Dir: "modules/m", Paths: []string{"src"}}
|
||||
goAsk := nodebuildgo.StepAsk{V: sdkstep.StepAskVersion, ID: sdkstep.NewID(), Kind: sdkstep.AskDelivery, Language: sdkstep.AskGo, Asker: "mesh-delivery-builder", At: &at,
|
||||
Build: &nodebuildgo.StepAskBuild{Module: "m", Path: "modules/m", Fetch: fetch, Artifacts: []nodebuildgo.AskArtifact{{Artifact: "bundle"}}},
|
||||
Packages: map[string]string{"git.novox.be/novox/mesh-sdk/go": runCommit}}
|
||||
a, err := sdkstep.AskOf(goAsk)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if read := askRead(a); read.Kind != sdkstep.AskDelivery || read.Build == nil || read.Build.Module != "m" {
|
||||
t.Errorf("the step description reads %+v", read)
|
||||
}
|
||||
r := &recorder{fetch: map[string]any{"workspace": "/w"}}
|
||||
if _, err := RunAsk(context.Background(), r.steps(), a); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := `fetch {"commit":"` + runCommit + `","dir":"modules/m","packages":{"git.novox.be/novox/mesh-sdk/go":"` + runCommit + `"},"paths":["src"],"repository":"novox/x"}`
|
||||
if len(r.calls) == 0 || r.calls[0] != want {
|
||||
t.Errorf("the fetch tool was called with %v, want %s", r.calls, want)
|
||||
}
|
||||
if back, err := sdkstep.AskAs[nodebuildnode.StepAsk](a); err != nil || back.Build == nil || back.Build.Module != "m" {
|
||||
t.Errorf("the ask as another seat's type: %+v, %v", back, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 086 A-C2 (ADR 0666 §7, §8, §21; design 47 *Asked on a work queue, ended by an event*, test 65): a pipeline step ask
|
||||
// runs its one level as a check of it, carries the packages the runner kept at their digest, keeps what it passed, says
|
||||
// its counts, and says its end once on its own node; a level with no tests ends nothing to run, a fault of the source
|
||||
// could not run, and a transport fault or an end that could not be said is handed back unsaid, to be given again.
|
||||
func TestRunAskRunsAPipelineStepAndSaysItsEnd(t *testing.T) {
|
||||
type said struct{ ends []sdkstep.StepEnded }
|
||||
stepOf := func(step string, kept ...sdkstep.KeptResult) sdkstep.StepAsk {
|
||||
return sdkstep.StepAsk{ID: sdkstep.NewID(), Kind: sdkstep.AskStep, Language: sdkstep.AskGo, Asker: "mesh-delivery-pipeline", At: time.Now(),
|
||||
Step: &sdkstep.StepRun{Pipeline: "main.12.m", Step: step, Module: "m", Artifact: "m:program", Commit: runCommit, Fingerprint: "fp-1",
|
||||
Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: runCommit, Dir: "modules/m"}, Kept: kept}}
|
||||
}
|
||||
for _, row := range []struct {
|
||||
name string
|
||||
ask sdkstep.StepAsk
|
||||
digests map[string]string
|
||||
tests map[string]bool
|
||||
lint StepFunc
|
||||
fetchErr error
|
||||
sayErr error
|
||||
outcome string // "" when nothing is said
|
||||
counts int
|
||||
carried []string
|
||||
kept []string
|
||||
passing bool // the error is a transport fault
|
||||
noTests bool // the level holds no test, as the test body says it
|
||||
}{
|
||||
{name: "unit: the kept package carried, the other tested and kept", ask: stepOf(sdkstep.StepUnit, sdkstep.KeptResult{Package: "x/a", Digest: "d-a", Level: "unit"}),
|
||||
digests: map[string]string{"x/a": "d-a", "x/b": "d-b"}, tests: map[string]bool{"x/b": true}, outcome: sdkstep.OutcomePassed, counts: 1, carried: []string{"x/a"}, kept: []string{"x/b"}},
|
||||
{name: "unit failed: said with why, nothing kept", ask: stepOf(sdkstep.StepUnit), digests: map[string]string{"x/b": "d-b"}, tests: map[string]bool{"x/b": false},
|
||||
outcome: sdkstep.OutcomeFailed, counts: 1},
|
||||
{name: "integration with no tests: nothing to run", ask: stepOf(sdkstep.StepIntegration), digests: map[string]string{}, noTests: true, outcome: sdkstep.OutcomeNothingToRun},
|
||||
{name: "lint failed", ask: stepOf(sdkstep.StepLint), lint: func(context.Context, map[string]any) (any, error) {
|
||||
return LintAnswer{Failed: []sdkstep.Failure{{Package: "modules/m", Test: "gofmt", Cause: "not as gofmt writes them"}}}, nil
|
||||
}, outcome: sdkstep.OutcomeFailed},
|
||||
{name: "lint passed", ask: stepOf(sdkstep.StepLint), lint: func(context.Context, map[string]any) (any, error) { return LintAnswer{}, nil }, outcome: sdkstep.OutcomePassed},
|
||||
{name: "no lint body: nothing to run", ask: stepOf(sdkstep.StepLint), outcome: sdkstep.OutcomeNothingToRun},
|
||||
{name: "the source cannot be fetched: could not run", ask: stepOf(sdkstep.StepUnit), fetchErr: Refuse("no commit %s", runCommit), outcome: sdkstep.OutcomeCouldNotRun},
|
||||
{name: "a transport fault: unsaid, given again", ask: stepOf(sdkstep.StepUnit), fetchErr: &sdkstep.Passing{Err: errors.New("no responders")}, passing: true},
|
||||
{name: "the end cannot be said: given again", ask: stepOf(sdkstep.StepLint), sayErr: errors.New("bus down"), passing: true},
|
||||
} {
|
||||
r := &recorder{fetch: map[string]any{"workspace": "/w"}, tests: row.tests, digests: row.digests}
|
||||
s := r.steps()
|
||||
if row.fetchErr != nil {
|
||||
s.Fetch = func(context.Context, map[string]any) (any, error) { return nil, row.fetchErr }
|
||||
}
|
||||
s.Lint, s.Node = row.lint, "ace"
|
||||
if row.noTests {
|
||||
s.Test = func(context.Context, map[string]any) (any, error) {
|
||||
return map[string]any{"passed": true, "tests": 0, "no_tests": true}, nil
|
||||
}
|
||||
}
|
||||
var got said
|
||||
s.SayStep = func(e sdkstep.StepEnded) error {
|
||||
if row.sayErr != nil {
|
||||
return row.sayErr
|
||||
}
|
||||
got.ends = append(got.ends, e)
|
||||
return nil
|
||||
}
|
||||
out, err := RunAsk(context.Background(), s, row.ask)
|
||||
if row.passing {
|
||||
if !sdkstep.IsPassing(err) || len(got.ends) != 0 {
|
||||
t.Errorf("%s: %v, said %+v; want a transport fault, nothing said", row.name, err, got.ends)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil || len(got.ends) != 1 {
|
||||
t.Fatalf("%s: %v, said %+v; want one end", row.name, err, got.ends)
|
||||
}
|
||||
e := got.ends[0]
|
||||
if e.Outcome != row.outcome || e.Node != "ace" || e.ID != row.ask.ID || e.Fingerprint != "fp-1" || out.(StepAnswer).Ended.Outcome != row.outcome {
|
||||
t.Errorf("%s: said %+v, want %s on ace for the ask", row.name, e, row.outcome)
|
||||
}
|
||||
if (row.outcome == sdkstep.OutcomeFailed || row.outcome == sdkstep.OutcomeCouldNotRun) && e.Said == "" {
|
||||
t.Errorf("%s: %s and not why", row.name, row.outcome)
|
||||
}
|
||||
if n := 0; e.Tests != nil {
|
||||
n = e.Tests.Unit + e.Tests.Integration
|
||||
if n != row.counts {
|
||||
t.Errorf("%s: counts %+v, want %d", row.name, e.Tests, row.counts)
|
||||
}
|
||||
} else if row.counts != 0 {
|
||||
t.Errorf("%s: no counts, want %d", row.name, row.counts)
|
||||
}
|
||||
pkgs := func(ks []sdkstep.KeptResult) (out []string) {
|
||||
for _, k := range ks {
|
||||
out = append(out, k.Package)
|
||||
}
|
||||
return out
|
||||
}
|
||||
if !slices.Equal(pkgs(e.Carried), row.carried) || !slices.Equal(pkgs(e.Kept), row.kept) {
|
||||
t.Errorf("%s: carried %v kept %v, want %v and %v", row.name, pkgs(e.Carried), pkgs(e.Kept), row.carried, row.kept)
|
||||
}
|
||||
if _, err := sdkstep.EncodeStepEnded(e); err != nil {
|
||||
t.Errorf("%s: the end does not encode: %v", row.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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: "<quota µs> <period µs>", 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
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package buildcpu
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// The share's names the composer gives are the build modules' (catalogue writes them too, so it does not import this).
|
||||
func TestTheShareNamesAgreeWithTheCatalogues(t *testing.T) {
|
||||
if ShareSetting != catalogue.BuildCPUShareSetting || ShareWord != catalogue.BuildCPUShareWord || DefaultPercent != catalogue.BuildCPUDefaultPercent {
|
||||
t.Fatal("buildcpu's share names differ from catalogue's")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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, ""
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package buildstep
|
||||
|
||||
import (
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// 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[sdkstep.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
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package buildstep
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/buildcpu"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// **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.<node>` 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{sdkstep.AskGo: 6, sdkstep.AskNode: 2, sdkstep.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) sdkstep.Capacity {
|
||||
if coresPerStep <= 0 || math.IsNaN(coresPerStep) || math.IsInf(coresPerStep, 0) {
|
||||
coresPerStep = 1
|
||||
}
|
||||
c := sdkstep.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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
// mesh-pack builds a Go bundle by hand with the recipe of the mesh-build-go module and packs it with mesh-sdk's own
|
||||
// buildstep.Pack, printing the digest, so a hand build has the form of a built artifact and, with the same Go version,
|
||||
// its digest (novox/hq ADR 0526; fallback record F1; issue 1499 for the Go version).
|
||||
//
|
||||
// mesh-pack -dir <module dir> -from <package> -binary <name> [-system arch] [-out <archive file>]
|
||||
// mesh-pack -pack <directory> [-out <archive file>] (a `files` archive: pack a tree as it is)
|
||||
//
|
||||
// The recipe is mesh-catalog modules/mesh-build-go cmd/mesh-build-go/build.go (recipeFlags, recipeLinkerFlags,
|
||||
// recipeSystemStamp, buildEnv): CGO_ENABLED=0, GOFLAGS "-trimpath -buildvcs=false -mod=mod" (-mod=vendor when the
|
||||
// directory vendors), GOTOOLCHAIN=local, GOPROXY=off, and -ldflags "-s -w -X main.builtFor=<system>".
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
)
|
||||
|
||||
const (
|
||||
recipeFlags = "-trimpath -buildvcs=false"
|
||||
recipeLinkerFlags = "-s -w"
|
||||
recipeSystemStamp = "main.builtFor"
|
||||
)
|
||||
|
||||
// Env is the build module's environment for the compile of dir, added to the caller's own.
|
||||
func Env(dir string) []string {
|
||||
mod := "-mod=mod"
|
||||
if _, err := os.Stat(filepath.Join(dir, "vendor", "modules.txt")); err == nil {
|
||||
mod = "-mod=vendor"
|
||||
}
|
||||
return append(os.Environ(),
|
||||
"CGO_ENABLED=0",
|
||||
"GOFLAGS="+recipeFlags+" "+mod,
|
||||
"GOTOOLCHAIN=local",
|
||||
"GOPROXY=off",
|
||||
)
|
||||
}
|
||||
|
||||
// Args is the arguments of the go command of the recipe.
|
||||
func Args(dir, from, out, binary, system string) []string {
|
||||
linker := recipeLinkerFlags
|
||||
if s := strings.TrimSpace(system); s != "" {
|
||||
linker += " -X " + recipeSystemStamp + "=" + s
|
||||
}
|
||||
return []string{"-C", dir, "build", "-ldflags", linker, "-o", filepath.Join(out, binary), "./" + strings.Trim(from, "./")}
|
||||
}
|
||||
|
||||
// Build compiles the package into a fresh directory and answers it.
|
||||
func Build(dir, from, binary, system string) (string, error) {
|
||||
out, err := os.MkdirTemp("", "mesh-pack-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cmd := exec.Command("go", Args(dir, from, out, binary, system)...)
|
||||
cmd.Env = Env(dir)
|
||||
cmd.Stdout, cmd.Stderr = os.Stderr, os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
os.RemoveAll(out)
|
||||
return "", fmt.Errorf("go build of %s: %w", from, err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func run(args []string) (string, error) {
|
||||
fs := flag.NewFlagSet("mesh-pack", flag.ContinueOnError)
|
||||
dir := fs.String("dir", ".", "the Go module's directory")
|
||||
from := fs.String("from", "", "the package to build, relative to -dir (the artifact's from)")
|
||||
binary := fs.String("binary", "", "the binary's name in the archive")
|
||||
system := fs.String("system", "", "the system stamped into main.builtFor (an artifact's system, e.g. arch)")
|
||||
pack := fs.String("pack", "", "pack this directory as it is (a files archive) instead of building")
|
||||
outFile := fs.String("out", "", "write the archive here")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return "", err
|
||||
}
|
||||
root := *pack
|
||||
if root == "" {
|
||||
if *from == "" || *binary == "" {
|
||||
return "", fmt.Errorf("-from and -binary are needed (or -pack <directory>)")
|
||||
}
|
||||
out, err := Build(*dir, *from, *binary, *system)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.RemoveAll(out)
|
||||
root = out
|
||||
}
|
||||
archive, digest, err := buildstep.Pack(root)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if *outFile != "" {
|
||||
if err := os.WriteFile(*outFile, archive, 0o644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return digest, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
digest, err := run(os.Args[1:])
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "mesh-pack:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(digest)
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
)
|
||||
|
||||
func fixture(t *testing.T) string {
|
||||
t.Helper()
|
||||
d := t.TempDir()
|
||||
os.MkdirAll(filepath.Join(d, "sub"), 0o755)
|
||||
os.WriteFile(filepath.Join(d, "a.txt"), []byte("alpha"), 0o644)
|
||||
os.WriteFile(filepath.Join(d, "sub", "run"), []byte("#!/bin/sh\n"), 0o755)
|
||||
return d
|
||||
}
|
||||
|
||||
func TestPackFixtureTwiceGivesBuildstepsDigest(t *testing.T) {
|
||||
d := fixture(t)
|
||||
_, want, err := buildstep.Pack(d)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
got, err := run([]string{"-pack", d, "-out", filepath.Join(t.TempDir(), "x.tgz")})
|
||||
if err != nil || got != want {
|
||||
t.Fatalf("run %d: %q, %v; want %q", i, got, err, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildTwiceGivesOneDigestAndRecipeArgs(t *testing.T) {
|
||||
m := t.TempDir()
|
||||
os.WriteFile(filepath.Join(m, "go.mod"), []byte("module x\n\ngo 1.21\n"), 0o644)
|
||||
os.MkdirAll(filepath.Join(m, "cmd", "p"), 0o755)
|
||||
os.WriteFile(filepath.Join(m, "cmd", "p", "main.go"), []byte("package main\n\nvar builtFor string\n\nfunc main() { println(builtFor) }\n"), 0o644)
|
||||
args := []string{"-dir", m, "-from", "cmd/p", "-binary", "p", "-system", "arch"}
|
||||
a, err := run(args)
|
||||
if err != nil {
|
||||
t.Skipf("no go toolchain that builds offline: %v", err)
|
||||
}
|
||||
b, err := run(args)
|
||||
if err != nil || a != b {
|
||||
t.Fatalf("two builds: %q, %q, %v", a, b, err)
|
||||
}
|
||||
got := Args("/d", "./cmd/p/", "/o", "p", "arch")
|
||||
if got[len(got)-1] != "./cmd/p" || got[4] != "-s -w -X main.builtFor=arch" {
|
||||
t.Fatalf("args %v", got)
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// Command seatgen writes, or with -check compares, a contracts module's generated Go and TypeScript.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/seatgen"
|
||||
)
|
||||
|
||||
func main() {
|
||||
check := flag.Bool("check", false, "compare instead of writing: name every path out of step")
|
||||
goModule := flag.String("go-module", "", "the generated Go tree's module path, such as git.novox.be/novox/mesh-contracts/<folder>/go (there is no root module)")
|
||||
tsPackage := flag.String("ts-package", "", "the generated TypeScript package's name, such as @novox/mesh-contracts-<folder>")
|
||||
domainSDK := flag.String("domain-sdk", "", "the Go module of the contracts module's domain SDK, which a client may import (ADR 0634 §2); empty when it has none")
|
||||
flag.Parse()
|
||||
dir := flag.Arg(0)
|
||||
m, err := seatgen.Load(dir, *goModule, *tsPackage)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
m.DomainSDK = *domainSDK
|
||||
files, err := seatgen.Generate(m)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if *check {
|
||||
out, err := seatgen.Compare(dir, files)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// The clients beside the contracts (ADR 0627 §3) and module.json's Go and npm package artifacts (ADR 0625 §1, §3).
|
||||
for _, check := range []func(string, *seatgen.Module) ([]string, error){seatgen.CheckClients, seatgen.CheckArtifacts} {
|
||||
more, err := check(dir, m)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
out = append(out, more...)
|
||||
}
|
||||
for _, p := range out {
|
||||
fmt.Println(p)
|
||||
}
|
||||
if len(out) > 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
for _, root := range seatgen.GeneratedRoots {
|
||||
_ = os.RemoveAll(filepath.Join(dir, root, "src"))
|
||||
}
|
||||
// Each seat's npm package's sources too (ADR 0625 §1), so a file no definition writes any more is gone.
|
||||
stale, _ := filepath.Glob(filepath.Join(dir, "ts", "seats", "*", "src"))
|
||||
for _, p := range stale {
|
||||
_ = os.RemoveAll(p)
|
||||
}
|
||||
paths := make([]string, 0, len(files))
|
||||
for p := range files {
|
||||
paths = append(paths, p)
|
||||
}
|
||||
sort.Strings(paths)
|
||||
for _, p := range paths {
|
||||
full := filepath.Join(dir, p)
|
||||
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := os.WriteFile(full, files[p], 0o644); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
fmt.Println(len(paths), "files")
|
||||
}
|
||||
@@ -1,81 +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/<case>`, `GOTOOLCHAIN=local GOFLAGS=-mod=mod go test -json -count=1 ./... > ../<case>.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-<case>`
|
||||
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 <pkg>"), 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/<case>`, the
|
||||
way mesh-build-node runs a test script:
|
||||
|
||||
PATH=/usr/bin:$PATH NODE_OPTIONS="--test-reporter=tap --test-reporter-destination=/tmp/1901k/node/<case>.tap" \
|
||||
npm test > ../<case>.stdout 2> ../<case>.stderr
|
||||
|
||||
`<case>.tap` is the report file, `<case>.console` the stdout followed by the stderr. Each project's package.json is
|
||||
`{"name":"<case>","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 <typescript>/bin/tsc --noEmit --module NodeNext --moduleResolution NodeNext --target ES2022 --rootDir . ok.ts seat.ts
|
||||
|
||||
and the file is its stdout followed by its stderr.
|
||||
|
||||
## passing/ — a runner's words when the network did not answer (novox/hq issue 1748, ADR 0664 §1)
|
||||
|
||||
Not captured by running: a network cannot be cut on demand in a test. Each file holds the words a runner wrote when a
|
||||
build step met the fault on the mesh (the journal of ace on 2026-11-09 for ssh and go, the forge's sshd penalty; npm's
|
||||
error codes and buildah's pull failure as those programs print them), and for each a negative case, a failure of the
|
||||
source whose words are never a network's (an unknown revision, a checksum, a missing package, a failed RUN). Name:
|
||||
`<runner>-<case>.txt`; the kit's table (conformance.go, PassingCases) says which are transport faults.
|
||||
@@ -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]"}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -1,2 +0,0 @@
|
||||
npm notice run test
|
||||
npm notice run node --test --experimental-strip-types 'test/*.test.ts'
|
||||
@@ -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
|
||||
@@ -1,2 +0,0 @@
|
||||
npm notice run test
|
||||
npm notice run node --test --experimental-strip-types 'test/*.test.ts'
|
||||
@@ -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
|
||||
@@ -1,2 +0,0 @@
|
||||
npm notice run test
|
||||
npm notice run node --test --experimental-strip-types 'test/*.test.ts'
|
||||
@@ -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:
|
||||
[32m+ actual[39m [31m- expected[39m
|
||||
|
||||
[39m {
|
||||
[32m+[39m holders: 2
|
||||
[31m-[39m holders: 1
|
||||
[39m }
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
holders: 1
|
||||
actual:
|
||||
holders: 2
|
||||
operator: 'deepStrictEqual'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (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
|
||||
@@ -1,2 +0,0 @@
|
||||
npm notice run test
|
||||
npm notice run node --test --experimental-strip-types 'test/*.test.ts'
|
||||
@@ -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 '<eof>'
|
||||
# [90m at parseTypeScript (node:internal/modules/typescript:61:36)[39m
|
||||
# [90m at processTypeScriptCode (node:internal/modules/typescript:129:37)[39m
|
||||
# [90m at Object.stripTypeScriptModuleTypes (node:internal/modules/typescript:172:22)[39m
|
||||
# [90m at stripTypeScriptModuleTypes (node:internal/modules/esm/translators:35:49)[39m
|
||||
# [90m at ModuleLoader.<anonymous> (node:internal/modules/esm/translators:688:29)[39m
|
||||
# [90m at \#translate (node:internal/modules/esm/loader:441:20)[39m
|
||||
# [90m at afterLoad (node:internal/modules/esm/loader:509:29)[39m
|
||||
# [90m at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:514:12)[39m
|
||||
# [90m at \#getOrCreateModuleJobAfterResolve (node:internal/modules/esm/loader:567:36)[39m
|
||||
# [90m at afterResolve (node:internal/modules/esm/loader:614:52)[39m {
|
||||
# code: [32m'ERR_INVALID_TYPESCRIPT_SYNTAX'[39m
|
||||
# }
|
||||
# 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
|
||||
@@ -1,2 +0,0 @@
|
||||
npm notice run test
|
||||
npm notice run node --test --experimental-strip-types 'test/*.test.ts'
|
||||
@@ -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
|
||||
@@ -1,2 +0,0 @@
|
||||
npm notice run test
|
||||
npm notice run node --test --experimental-strip-types 'test/*.test.ts'
|
||||
@@ -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.<anonymous> (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
|
||||
@@ -1 +0,0 @@
|
||||
Error: building at STEP "RUN false": exit status 1
|
||||
@@ -1 +0,0 @@
|
||||
Error: creating build container: initializing source docker://alpine: pinging container registry registry-1.docker.io: Get "https://registry-1.docker.io/v2/": dial tcp: lookup registry-1.docker.io on 10.10.0.3:53: i/o timeout
|
||||
@@ -1,2 +0,0 @@
|
||||
Connection closed by 10.10.0.1 port 222
|
||||
fatal: Could not read from remote repository.
|
||||
@@ -1,6 +0,0 @@
|
||||
kex_exchange_identification: read: Connection reset by peer
|
||||
Connection reset by 10.10.0.1 port 222
|
||||
fatal: Could not read from remote repository.
|
||||
|
||||
Please make sure you have the correct access rights
|
||||
and the repository exists.
|
||||
@@ -1 +0,0 @@
|
||||
fatal: couldn't find remote ref 0123456789abcdef0123456789abcdef01234567
|
||||
@@ -1,2 +0,0 @@
|
||||
ssh: Could not resolve hostname mesh-forge: Temporary failure in name resolution
|
||||
fatal: Could not read from remote repository.
|
||||
@@ -1 +0,0 @@
|
||||
{"Path":"git.novox.be/novox/mesh-sdk/go","Error":"verifying module: checksum mismatch"}
|
||||
@@ -1 +0,0 @@
|
||||
{"Path":"git.novox.be/novox/mesh-sdk/go","Error":"git.novox.be/novox/mesh-sdk/go@abc: dial tcp 10.10.0.1:5100: i/o timeout"}
|
||||
@@ -1 +0,0 @@
|
||||
{"Path":"git.novox.be/novox/mesh-sdk/go","Error":"git.novox.be/novox/mesh-sdk/go@abc: dial tcp: lookup git.novox.be on 10.10.0.3:53: read udp 10.10.0.3:42292->10.10.0.3:53: i/o timeout"}
|
||||
@@ -1 +0,0 @@
|
||||
{"Path":"git.novox.be/novox/mesh-sdk/go","Error":"invalid version: unknown revision abc"}
|
||||
@@ -1,2 +0,0 @@
|
||||
npm error code E404
|
||||
npm error 404 Not Found - GET https://git.novox.be/api/packages/novox/npm/@novox%2fx
|
||||
@@ -1,3 +0,0 @@
|
||||
npm error code EAI_AGAIN
|
||||
npm error syscall getaddrinfo
|
||||
npm error errno EAI_AGAIN
|
||||
@@ -1,4 +0,0 @@
|
||||
npm error code ECONNRESET
|
||||
npm error syscall read
|
||||
npm error errno -104
|
||||
npm error network aborted
|
||||
@@ -1 +0,0 @@
|
||||
seat.ts(2,14): error TS2322: Type 'number' is not assignable to type 'string'.
|
||||
@@ -1,279 +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"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/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(<file>): 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 '<eof>'"},
|
||||
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) }
|
||||
|
||||
// The runners whose network failures the kit holds words of (Passing).
|
||||
const (
|
||||
GitFetch = "git fetch"
|
||||
GoDownload = "go mod download"
|
||||
NpmCI = "npm ci"
|
||||
BuildahBud = "buildah bud"
|
||||
)
|
||||
|
||||
// PassingCase is the words of one runner and whether they are a transport fault: a network that did not answer, which the
|
||||
// bus gives the ask again for (novox/hq ADR 0664 §1), or a failure of the source, which fails the step at once.
|
||||
type PassingCase struct {
|
||||
Name, Runner string
|
||||
Transport bool
|
||||
}
|
||||
|
||||
// PassingCases are the kit's cases of the network's failures (captured/passing).
|
||||
var PassingCases = []PassingCase{
|
||||
{"git-fetch-kex", GitFetch, true}, {"git-fetch-closed", GitFetch, true}, {"git-fetch-resolve", GitFetch, true}, {"git-fetch-noref", GitFetch, false},
|
||||
{"go-download-lookup", GoDownload, true}, {"go-download-dial", GoDownload, true}, {"go-download-unknown", GoDownload, false}, {"go-download-checksum", GoDownload, false},
|
||||
{"npm-ci-econnreset", NpmCI, true}, {"npm-ci-eaiagain", NpmCI, true}, {"npm-ci-404", NpmCI, false},
|
||||
{"buildah-bud-timeout", BuildahBud, true}, {"buildah-bud-run", BuildahBud, false},
|
||||
}
|
||||
|
||||
// Failed is a build module's own path from a runner's words to the error its step ends with: the same code its step runs on
|
||||
// a failed command, fed the words of a runner's failed run.
|
||||
type Failed func(runner, words string) error
|
||||
|
||||
// Passing runs every case of the runners through failed, each as a subtest, and fails a build module whose step does not
|
||||
// say a network's failure as a buildstep.Passing (the bus then gives the ask again) or says a failure of the source as one.
|
||||
// A build module names the runners its steps use; the case's words are in captured/passing.
|
||||
func Passing(t *testing.T, runners []string, failed Failed) {
|
||||
t.Helper()
|
||||
ran := 0
|
||||
for _, c := range PassingCases {
|
||||
if !slices.Contains(runners, c.Runner) {
|
||||
continue
|
||||
}
|
||||
ran++
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
if problem := judgePassing(c, failed); problem != "" {
|
||||
t.Error(problem)
|
||||
}
|
||||
})
|
||||
}
|
||||
if ran == 0 {
|
||||
t.Fatalf("the kit holds no passing case of the runners %v", runners)
|
||||
}
|
||||
}
|
||||
|
||||
// JudgePassing is the problems of failed over every case of the runners: what Passing fails a build module for.
|
||||
func JudgePassing(runners []string, failed Failed) []string {
|
||||
var out []string
|
||||
for _, c := range PassingCases {
|
||||
if slices.Contains(runners, c.Runner) {
|
||||
if p := judgePassing(c, failed); p != "" {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func judgePassing(c PassingCase, failed Failed) string {
|
||||
words, err := captured.ReadFile("captured/passing/" + c.Name + ".txt")
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
got := failed(c.Runner, string(words))
|
||||
switch {
|
||||
case got == nil:
|
||||
return c.Name + ": the step passed on a failed run"
|
||||
case buildstep.IsPassing(got) != c.Transport:
|
||||
return fmt.Sprintf("%s: the step ends %q, a transport fault %v, want %v", c.Name, got, buildstep.IsPassing(got), c.Transport)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
package conformance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/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) []sdkstep.Failure { return buildstep.ReadGoTest(o.Stdout, ".").Failed })
|
||||
Run(t, NodeTest, func(o Output) []sdkstep.Failure { return buildstep.ReadTAP(".", "test", o.Dir, o.Report, o.Stdout) })
|
||||
Run(t, TypeCheck, func(o Output) []sdkstep.Failure {
|
||||
return []sdkstep.Failure{sdkstep.ConsoleFailure(".", "type-check", o.Stdout+o.Stderr)}
|
||||
})
|
||||
})
|
||||
|
||||
// ADR 0664 §1 (issue 1748): the shared wrapper keeps the passing cases; a step that never says a network's failure as a
|
||||
// transport fault, or says every failure as one, is refused.
|
||||
t.Run("the passing cases", func(t *testing.T) {
|
||||
all := []string{GitFetch, GoDownload, NpmCI, BuildahBud}
|
||||
Passing(t, all, func(_, words string) error { return buildstep.Transport(errors.New(words)) })
|
||||
if p := JudgePassing(all, func(_, words string) error { return errors.New(words) }); len(p) != 8 {
|
||||
t.Errorf("a step that wraps nothing: %d problems, want 8 (one per transport case):\n%s", len(p), strings.Join(p, "\n"))
|
||||
}
|
||||
if p := JudgePassing(all, func(_, words string) error { return &sdkstep.Passing{Err: errors.New(words)} }); len(p) != 5 {
|
||||
t.Errorf("a step that wraps everything: %d problems, want 5 (one per source failure):\n%s", len(p), strings.Join(p, "\n"))
|
||||
}
|
||||
})
|
||||
|
||||
// 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 buildrun.Scan, node string, before time.Time, logf func(string, ...any)) {
|
||||
buildrun.EndStaleAtStart(ctx, scan, node, before, logf)
|
||||
})
|
||||
if p := JudgeLeftRunning(func(context.Context, buildrun.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 buildrun.Scan, _ string, before time.Time, logf func(string, ...any)) {
|
||||
buildrun.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) []sdkstep.Failure
|
||||
}{
|
||||
{NodeTest, "two-files", func(o Output) []sdkstep.Failure {
|
||||
return []sdkstep.Failure{{Package: ".", Test: "test", Output: firstLines(o.Report)}}
|
||||
}},
|
||||
{GoTest, "passfirst", func(o Output) []sdkstep.Failure {
|
||||
return []sdkstep.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 sdkstep.PassingLine(c.line) != c.passing {
|
||||
t.Errorf("PassingLine(%q) = %v, want %v", c.line, !c.passing, c.passing)
|
||||
}
|
||||
}
|
||||
if sdkstep.Hidden(sdkstep.Failure{Cause: "the seat has 2 holders"}) != "" || sdkstep.Hidden(sdkstep.Failure{}) == "" ||
|
||||
sdkstep.Hidden(sdkstep.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 := sdkstep.Clip(lines, cause)
|
||||
if len(out) > sdkstep.OutputBytes+sdkstep.LineBytes {
|
||||
t.Errorf("1901/AC1: kept %d bytes, bound is %d", len(out), sdkstep.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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
package conformance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
buildrun "git.novox.be/novox/mesh-delivery/sdk/buildrun"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
)
|
||||
|
||||
// **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 (buildrun.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 buildrun.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: buildrun.Owner, age: time.Minute}, // this process's
|
||||
{id: "minehand1", tool: "build_x_test", owner: buildrun.Owner, age: -time.Second},
|
||||
{id: "donehand1", tool: "build_x_test", owner: gone, age: time.Hour, state: sdkstep.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 = sdkstep.StepRunning
|
||||
}
|
||||
key, _ := sdkstep.StepKey(node, c.id)
|
||||
raw, err := sdkstep.EncodeStep(sdkstep.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 := buildrun.StaleServed
|
||||
buildrun.StaleServed = func() <-chan struct{} { return served }
|
||||
defer func() { buildrun.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, _ := sdkstep.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 := sdkstep.DecodeStep(raw)
|
||||
if err != nil {
|
||||
problems = append(problems, key+": "+err.Error())
|
||||
continue
|
||||
}
|
||||
want := c.state
|
||||
if want == "" {
|
||||
want = sdkstep.StepRunning
|
||||
}
|
||||
if c.ended {
|
||||
want = sdkstep.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() buildrun.Scan {
|
||||
return buildrun.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
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package buildstep
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/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)
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
package buildstep
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// 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][]sdkstep.Step
|
||||
refused int
|
||||
}
|
||||
|
||||
func newRuntimeState() *runtimeState { return &runtimeState{took: map[string][]sdkstep.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 := sdkstep.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) (sdkstep.Step, bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
t := s.took[key]
|
||||
if len(t) == 0 {
|
||||
return sdkstep.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(sdkstep.CheckJob{Levels: []string{sdkstep.LevelUnit},
|
||||
Fetch: sdkstep.Fetch{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", sdkstep.Step{ID: "a1", Tool: "check ask", Node: "shanks", State: sdkstep.StepRunning, Started: time.Now()}, out, nil, t.Logf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, ok := st.last("shanks.a1")
|
||||
if !ok || s.State != sdkstep.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 sdkstep.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 := []sdkstep.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 []sdkstep.Step
|
||||
put := func(key string, value any) (uint64, error) {
|
||||
st, _ := sdkstep.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", sdkstep.Step{ID: "a2", Tool: "check ask", Node: "shanks", State: sdkstep.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 != sdkstep.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", sdkstep.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(sdkstep.AskCheck)
|
||||
_ = src.PublishAsk(a)
|
||||
var mu sync.Mutex
|
||||
var ends []sdkstep.Step
|
||||
put := func(key string, value any) (uint64, error) {
|
||||
st, err := sdkstep.DecodeStep(value.(json.RawMessage))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if st.State != sdkstep.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: sdkstep.AskGo, Node: "shanks", Put: put, Capacity: func() int { return 1 },
|
||||
PullWait: 10 * time.Millisecond, Logf: t.Logf,
|
||||
Run: func(ctx context.Context, a sdkstep.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 != sdkstep.StepFailed || !strings.Contains(last.Reason, "refused") {
|
||||
t.Errorf("the end %+v, want the small failed end", last)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
package buildstep
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
dforgekey "git.novox.be/novox/mesh-delivery/sdk/forgekey"
|
||||
"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._/-]+$`)
|
||||
)
|
||||
|
||||
// gitFailure is a failed git command said in git's own words; one whose words are a network's (a lookup, a timeout, ssh's key
|
||||
// exchange cut by a forge's per-source penalty) is a sdkstep.Passing, so the ask is given again after a delay (ADR 0664 §1).
|
||||
func gitFailure(verb, said string) error {
|
||||
err := fmt.Errorf("git %s: %s", verb, said)
|
||||
if sdkstep.PassingCause(said) {
|
||||
return &sdkstep.Passing{Err: err}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 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://<ssh name>/<owner>/<repo>.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/<n>/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 "", gitFailure(args[0], dforgekey.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 gitFailure(verb, dforgekey.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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package buildstep
|
||||
|
||||
import (
|
||||
"context"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"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")
|
||||
}
|
||||
|
||||
// ADR 0664 §1 (issue 1748): the words a forge's sshd penalty leaves (captured on ace, 2026-11-09) are a transport fault the bus
|
||||
// gives the ask again for; a missing commit is not.
|
||||
for _, c := range []struct {
|
||||
said string
|
||||
passing bool
|
||||
}{
|
||||
{"kex_exchange_identification: read: Connection reset by peer\nfatal: Could not read from remote repository.", true},
|
||||
{"Connection closed by 10.10.0.1 port 222", true},
|
||||
{"ssh: Could not resolve hostname mesh-forge: Temporary failure in name resolution", true},
|
||||
{"fatal: couldn't find remote ref " + sha, false},
|
||||
} {
|
||||
x := &fakeGit{rule: func(l string) forgekey.Ran {
|
||||
if strings.Contains(l, " fetch ") {
|
||||
return forgekey.Ran{Status: 128, Stderr: c.said}
|
||||
}
|
||||
return forgekey.Ran{}
|
||||
}}
|
||||
if err := FetchCommit(context.Background(), x.run, "/w", "ssh://mesh-forge/o/r.git", sha, ""); err == nil || sdkstep.IsPassing(err) != c.passing {
|
||||
t.Errorf("%q: %v, passing %v, want %v", c.said, err, err != nil && sdkstep.IsPassing(err), c.passing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
package buildstep
|
||||
|
||||
// **Regenerate and compare** (CAT-BG5, CAT-BN2; novox/hq ADR 0608 §5, design 26 *Contracts modules*). A module whose
|
||||
// package artifact names `generated-from` holds contracts only: every file under its generated roots is written by the
|
||||
// seat generator (package seatgen beside this file, command cmd/seatgen) from the definitions in that directory. A build module regenerates them with the generator
|
||||
// it was built with (its version is this package's requirement, so a new generator is a new build module and a new
|
||||
// fingerprint of what it builds) and names each file out of step: one that differs, one missing, one under a generated
|
||||
// root the generator did not write. The test step fails on any; a trunk build's ask answers them in its out-of-step
|
||||
// (sdkstep.BuildAnswer.OutOfStep), so the builder's build end raises generated-types-out-of-step even where no test
|
||||
// step runs. Each build module compares its own roots: mesh-build-go `go`, mesh-build-node `ts`.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/seatgen"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// GeneratedRootGo and GeneratedRootTS are the generated roots of a contracts module, one per build module.
|
||||
const (
|
||||
GeneratedRootGo = "go"
|
||||
GeneratedRootTS = "ts"
|
||||
)
|
||||
|
||||
var goModuleLine = regexp.MustCompile(`(?m)^module\s+(\S+)`)
|
||||
|
||||
// OutOfStep is every file under root, a path from the module's directory, that the generator writes otherwise than the
|
||||
// module holds it, for each `generated-from` its package artifacts name, sorted and each once; none for a module naming
|
||||
// none. The generated trees' names are read from the seats already generated in <from> (generatedNames).
|
||||
func OutOfStep(moduleDir, root string) ([]string, error) {
|
||||
raw, err := os.ReadFile(filepath.Join(moduleDir, "module.json"))
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, nil // no manifest here: nothing names generated-from
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the module's manifest cannot be read for its generated files: %w", err)
|
||||
}
|
||||
var m catalogue.Manifest
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
return nil, fmt.Errorf("the module's manifest does not parse: %w", err)
|
||||
}
|
||||
froms := map[string]bool{}
|
||||
if m.Build != nil {
|
||||
for _, a := range m.Build.Artifacts {
|
||||
if a.GeneratedFrom != "" {
|
||||
froms[filepath.Clean(a.GeneratedFrom)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for from := range froms {
|
||||
if filepath.IsAbs(from) || from == ".." || strings.HasPrefix(from, "../") {
|
||||
return nil, fmt.Errorf("generated-from %q is not inside the module", from)
|
||||
}
|
||||
dir := filepath.Join(moduleDir, from)
|
||||
goModule, tsPackage, err := generatedNames(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g, err := seatgen.Load(dir, goModule, tsPackage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the seat definitions in %s do not load: %w", from, err)
|
||||
}
|
||||
files, err := seatgen.Generate(g)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the seat definitions in %s do not generate: %w", from, err)
|
||||
}
|
||||
out, err := seatgen.Compare(dir, files)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, line := range out {
|
||||
p, _, _ := strings.Cut(line, ": ")
|
||||
if p == root || strings.HasPrefix(p, root+"/") {
|
||||
seen[filepath.ToSlash(filepath.Join(from, p))] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
paths := make([]string, 0, len(seen))
|
||||
for p := range seen {
|
||||
paths = append(paths, p)
|
||||
}
|
||||
sort.Strings(paths)
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
// generatedNames reads the names the generated trees are rooted at from the seats already generated in dir: a seat's Go
|
||||
// module is `<root>/seats/<seat without hyphens>` and its npm package `<root>-<seat>` (a contracts module has no root
|
||||
// package of either kind, ADR 0635 §1). A module with no seat generated yet gives none, and the generator says so only
|
||||
// if a seat is defined whole.
|
||||
func generatedNames(dir string) (goModule, tsPackage string, err error) {
|
||||
names, _ := filepath.Glob(filepath.Join(dir, "seats", "*", "seat.json"))
|
||||
for _, p := range names {
|
||||
seat := filepath.Base(filepath.Dir(p))
|
||||
pkg := strings.ReplaceAll(seat, "-", "")
|
||||
if goModule == "" {
|
||||
if raw, err := os.ReadFile(filepath.Join(dir, GeneratedRootGo, "seats", pkg, "go.mod")); err == nil {
|
||||
if m := goModuleLine.FindSubmatch(raw); m != nil {
|
||||
goModule = strings.TrimSuffix(string(m[1]), "/seats/"+pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
if tsPackage == "" {
|
||||
if raw, err := os.ReadFile(filepath.Join(dir, GeneratedRootTS, "seats", pkg, "package.json")); err == nil {
|
||||
var p struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if json.Unmarshal(raw, &p) == nil {
|
||||
tsPackage = strings.TrimSuffix(p.Name, "-"+seat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return goModule, tsPackage, nil
|
||||
}
|
||||
|
||||
// ModuleDirs is every directory in tree that holds a module.json, in any folder (ADR 0634 §8: a module is found by its
|
||||
// manifest, whether it lies at the root, under modules/ or in a folder of another module), the tree's own first, then
|
||||
// sorted. Hidden directories, node_modules and testdata are not entered.
|
||||
func ModuleDirs(tree string) ([]string, error) {
|
||||
var out []string
|
||||
err := filepath.WalkDir(tree, func(p string, e os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.IsDir() {
|
||||
if n := e.Name(); p != tree && (strings.HasPrefix(n, ".") || n == "node_modules" || n == "testdata") {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if e.Name() == "module.json" {
|
||||
out = append(out, filepath.Dir(p))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
sort.Strings(out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// CompareStep is AskSteps.Compare for a build module comparing root: it reads {"workspace", "module"}, the workspace's
|
||||
// tree where tree says it is (the build module's own layout), and answers {"out-of-step": [...]}.
|
||||
func CompareStep(root string, tree func(workspace string) string) StepFunc {
|
||||
return func(_ context.Context, args map[string]any) (any, error) {
|
||||
ws, _ := args["workspace"].(string)
|
||||
mod, _ := args["module"].(string)
|
||||
if ws == "" {
|
||||
return nil, Refuse("the compare names no workspace")
|
||||
}
|
||||
paths, err := OutOfStep(filepath.Join(tree(ws), filepath.FromSlash(mod)), root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]any{"out-of-step": paths}, nil
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
package buildstep
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/seatgen"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
)
|
||||
|
||||
// contracts writes a contracts module of one seat, its generated files as the generator writes them, and answers its
|
||||
// directory and the generated paths.
|
||||
func contracts(t *testing.T, manifest string) (string, []string) {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
for p, text := range map[string]string{
|
||||
"module.json": manifest,
|
||||
"seats/one-store/seat.json": `{"format":1,"seat":"one-store","scope":"mesh","grain":"whole","domain":"Module","decision":"ADR 1","verbs":[{"name":"query","input":{"type":"object"},"answer":{"type":"object"}}]}`,
|
||||
// A definition at the seat-list grain is read and generates nothing (ADR 0635 §1, §2).
|
||||
"seats/other/seat.json": `{"format":1,"seat":"other","scope":"mesh","grain":"seat-list","domain":"Module","decision":"ADR 1","verbs":[{"name":"ask","input":{"type":"object"}}]}`,
|
||||
} {
|
||||
writeContract(t, filepath.Join(dir, p), text)
|
||||
}
|
||||
m, err := seatgen.Load(dir, "example.org/c/go", "@example/c")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
files, err := seatgen.Generate(m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var paths []string
|
||||
for p, b := range files {
|
||||
writeContract(t, filepath.Join(dir, p), string(b))
|
||||
paths = append(paths, p)
|
||||
}
|
||||
return dir, paths
|
||||
}
|
||||
|
||||
func writeContract(t *testing.T, path, text string) {
|
||||
t.Helper()
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte(text), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// **CAT-BG5, CAT-BN2**: a contracts module's generated files are regenerated and compared, each build module its own
|
||||
// root; a file that differs, one missing and one the generator did not write are each named; a clean tree and a module
|
||||
// naming no generated-from name none; a build ask answers them in its out-of-step.
|
||||
func TestAContractsModulesGeneratedFilesAreComparedWithTheGenerator(t *testing.T) {
|
||||
marked := `{"module":"c","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"],"generated-from":"."}]}}`
|
||||
dir, paths := contracts(t, marked)
|
||||
var goFile, tsFile string
|
||||
for _, p := range paths {
|
||||
if strings.HasPrefix(p, "go/") && strings.HasSuffix(p, ".go") && goFile == "" {
|
||||
goFile = p
|
||||
}
|
||||
if strings.HasPrefix(p, "ts/") && strings.HasSuffix(p, ".ts") && tsFile == "" {
|
||||
tsFile = p
|
||||
}
|
||||
}
|
||||
for _, p := range paths {
|
||||
if strings.Contains(p, "other") || p == "go/go.mod" || p == "ts/package.json" {
|
||||
t.Errorf("%s is generated: a seat-list definition generates nothing and a contracts module has no root package", p)
|
||||
}
|
||||
}
|
||||
if goFile == "" || tsFile == "" {
|
||||
t.Fatalf("the generator wrote no Go or no TypeScript file: %v", paths)
|
||||
}
|
||||
for _, root := range []string{GeneratedRootGo, GeneratedRootTS} {
|
||||
if got, err := OutOfStep(dir, root); err != nil || len(got) != 0 {
|
||||
t.Fatalf("a clean tree, root %s: %v %v", root, got, err)
|
||||
}
|
||||
}
|
||||
writeContract(t, filepath.Join(dir, goFile), "package changed\n")
|
||||
writeContract(t, filepath.Join(dir, "go/extra.go"), "package extra\n")
|
||||
writeContract(t, filepath.Join(dir, tsFile), "// changed\n")
|
||||
got, err := OutOfStep(dir, GeneratedRootGo)
|
||||
if want := sorted(goFile, "go/extra.go"); err != nil || !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("Go root: %v %v, want %v", got, err, want)
|
||||
}
|
||||
if got, _ := OutOfStep(dir, GeneratedRootTS); !reflect.DeepEqual(got, []string{tsFile}) {
|
||||
t.Errorf("TypeScript root: %v, want %s alone", got, tsFile)
|
||||
}
|
||||
_ = os.Remove(filepath.Join(dir, goFile))
|
||||
if got, _ := OutOfStep(dir, GeneratedRootGo); !reflect.DeepEqual(got, sorted(goFile, "go/extra.go")) {
|
||||
t.Errorf("a missing file is not named: %v", got)
|
||||
}
|
||||
plain, _ := contracts(t, `{"module":"p","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"]}]}}`)
|
||||
writeContract(t, filepath.Join(plain, "go/extra.go"), "package extra\n")
|
||||
if got, err := OutOfStep(plain, GeneratedRootGo); err != nil || len(got) != 0 {
|
||||
t.Errorf("a module naming no generated-from compared: %v %v", got, err)
|
||||
}
|
||||
|
||||
// The build ask answers them beside what it built, and with a failed build too.
|
||||
built := 0
|
||||
steps := AskSteps{Compare: CompareStep(GeneratedRootGo, func(ws string) string { return ws }),
|
||||
Fetch: func(context.Context, map[string]any) (any, error) {
|
||||
return map[string]any{"workspace": filepath.Dir(dir)}, nil
|
||||
},
|
||||
Build: func(context.Context, map[string]any) (any, error) {
|
||||
if built++; built > 1 {
|
||||
return nil, errors.New("the second artifact does not build")
|
||||
}
|
||||
return map[string]any{"digest": "sha256:aa"}, nil
|
||||
}}
|
||||
job := sdkstep.BuildJob{Module: "c", Path: filepath.Base(dir), Fetch: &sdkstep.Fetch{Repository: "novox/c", Commit: "0123456789abcdef0123456789abcdef01234567"},
|
||||
Artifacts: []sdkstep.AskArtifact{{Artifact: "go"}}}
|
||||
out, err := RunAsk(context.Background(), steps, ask(job))
|
||||
if a, ok := out.(sdkstep.BuildAnswer); err != nil || !ok || !reflect.DeepEqual(a.OutOfStep, sorted(goFile, "go/extra.go")) || len(a.Artifacts) != 1 {
|
||||
t.Errorf("the build ask's out-of-step: %+v %v", out, err)
|
||||
}
|
||||
job.Artifacts = append(job.Artifacts, sdkstep.AskArtifact{Artifact: "go-other"})
|
||||
built = 0
|
||||
out, err = RunAsk(context.Background(), steps, ask(job))
|
||||
if a, ok := out.(sdkstep.BuildAnswer); err == nil || !ok || len(a.OutOfStep) != 2 {
|
||||
t.Errorf("a failed build answered no out-of-step: %+v %v", out, err)
|
||||
}
|
||||
}
|
||||
|
||||
func sorted(paths ...string) []string {
|
||||
sort.Strings(paths)
|
||||
return paths
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
module git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/gopackageregistryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/npmpackageregistryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/ociartifactregistryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildnode v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
github.com/klauspost/compress v1.20.1 // indirect
|
||||
github.com/minio/highwayhash v1.0.4 // indirect
|
||||
github.com/nats-io/jwt/v2 v2.8.2 // indirect
|
||||
github.com/nats-io/nats-server/v2 v2.11.17 // indirect
|
||||
github.com/nats-io/nkeys v0.4.16 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
golang.org/x/crypto v0.57.0 // indirect
|
||||
golang.org/x/sys v0.48.0 // indirect
|
||||
golang.org/x/time v0.16.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 // indirect
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user