Split the unit level from the integration level in cmd, builder, link, catalogue, inventory; cheap fixtures for the archive-entry bound, the registry waits and the catalogue copy (issue 895)

This commit is contained in:
2026-10-17 21:32:23 +02:00
parent aac4bae5a4
commit 66dc501fc4
438 changed files with 22014 additions and 19825 deletions
+2
View File
@@ -1,3 +1,5 @@
//go:build integration
package builder
import (
@@ -0,0 +1,362 @@
//go:build integration
package builder
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/novox/mesh-controller/internal/catalogue"
)
func TestABuildProducesAManifestThePinsAreIn(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/theme.conf": "dark",
})
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if got.Commit != "c0ffeec0ffeec0ffeec0ffeec0ffeec0ffeec0ff" {
t.Fatalf("the commit was not recorded: %q", got.Commit)
}
if got.Manifest.Resources[0]["image"] == nil {
t.Fatalf("the image was not pinned: %v", got.Manifest.Resources[0])
}
digest, _ := got.Manifest.Resources[1]["digest"].(string)
if !strings.HasPrefix(digest, "sha256:") {
t.Fatalf("the archive was not pinned: %v", got.Manifest.Resources[1])
}
}
func TestTwoBuildsOfOneCommitProduceOneDigest(t *testing.T) {
// Or nothing downstream can tell "this changed" from "this was built again", and every
// rebuild looks like a change to every machine holding it.
var digests []string
for i := 0; i < 2; i++ {
r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/a.conf": "one", "files/b.conf": "two",
})
// A year apart, so a packer carrying timestamps cannot accidentally agree.
r.stamped = time.Date(2020+i, time.March, 3, 4, 5, 6, 0, time.UTC)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
for _, b := range got.Built {
if b.Kind == catalogue.ArtifactArchive {
digests = append(digests, b.Digest)
}
}
}
if digests[0] != digests[1] {
t.Fatalf("two builds of one commit produced %s and %s", digests[0], digests[1])
}
}
func TestNothingIsPublishedUntilEverythingIsBuilt(t *testing.T) {
// Half a module in the store under a digest the mesh never records is reachable,
// unreferenced, and indistinguishable from something in use.
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch"})
// `files` is missing, so packing the archive fails — after the image would have been pushed.
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatal("a build with a missing input succeeded")
}
if len(r.archives) != 0 {
t.Fatalf("an archive was published by a failed build: %v", r.archives)
}
}
func TestARepositoryWithNoManifestSaysSo(t *testing.T) {
workspace := t.TempDir()
r := &recorded{contents: map[string]string{"README.md": "nothing to see"}}
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatal("a repository with nothing saying what it is was built")
}
if !strings.Contains(err.Error(), ManifestName) {
t.Fatalf("the failure does not name what is missing: %v", err)
}
}
func TestAModuleThatBuildsNothingStillProducesAManifest(t *testing.T) {
// Most of what a person installs is configuration.
r, workspace := aRepository(t, `{"module":"shell","version":"1","resources":[
{"id":"rc","type":"file","path":"/etc/zsh/zshrc","content":"setopt"}]}`, nil)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/shell.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if len(got.Built) != 0 {
t.Fatalf("something was built: %v", got.Built)
}
if got.Manifest.Module != "shell" || len(got.Manifest.Resources) != 1 {
t.Fatalf("got %+v", got.Manifest)
}
for _, line := range r.ran {
if strings.HasPrefix(line, "docker") {
t.Fatalf("docker was run for a module that builds nothing: %q", line)
}
}
}
func TestTheTreeIsFreshEveryTime(t *testing.T) {
// A build that reuses a working tree can succeed because of something a previous build left
// behind, and that is a build nobody can reproduce.
r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/a": "b",
})
leftover := filepath.Join(workspace, "source", "files", "from-last-time")
if err := os.MkdirAll(filepath.Dir(leftover), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(leftover, []byte("stale"), 0o644); err != nil {
t.Fatal(err)
}
if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(leftover); err == nil {
t.Fatal("a previous build's file survived into this one")
}
}
func TestABuildThatCannotPushFails(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/a": "b",
})
r.failPush = true
if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil); err == nil {
t.Fatal("a build that could publish nothing reported success")
}
}
func TestAnUpstreamImageIsMirroredRatherThanBuilt(t *testing.T) {
// A module usually runs software it did not write. Naming the upstream reference directly
// would need every machine to reach a public registry, and would pin to a tag somebody else
// can move.
const mirrors = `{"module":"postgres","version":"1",
"build":{"artifacts":[{"name":"store","kind":"upstream","from":"postgres:17-alpine"}]},
"resources":[{"id":"db","type":"container","name":"mesh-postgres","artifact":"store"}]}`
r, workspace := aRepository(t, mirrors, nil)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/postgres.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
// Pulled, not built.
var pulled, built bool
for _, line := range r.ran {
if strings.HasPrefix(line, "docker pull postgres:17-alpine") {
pulled = true
}
if strings.HasPrefix(line, "docker build") {
built = true
}
}
if !pulled {
t.Fatalf("the upstream image was not fetched: %v", r.ran)
}
if built {
t.Fatalf("something was built for an image that is mirrored: %v", r.ran)
}
// And the resource names what this registry serves, pinned by the digest it assigned.
image, _ := got.Manifest.Resources[0]["image"].(string)
if !strings.Contains(image, "@sha256:") {
t.Fatalf("the mirrored image is not pinned by digest: %q", image)
}
if strings.Contains(image, "17-alpine") {
t.Fatalf("the resource still names the upstream tag: %q", image)
}
}
func TestAModuleIsBuiltFromItsPathWithinTheRepository(t *testing.T) {
r := &recorded{contents: map[string]string{
"README.md": "this repository holds several modules",
"modules/shell/" + ManifestName: withBoth,
"modules/shell/Dockerfile": "FROM scratch",
"modules/shell/files/theme.conf": "dark",
// A second module beside it, so what is built is chosen by the path rather than by
// happening to be the only manifest in the clone.
"modules/other/" + ManifestName: `{"module":"other","version":"1"}`,
}}
got, err := Build(context.Background(), r.run, r,
"https://forge.invalid/catalogue.git", "modules/shell", "", t.TempDir(), nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if got.Manifest.Module != "meshboard" {
t.Fatalf("built %q, which is not the module at the path asked for", got.Manifest.Module)
}
if got.Manifest.Resources[0]["image"] == nil {
t.Fatalf("the image was not pinned: %v", got.Manifest.Resources[0])
}
}
func TestAPathThatLeavesTheRepositoryIsRefused(t *testing.T) {
for _, escaping := range []string{"../../etc", "/etc"} {
r := &recorded{contents: map[string]string{ManifestName: withBoth}}
_, err := Build(context.Background(), r.run, r,
"https://forge.invalid/x.git", escaping, "", t.TempDir(), nil, Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatalf("%q was accepted as a module's path", escaping)
}
if !strings.Contains(err.Error(), "leaves the repository") &&
!strings.Contains(err.Error(), "absolute path") {
t.Fatalf("the refusal of %q does not say why: %v", escaping, err)
}
}
}
func TestAnArtifactWithItsOwnContextIsBuiltFromThere(t *testing.T) {
const withContext = `{"module":"route-proxy","version":"1",
"build":{"artifacts":[
{"name":"server","kind":"image","from":"Dockerfile",
"context":{"repository":"https://forge.invalid/source.git","ref":"main"}}]}}`
r := &recorded{
contents: map[string]string{
ManifestName: withContext,
// The recipe lives with the packaging, not the source — read from here regardless of
// where the build context comes from. FROM scratch declares no base, so what is under
// test — where the context comes from — is not entangled with ADR 0097's own checks.
"Dockerfile": "FROM scratch\nCOPY go.mod ./\n",
},
secondary: map[string]map[string]string{
// go.mod exists only in the second repository. A build context taken from the wrong
// place would never find it, which a real docker build would refuse on — the fake
// does not read files, so what is checked below is that the build was even pointed
// at the right place, not that COPY would have succeeded.
"https://forge.invalid/source.git": {"go.mod": "module route-proxy\n"},
},
}
_, err := Build(context.Background(), r.run, r,
"https://forge.invalid/catalogue.git", "", "", t.TempDir(), nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
var clonedSource bool
for _, line := range r.ran {
if strings.HasPrefix(line, "git clone") && strings.Contains(line, "https://forge.invalid/source.git") {
clonedSource = true
}
}
if !clonedSource {
t.Fatalf("the artifact's own context was never cloned: %v", r.ran)
}
buildIndex := -1
for i, line := range r.ran {
if strings.HasPrefix(line, "docker build ") {
buildIndex = i
}
}
if buildIndex == -1 {
t.Fatal("no docker build was run")
}
build := r.ran[buildIndex]
buildDir := r.dirs[buildIndex]
if !strings.Contains(buildDir, "context-server") {
t.Errorf("docker build ran from %q, not the artifact's own cloned context", buildDir)
}
recipe := strings.SplitN(strings.SplitN(build, "-f ", 2)[1], " ", 2)[0]
if !filepath.IsAbs(recipe) {
t.Errorf("the recipe %q is not an absolute path, so it is read relative to whatever "+
"directory the build context moved to rather than where it actually is", recipe)
}
if !strings.HasSuffix(recipe, string(filepath.Separator)+"Dockerfile") {
t.Errorf("the recipe is not the module's own Dockerfile: %q", recipe)
}
if !strings.HasSuffix(build, " .") {
t.Errorf("the build was not given a context: %s", build)
}
}
func TestABuildOffersTheForgesCredentialThroughGitsOwnStore(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/theme.conf": "dark",
})
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "",
workspace, nil, Npmrc{},
GitCredential{URL: "http://mesh_novox_builder:sw0rdfi5h@forge.invalid:20000"}, nil)
if err != nil {
t.Fatal(err)
}
clone := r.ran[0]
stored := storeNamedIn(t, clone)
for _, line := range r.ran {
if strings.Contains(line, "sw0rdfi5h") {
t.Fatalf("the secret is in a command line, readable by anything that can list processes: %s", line)
}
}
// Outside the workspace a check's container mounts, and gone once the build is (novox/hq issue 462); what
// it held while git read it is checked with the check's clones (TestACheckContainerSeesNoForgeCredential).
if withinDir(workspace, stored) {
t.Fatalf("the credential store %s is inside the workspace %s", stored, workspace)
}
if _, err := os.Stat(stored); !os.IsNotExist(err) {
t.Fatalf("the credential store outlives the build: %v", err)
}
if _, err := os.Stat(filepath.Join(workspace, "git-credentials")); !os.IsNotExist(err) {
t.Fatal("a credential file is left in the workspace")
}
}
func TestABuildWithNoCredentialClonesExactlyAsBefore(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/theme.conf": "dark",
})
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "",
workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(r.ran[0], "git clone --quiet ") {
t.Fatalf("a credential-less clone grew flags: %s", r.ran[0])
}
if _, err := os.Stat(filepath.Join(workspace, "git-credentials")); !os.IsNotExist(err) {
t.Fatal("a credential file was written with no credential to put in it")
}
}
func TestAContextCloneCarriesTheSameCredentialStore(t *testing.T) {
const withContext = `{"module":"route-proxy","version":"1",
"build":{"artifacts":[
{"name":"server","kind":"image","from":"Dockerfile",
"context":{"repository":"https://forge.invalid/source.git","ref":"main"}}]}}`
r := &recorded{
contents: map[string]string{
ManifestName: withContext,
"Dockerfile": "FROM scratch\nCOPY go.mod ./\n",
},
secondary: map[string]map[string]string{
"https://forge.invalid/source.git": {"go.mod": "module route-proxy\n"},
},
}
workspace := t.TempDir()
_, err := Build(context.Background(), r.run, r,
"https://forge.invalid/catalogue.git", "", "", workspace, nil, Npmrc{},
GitCredential{URL: "https://builder:s3cret@forge.invalid"}, nil)
if err != nil {
t.Fatal(err)
}
stored := storeNamedIn(t, r.ran[0])
var contextClone string
for _, line := range r.ran {
if strings.Contains(line, "clone") && strings.Contains(line, "source.git") {
contextClone = line
}
}
if contextClone == "" {
t.Fatalf("the context was never cloned: %v", r.ran)
}
if !strings.Contains(contextClone, "credential.helper=store --file="+stored) {
t.Fatalf("the context clone does not name the credential store: %s", contextClone)
}
}
-342
View File
@@ -130,168 +130,6 @@ const withBoth = `{"module":"meshboard","version":"1",
{"id":"svc","type":"container","name":"meshboard","artifact":"server"},
{"id":"theme","type":"archive","path":"/opt/meshboard","artifact":"look"}]}`
func TestABuildProducesAManifestThePinsAreIn(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/theme.conf": "dark",
})
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if got.Commit != "c0ffeec0ffeec0ffeec0ffeec0ffeec0ffeec0ff" {
t.Fatalf("the commit was not recorded: %q", got.Commit)
}
if got.Manifest.Resources[0]["image"] == nil {
t.Fatalf("the image was not pinned: %v", got.Manifest.Resources[0])
}
digest, _ := got.Manifest.Resources[1]["digest"].(string)
if !strings.HasPrefix(digest, "sha256:") {
t.Fatalf("the archive was not pinned: %v", got.Manifest.Resources[1])
}
}
func TestTwoBuildsOfOneCommitProduceOneDigest(t *testing.T) {
// Or nothing downstream can tell "this changed" from "this was built again", and every
// rebuild looks like a change to every machine holding it.
var digests []string
for i := 0; i < 2; i++ {
r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/a.conf": "one", "files/b.conf": "two",
})
// A year apart, so a packer carrying timestamps cannot accidentally agree.
r.stamped = time.Date(2020+i, time.March, 3, 4, 5, 6, 0, time.UTC)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
for _, b := range got.Built {
if b.Kind == catalogue.ArtifactArchive {
digests = append(digests, b.Digest)
}
}
}
if digests[0] != digests[1] {
t.Fatalf("two builds of one commit produced %s and %s", digests[0], digests[1])
}
}
func TestNothingIsPublishedUntilEverythingIsBuilt(t *testing.T) {
// Half a module in the store under a digest the mesh never records is reachable,
// unreferenced, and indistinguishable from something in use.
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch"})
// `files` is missing, so packing the archive fails — after the image would have been pushed.
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatal("a build with a missing input succeeded")
}
if len(r.archives) != 0 {
t.Fatalf("an archive was published by a failed build: %v", r.archives)
}
}
func TestARepositoryWithNoManifestSaysSo(t *testing.T) {
workspace := t.TempDir()
r := &recorded{contents: map[string]string{"README.md": "nothing to see"}}
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatal("a repository with nothing saying what it is was built")
}
if !strings.Contains(err.Error(), ManifestName) {
t.Fatalf("the failure does not name what is missing: %v", err)
}
}
func TestAModuleThatBuildsNothingStillProducesAManifest(t *testing.T) {
// Most of what a person installs is configuration.
r, workspace := aRepository(t, `{"module":"shell","version":"1","resources":[
{"id":"rc","type":"file","path":"/etc/zsh/zshrc","content":"setopt"}]}`, nil)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/shell.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if len(got.Built) != 0 {
t.Fatalf("something was built: %v", got.Built)
}
if got.Manifest.Module != "shell" || len(got.Manifest.Resources) != 1 {
t.Fatalf("got %+v", got.Manifest)
}
for _, line := range r.ran {
if strings.HasPrefix(line, "docker") {
t.Fatalf("docker was run for a module that builds nothing: %q", line)
}
}
}
func TestTheTreeIsFreshEveryTime(t *testing.T) {
// A build that reuses a working tree can succeed because of something a previous build left
// behind, and that is a build nobody can reproduce.
r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/a": "b",
})
leftover := filepath.Join(workspace, "source", "files", "from-last-time")
if err := os.MkdirAll(filepath.Dir(leftover), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(leftover, []byte("stale"), 0o644); err != nil {
t.Fatal(err)
}
if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(leftover); err == nil {
t.Fatal("a previous build's file survived into this one")
}
}
func TestABuildThatCannotPushFails(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/a": "b",
})
r.failPush = true
if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil); err == nil {
t.Fatal("a build that could publish nothing reported success")
}
}
func TestAnUpstreamImageIsMirroredRatherThanBuilt(t *testing.T) {
// A module usually runs software it did not write. Naming the upstream reference directly
// would need every machine to reach a public registry, and would pin to a tag somebody else
// can move.
const mirrors = `{"module":"postgres","version":"1",
"build":{"artifacts":[{"name":"store","kind":"upstream","from":"postgres:17-alpine"}]},
"resources":[{"id":"db","type":"container","name":"mesh-postgres","artifact":"store"}]}`
r, workspace := aRepository(t, mirrors, nil)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/postgres.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
// Pulled, not built.
var pulled, built bool
for _, line := range r.ran {
if strings.HasPrefix(line, "docker pull postgres:17-alpine") {
pulled = true
}
if strings.HasPrefix(line, "docker build") {
built = true
}
}
if !pulled {
t.Fatalf("the upstream image was not fetched: %v", r.ran)
}
if built {
t.Fatalf("something was built for an image that is mirrored: %v", r.ran)
}
// And the resource names what this registry serves, pinned by the digest it assigned.
image, _ := got.Manifest.Resources[0]["image"].(string)
if !strings.Contains(image, "@sha256:") {
t.Fatalf("the mirrored image is not pinned by digest: %q", image)
}
if strings.Contains(image, "17-alpine") {
t.Fatalf("the resource still names the upstream tag: %q", image)
}
}
func TestAnUpstreamImageWithNoTagIsRefused(t *testing.T) {
// What gets mirrored would be whatever `latest` means today, and a module pinned to that is
// not pinned.
@@ -317,148 +155,19 @@ func TestAnUpstreamReferenceIsNotAPathInTheRepository(t *testing.T) {
// **A module is a repository and a path within it** (novox/hq ADR 0069). The catalogue holds its
// modules one to a directory and the system this replaces has always built one that way, so a
// builder that could only read a repository's root could build none of what exists.
func TestAModuleIsBuiltFromItsPathWithinTheRepository(t *testing.T) {
r := &recorded{contents: map[string]string{
"README.md": "this repository holds several modules",
"modules/shell/" + ManifestName: withBoth,
"modules/shell/Dockerfile": "FROM scratch",
"modules/shell/files/theme.conf": "dark",
// A second module beside it, so what is built is chosen by the path rather than by
// happening to be the only manifest in the clone.
"modules/other/" + ManifestName: `{"module":"other","version":"1"}`,
}}
got, err := Build(context.Background(), r.run, r,
"https://forge.invalid/catalogue.git", "modules/shell", "", t.TempDir(), nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if got.Manifest.Module != "meshboard" {
t.Fatalf("built %q, which is not the module at the path asked for", got.Manifest.Module)
}
if got.Manifest.Resources[0]["image"] == nil {
t.Fatalf("the image was not pinned: %v", got.Manifest.Resources[0])
}
}
// A build reads only its own tree. A path climbing out of the clone would otherwise let a build
// read — and an archive artifact publish — whatever the build machine happens to hold, which is
// the one thing a machine that builds other people's repositories must not do.
func TestAPathThatLeavesTheRepositoryIsRefused(t *testing.T) {
for _, escaping := range []string{"../../etc", "/etc"} {
r := &recorded{contents: map[string]string{ManifestName: withBoth}}
_, err := Build(context.Background(), r.run, r,
"https://forge.invalid/x.git", escaping, "", t.TempDir(), nil, Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatalf("%q was accepted as a module's path", escaping)
}
if !strings.Contains(err.Error(), "leaves the repository") &&
!strings.Contains(err.Error(), "absolute path") {
t.Fatalf("the refusal of %q does not say why: %v", escaping, err)
}
}
}
// **Packaging and source are allowed to live apart** — a module that ships only the recipe for
// source that lives in a second repository (the reference route-proxy, packaged in the catalogue
// but built from mesh-controller's own repository) names where that source actually is, rather
// than vendoring a second copy the two could drift from.
func TestAnArtifactWithItsOwnContextIsBuiltFromThere(t *testing.T) {
const withContext = `{"module":"route-proxy","version":"1",
"build":{"artifacts":[
{"name":"server","kind":"image","from":"Dockerfile",
"context":{"repository":"https://forge.invalid/source.git","ref":"main"}}]}}`
r := &recorded{
contents: map[string]string{
ManifestName: withContext,
// The recipe lives with the packaging, not the source — read from here regardless of
// where the build context comes from. FROM scratch declares no base, so what is under
// test — where the context comes from — is not entangled with ADR 0097's own checks.
"Dockerfile": "FROM scratch\nCOPY go.mod ./\n",
},
secondary: map[string]map[string]string{
// go.mod exists only in the second repository. A build context taken from the wrong
// place would never find it, which a real docker build would refuse on — the fake
// does not read files, so what is checked below is that the build was even pointed
// at the right place, not that COPY would have succeeded.
"https://forge.invalid/source.git": {"go.mod": "module route-proxy\n"},
},
}
_, err := Build(context.Background(), r.run, r,
"https://forge.invalid/catalogue.git", "", "", t.TempDir(), nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
var clonedSource bool
for _, line := range r.ran {
if strings.HasPrefix(line, "git clone") && strings.Contains(line, "https://forge.invalid/source.git") {
clonedSource = true
}
}
if !clonedSource {
t.Fatalf("the artifact's own context was never cloned: %v", r.ran)
}
buildIndex := -1
for i, line := range r.ran {
if strings.HasPrefix(line, "docker build ") {
buildIndex = i
}
}
if buildIndex == -1 {
t.Fatal("no docker build was run")
}
build := r.ran[buildIndex]
buildDir := r.dirs[buildIndex]
if !strings.Contains(buildDir, "context-server") {
t.Errorf("docker build ran from %q, not the artifact's own cloned context", buildDir)
}
recipe := strings.SplitN(strings.SplitN(build, "-f ", 2)[1], " ", 2)[0]
if !filepath.IsAbs(recipe) {
t.Errorf("the recipe %q is not an absolute path, so it is read relative to whatever "+
"directory the build context moved to rather than where it actually is", recipe)
}
if !strings.HasSuffix(recipe, string(filepath.Separator)+"Dockerfile") {
t.Errorf("the recipe is not the module's own Dockerfile: %q", recipe)
}
if !strings.HasSuffix(build, " .") {
t.Errorf("the build was not given a context: %s", build)
}
}
// The forge credential is offered through git's own credential store — a file, never argv — and
// git decides when it applies. What is checked: the clone names the store, the secret never
// appears in a command line, and the file holds exactly the URL at 0600.
func TestABuildOffersTheForgesCredentialThroughGitsOwnStore(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/theme.conf": "dark",
})
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "",
workspace, nil, Npmrc{},
GitCredential{URL: "http://mesh_novox_builder:sw0rdfi5h@forge.invalid:20000"}, nil)
if err != nil {
t.Fatal(err)
}
clone := r.ran[0]
stored := storeNamedIn(t, clone)
for _, line := range r.ran {
if strings.Contains(line, "sw0rdfi5h") {
t.Fatalf("the secret is in a command line, readable by anything that can list processes: %s", line)
}
}
// Outside the workspace a check's container mounts, and gone once the build is (novox/hq issue 462); what
// it held while git read it is checked with the check's clones (TestACheckContainerSeesNoForgeCredential).
if withinDir(workspace, stored) {
t.Fatalf("the credential store %s is inside the workspace %s", stored, workspace)
}
if _, err := os.Stat(stored); !os.IsNotExist(err) {
t.Fatalf("the credential store outlives the build: %v", err)
}
if _, err := os.Stat(filepath.Join(workspace, "git-credentials")); !os.IsNotExist(err) {
t.Fatal("a credential file is left in the workspace")
}
}
// storeNamedIn is the credential store a git command line offers.
func storeNamedIn(t *testing.T, line string) string {
@@ -472,57 +181,6 @@ func storeNamedIn(t *testing.T, line string) string {
// Without a credential, a clone is exactly the invocation it always was, and no credential file
// appears — the builder a mesh of public repositories runs is unchanged.
func TestABuildWithNoCredentialClonesExactlyAsBefore(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/theme.conf": "dark",
})
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "",
workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(r.ran[0], "git clone --quiet ") {
t.Fatalf("a credential-less clone grew flags: %s", r.ran[0])
}
if _, err := os.Stat(filepath.Join(workspace, "git-credentials")); !os.IsNotExist(err) {
t.Fatal("a credential file was written with no credential to put in it")
}
}
// An artifact's own context is cloned with the same offer: a private module whose context is a
// second private repository on the same forge builds, and the secret still never reaches argv.
func TestAContextCloneCarriesTheSameCredentialStore(t *testing.T) {
const withContext = `{"module":"route-proxy","version":"1",
"build":{"artifacts":[
{"name":"server","kind":"image","from":"Dockerfile",
"context":{"repository":"https://forge.invalid/source.git","ref":"main"}}]}}`
r := &recorded{
contents: map[string]string{
ManifestName: withContext,
"Dockerfile": "FROM scratch\nCOPY go.mod ./\n",
},
secondary: map[string]map[string]string{
"https://forge.invalid/source.git": {"go.mod": "module route-proxy\n"},
},
}
workspace := t.TempDir()
_, err := Build(context.Background(), r.run, r,
"https://forge.invalid/catalogue.git", "", "", workspace, nil, Npmrc{},
GitCredential{URL: "https://builder:s3cret@forge.invalid"}, nil)
if err != nil {
t.Fatal(err)
}
stored := storeNamedIn(t, r.ran[0])
var contextClone string
for _, line := range r.ran {
if strings.Contains(line, "clone") && strings.Contains(line, "source.git") {
contextClone = line
}
}
if contextClone == "" {
t.Fatalf("the context was never cloned: %v", r.ran)
}
if !strings.Contains(contextClone, "credential.helper=store --file="+stored) {
t.Fatalf("the context clone does not name the credential store: %s", contextClone)
}
}
+166
View File
@@ -0,0 +1,166 @@
//go:build integration
package builder
import (
"context"
"strings"
"testing"
)
func TestABundleIsCompiledAndPackedWithNoDockerfile(t *testing.T) {
r, workspace := aRepository(t, aBundle, map[string]string{"index.ts": "console.log(1)"})
held := map[string]string{"mesh-tools/build": "registry.invalid/mesh-tools/build@sha256:" + strings.Repeat("b", 64)}
got, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/greeter.git", "", "", workspace, held, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatalf("a module with a language and no Dockerfile did not build: %v", err)
}
// Compiled in the toolchain the mesh chose, not in one the module named.
var compiled string
for _, line := range r.ran {
if strings.HasPrefix(line, "docker run") {
compiled = line
}
}
if compiled == "" {
t.Fatalf("nothing was compiled:\n%s", strings.Join(r.ran, "\n"))
}
if !strings.Contains(compiled, "mesh-tools/build@sha256:") {
t.Fatalf("the compile did not run in the mesh's own toolchain: %s", compiled)
}
if strings.Contains(strings.Join(r.ran, "\n"), "docker build") {
t.Fatalf("a bundle invoked a Dockerfile build, which is the thing it exists to avoid:\n%s",
strings.Join(r.ran, "\n"))
}
// And pinned by a digest of what came out, like any other artifact.
digest, _ := got.Manifest.Resources[0]["digest"].(string)
if !strings.HasPrefix(digest, "sha256:") {
t.Fatalf("the bundle was not pinned: %v", got.Manifest.Resources[0])
}
// **One file per entrypoint and launcher, in the toolchain's bundler** (novox/hq ADR 0193). A
// second run in the same toolchain image bundles each into the artifact's bundled output, the SDK
// inlined, refusing by name in an image that predates the bundler; and the toolchain's
// node_modules is no longer copied into a bundle that keeps nothing external.
var bundling []string
for _, line := range r.ran {
if strings.HasPrefix(line, "docker run") && strings.Contains(line, "esbuild") {
bundling = append(bundling, line)
}
}
if len(bundling) != 2 {
t.Fatalf("want one bundling run for the entrypoints and one for the launchers:\n%s", strings.Join(r.ran, "\n"))
}
for _, want := range []string{"mesh-tools/build@sha256:", "predates one-file bundles", "--bundle", "--format=esm",
"--platform=node", "--outdir=" + Out("code") + ".bundled", Out("code") + "/index.js"} {
if !strings.Contains(bundling[0], want) {
t.Errorf("the entrypoints' bundling lacks %q: %s", want, bundling[0])
}
}
if !strings.Contains(bundling[1], Out("code")+"/index.serve.mjs") || !strings.Contains(bundling[1], "--out-extension:.js=.mjs") {
t.Errorf("the launcher is not bundled under its own name: %s", bundling[1])
}
if strings.Contains(strings.Join(r.ran, "\n"), "/app/runtime") {
t.Errorf("the toolchain's node_modules was copied into a bundle that keeps nothing external:\n%s", strings.Join(r.ran, "\n"))
}
if strings.Index(strings.Join(r.ran, "\n"), "--outDir") > strings.Index(strings.Join(r.ran, "\n"), "esbuild") {
t.Fatal("the bundler ran before the compile wrote its output")
}
}
func TestABundleKeepingAPackageExternalCarriesTheToolchainsModules(t *testing.T) {
manifest := strings.Replace(aBundle, `"entrypoints":["index.js"]`, `"entrypoints":["index.js"],"external":["sharp"]`, 1)
r, workspace := aRepository(t, manifest, map[string]string{"index.ts": "console.log(1)"})
held := map[string]string{"mesh-tools/build": "registry.invalid/mesh-tools/build@sha256:" + strings.Repeat("b", 64)}
if _, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/greeter.git", "", "", workspace, held, Npmrc{}, GitCredential{}, nil); err != nil {
t.Fatal(err)
}
all := strings.Join(r.ran, "\n")
if !strings.Contains(all, "--external:sharp") || !strings.Contains(all, "/app/runtime") {
t.Errorf("an external package was not kept as an import with the toolchain's modules beside it:\n%s", all)
}
}
func TestABundleWhoseToolchainIsNotHeldIsRefusedFirst(t *testing.T) {
r, workspace := aRepository(t, aBundle, map[string]string{"index.ts": "console.log(1)"})
_, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/greeter.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatal("a bundle was built with no toolchain to compile it in")
}
if !strings.Contains(err.Error(), "mesh-tools") {
t.Fatalf("the refusal does not name what has to be built first: %v", err)
}
for _, line := range r.ran {
if strings.HasPrefix(line, "docker run") {
t.Fatalf("a compile was attempted before the refusal: %s", line)
}
}
}
func TestABundleInAnUnknownLanguageIsRefused(t *testing.T) {
manifest := strings.Replace(aBundle, `"language":"typescript"`, `"language":"cobol"`, 1)
r, workspace := aRepository(t, manifest, map[string]string{"index.ts": "x"})
_, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/greeter.git", "", "", workspace,
map[string]string{"mesh-tools/build": "registry.invalid/x@sha256:" + strings.Repeat("c", 64)}, Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatal("a language nothing can compile was accepted")
}
if !strings.Contains(err.Error(), "typescript") {
t.Fatalf("the refusal does not say what would have worked: %v", err)
}
}
func TestTwoBundlesInOneModuleArePackedSeparately(t *testing.T) {
const two = `{"module":"greeter","version":"1",
"build":{"artifacts":[
{"name":"daemon","kind":"bundle","language":"typescript","entrypoints":["index.js"]},
{"name":"tools","kind":"bundle","language":"typescript","entrypoints":["index.js"]}]},
"resources":[
{"id":"a","type":"archive","path":"/opt/greeter/daemon","artifact":"daemon"},
{"id":"b","type":"archive","path":"/opt/greeter/tools","artifact":"tools"}]}`
r, workspace := aRepository(t, two, map[string]string{"index.ts": "console.log(1)"})
held := map[string]string{"mesh-tools/build": "registry.invalid/mesh-tools/build@sha256:" + strings.Repeat("b", 64)}
got, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/greeter.git", "", "", workspace, held, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatalf("a module with two bundles did not build: %v", err)
}
// Compiled into two different places. Only the compile lines: the copy of each bundle's
// dependencies names the same directory again, deliberately.
var outputs []string
for _, line := range r.ran {
if !strings.Contains(line, "--outDir") {
continue
}
for _, part := range strings.Fields(line) {
if strings.HasPrefix(part, ".mesh-build/") {
outputs = append(outputs, part)
}
}
}
if len(outputs) != 2 || outputs[0] == outputs[1] {
t.Fatalf("two bundles did not get their own output directories: %v", outputs)
}
// And published as two artifacts, each with its own digest.
if len(r.archives) != 2 {
t.Fatalf("expected two archives published, got %v", r.archives)
}
first, _ := got.Manifest.Resources[0]["digest"].(string)
second, _ := got.Manifest.Resources[1]["digest"].(string)
if first == "" || second == "" {
t.Fatalf("a bundle was not pinned: %v", got.Manifest.Resources)
}
}
-153
View File
@@ -4,7 +4,6 @@ import (
"context"
"os"
"path/filepath"
"strings"
"testing"
)
@@ -49,85 +48,9 @@ func (c compiling) run(ctx context.Context, dir, name string, args ...string) (s
// **A module says what it is written in, and needs no Dockerfile.** This is the whole point of the
// bundle recipe: the same module previously needed a hand-written recipe repeating an incantation
// that is easy to get wrong in ways that fail somewhere else.
func TestABundleIsCompiledAndPackedWithNoDockerfile(t *testing.T) {
r, workspace := aRepository(t, aBundle, map[string]string{"index.ts": "console.log(1)"})
held := map[string]string{"mesh-tools/build": "registry.invalid/mesh-tools/build@sha256:" + strings.Repeat("b", 64)}
got, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/greeter.git", "", "", workspace, held, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatalf("a module with a language and no Dockerfile did not build: %v", err)
}
// Compiled in the toolchain the mesh chose, not in one the module named.
var compiled string
for _, line := range r.ran {
if strings.HasPrefix(line, "docker run") {
compiled = line
}
}
if compiled == "" {
t.Fatalf("nothing was compiled:\n%s", strings.Join(r.ran, "\n"))
}
if !strings.Contains(compiled, "mesh-tools/build@sha256:") {
t.Fatalf("the compile did not run in the mesh's own toolchain: %s", compiled)
}
if strings.Contains(strings.Join(r.ran, "\n"), "docker build") {
t.Fatalf("a bundle invoked a Dockerfile build, which is the thing it exists to avoid:\n%s",
strings.Join(r.ran, "\n"))
}
// And pinned by a digest of what came out, like any other artifact.
digest, _ := got.Manifest.Resources[0]["digest"].(string)
if !strings.HasPrefix(digest, "sha256:") {
t.Fatalf("the bundle was not pinned: %v", got.Manifest.Resources[0])
}
// **One file per entrypoint and launcher, in the toolchain's bundler** (novox/hq ADR 0193). A
// second run in the same toolchain image bundles each into the artifact's bundled output, the SDK
// inlined, refusing by name in an image that predates the bundler; and the toolchain's
// node_modules is no longer copied into a bundle that keeps nothing external.
var bundling []string
for _, line := range r.ran {
if strings.HasPrefix(line, "docker run") && strings.Contains(line, "esbuild") {
bundling = append(bundling, line)
}
}
if len(bundling) != 2 {
t.Fatalf("want one bundling run for the entrypoints and one for the launchers:\n%s", strings.Join(r.ran, "\n"))
}
for _, want := range []string{"mesh-tools/build@sha256:", "predates one-file bundles", "--bundle", "--format=esm",
"--platform=node", "--outdir=" + Out("code") + ".bundled", Out("code") + "/index.js"} {
if !strings.Contains(bundling[0], want) {
t.Errorf("the entrypoints' bundling lacks %q: %s", want, bundling[0])
}
}
if !strings.Contains(bundling[1], Out("code")+"/index.serve.mjs") || !strings.Contains(bundling[1], "--out-extension:.js=.mjs") {
t.Errorf("the launcher is not bundled under its own name: %s", bundling[1])
}
if strings.Contains(strings.Join(r.ran, "\n"), "/app/runtime") {
t.Errorf("the toolchain's node_modules was copied into a bundle that keeps nothing external:\n%s", strings.Join(r.ran, "\n"))
}
if strings.Index(strings.Join(r.ran, "\n"), "--outDir") > strings.Index(strings.Join(r.ran, "\n"), "esbuild") {
t.Fatal("the bundler ran before the compile wrote its output")
}
}
// A bundle naming packages it keeps external is bundled with them as imports, and carries the
// toolchain's node_modules for them — the one case it still does.
func TestABundleKeepingAPackageExternalCarriesTheToolchainsModules(t *testing.T) {
manifest := strings.Replace(aBundle, `"entrypoints":["index.js"]`, `"entrypoints":["index.js"],"external":["sharp"]`, 1)
r, workspace := aRepository(t, manifest, map[string]string{"index.ts": "console.log(1)"})
held := map[string]string{"mesh-tools/build": "registry.invalid/mesh-tools/build@sha256:" + strings.Repeat("b", 64)}
if _, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/greeter.git", "", "", workspace, held, Npmrc{}, GitCredential{}, nil); err != nil {
t.Fatal(err)
}
all := strings.Join(r.ran, "\n")
if !strings.Contains(all, "--external:sharp") || !strings.Contains(all, "/app/runtime") {
t.Errorf("an external package was not kept as an import with the toolchain's modules beside it:\n%s", all)
}
}
// A language whose bundle carries its own dependencies copies nothing in: a Go binary is static.
func TestOnlyALanguageWithARuntimeDirectoryCopiesDependenciesIn(t *testing.T) {
@@ -146,39 +69,8 @@ func TestOnlyALanguageWithARuntimeDirectoryCopiesDependenciesIn(t *testing.T) {
// **Refused before anything is built, naming what to build first.** A base the mesh has not built
// is not a compile that fails on its first line — it is a question somebody can answer, and saying
// it early is the difference between a fixable message and one about a missing image.
func TestABundleWhoseToolchainIsNotHeldIsRefusedFirst(t *testing.T) {
r, workspace := aRepository(t, aBundle, map[string]string{"index.ts": "console.log(1)"})
_, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/greeter.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatal("a bundle was built with no toolchain to compile it in")
}
if !strings.Contains(err.Error(), "mesh-tools") {
t.Fatalf("the refusal does not name what has to be built first: %v", err)
}
for _, line := range r.ran {
if strings.HasPrefix(line, "docker run") {
t.Fatalf("a compile was attempted before the refusal: %s", line)
}
}
}
// A language the mesh does not build is refused the same way, and names what it can build.
func TestABundleInAnUnknownLanguageIsRefused(t *testing.T) {
manifest := strings.Replace(aBundle, `"language":"typescript"`, `"language":"cobol"`, 1)
r, workspace := aRepository(t, manifest, map[string]string{"index.ts": "x"})
_, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/greeter.git", "", "", workspace,
map[string]string{"mesh-tools/build": "registry.invalid/x@sha256:" + strings.Repeat("c", 64)}, Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatal("a language nothing can compile was accepted")
}
if !strings.Contains(err.Error(), "typescript") {
t.Fatalf("the refusal does not say what would have worked: %v", err)
}
}
// **One module, two bundles, and neither packs the other.**
//
@@ -186,48 +78,3 @@ func TestABundleInAnUnknownLanguageIsRefused(t *testing.T) {
// daemon in one language and tools in another (ADR 0040). An earlier version of this compiled
// every bundle into the toolchain's single output directory, so two of them would overwrite each
// other and then be packed together — one artifact containing both, twice.
func TestTwoBundlesInOneModuleArePackedSeparately(t *testing.T) {
const two = `{"module":"greeter","version":"1",
"build":{"artifacts":[
{"name":"daemon","kind":"bundle","language":"typescript","entrypoints":["index.js"]},
{"name":"tools","kind":"bundle","language":"typescript","entrypoints":["index.js"]}]},
"resources":[
{"id":"a","type":"archive","path":"/opt/greeter/daemon","artifact":"daemon"},
{"id":"b","type":"archive","path":"/opt/greeter/tools","artifact":"tools"}]}`
r, workspace := aRepository(t, two, map[string]string{"index.ts": "console.log(1)"})
held := map[string]string{"mesh-tools/build": "registry.invalid/mesh-tools/build@sha256:" + strings.Repeat("b", 64)}
got, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/greeter.git", "", "", workspace, held, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatalf("a module with two bundles did not build: %v", err)
}
// Compiled into two different places. Only the compile lines: the copy of each bundle's
// dependencies names the same directory again, deliberately.
var outputs []string
for _, line := range r.ran {
if !strings.Contains(line, "--outDir") {
continue
}
for _, part := range strings.Fields(line) {
if strings.HasPrefix(part, ".mesh-build/") {
outputs = append(outputs, part)
}
}
}
if len(outputs) != 2 || outputs[0] == outputs[1] {
t.Fatalf("two bundles did not get their own output directories: %v", outputs)
}
// And published as two artifacts, each with its own digest.
if len(r.archives) != 2 {
t.Fatalf("expected two archives published, got %v", r.archives)
}
first, _ := got.Manifest.Resources[0]["digest"].(string)
second, _ := got.Manifest.Resources[1]["digest"].(string)
if first == "" || second == "" {
t.Fatalf("a bundle was not pinned: %v", got.Manifest.Resources)
}
}
+2
View File
@@ -1,3 +1,5 @@
//go:build integration
package builder
import (
@@ -0,0 +1,180 @@
//go:build integration
package builder
import (
"context"
"errors"
"io/fs"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestACheckContainerSeesNoForgeCredential(t *testing.T) {
repo, head := aCheckedRepository(t, map[string]string{CheckScript: "echo checked\n"})
besideRepo, besideHead := aCheckedRepository(t, map[string]string{"README": "beside"})
// A clone source that carries userinfo, as a forge's clone URL may: git records it as given in the
// clone's .git/config, which the container reads.
besideURL := "file://beside-user:" + besideSecret + "@" + besideRepo
workspace := t.TempDir()
var stores []string
reached := false
var leaks []string
run := func(ctx context.Context, dir, name string, args ...string) (string, error) {
switch name {
case "git":
for _, a := range args {
if f, ok := strings.CutPrefix(a, "credential.helper=store --file="); ok {
stores = append(stores, f)
raw, err := os.ReadFile(f)
if err != nil || strings.TrimSpace(string(raw)) != forgeURL {
t.Errorf("git is offered a store that does not hold the credential as given: %q, %v", raw, err)
}
if info, err := os.Stat(f); err == nil && info.Mode().Perm() != 0o600 {
t.Errorf("the credential store is readable beyond its owner: %v", info.Mode())
}
}
}
if len(args) >= 2 && args[len(args)-3] == "--quiet" && hasString(args, "clone") {
source := args[len(args)-2]
if u, err := url.Parse(source); err == nil && u.User != nil {
// Git cannot reach a file:// URL with userinfo; clone it without, then record it as git
// would have: as given.
clone := append(append([]string{}, args[:len(args)-2]...), bareURL(t, source), args[len(args)-1])
if out, err := Command(ctx, dir, "git", clone...); err != nil {
return out, err
}
return Command(ctx, filepath.Join(dir, args[len(args)-1]), "git", "remote", "set-url", "origin", source)
}
}
return Command(ctx, dir, name, args...)
case "docker":
if !reached {
reached = true
// The first container: everything the workspace holds is what the toolchain container sees.
filepath.WalkDir(workspace, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() {
return nil
}
raw, _ := os.ReadFile(path)
s := string(raw)
if strings.Contains(s, forgeSecret) || strings.Contains(s, besideSecret) ||
strings.Contains(s, npmSecret) || d.Name() == "git-credentials" || d.Name() == ".npmrc" ||
strings.Contains(s, "credential.helper") {
leaks = append(leaks, path)
}
return nil
})
for _, f := range stores {
if _, err := os.Stat(f); !errors.Is(err, os.ErrNotExist) {
leaks = append(leaks, f+" (still there when the first container runs)")
}
if rel, err := filepath.Rel(workspace, f); err == nil && !strings.HasPrefix(rel, "..") {
leaks = append(leaks, f+" (inside the workspace the container mounts)")
}
}
}
if len(args) > 0 && args[0] == "ps" {
return "", nil
}
return "", errors.New("no container runtime in this test")
}
return "", errors.New("unexpected command " + name)
}
// A credential an older builder left in the workspace is removed too: the forge's, and the .npmrc a
// build wrote into the tree it cloned.
if err := os.WriteFile(filepath.Join(workspace, "git-credentials"), []byte(forgeURL+"\n"), 0o600); err != nil {
t.Fatal(err)
}
for _, dir := range []string{"source/x", "context-server"} {
if err := os.MkdirAll(filepath.Join(workspace, dir), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(workspace, dir, ".npmrc"),
[]byte("//forge.invalid/api/packages/novox/npm/:_authToken="+npmSecret+"\n"), 0o600); err != nil {
t.Fatal(err)
}
}
_, err := Check(t.Context(), run, CheckSpec{ID: "check-462", Repository: repo, Ref: head, Owner: "novox",
Repo: "mesh-controller", Number: 1, Toolchain: "golang", Beside: map[string]Beside{
"mesh-catalog": {Repository: besideURL, Ref: besideHead}}}, workspace, aFactsRegistry(t),
GitCredential{URL: forgeURL}, nil)
if err == nil {
t.Fatal("the check ran past its first container in a test with none")
}
if !reached {
t.Fatalf("the check never reached its first container: %v", err)
}
if len(stores) == 0 {
t.Fatal("no clone was offered the forge credential")
}
if len(leaks) > 0 {
t.Fatalf("the check's container sees the credential:\n%s", strings.Join(leaks, "\n"))
}
}
func TestACheckLinePublishedToTheLogIsRedacted(t *testing.T) {
var said []string
say := func(step, format string, args ...any) {
if step == "output" && len(args) > 0 {
said = append(said, args[0].(string))
}
}
var out tail
layer := ownCheck(t.Context(), CheckSpec{Toolchain: "golang"}, []ScriptPart{{Toolchain: "go", Script: CheckScript}},
t.TempDir(), &out, func() bool { return false }, func(string, string) *exec.Cmd {
return exec.CommandContext(t.Context(), "sh", "-c", "echo cloning http://mesh_builder:t0ps3cret-forge@forge.invalid/novox/x.git; "+
"echo token ghp_abcdefghijklmnopqrstuvwxyz0123456789")
}, say)
if layer == nil || layer.Verdict != "pass" {
t.Fatalf("the check answered %+v\n%s", layer, out.String())
}
joined := strings.Join(said, "\n")
if strings.Contains(joined, "t0ps3cret-forge") || strings.Contains(joined, "ghp_abcdef") {
t.Fatalf("a secret the check printed is published to the build's log:\n%s", joined)
}
if !strings.Contains(joined, "http://mesh_builder:[redacted: a password in a URI]@forge.invalid/novox/x.git") {
t.Fatalf("the line is not said with what was there named:\n%s", joined)
}
}
func TestABuildsCloneRecordsNoUserinfo(t *testing.T) {
repo, _ := aCheckedRepository(t, map[string]string{ManifestName: `{"module":"plain","version":"1"}`})
source := "file://build-user:" + besideSecret + "@" + repo
workspace := t.TempDir()
tree := filepath.Join(workspace, "source")
var configs []string
run := func(ctx context.Context, dir, name string, args ...string) (string, error) {
if name == "git" && hasString(args, "clone") && args[len(args)-2] == source {
clone := append(append([]string{}, args[:len(args)-2]...), bareURL(t, source), args[len(args)-1])
if out, err := Command(ctx, dir, "git", clone...); err != nil {
return out, err
}
return Command(ctx, args[len(args)-1], "git", "remote", "set-url", "origin", source)
}
if name == "git" && len(args) > 0 && args[0] == "rev-parse" {
raw, _ := os.ReadFile(filepath.Join(tree, ".git", "config"))
configs = append(configs, string(raw))
}
return Command(ctx, dir, name, args...)
}
if _, err := Build(t.Context(), run, &recorded{}, source, "", "", workspace, nil, Npmrc{}, GitCredential{}, nil); err != nil {
t.Fatal(err)
}
if len(configs) == 0 {
t.Fatal("the build never read its clone")
}
for _, c := range configs {
if strings.Contains(c, besideSecret) || strings.Contains(c, "build-user") {
t.Fatalf("the build's clone records the credential it was cloned with:\n%s", c)
}
}
if _, err := os.Stat(tree); !os.IsNotExist(err) {
t.Fatalf("the build's tree outlives the build: %v", err)
}
}
-169
View File
@@ -1,18 +1,12 @@
package builder
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"io/fs"
"net/http"
"net/http/httptest"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
@@ -67,135 +61,7 @@ func bareURL(t *testing.T, raw string) string {
return u.String()
}
func TestACheckContainerSeesNoForgeCredential(t *testing.T) {
repo, head := aCheckedRepository(t, map[string]string{CheckScript: "echo checked\n"})
besideRepo, besideHead := aCheckedRepository(t, map[string]string{"README": "beside"})
// A clone source that carries userinfo, as a forge's clone URL may: git records it as given in the
// clone's .git/config, which the container reads.
besideURL := "file://beside-user:" + besideSecret + "@" + besideRepo
workspace := t.TempDir()
var stores []string
reached := false
var leaks []string
run := func(ctx context.Context, dir, name string, args ...string) (string, error) {
switch name {
case "git":
for _, a := range args {
if f, ok := strings.CutPrefix(a, "credential.helper=store --file="); ok {
stores = append(stores, f)
raw, err := os.ReadFile(f)
if err != nil || strings.TrimSpace(string(raw)) != forgeURL {
t.Errorf("git is offered a store that does not hold the credential as given: %q, %v", raw, err)
}
if info, err := os.Stat(f); err == nil && info.Mode().Perm() != 0o600 {
t.Errorf("the credential store is readable beyond its owner: %v", info.Mode())
}
}
}
if len(args) >= 2 && args[len(args)-3] == "--quiet" && hasString(args, "clone") {
source := args[len(args)-2]
if u, err := url.Parse(source); err == nil && u.User != nil {
// Git cannot reach a file:// URL with userinfo; clone it without, then record it as git
// would have: as given.
clone := append(append([]string{}, args[:len(args)-2]...), bareURL(t, source), args[len(args)-1])
if out, err := Command(ctx, dir, "git", clone...); err != nil {
return out, err
}
return Command(ctx, filepath.Join(dir, args[len(args)-1]), "git", "remote", "set-url", "origin", source)
}
}
return Command(ctx, dir, name, args...)
case "docker":
if !reached {
reached = true
// The first container: everything the workspace holds is what the toolchain container sees.
filepath.WalkDir(workspace, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() {
return nil
}
raw, _ := os.ReadFile(path)
s := string(raw)
if strings.Contains(s, forgeSecret) || strings.Contains(s, besideSecret) ||
strings.Contains(s, npmSecret) || d.Name() == "git-credentials" || d.Name() == ".npmrc" ||
strings.Contains(s, "credential.helper") {
leaks = append(leaks, path)
}
return nil
})
for _, f := range stores {
if _, err := os.Stat(f); !errors.Is(err, os.ErrNotExist) {
leaks = append(leaks, f+" (still there when the first container runs)")
}
if rel, err := filepath.Rel(workspace, f); err == nil && !strings.HasPrefix(rel, "..") {
leaks = append(leaks, f+" (inside the workspace the container mounts)")
}
}
}
if len(args) > 0 && args[0] == "ps" {
return "", nil
}
return "", errors.New("no container runtime in this test")
}
return "", errors.New("unexpected command " + name)
}
// A credential an older builder left in the workspace is removed too: the forge's, and the .npmrc a
// build wrote into the tree it cloned.
if err := os.WriteFile(filepath.Join(workspace, "git-credentials"), []byte(forgeURL+"\n"), 0o600); err != nil {
t.Fatal(err)
}
for _, dir := range []string{"source/x", "context-server"} {
if err := os.MkdirAll(filepath.Join(workspace, dir), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(workspace, dir, ".npmrc"),
[]byte("//forge.invalid/api/packages/novox/npm/:_authToken="+npmSecret+"\n"), 0o600); err != nil {
t.Fatal(err)
}
}
_, err := Check(t.Context(), run, CheckSpec{ID: "check-462", Repository: repo, Ref: head, Owner: "novox",
Repo: "mesh-controller", Number: 1, Toolchain: "golang", Beside: map[string]Beside{
"mesh-catalog": {Repository: besideURL, Ref: besideHead}}}, workspace, aFactsRegistry(t),
GitCredential{URL: forgeURL}, nil)
if err == nil {
t.Fatal("the check ran past its first container in a test with none")
}
if !reached {
t.Fatalf("the check never reached its first container: %v", err)
}
if len(stores) == 0 {
t.Fatal("no clone was offered the forge credential")
}
if len(leaks) > 0 {
t.Fatalf("the check's container sees the credential:\n%s", strings.Join(leaks, "\n"))
}
}
// Every line a repository's own check prints is published to the build's log redacted.
func TestACheckLinePublishedToTheLogIsRedacted(t *testing.T) {
var said []string
say := func(step, format string, args ...any) {
if step == "output" && len(args) > 0 {
said = append(said, args[0].(string))
}
}
var out tail
layer := ownCheck(t.Context(), CheckSpec{Toolchain: "golang"}, []ScriptPart{{Toolchain: "go", Script: CheckScript}},
t.TempDir(), &out, func() bool { return false }, func(string, string) *exec.Cmd {
return exec.CommandContext(t.Context(), "sh", "-c", "echo cloning http://mesh_builder:t0ps3cret-forge@forge.invalid/novox/x.git; "+
"echo token ghp_abcdefghijklmnopqrstuvwxyz0123456789")
}, say)
if layer == nil || layer.Verdict != "pass" {
t.Fatalf("the check answered %+v\n%s", layer, out.String())
}
joined := strings.Join(said, "\n")
if strings.Contains(joined, "t0ps3cret-forge") || strings.Contains(joined, "ghp_abcdef") {
t.Fatalf("a secret the check printed is published to the build's log:\n%s", joined)
}
if !strings.Contains(joined, "http://mesh_builder:[redacted: a password in a URI]@forge.invalid/novox/x.git") {
t.Fatalf("the line is not said with what was there named:\n%s", joined)
}
}
func TestTheRedactorHidesTheForgeCredentialAndShapes(t *testing.T) {
r := redactorFor(GitCredential{URL: forgeURL})
@@ -214,38 +80,3 @@ func TestTheRedactorHidesTheForgeCredentialAndShapes(t *testing.T) {
// A build's clone of a URL carrying userinfo records it without, so no build leaves a credential in
// workspace/source/.git/config while it runs either (novox/hq issue 462); the tree itself is gone when the
// build ends.
func TestABuildsCloneRecordsNoUserinfo(t *testing.T) {
repo, _ := aCheckedRepository(t, map[string]string{ManifestName: `{"module":"plain","version":"1"}`})
source := "file://build-user:" + besideSecret + "@" + repo
workspace := t.TempDir()
tree := filepath.Join(workspace, "source")
var configs []string
run := func(ctx context.Context, dir, name string, args ...string) (string, error) {
if name == "git" && hasString(args, "clone") && args[len(args)-2] == source {
clone := append(append([]string{}, args[:len(args)-2]...), bareURL(t, source), args[len(args)-1])
if out, err := Command(ctx, dir, "git", clone...); err != nil {
return out, err
}
return Command(ctx, args[len(args)-1], "git", "remote", "set-url", "origin", source)
}
if name == "git" && len(args) > 0 && args[0] == "rev-parse" {
raw, _ := os.ReadFile(filepath.Join(tree, ".git", "config"))
configs = append(configs, string(raw))
}
return Command(ctx, dir, name, args...)
}
if _, err := Build(t.Context(), run, &recorded{}, source, "", "", workspace, nil, Npmrc{}, GitCredential{}, nil); err != nil {
t.Fatal(err)
}
if len(configs) == 0 {
t.Fatal("the build never read its clone")
}
for _, c := range configs {
if strings.Contains(c, besideSecret) || strings.Contains(c, "build-user") {
t.Fatalf("the build's clone records the credential it was cloned with:\n%s", c)
}
}
if _, err := os.Stat(tree); !os.IsNotExist(err) {
t.Fatalf("the build's tree outlives the build: %v", err)
}
}
+399
View File
@@ -0,0 +1,399 @@
//go:build integration
package builder
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
)
func TestTheThrowawayStoreFlushesNothing(t *testing.T) {
said := strings.Join(StoreSettings, " ")
for _, want := range []string{"-c fsync=off", "-c synchronous_commit=off", "-c full_page_writes=off"} {
if !strings.Contains(said, want) {
t.Errorf("the store is raised with %q, not %q", said, want)
}
}
if os.Getenv("MESH_TEST_DOCKER") != "1" {
t.Skip("MESH_TEST_DOCKER=1: the settings are proven on a raised store")
}
id := fmt.Sprintf("check-store-%d", time.Now().UnixNano())
labelledRun := Labelled(Command, id)
t.Cleanup(func() { _, _ = RemoveContainersOf(context.Background(), Command, id) })
if _, err := throwaway(t.Context(), labelledRun, Command, id+"-store", StoreImage("17.11"), 5432,
[]string{"-e", "POSTGRES_PASSWORD=check"}, StoreSettings); err != nil {
t.Fatal(err)
}
if err := waitFor(t.Context(), Command, id+"-store", []string{"pg_isready", "-U", "postgres"}); err != nil {
t.Fatal(err)
}
for setting, want := range map[string]string{"fsync": "off", "synchronous_commit": "off", "full_page_writes": "off"} {
var got string
for try := 0; try < 10; try++ {
out, err := exec.Command("docker", "exec", id+"-store", "psql", "-U", "postgres", "-tAc", "SHOW "+setting).CombinedOutput()
if got = strings.TrimSpace(string(out)); err == nil {
break
}
time.Sleep(500 * time.Millisecond)
}
if got != want {
t.Errorf("%s is %q on the raised store", setting, got)
}
}
}
func aCheckedRepository(t *testing.T, files map[string]string) (string, string) {
t.Helper()
dir := t.TempDir()
git := func(args ...string) string {
cmd := exec.Command("git", args...)
cmd.Dir = dir
cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.org",
"GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.org")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
return strings.TrimSpace(string(out))
}
git("init", "--quiet", "-b", "main")
for name, body := range files {
if err := os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o755); err != nil {
t.Fatal(err)
}
}
git("add", "-A")
git("commit", "--quiet", "-m", "x")
return dir, git("rev-parse", "HEAD")
}
func labelled(id string) []string {
out, _ := exec.Command("docker", "ps", "-aq", "--filter", "label="+BuildLabel+"="+id).Output()
return strings.Fields(string(out))
}
func TestACheckRunsTheRepositorysOwnScriptBesideTheMeshsVersionsAndLeavesNothing(t *testing.T) {
registry := checkEnvironment(t)
// The script proves what it was given: the facts, a store that answers, a bus that answers — and no
// container runtime socket.
script := `set -e
test -s "$MESH_FACTS"
grep -q '"bus": "2.11.17"' "$MESH_FACTS" || grep -q '"bus":"2.11.17"' "$MESH_FACTS"
case "$MESH_TEST_POSTGRES" in postgres://*127.0.0.1:*) ;; *) echo "no store: $MESH_TEST_POSTGRES"; exit 1;; esac
case "$MESH_TEST_NATS" in nats://127.0.0.1:*) ;; *) echo "no bus: $MESH_TEST_NATS"; exit 1;; esac
test "$MESH_CHECK_REPOSITORY" = "novox/hq"
test "$MESH_CHECK_CHANGED" = "a.go,b.go"
test ! -S /var/run/docker.sock || { echo "the check holds the container runtime's socket"; exit 1; }
echo checked
`
repo, head := aCheckedRepository(t, map[string]string{CheckScript: script})
id := fmt.Sprintf("check-test-%d", time.Now().UnixNano())
v, err := Check(t.Context(), Command, CheckSpec{ID: id, Repository: repo, Ref: head, Owner: "novox",
Repo: "hq", Number: 7, Paths: []string{"a.go", "b.go"}, Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
// Nothing of the graph touched: the gate a pass that says so, the repository's own check run.
if v.Gate == nil || v.Gate.Verdict != "pass" || v.Gate.Summary != noModule {
t.Errorf("the gate answered %+v", v.Gate)
}
if v.Repo == nil || v.Repo.Verdict != "pass" || !strings.Contains(v.Report, "checked") {
t.Fatalf("the repository's check answered %+v\n%s", v.Repo, v.Report)
}
if left := labelled(id); len(left) > 0 {
t.Errorf("the check left %d container(s) behind", len(left))
}
}
func TestAFailingCheckFailsAndOneThatCannotRunIsNeverAPass(t *testing.T) {
registry := checkEnvironment(t)
repo, head := aCheckedRepository(t, map[string]string{CheckScript: "echo 'resource \"x.service\": refused'; exit 3\n"})
v, err := Check(t.Context(), Command, CheckSpec{ID: fmt.Sprintf("check-fail-%d", time.Now().UnixNano()),
Repository: repo, Ref: head, Owner: "novox", Repo: "hq", Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if v.Repo == nil || v.Repo.Verdict != "fail" || !strings.Contains(v.Repo.Summary, "refused") {
t.Fatalf("a failing script answered %+v", v.Repo)
}
// A script in a toolchain the mesh does not hold: an error, never a pass.
repo, head = aCheckedRepository(t, map[string]string{CheckScript: "# mesh-check-toolchain: cobol\nexit 0\n"})
v, err = Check(t.Context(), Command, CheckSpec{ID: fmt.Sprintf("check-cobol-%d", time.Now().UnixNano()),
Repository: repo, Ref: head, Owner: "novox", Repo: "hq", Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil || v.Repo == nil || v.Repo.Verdict != "error" {
t.Fatalf("a script in a toolchain nobody holds answered %+v, %v", v.Repo, err)
}
// Past its bound: an error, not a pass.
was := CheckTimeout
CheckTimeout = 25 * time.Second
t.Cleanup(func() { CheckTimeout = was })
repo, head = aCheckedRepository(t, map[string]string{CheckScript: "sleep 120\n"})
v, err = Check(context.Background(), Command, CheckSpec{ID: fmt.Sprintf("check-slow-%d", time.Now().UnixNano()),
Repository: repo, Ref: head, Owner: "novox", Repo: "hq", Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err == nil && (v.Repo == nil || v.Repo.Verdict != "error") {
t.Fatalf("a check past its bound answered %+v", v.Repo)
}
// No facts: it cannot run, and says so.
repo, head = aCheckedRepository(t, map[string]string{CheckScript: "exit 0\n"})
_, err = Check(t.Context(), Command, CheckSpec{ID: "check-nofacts", Repository: repo, Ref: head, Owner: "novox",
Repo: "hq", Toolchain: goToolchain}, t.TempDir(), "127.0.0.1:1", GitCredential{}, nil)
if err == nil || !strings.Contains(err.Error(), "facts snapshot") {
t.Fatalf("a check with no facts said %v", err)
}
}
func TestACheckWithNothingToRunSaysSo(t *testing.T) {
repo, head := aCheckedRepository(t, map[string]string{"README.md": "x"})
v, err := Check(t.Context(), Command, CheckSpec{ID: "check-nothing", Repository: repo, Ref: head, Owner: "novox",
Repo: "hq"}, t.TempDir(), "", GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if v.Gate == nil || v.Gate.Verdict != "pass" || v.Repo == nil || v.Repo.Verdict != "warning" ||
!strings.Contains(v.Repo.Summary, CheckScript) {
t.Fatalf("a check with nothing to run said %+v / %+v", v.Gate, v.Repo)
}
// A gated change never takes that path: with no store to read the facts from, it cannot run.
_, err = Check(t.Context(), Command, CheckSpec{ID: "check-gated", Repository: repo, Ref: head, Owner: "novox",
Repo: "mesh-catalog", Modules: []string{"gitea"}}, t.TempDir(), "", GitCredential{}, nil)
if err == nil {
t.Fatal("a change touching a module ran nothing and was not refused")
}
}
func TestTheGateRunsWhenTheGraphIsTouchedBesideTheRepositorysOwnCheck(t *testing.T) {
registry := checkEnvironment(t)
judgeRepo, judgeHead := aCheckedRepository(t, aJudge)
beside := map[string]Beside{"mesh-controller": {Repository: judgeRepo, Ref: judgeHead}}
check := func(files map[string]string, judge string) CheckVerdict {
t.Helper()
repo, head := aCheckedRepository(t, files)
id := fmt.Sprintf("check-gate-%d", time.Now().UnixNano())
v, err := Check(t.Context(), Command, CheckSpec{ID: id, Repository: repo, Ref: head, Owner: "novox",
Repo: "mesh-catalog", Number: 9, Paths: []string{"modules/gitea/index.ts"}, Beside: beside,
Modules: []string{"gitea"}, Manifests: []string{"modules/gitea/module.json"}, Judge: judge,
Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if left := labelled(id); len(left) > 0 {
t.Errorf("the check left %d container(s) behind", len(left))
}
return v
}
v := check(map[string]string{"modules/gitea/module.json": `{"module":"gitea"}`, CheckScript: "echo own; exit 0\n"}, "")
if v.Gate == nil || v.Gate.Verdict != "warning" || v.Gate.Summary != "a merge rebuilds 14 module(s)" ||
strings.Join(v.Gate.Modules, ",") != "gitea" || v.Verdict != "warning" {
t.Fatalf("the gate answered %+v\n%s", v.Gate, v.Report)
}
if v.Repo == nil || v.Repo.Verdict != "pass" {
t.Fatalf("its own check answered %+v", v.Repo)
}
v = check(map[string]string{"modules/gitea/module.json": `{"module":"gitea","broken":true}`}, "")
if v.Gate.Verdict != "fail" || !strings.Contains(v.Gate.Summary, "module check") || v.Repo.Verdict != "warning" {
t.Fatalf("a manifest the judge refuses answered %+v / %+v\n%s", v.Gate, v.Repo, v.Report)
}
// A fault the base branch already had is said and fails nothing; one the change brings fails.
repo, _ := aCheckedRepository(t, map[string]string{"modules/gitea/module.json": `{"module":"gitea","broken":true}`})
git := func(args ...string) string {
cmd := exec.Command("git", args...)
cmd.Dir = repo
cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.org",
"GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.org")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
return strings.TrimSpace(string(out))
}
git("checkout", "--quiet", "-b", "change")
if err := os.WriteFile(filepath.Join(repo, "modules/gitea/index.ts"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
git("add", "-A")
git("commit", "--quiet", "-m", "y")
id := fmt.Sprintf("check-base-%d", time.Now().UnixNano())
v, err := Check(t.Context(), Command, CheckSpec{ID: id, Repository: repo, Ref: git("rev-parse", "HEAD"), Owner: "novox",
Repo: "mesh-catalog", Base: "main", Paths: []string{"modules/gitea/index.ts"}, Beside: beside,
Modules: []string{"gitea"}, Manifests: []string{"modules/gitea/module.json"}, Toolchain: goToolchain},
t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if v.Gate.Verdict != "warning" || !strings.Contains(v.Report, "already") {
t.Fatalf("a fault the base already had failed the change: %+v\n%s", v.Gate, v.Report)
}
// A delivery group (novox/hq ADR 0239): the other heads cloned beside it at their heads and handed to the
// gate to compose with this one; the repository's own check is each pull request's, not the group's.
app, appHead := aCheckedRepository(t, map[string]string{"module.json": `{"module":"app"}`})
repo2, head2 := aCheckedRepository(t, map[string]string{"modules/gitea/module.json": `{"module":"gitea"}`,
CheckScript: "exit 1\n"})
gid := fmt.Sprintf("check-group-%d", time.Now().UnixNano())
v, err = Check(t.Context(), Command, CheckSpec{ID: gid, Repository: repo2, Ref: head2, Owner: "novox",
Repo: "mesh-catalog", Paths: []string{"modules/gitea/module.json"}, Beside: beside, Modules: []string{"gitea"},
Manifests: []string{"modules/gitea/module.json"}, Toolchain: goToolchain,
Group: []GroupHead{{Owner: "novox", Repo: "app", Repository: app, Ref: appHead, Paths: []string{"module.json"}}}},
t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if v.Gate.Verdict != "pass" || v.Gate.Summary != "composed with novox/app" {
t.Fatalf("the group's other head was not composed: %+v\n%s", v.Gate, v.Report)
}
if v.Repo == nil || v.Repo.Verdict != "pass" || !strings.Contains(v.Repo.Summary, "group") {
t.Fatalf("a group's check ran a member's own script: %+v", v.Repo)
}
// A change to the controller judges itself: one that does not build fails its own gate.
v = check(map[string]string{"go.mod": "module x\n\ngo 1.22\n", "cmd/mesh-controller/main.go": "package main\nfunc main() { nope }\n",
"modules/gitea/module.json": "{}"}, "self")
if v.Gate.Verdict != "fail" || !strings.Contains(v.Gate.Summary, "does not build") {
t.Fatalf("a controller that does not build judged itself %+v", v.Gate)
}
}
func TestABuildSaysWhetherItsCommitIsOnTheTrunk(t *testing.T) {
repo, onMain := aCheckedRepository(t, map[string]string{"module.json": `{"module":"x","version":"1"}`})
git := func(dir string, args ...string) string {
cmd := exec.Command("git", args...)
cmd.Dir = dir
cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.org",
"GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.org")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
return strings.TrimSpace(string(out))
}
git(repo, "checkout", "--quiet", "-b", "feature")
if err := os.WriteFile(filepath.Join(repo, "x"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
git(repo, "add", "-A")
git(repo, "commit", "--quiet", "-m", "off the trunk")
offMain := git(repo, "rev-parse", "HEAD")
git(repo, "checkout", "--quiet", "main")
clone := filepath.Join(t.TempDir(), "clone")
git(filepath.Dir(clone), "clone", "--quiet", repo, clone)
if trunk, on := trunkOf(t.Context(), Command, clone, onMain); trunk != "main" || !on {
t.Errorf("a commit on main reads as on %q: %v", trunk, on)
}
if trunk, on := trunkOf(t.Context(), Command, clone, offMain); trunk != "main" || on {
t.Errorf("a feature branch's commit reads as on %q: %v", trunk, on)
}
if got := strings.Join(branchesHolding(t.Context(), Command, clone, offMain), ","); got != "feature" {
t.Errorf("the feature branch's commit is said to be on %q", got)
}
if got := strings.Join(branchesHolding(t.Context(), Command, clone, onMain), ","); got != "feature,main" {
t.Errorf("main's commit is said to be on %q", got)
}
// A tree that says no trunk is not known — never read as on it.
if trunk, on := trunkOf(t.Context(), Command, repo, onMain); trunk != "" || on {
t.Errorf("a repository with no origin reads as trunk %q, on %v", trunk, on)
}
}
func TestARedeliveredCheckRemovesWhatItsEarlierDeliveryLeft(t *testing.T) {
registry := checkEnvironment(t)
repo, head := aCheckedRepository(t, map[string]string{CheckScript: "echo checked\n"})
id := fmt.Sprintf("check-again-%d", time.Now().UnixNano())
if out, err := exec.Command("docker", "run", "-d", "--rm", "--label", BuildLabel+"="+id, "--name", id+"-store",
"postgres:17-alpine", "sleep", "300").CombinedOutput(); err != nil {
t.Skipf("no container for the earlier delivery: %v %s", err, out)
}
v, err := Check(t.Context(), Command, CheckSpec{ID: id, Repository: repo, Ref: head, Owner: "novox",
Repo: "hq", Number: 7, Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatalf("a redelivered check could not run: %v", err)
}
if v.Repo == nil || v.Repo.Verdict != "pass" {
t.Errorf("the repository's check answered %+v", v.Repo)
}
if left := labelled(id); len(left) > 0 {
t.Errorf("the check left %d container(s) behind", len(left))
}
}
func TestARepositoryInTwoLanguagesIsCheckedInBoth(t *testing.T) {
script := "#!/bin/sh\n# mesh-check-toolchain: typescript\n# mesh-check-also: go replays/merge-check.sh\nnpm test\n"
parts := ScriptParts([]byte(script))
if len(parts) != 2 || parts[0] != (ScriptPart{"typescript", CheckScript}) ||
parts[1] != (ScriptPart{"go", "replays/merge-check.sh"}) {
t.Fatalf("the parts read as %+v", parts)
}
if got := ScriptParts([]byte("#!/bin/sh\nset -eu\n")); len(got) != 1 || got[0] != (ScriptPart{"go", CheckScript}) {
t.Fatalf("a script declaring nothing reads as %+v", got)
}
// Each part run where its toolchain is: here, the image the part was given is what the command says.
held := CheckSpec{Toolchain: "go-image", Toolchains: map[string]string{"typescript": "ts-image", "go": "go-image"}}
run := func(t *testing.T, spec CheckSpec, files map[string]string, parts []ScriptPart) (*Layer, []string) {
t.Helper()
tree := t.TempDir()
for name, body := range files {
if err := os.MkdirAll(filepath.Dir(filepath.Join(tree, name)), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(tree, name), []byte(body), 0o755); err != nil {
t.Fatal(err)
}
}
var images []string
var out tail
layer := ownCheck(t.Context(), spec, parts, tree, &out, func() bool { return false },
func(image, script string) *exec.Cmd {
images = append(images, image+" "+script)
cmd := exec.CommandContext(t.Context(), "sh", script)
cmd.Dir = tree
return cmd
}, func(string, string, ...any) {})
return layer, images
}
twoParts := []ScriptPart{{"typescript", CheckScript}, {"go", "replays/merge-check.sh"}}
layer, images := run(t, held, map[string]string{CheckScript: "exit 0\n", "replays/merge-check.sh": "exit 0\n"}, twoParts)
if layer.Verdict != "pass" || !strings.Contains(layer.Summary, "replays/merge-check.sh (go)") ||
strings.Join(images, ", ") != "ts-image merge-check.sh, go-image replays/merge-check.sh" {
t.Errorf("both parts passing answered %+v, ran %v", layer, images)
}
layer, _ = run(t, held, map[string]string{CheckScript: "exit 0\n",
"replays/merge-check.sh": "echo 'not gofmt'\"'\"'d:'; echo register.go; exit 1\n"}, twoParts)
if layer.Verdict != "fail" || !strings.Contains(layer.Summary, "replays/merge-check.sh failed") ||
!strings.Contains(layer.Summary, "register.go") || !strings.Contains(layer.Summary, "merge-check.sh (typescript) passed") {
t.Errorf("a failing Go part answered %+v", layer)
}
layer, images = run(t, held, map[string]string{CheckScript: "exit 2\n", "replays/merge-check.sh": "exit 0\n"}, twoParts)
if layer.Verdict != "fail" || len(images) != 1 {
t.Errorf("a failing first part answered %+v and ran %v", layer, images)
}
layer, _ = run(t, held, map[string]string{CheckScript: "exit 0\n"}, twoParts)
if layer.Verdict != "fail" || !strings.Contains(layer.Summary, "does not hold") {
t.Errorf("a declared part the repository lacks answered %+v", layer)
}
layer, _ = run(t, held, map[string]string{CheckScript: "exit 0\n"}, []ScriptPart{{"typescript", CheckScript},
{"go", "../elsewhere.sh"}})
if layer.Verdict != "fail" || !strings.Contains(layer.Summary, "not a path inside") {
t.Errorf("a part outside the repository answered %+v", layer)
}
layer, _ = run(t, CheckSpec{Toolchains: map[string]string{"typescript": "ts-image"}},
map[string]string{CheckScript: "exit 0\n", "replays/merge-check.sh": "exit 0\n"}, twoParts)
if layer.Verdict != "error" || !strings.Contains(layer.Summary, "go toolchain") {
t.Errorf("a part in a toolchain the mesh does not hold answered %+v — never a pass", layer)
}
}
+2
View File
@@ -1,3 +1,5 @@
//go:build integration
package builder
import (
-382
View File
@@ -1,11 +1,8 @@
package builder
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
@@ -35,69 +32,8 @@ func TestTheStoreAndBusAChecksStandsOnAreTheOnesTheMeshRuns(t *testing.T) {
// **Issue 306**: the throwaway store is raised without durability — it lives for one check — so a suite
// that makes a database per test does not wait on the disk of a busy machine for every flush.
func TestTheThrowawayStoreFlushesNothing(t *testing.T) {
said := strings.Join(StoreSettings, " ")
for _, want := range []string{"-c fsync=off", "-c synchronous_commit=off", "-c full_page_writes=off"} {
if !strings.Contains(said, want) {
t.Errorf("the store is raised with %q, not %q", said, want)
}
}
if os.Getenv("MESH_TEST_DOCKER") != "1" {
t.Skip("MESH_TEST_DOCKER=1: the settings are proven on a raised store")
}
id := fmt.Sprintf("check-store-%d", time.Now().UnixNano())
labelledRun := Labelled(Command, id)
t.Cleanup(func() { _, _ = RemoveContainersOf(context.Background(), Command, id) })
if _, err := throwaway(t.Context(), labelledRun, Command, id+"-store", StoreImage("17.11"), 5432,
[]string{"-e", "POSTGRES_PASSWORD=check"}, StoreSettings); err != nil {
t.Fatal(err)
}
if err := waitFor(t.Context(), Command, id+"-store", []string{"pg_isready", "-U", "postgres"}); err != nil {
t.Fatal(err)
}
for setting, want := range map[string]string{"fsync": "off", "synchronous_commit": "off", "full_page_writes": "off"} {
var got string
for try := 0; try < 10; try++ {
out, err := exec.Command("docker", "exec", id+"-store", "psql", "-U", "postgres", "-tAc", "SHOW "+setting).CombinedOutput()
if got = strings.TrimSpace(string(out)); err == nil {
break
}
time.Sleep(500 * time.Millisecond)
}
if got != want {
t.Errorf("%s is %q on the raised store", setting, got)
}
}
}
// aRepository is a git repository holding these files, committed, and its head.
func aCheckedRepository(t *testing.T, files map[string]string) (string, string) {
t.Helper()
dir := t.TempDir()
git := func(args ...string) string {
cmd := exec.Command("git", args...)
cmd.Dir = dir
cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.org",
"GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.org")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
return strings.TrimSpace(string(out))
}
git("init", "--quiet", "-b", "main")
for name, body := range files {
if err := os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o755); err != nil {
t.Fatal(err)
}
}
git("add", "-A")
git("commit", "--quiet", "-m", "x")
return dir, git("rev-parse", "HEAD")
}
func checkEnvironment(t *testing.T) string {
t.Helper()
@@ -121,104 +57,8 @@ func checkEnvironment(t *testing.T) string {
// goToolchain is the Go image a check's script runs in here: the base the controller's own image is built on.
const goToolchain = "golang@sha256:8ac98ca534ac3f51e1f420a1dd2c15e74c75cfa0f23f3ad27eb5d7236c349a0c"
func labelled(id string) []string {
out, _ := exec.Command("docker", "ps", "-aq", "--filter", "label="+BuildLabel+"="+id).Output()
return strings.Fields(string(out))
}
func TestACheckRunsTheRepositorysOwnScriptBesideTheMeshsVersionsAndLeavesNothing(t *testing.T) {
registry := checkEnvironment(t)
// The script proves what it was given: the facts, a store that answers, a bus that answers — and no
// container runtime socket.
script := `set -e
test -s "$MESH_FACTS"
grep -q '"bus": "2.11.17"' "$MESH_FACTS" || grep -q '"bus":"2.11.17"' "$MESH_FACTS"
case "$MESH_TEST_POSTGRES" in postgres://*127.0.0.1:*) ;; *) echo "no store: $MESH_TEST_POSTGRES"; exit 1;; esac
case "$MESH_TEST_NATS" in nats://127.0.0.1:*) ;; *) echo "no bus: $MESH_TEST_NATS"; exit 1;; esac
test "$MESH_CHECK_REPOSITORY" = "novox/hq"
test "$MESH_CHECK_CHANGED" = "a.go,b.go"
test ! -S /var/run/docker.sock || { echo "the check holds the container runtime's socket"; exit 1; }
echo checked
`
repo, head := aCheckedRepository(t, map[string]string{CheckScript: script})
id := fmt.Sprintf("check-test-%d", time.Now().UnixNano())
v, err := Check(t.Context(), Command, CheckSpec{ID: id, Repository: repo, Ref: head, Owner: "novox",
Repo: "hq", Number: 7, Paths: []string{"a.go", "b.go"}, Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
// Nothing of the graph touched: the gate a pass that says so, the repository's own check run.
if v.Gate == nil || v.Gate.Verdict != "pass" || v.Gate.Summary != noModule {
t.Errorf("the gate answered %+v", v.Gate)
}
if v.Repo == nil || v.Repo.Verdict != "pass" || !strings.Contains(v.Report, "checked") {
t.Fatalf("the repository's check answered %+v\n%s", v.Repo, v.Report)
}
if left := labelled(id); len(left) > 0 {
t.Errorf("the check left %d container(s) behind", len(left))
}
}
func TestAFailingCheckFailsAndOneThatCannotRunIsNeverAPass(t *testing.T) {
registry := checkEnvironment(t)
repo, head := aCheckedRepository(t, map[string]string{CheckScript: "echo 'resource \"x.service\": refused'; exit 3\n"})
v, err := Check(t.Context(), Command, CheckSpec{ID: fmt.Sprintf("check-fail-%d", time.Now().UnixNano()),
Repository: repo, Ref: head, Owner: "novox", Repo: "hq", Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if v.Repo == nil || v.Repo.Verdict != "fail" || !strings.Contains(v.Repo.Summary, "refused") {
t.Fatalf("a failing script answered %+v", v.Repo)
}
// A script in a toolchain the mesh does not hold: an error, never a pass.
repo, head = aCheckedRepository(t, map[string]string{CheckScript: "# mesh-check-toolchain: cobol\nexit 0\n"})
v, err = Check(t.Context(), Command, CheckSpec{ID: fmt.Sprintf("check-cobol-%d", time.Now().UnixNano()),
Repository: repo, Ref: head, Owner: "novox", Repo: "hq", Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil || v.Repo == nil || v.Repo.Verdict != "error" {
t.Fatalf("a script in a toolchain nobody holds answered %+v, %v", v.Repo, err)
}
// Past its bound: an error, not a pass.
was := CheckTimeout
CheckTimeout = 25 * time.Second
t.Cleanup(func() { CheckTimeout = was })
repo, head = aCheckedRepository(t, map[string]string{CheckScript: "sleep 120\n"})
v, err = Check(context.Background(), Command, CheckSpec{ID: fmt.Sprintf("check-slow-%d", time.Now().UnixNano()),
Repository: repo, Ref: head, Owner: "novox", Repo: "hq", Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err == nil && (v.Repo == nil || v.Repo.Verdict != "error") {
t.Fatalf("a check past its bound answered %+v", v.Repo)
}
// No facts: it cannot run, and says so.
repo, head = aCheckedRepository(t, map[string]string{CheckScript: "exit 0\n"})
_, err = Check(t.Context(), Command, CheckSpec{ID: "check-nofacts", Repository: repo, Ref: head, Owner: "novox",
Repo: "hq", Toolchain: goToolchain}, t.TempDir(), "127.0.0.1:1", GitCredential{}, nil)
if err == nil || !strings.Contains(err.Error(), "facts snapshot") {
t.Fatalf("a check with no facts said %v", err)
}
}
// A repository that touches nothing of the graph and has no script of its own runs nothing, and says
// both: the gate a pass that names why, the repository a warning — never silent.
func TestACheckWithNothingToRunSaysSo(t *testing.T) {
repo, head := aCheckedRepository(t, map[string]string{"README.md": "x"})
v, err := Check(t.Context(), Command, CheckSpec{ID: "check-nothing", Repository: repo, Ref: head, Owner: "novox",
Repo: "hq"}, t.TempDir(), "", GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if v.Gate == nil || v.Gate.Verdict != "pass" || v.Repo == nil || v.Repo.Verdict != "warning" ||
!strings.Contains(v.Repo.Summary, CheckScript) {
t.Fatalf("a check with nothing to run said %+v / %+v", v.Gate, v.Repo)
}
// A gated change never takes that path: with no store to read the facts from, it cannot run.
_, err = Check(t.Context(), Command, CheckSpec{ID: "check-gated", Repository: repo, Ref: head, Owner: "novox",
Repo: "mesh-catalog", Modules: []string{"gitea"}}, t.TempDir(), "", GitCredential{}, nil)
if err == nil {
t.Fatal("a change touching a module ran nothing and was not refused")
}
}
// A script declares its toolchain among its first lines; go when it declares none.
func TestAScriptDeclaresItsToolchain(t *testing.T) {
@@ -314,143 +154,8 @@ func main() {
`,
}
func TestTheGateRunsWhenTheGraphIsTouchedBesideTheRepositorysOwnCheck(t *testing.T) {
registry := checkEnvironment(t)
judgeRepo, judgeHead := aCheckedRepository(t, aJudge)
beside := map[string]Beside{"mesh-controller": {Repository: judgeRepo, Ref: judgeHead}}
check := func(files map[string]string, judge string) CheckVerdict {
t.Helper()
repo, head := aCheckedRepository(t, files)
id := fmt.Sprintf("check-gate-%d", time.Now().UnixNano())
v, err := Check(t.Context(), Command, CheckSpec{ID: id, Repository: repo, Ref: head, Owner: "novox",
Repo: "mesh-catalog", Number: 9, Paths: []string{"modules/gitea/index.ts"}, Beside: beside,
Modules: []string{"gitea"}, Manifests: []string{"modules/gitea/module.json"}, Judge: judge,
Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if left := labelled(id); len(left) > 0 {
t.Errorf("the check left %d container(s) behind", len(left))
}
return v
}
v := check(map[string]string{"modules/gitea/module.json": `{"module":"gitea"}`, CheckScript: "echo own; exit 0\n"}, "")
if v.Gate == nil || v.Gate.Verdict != "warning" || v.Gate.Summary != "a merge rebuilds 14 module(s)" ||
strings.Join(v.Gate.Modules, ",") != "gitea" || v.Verdict != "warning" {
t.Fatalf("the gate answered %+v\n%s", v.Gate, v.Report)
}
if v.Repo == nil || v.Repo.Verdict != "pass" {
t.Fatalf("its own check answered %+v", v.Repo)
}
v = check(map[string]string{"modules/gitea/module.json": `{"module":"gitea","broken":true}`}, "")
if v.Gate.Verdict != "fail" || !strings.Contains(v.Gate.Summary, "module check") || v.Repo.Verdict != "warning" {
t.Fatalf("a manifest the judge refuses answered %+v / %+v\n%s", v.Gate, v.Repo, v.Report)
}
// A fault the base branch already had is said and fails nothing; one the change brings fails.
repo, _ := aCheckedRepository(t, map[string]string{"modules/gitea/module.json": `{"module":"gitea","broken":true}`})
git := func(args ...string) string {
cmd := exec.Command("git", args...)
cmd.Dir = repo
cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.org",
"GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.org")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
return strings.TrimSpace(string(out))
}
git("checkout", "--quiet", "-b", "change")
if err := os.WriteFile(filepath.Join(repo, "modules/gitea/index.ts"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
git("add", "-A")
git("commit", "--quiet", "-m", "y")
id := fmt.Sprintf("check-base-%d", time.Now().UnixNano())
v, err := Check(t.Context(), Command, CheckSpec{ID: id, Repository: repo, Ref: git("rev-parse", "HEAD"), Owner: "novox",
Repo: "mesh-catalog", Base: "main", Paths: []string{"modules/gitea/index.ts"}, Beside: beside,
Modules: []string{"gitea"}, Manifests: []string{"modules/gitea/module.json"}, Toolchain: goToolchain},
t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if v.Gate.Verdict != "warning" || !strings.Contains(v.Report, "already") {
t.Fatalf("a fault the base already had failed the change: %+v\n%s", v.Gate, v.Report)
}
// A delivery group (novox/hq ADR 0239): the other heads cloned beside it at their heads and handed to the
// gate to compose with this one; the repository's own check is each pull request's, not the group's.
app, appHead := aCheckedRepository(t, map[string]string{"module.json": `{"module":"app"}`})
repo2, head2 := aCheckedRepository(t, map[string]string{"modules/gitea/module.json": `{"module":"gitea"}`,
CheckScript: "exit 1\n"})
gid := fmt.Sprintf("check-group-%d", time.Now().UnixNano())
v, err = Check(t.Context(), Command, CheckSpec{ID: gid, Repository: repo2, Ref: head2, Owner: "novox",
Repo: "mesh-catalog", Paths: []string{"modules/gitea/module.json"}, Beside: beside, Modules: []string{"gitea"},
Manifests: []string{"modules/gitea/module.json"}, Toolchain: goToolchain,
Group: []GroupHead{{Owner: "novox", Repo: "app", Repository: app, Ref: appHead, Paths: []string{"module.json"}}}},
t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if v.Gate.Verdict != "pass" || v.Gate.Summary != "composed with novox/app" {
t.Fatalf("the group's other head was not composed: %+v\n%s", v.Gate, v.Report)
}
if v.Repo == nil || v.Repo.Verdict != "pass" || !strings.Contains(v.Repo.Summary, "group") {
t.Fatalf("a group's check ran a member's own script: %+v", v.Repo)
}
// A change to the controller judges itself: one that does not build fails its own gate.
v = check(map[string]string{"go.mod": "module x\n\ngo 1.22\n", "cmd/mesh-controller/main.go": "package main\nfunc main() { nope }\n",
"modules/gitea/module.json": "{}"}, "self")
if v.Gate.Verdict != "fail" || !strings.Contains(v.Gate.Summary, "does not build") {
t.Fatalf("a controller that does not build judged itself %+v", v.Gate)
}
}
// **Only a commit on the trunk is published** (novox/hq ADR 0238): the build seat reads, from the clone it
// just made, the branch the forge names its default and whether the commit built is on it.
func TestABuildSaysWhetherItsCommitIsOnTheTrunk(t *testing.T) {
repo, onMain := aCheckedRepository(t, map[string]string{"module.json": `{"module":"x","version":"1"}`})
git := func(dir string, args ...string) string {
cmd := exec.Command("git", args...)
cmd.Dir = dir
cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.org",
"GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.org")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
return strings.TrimSpace(string(out))
}
git(repo, "checkout", "--quiet", "-b", "feature")
if err := os.WriteFile(filepath.Join(repo, "x"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
git(repo, "add", "-A")
git(repo, "commit", "--quiet", "-m", "off the trunk")
offMain := git(repo, "rev-parse", "HEAD")
git(repo, "checkout", "--quiet", "main")
clone := filepath.Join(t.TempDir(), "clone")
git(filepath.Dir(clone), "clone", "--quiet", repo, clone)
if trunk, on := trunkOf(t.Context(), Command, clone, onMain); trunk != "main" || !on {
t.Errorf("a commit on main reads as on %q: %v", trunk, on)
}
if trunk, on := trunkOf(t.Context(), Command, clone, offMain); trunk != "main" || on {
t.Errorf("a feature branch's commit reads as on %q: %v", trunk, on)
}
if got := strings.Join(branchesHolding(t.Context(), Command, clone, offMain), ","); got != "feature" {
t.Errorf("the feature branch's commit is said to be on %q", got)
}
if got := strings.Join(branchesHolding(t.Context(), Command, clone, onMain), ","); got != "feature,main" {
t.Errorf("main's commit is said to be on %q", got)
}
// A tree that says no trunk is not known — never read as on it.
if trunk, on := trunkOf(t.Context(), Command, repo, onMain); trunk != "" || on {
t.Errorf("a repository with no origin reads as trunk %q, on %v", trunk, on)
}
}
// **Issue 286**: a failed merge-check.sh was said by its last line — a bare "FAIL", or the name of a file
// gofmt listed — which named nothing a reader could act on. The status says what failed.
@@ -497,99 +202,12 @@ func TestTheSeatCallsAGateThatRaisedNoMachineAnError(t *testing.T) {
// **Issue 285**: an ask redelivered after its holder stopped mid-check found its own throwaway store still
// there under its name, and the check said it could not run. What an earlier delivery left goes first.
func TestARedeliveredCheckRemovesWhatItsEarlierDeliveryLeft(t *testing.T) {
registry := checkEnvironment(t)
repo, head := aCheckedRepository(t, map[string]string{CheckScript: "echo checked\n"})
id := fmt.Sprintf("check-again-%d", time.Now().UnixNano())
if out, err := exec.Command("docker", "run", "-d", "--rm", "--label", BuildLabel+"="+id, "--name", id+"-store",
"postgres:17-alpine", "sleep", "300").CombinedOutput(); err != nil {
t.Skipf("no container for the earlier delivery: %v %s", err, out)
}
v, err := Check(t.Context(), Command, CheckSpec{ID: id, Repository: repo, Ref: head, Owner: "novox",
Repo: "hq", Number: 7, Toolchain: goToolchain}, t.TempDir(), registry, GitCredential{}, nil)
if err != nil {
t.Fatalf("a redelivered check could not run: %v", err)
}
if v.Repo == nil || v.Repo.Verdict != "pass" {
t.Errorf("the repository's check answered %+v", v.Repo)
}
if left := labelled(id); len(left) > 0 {
t.Errorf("the check left %d container(s) behind", len(left))
}
}
// **A repository in two languages checks both, each in its own toolchain** (novox/hq issue 302): the lab's
// merge-check.sh runs in the TypeScript toolchain, and its Go replays were said "NOT CHECKED HERE" on every
// pull request, so a register that did not compile would have merged green. Its script declares the Go
// part; the check runs it in the Go toolchain's container after the script, and the layer passes only
// when both do.
func TestARepositoryInTwoLanguagesIsCheckedInBoth(t *testing.T) {
script := "#!/bin/sh\n# mesh-check-toolchain: typescript\n# mesh-check-also: go replays/merge-check.sh\nnpm test\n"
parts := ScriptParts([]byte(script))
if len(parts) != 2 || parts[0] != (ScriptPart{"typescript", CheckScript}) ||
parts[1] != (ScriptPart{"go", "replays/merge-check.sh"}) {
t.Fatalf("the parts read as %+v", parts)
}
if got := ScriptParts([]byte("#!/bin/sh\nset -eu\n")); len(got) != 1 || got[0] != (ScriptPart{"go", CheckScript}) {
t.Fatalf("a script declaring nothing reads as %+v", got)
}
// Each part run where its toolchain is: here, the image the part was given is what the command says.
held := CheckSpec{Toolchain: "go-image", Toolchains: map[string]string{"typescript": "ts-image", "go": "go-image"}}
run := func(t *testing.T, spec CheckSpec, files map[string]string, parts []ScriptPart) (*Layer, []string) {
t.Helper()
tree := t.TempDir()
for name, body := range files {
if err := os.MkdirAll(filepath.Dir(filepath.Join(tree, name)), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(tree, name), []byte(body), 0o755); err != nil {
t.Fatal(err)
}
}
var images []string
var out tail
layer := ownCheck(t.Context(), spec, parts, tree, &out, func() bool { return false },
func(image, script string) *exec.Cmd {
images = append(images, image+" "+script)
cmd := exec.CommandContext(t.Context(), "sh", script)
cmd.Dir = tree
return cmd
}, func(string, string, ...any) {})
return layer, images
}
twoParts := []ScriptPart{{"typescript", CheckScript}, {"go", "replays/merge-check.sh"}}
layer, images := run(t, held, map[string]string{CheckScript: "exit 0\n", "replays/merge-check.sh": "exit 0\n"}, twoParts)
if layer.Verdict != "pass" || !strings.Contains(layer.Summary, "replays/merge-check.sh (go)") ||
strings.Join(images, ", ") != "ts-image merge-check.sh, go-image replays/merge-check.sh" {
t.Errorf("both parts passing answered %+v, ran %v", layer, images)
}
layer, _ = run(t, held, map[string]string{CheckScript: "exit 0\n",
"replays/merge-check.sh": "echo 'not gofmt'\"'\"'d:'; echo register.go; exit 1\n"}, twoParts)
if layer.Verdict != "fail" || !strings.Contains(layer.Summary, "replays/merge-check.sh failed") ||
!strings.Contains(layer.Summary, "register.go") || !strings.Contains(layer.Summary, "merge-check.sh (typescript) passed") {
t.Errorf("a failing Go part answered %+v", layer)
}
layer, images = run(t, held, map[string]string{CheckScript: "exit 2\n", "replays/merge-check.sh": "exit 0\n"}, twoParts)
if layer.Verdict != "fail" || len(images) != 1 {
t.Errorf("a failing first part answered %+v and ran %v", layer, images)
}
layer, _ = run(t, held, map[string]string{CheckScript: "exit 0\n"}, twoParts)
if layer.Verdict != "fail" || !strings.Contains(layer.Summary, "does not hold") {
t.Errorf("a declared part the repository lacks answered %+v", layer)
}
layer, _ = run(t, held, map[string]string{CheckScript: "exit 0\n"}, []ScriptPart{{"typescript", CheckScript},
{"go", "../elsewhere.sh"}})
if layer.Verdict != "fail" || !strings.Contains(layer.Summary, "not a path inside") {
t.Errorf("a part outside the repository answered %+v", layer)
}
layer, _ = run(t, CheckSpec{Toolchains: map[string]string{"typescript": "ts-image"}},
map[string]string{CheckScript: "exit 0\n", "replays/merge-check.sh": "exit 0\n"}, twoParts)
if layer.Verdict != "error" || !strings.Contains(layer.Summary, "go toolchain") {
t.Errorf("a part in a toolchain the mesh does not hold answered %+v — never a pass", layer)
}
}
// A branch tried before its pull request may narrow what the repository's script runs (novox/hq issue 686); a
// pull request's check is handed nothing, and runs whole.
+2
View File
@@ -1,3 +1,5 @@
//go:build integration
package builder
import (
@@ -0,0 +1,152 @@
//go:build integration
package builder
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func ownCheckOf(t *testing.T, printed string) (*Layer, []string) {
t.Helper()
tree := t.TempDir()
if err := os.WriteFile(filepath.Join(tree, "printed.txt"), []byte(printed), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(tree, CheckScript), []byte("cat printed.txt\nexit 1\n"), 0o755); err != nil {
t.Fatal(err)
}
var out tail
var logged []string
layer := ownCheck(t.Context(), CheckSpec{Toolchain: "go-image"}, []ScriptPart{{"go", CheckScript}}, tree, &out,
func() bool { return false },
func(_, script string) *exec.Cmd {
cmd := exec.CommandContext(t.Context(), "sh", script)
cmd.Dir = tree
return cmd
}, func(step, format string, args ...any) {
if step == "output" {
logged = append(logged, fmt.Sprintf(format, args...))
}
})
if layer == nil || layer.Verdict != "fail" {
t.Fatalf("a failing check answered %+v", layer)
}
return layer, logged
}
func TestAFailureEarlyInALongRunIsStillNamed(t *testing.T) {
layer, _ := ownCheckOf(t, captured(t, "a-long-run-failing-early.txt"))
if !strings.Contains(layer.Summary, "failed: --- FAIL: TestTheEnvelopeIsPinned (0.00s)") {
t.Errorf("a failure 600 lines from the end is said as %q", layer.Summary)
}
if !strings.Contains(layer.Summary, "TestSubtests/the_hub") {
t.Errorf("the second failing test is not named: %q", layer.Summary)
}
}
func TestARaceRunOfEveryPackageNamesItsFirstFailure(t *testing.T) {
layer, _ := ownCheckOf(t, captured(t, "a-race-run-of-every-package.txt"))
if !strings.Contains(layer.Summary, "failed: --- FAIL: TestTheEnvelopeIsPinned (0.00s)") ||
!strings.Contains(layer.Summary, "TestAMapIsNil") || !strings.Contains(layer.Summary, "TestACounterIsShared") {
t.Errorf("a run failing in four packages is said as %q", layer.Summary)
}
}
func TestABuildErrorIsNamedByTheError(t *testing.T) {
layer, _ := ownCheckOf(t, captured(t, "a-build-error.txt"))
if !strings.HasSuffix(layer.Summary, "failed: broken/broken.go:3:28: undefined: undefinedThing") {
t.Errorf("a build error is said as %q", layer.Summary)
}
}
func TestTheWholeOutputIsInTheBuildsLog(t *testing.T) {
printed := captured(t, "a-long-run-failing-early.txt")
_, logged := ownCheckOf(t, printed)
want := strings.Split(strings.TrimRight(printed, "\n"), "\n")
if len(logged) != len(want) {
t.Fatalf("the build's log holds %d line(s) of the %d printed", len(logged), len(want))
}
for i := range want {
if logged[i] != want[i] {
t.Fatalf("line %d is logged as %q, printed as %q", i+1, logged[i], want[i])
}
}
}
func TestTheVerdictKeepsWhatFailedWhole(t *testing.T) {
layer, _ := ownCheckOf(t, captured(t, "a-long-run-failing-early.txt"))
want := "--- FAIL: TestTheEnvelopeIsPinned (0.00s)\n" +
" early_test.go:9: the envelope's subject is \"mesh.a\", not \"mesh.b\"\n" +
" early_test.go:10: a second line of the same failure\n" +
"--- FAIL: TestSubtests (0.00s)\n" +
" --- FAIL: TestSubtests/the_hub (0.00s)\n" +
" early_test.go:14: the hub does not compose\n" +
"FAIL\texample.com/cap/early\t"
if !strings.HasPrefix(layer.Failed, want) || strings.Contains(layer.Failed, "kept what home-server says") {
t.Errorf("the early failure is kept as:\n%s", layer.Failed)
}
layer, _ = ownCheckOf(t, captured(t, "a-panic.txt"))
for _, line := range []string{"--- FAIL: TestAMapIsNil (0.00s)", "panic: assignment to entry in nil map",
"[running]:", "example.com/cap/panics.TestAMapIsNil(", "/src/cap/panics/panics_test.go:7",
"FAIL\texample.com/cap/panics\t"} {
if !strings.Contains(layer.Failed, line) {
t.Errorf("a panic is kept without %q:\n%s", line, layer.Failed)
}
}
layer, _ = ownCheckOf(t, captured(t, "a-data-race.txt"))
race := captured(t, "a-data-race.txt")
report := race[:strings.Index(race, "--- FAIL:")]
if !strings.HasPrefix(layer.Failed, report) ||
!strings.Contains(layer.Failed, "--- FAIL: TestACounterIsShared (0.00s)\n testing.go:1865: race detected") {
t.Errorf("a data race is kept as:\n%s", layer.Failed)
}
layer, _ = ownCheckOf(t, captured(t, "a-build-error.txt"))
if layer.Failed != "# example.com/cap/broken\nbroken/broken.go:3:28: undefined: undefinedThing\n"+
"FAIL\texample.com/cap/broken [build failed]" {
t.Errorf("a build error is kept as:\n%s", layer.Failed)
}
}
func TestWhatFailedIsBoundedAndSurvivesTheReportsCut(t *testing.T) {
var b strings.Builder
for i := range 3000 {
fmt.Fprintf(&b, "--- FAIL: TestNumber%d (0.00s)\n x_test.go:1: %s\n", i, strings.Repeat("why ", 600))
fmt.Fprintf(&b, "2026/10/11 01:30:03 a log line %d\n", i)
}
b.WriteString("FAIL\tx/everything\t1s\nFAIL\n")
layer, logged := ownCheckOf(t, b.String())
if len(layer.Failed) > failureBytes+2*failureLineBytes || !strings.Contains(layer.Failed, "more line(s) naming what failed") {
t.Errorf("what failed in a run that fails everywhere is %d bytes, ending %q", len(layer.Failed),
layer.Failed[max(0, len(layer.Failed)-200):])
}
if !strings.Contains(layer.Summary, "--- FAIL: TestNumber0 (0.00s); and TestNumber1, ") ||
!strings.Contains(layer.Summary, "and 2994 more") {
t.Errorf("a run failing 3000 tests is said as %q", layer.Summary)
}
var out tail
out.Write([]byte(b.String()))
report := withWhatFailed(out.String(), layer)
const maxCheckReport = 60 << 10 // as the controller cuts it, from the front
if len(report) > maxCheckReport {
report = report[len(report)-maxCheckReport:]
}
if !strings.Contains(report, "--- what failed") || !strings.Contains(report, "--- FAIL: TestNumber0 (0.00s)") {
t.Error("what failed did not survive the controller's cut of the report")
}
// The build's log keeps its first logLines lines, says how many it left out, then what failed.
if len(logged) < logLines+2 || logged[logLines] != "… 1002 more line(s) of its output are not in this log, which keeps its first 8000" ||
logged[logLines+1] != "--- what failed, picked from the whole of its output" ||
logged[logLines+2] != "--- FAIL: TestNumber0 (0.00s)" {
t.Errorf("the log past its bound says %q", logged[logLines:min(len(logged), logLines+3)])
}
}
-136
View File
@@ -1,43 +1,13 @@
package builder
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// ownCheckOf runs a repository's own check whose script prints a captured output and fails, as the build
// seat runs it, and answers the layer and every line it said in the build's log.
func ownCheckOf(t *testing.T, printed string) (*Layer, []string) {
t.Helper()
tree := t.TempDir()
if err := os.WriteFile(filepath.Join(tree, "printed.txt"), []byte(printed), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(tree, CheckScript), []byte("cat printed.txt\nexit 1\n"), 0o755); err != nil {
t.Fatal(err)
}
var out tail
var logged []string
layer := ownCheck(t.Context(), CheckSpec{Toolchain: "go-image"}, []ScriptPart{{"go", CheckScript}}, tree, &out,
func() bool { return false },
func(_, script string) *exec.Cmd {
cmd := exec.CommandContext(t.Context(), "sh", script)
cmd.Dir = tree
return cmd
}, func(step, format string, args ...any) {
if step == "output" {
logged = append(logged, fmt.Sprintf(format, args...))
}
})
if layer == nil || layer.Verdict != "fail" {
t.Fatalf("a failing check answered %+v", layer)
}
return layer, logged
}
func captured(t *testing.T, name string) string {
t.Helper()
@@ -52,124 +22,18 @@ func captured(t *testing.T, name string) string {
// name: the package's own logs, printed after its `--- FAIL:` lines, pushed them out of the last 200 lines,
// which was all the summary was named from. The first failing test is named, and the others after it,
// however much came after them.
func TestAFailureEarlyInALongRunIsStillNamed(t *testing.T) {
layer, _ := ownCheckOf(t, captured(t, "a-long-run-failing-early.txt"))
if !strings.Contains(layer.Summary, "failed: --- FAIL: TestTheEnvelopeIsPinned (0.00s)") {
t.Errorf("a failure 600 lines from the end is said as %q", layer.Summary)
}
if !strings.Contains(layer.Summary, "TestSubtests/the_hub") {
t.Errorf("the second failing test is not named: %q", layer.Summary)
}
}
// One -race run of every package: the first failure is the earliest, not the last within the tail.
func TestARaceRunOfEveryPackageNamesItsFirstFailure(t *testing.T) {
layer, _ := ownCheckOf(t, captured(t, "a-race-run-of-every-package.txt"))
if !strings.Contains(layer.Summary, "failed: --- FAIL: TestTheEnvelopeIsPinned (0.00s)") ||
!strings.Contains(layer.Summary, "TestAMapIsNil") || !strings.Contains(layer.Summary, "TestACounterIsShared") {
t.Errorf("a run failing in four packages is said as %q", layer.Summary)
}
}
// A package that does not build is named by its error, not by its bare "[build failed]".
func TestABuildErrorIsNamedByTheError(t *testing.T) {
layer, _ := ownCheckOf(t, captured(t, "a-build-error.txt"))
if !strings.HasSuffix(layer.Summary, "failed: broken/broken.go:3:28: undefined: undefinedThing") {
t.Errorf("a build error is said as %q", layer.Summary)
}
}
// The whole of what the check printed is in the build's log, line by line — where before none of it was.
func TestTheWholeOutputIsInTheBuildsLog(t *testing.T) {
printed := captured(t, "a-long-run-failing-early.txt")
_, logged := ownCheckOf(t, printed)
want := strings.Split(strings.TrimRight(printed, "\n"), "\n")
if len(logged) != len(want) {
t.Fatalf("the build's log holds %d line(s) of the %d printed", len(logged), len(want))
}
for i := range want {
if logged[i] != want[i] {
t.Fatalf("line %d is logged as %q, printed as %q", i+1, logged[i], want[i])
}
}
}
// What failed is kept whole in the verdict: each --- FAIL with its messages, a panic with its first frames,
// a data race report from rule to rule — and none of the log lines around them.
func TestTheVerdictKeepsWhatFailedWhole(t *testing.T) {
layer, _ := ownCheckOf(t, captured(t, "a-long-run-failing-early.txt"))
want := "--- FAIL: TestTheEnvelopeIsPinned (0.00s)\n" +
" early_test.go:9: the envelope's subject is \"mesh.a\", not \"mesh.b\"\n" +
" early_test.go:10: a second line of the same failure\n" +
"--- FAIL: TestSubtests (0.00s)\n" +
" --- FAIL: TestSubtests/the_hub (0.00s)\n" +
" early_test.go:14: the hub does not compose\n" +
"FAIL\texample.com/cap/early\t"
if !strings.HasPrefix(layer.Failed, want) || strings.Contains(layer.Failed, "kept what home-server says") {
t.Errorf("the early failure is kept as:\n%s", layer.Failed)
}
layer, _ = ownCheckOf(t, captured(t, "a-panic.txt"))
for _, line := range []string{"--- FAIL: TestAMapIsNil (0.00s)", "panic: assignment to entry in nil map",
"[running]:", "example.com/cap/panics.TestAMapIsNil(", "/src/cap/panics/panics_test.go:7",
"FAIL\texample.com/cap/panics\t"} {
if !strings.Contains(layer.Failed, line) {
t.Errorf("a panic is kept without %q:\n%s", line, layer.Failed)
}
}
layer, _ = ownCheckOf(t, captured(t, "a-data-race.txt"))
race := captured(t, "a-data-race.txt")
report := race[:strings.Index(race, "--- FAIL:")]
if !strings.HasPrefix(layer.Failed, report) ||
!strings.Contains(layer.Failed, "--- FAIL: TestACounterIsShared (0.00s)\n testing.go:1865: race detected") {
t.Errorf("a data race is kept as:\n%s", layer.Failed)
}
layer, _ = ownCheckOf(t, captured(t, "a-build-error.txt"))
if layer.Failed != "# example.com/cap/broken\nbroken/broken.go:3:28: undefined: undefinedThing\n"+
"FAIL\texample.com/cap/broken [build failed]" {
t.Errorf("a build error is kept as:\n%s", layer.Failed)
}
}
// A run that fails everywhere is bounded in what it keeps, and what it keeps survives the controller's cut
// of a report to its last 60 KiB, said at the report's end.
func TestWhatFailedIsBoundedAndSurvivesTheReportsCut(t *testing.T) {
var b strings.Builder
for i := range 3000 {
fmt.Fprintf(&b, "--- FAIL: TestNumber%d (0.00s)\n x_test.go:1: %s\n", i, strings.Repeat("why ", 600))
fmt.Fprintf(&b, "2026/10/11 01:30:03 a log line %d\n", i)
}
b.WriteString("FAIL\tx/everything\t1s\nFAIL\n")
layer, logged := ownCheckOf(t, b.String())
if len(layer.Failed) > failureBytes+2*failureLineBytes || !strings.Contains(layer.Failed, "more line(s) naming what failed") {
t.Errorf("what failed in a run that fails everywhere is %d bytes, ending %q", len(layer.Failed),
layer.Failed[max(0, len(layer.Failed)-200):])
}
if !strings.Contains(layer.Summary, "--- FAIL: TestNumber0 (0.00s); and TestNumber1, ") ||
!strings.Contains(layer.Summary, "and 2994 more") {
t.Errorf("a run failing 3000 tests is said as %q", layer.Summary)
}
var out tail
out.Write([]byte(b.String()))
report := withWhatFailed(out.String(), layer)
const maxCheckReport = 60 << 10 // as the controller cuts it, from the front
if len(report) > maxCheckReport {
report = report[len(report)-maxCheckReport:]
}
if !strings.Contains(report, "--- what failed") || !strings.Contains(report, "--- FAIL: TestNumber0 (0.00s)") {
t.Error("what failed did not survive the controller's cut of the report")
}
// The build's log keeps its first logLines lines, says how many it left out, then what failed.
if len(logged) < logLines+2 || logged[logLines] != "… 1002 more line(s) of its output are not in this log, which keeps its first 8000" ||
logged[logLines+1] != "--- what failed, picked from the whole of its output" ||
logged[logLines+2] != "--- FAIL: TestNumber0 (0.00s)" {
t.Errorf("the log past its bound says %q", logged[logLines:min(len(logged), logLines+3)])
}
}
// A passing check says nothing of what failed.
func TestAPassingCheckReportsAsBefore(t *testing.T) {
@@ -0,0 +1,84 @@
//go:build integration
package builder
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestAGoProgramsBuildSourceIsItsImportClosure(t *testing.T) {
root := aGoTree(t, aProgram)
got, err := GoBuildSource(root, "cmd/prog")
if err != nil {
t.Fatal(err)
}
for _, c := range []struct {
file string
held bool
why string
}{
{"cmd/prog/main.go", true, "the program itself"},
{"cmd/prog/helper.go", true, "a file added to the program's package"},
{"lib/lib.go", true, "a package it imports"},
{"emb/emb_amd64.s", true, "assembly beside a package's Go"},
{"lib/lib_plan9.go", true, "a file one system builds"},
{"plan9only/p.go", true, "what a file one system builds imports"},
{"emb/static/index.html", true, "an embedded file"},
{"emb/static/deep/style.css", true, "an embedded file below the pattern's directory"},
{"emb/a b.txt", true, "an embed named outright, quoted"},
{"vendor/example.org/dep/dep.go", true, "a vendored package it imports"},
{"vendor/example.org/dep/sub/sub.go", true, "what a vendored package imports"},
{"vendor/example.net/local/pkg/p.go", true, "a replaced module, as vendored"},
{"third_party/local/pkg/p.go", true, "a replaced module, at its directory"},
{"third_party/local/go.mod", true, "a replaced module's requirements"},
{"go.mod", true, "the module's requirements"},
{"go.sum", true, "the module's sums"},
{"vendor/modules.txt", true, "the vendored modules"},
{"go.work", true, "a workspace, were one made"},
{"lib/lib_test.go", false, "a test is not built"},
{"testonly/t.go", false, "a package only a test imports"},
{"lib/sub/sub.go", false, "a package below an imported one, not imported"},
{"unrelated/u.go", false, "a package nothing imports"},
{"vendor/example.org/other/other.go", false, "a vendored package nothing imports"},
{"README.md", false, "a file no build reads"},
} {
if SourceHolds(got, c.file) != c.held {
t.Errorf("%s (%s): held %v, wanted %v\n %v", c.file, c.why, !c.held, c.held, got)
}
}
// **Never narrower than go list -deps**: every package go names, and every file it compiles or embeds,
// is held. Where no go command is at hand, said, not passed.
goCmd, err := exec.LookPath("go")
if err != nil {
t.Skip("NOT COMPARED: no go command to list the closure with")
}
list := exec.Command(goCmd, "list", "-deps", "-f",
`{{if not .Standard}}{{$d := .Dir}}{{range .GoFiles}}{{$d}}/{{.}}
{{end}}{{range .SFiles}}{{$d}}/{{.}}
{{end}}{{range .EmbedFiles}}{{$d}}/{{.}}
{{end}}{{end}}`, "./cmd/prog")
list.Dir = root
list.Env = append(os.Environ(), "GOFLAGS=-mod=vendor", "GOPROXY=off", "GOWORK=off", "GOOS=linux", "GOARCH=amd64")
out, err := list.CombinedOutput()
if err != nil {
t.Fatalf("go list: %v\n%s", err, out)
}
real, _ := filepath.EvalSymlinks(root)
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
if line == "" {
continue
}
rel, err := filepath.Rel(real, line)
if err != nil || strings.HasPrefix(rel, "..") {
rel, _ = filepath.Rel(root, line)
}
if !SourceHolds(got, filepath.ToSlash(rel)) {
t.Errorf("go list builds %s, and the build source does not hold it", rel)
}
}
}
-74
View File
@@ -2,7 +2,6 @@ package builder
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
@@ -60,79 +59,6 @@ var aProgram = map[string]string{
"README.md": "x",
}
func TestAGoProgramsBuildSourceIsItsImportClosure(t *testing.T) {
root := aGoTree(t, aProgram)
got, err := GoBuildSource(root, "cmd/prog")
if err != nil {
t.Fatal(err)
}
for _, c := range []struct {
file string
held bool
why string
}{
{"cmd/prog/main.go", true, "the program itself"},
{"cmd/prog/helper.go", true, "a file added to the program's package"},
{"lib/lib.go", true, "a package it imports"},
{"emb/emb_amd64.s", true, "assembly beside a package's Go"},
{"lib/lib_plan9.go", true, "a file one system builds"},
{"plan9only/p.go", true, "what a file one system builds imports"},
{"emb/static/index.html", true, "an embedded file"},
{"emb/static/deep/style.css", true, "an embedded file below the pattern's directory"},
{"emb/a b.txt", true, "an embed named outright, quoted"},
{"vendor/example.org/dep/dep.go", true, "a vendored package it imports"},
{"vendor/example.org/dep/sub/sub.go", true, "what a vendored package imports"},
{"vendor/example.net/local/pkg/p.go", true, "a replaced module, as vendored"},
{"third_party/local/pkg/p.go", true, "a replaced module, at its directory"},
{"third_party/local/go.mod", true, "a replaced module's requirements"},
{"go.mod", true, "the module's requirements"},
{"go.sum", true, "the module's sums"},
{"vendor/modules.txt", true, "the vendored modules"},
{"go.work", true, "a workspace, were one made"},
{"lib/lib_test.go", false, "a test is not built"},
{"testonly/t.go", false, "a package only a test imports"},
{"lib/sub/sub.go", false, "a package below an imported one, not imported"},
{"unrelated/u.go", false, "a package nothing imports"},
{"vendor/example.org/other/other.go", false, "a vendored package nothing imports"},
{"README.md", false, "a file no build reads"},
} {
if SourceHolds(got, c.file) != c.held {
t.Errorf("%s (%s): held %v, wanted %v\n %v", c.file, c.why, !c.held, c.held, got)
}
}
// **Never narrower than go list -deps**: every package go names, and every file it compiles or embeds,
// is held. Where no go command is at hand, said, not passed.
goCmd, err := exec.LookPath("go")
if err != nil {
t.Skip("NOT COMPARED: no go command to list the closure with")
}
list := exec.Command(goCmd, "list", "-deps", "-f",
`{{if not .Standard}}{{$d := .Dir}}{{range .GoFiles}}{{$d}}/{{.}}
{{end}}{{range .SFiles}}{{$d}}/{{.}}
{{end}}{{range .EmbedFiles}}{{$d}}/{{.}}
{{end}}{{end}}`, "./cmd/prog")
list.Dir = root
list.Env = append(os.Environ(), "GOFLAGS=-mod=vendor", "GOPROXY=off", "GOWORK=off", "GOOS=linux", "GOARCH=amd64")
out, err := list.CombinedOutput()
if err != nil {
t.Fatalf("go list: %v\n%s", err, out)
}
real, _ := filepath.EvalSymlinks(root)
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
if line == "" {
continue
}
rel, err := filepath.Rel(real, line)
if err != nil || strings.HasPrefix(rel, "..") {
rel, _ = filepath.Rel(root, line)
}
if !SourceHolds(got, filepath.ToSlash(rel)) {
t.Errorf("go list builds %s, and the build source does not hold it", rel)
}
}
}
// A file added to the program's import closure moves its build source with it: the closure read again
// grows by the package.
func TestTheBuildSourceGrowsWithAnImport(t *testing.T) {
+2
View File
@@ -1,3 +1,5 @@
//go:build integration
package builder
import (
+58
View File
@@ -0,0 +1,58 @@
//go:build integration
package builder
import (
"context"
"errors"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"testing"
"time"
)
func TestAKilledBuildEndsItsWholeProcessGroup(t *testing.T) {
dir := t.TempDir()
child := filepath.Join(dir, "child")
ctx, cancel := context.WithCancel(context.Background())
done := make(chan error, 1)
began := time.Now()
go func() {
// A shell that starts a grandchild and waits on it: killing the shell alone would leave the
// grandchild running, holding the output open.
_, err := Command(ctx, dir, "sh", "-c", `sleep 60 & echo $! > child; wait`)
done <- err
}()
var pid int
for deadline := time.Now().Add(5 * time.Second); time.Now().Before(deadline); time.Sleep(20 * time.Millisecond) {
if raw, err := os.ReadFile(child); err == nil && strings.TrimSpace(string(raw)) != "" {
pid, _ = strconv.Atoi(strings.TrimSpace(string(raw)))
break
}
}
if pid == 0 {
t.Fatal("the command never started its child")
}
cancel()
select {
case err := <-done:
if err == nil {
t.Fatal("a killed command reported success")
}
case <-time.After(KillWait + 5*time.Second):
t.Fatal("the killed command never returned")
}
if took := time.Since(began); took > KillWait {
t.Errorf("ending the command took %s: the group was not killed, the wait ran out", took)
}
for deadline := time.Now().Add(2 * time.Second); time.Now().Before(deadline); time.Sleep(20 * time.Millisecond) {
if err := syscall.Kill(pid, 0); errors.Is(err, syscall.ESRCH) {
return
}
}
_ = syscall.Kill(pid, syscall.SIGKILL)
t.Fatalf("the grandchild %d outlived the kill", pid)
}
-48
View File
@@ -2,61 +2,13 @@ package builder
import (
"context"
"errors"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"syscall"
"testing"
"time"
)
// A build killed by hand (novox/hq ADR 0219) ends every command it started: the context's end kills
// the command's whole process group, not the one process the context knows about.
func TestAKilledBuildEndsItsWholeProcessGroup(t *testing.T) {
dir := t.TempDir()
child := filepath.Join(dir, "child")
ctx, cancel := context.WithCancel(context.Background())
done := make(chan error, 1)
began := time.Now()
go func() {
// A shell that starts a grandchild and waits on it: killing the shell alone would leave the
// grandchild running, holding the output open.
_, err := Command(ctx, dir, "sh", "-c", `sleep 60 & echo $! > child; wait`)
done <- err
}()
var pid int
for deadline := time.Now().Add(5 * time.Second); time.Now().Before(deadline); time.Sleep(20 * time.Millisecond) {
if raw, err := os.ReadFile(child); err == nil && strings.TrimSpace(string(raw)) != "" {
pid, _ = strconv.Atoi(strings.TrimSpace(string(raw)))
break
}
}
if pid == 0 {
t.Fatal("the command never started its child")
}
cancel()
select {
case err := <-done:
if err == nil {
t.Fatal("a killed command reported success")
}
case <-time.After(KillWait + 5*time.Second):
t.Fatal("the killed command never returned")
}
if took := time.Since(began); took > KillWait {
t.Errorf("ending the command took %s: the group was not killed, the wait ran out", took)
}
for deadline := time.Now().Add(2 * time.Second); time.Now().Before(deadline); time.Sleep(20 * time.Millisecond) {
if err := syscall.Kill(pid, 0); errors.Is(err, syscall.ESRCH) {
return
}
}
_ = syscall.Kill(pid, syscall.SIGKILL)
t.Fatalf("the grandchild %d outlived the kill", pid)
}
// Every `docker run` a build starts carries its id as a label; nothing else is touched.
func TestEveryContainerABuildStartsCarriesItsID(t *testing.T) {
+26
View File
@@ -0,0 +1,26 @@
//go:build integration
package builder
import (
"context"
"strings"
"testing"
)
func TestAnImageIsPushedOnlyAfterItsBasesAreLent(t *testing.T) {
r, workspace := aRepository(t, anImage, map[string]string{"modules/bus/Dockerfile": "ARG BASE\nFROM ${BASE}"})
r.contents["modules/bus/module.json"] = anImage
l := &lending{mirroring: &mirroring{recorded: r, at: "127.0.0.1:5000"}}
if _, err := Build(context.Background(), r.run, l, "https://forge.invalid/catalogue.git", "modules/bus", "", workspace,
nil, Npmrc{}, GitCredential{}, nil); err != nil {
t.Fatal(err)
}
want := []string{
"lend bus/server 127.0.0.1:5000/upstream/docker.io/vendor/server@sha256:" + strings.Repeat("1", 64),
"push bus/server",
}
if strings.Join(l.order, "\n") != strings.Join(want, "\n") {
t.Fatalf("the build did:\n%s\nwant:\n%s", strings.Join(l.order, "\n"), strings.Join(want, "\n"))
}
}
-17
View File
@@ -121,20 +121,3 @@ func (l *lending) PublishImage(ctx context.Context, localTag, repository string)
l.order = append(l.order, "push "+repository)
return l.mirroring.PublishImage(ctx, localTag, repository)
}
func TestAnImageIsPushedOnlyAfterItsBasesAreLent(t *testing.T) {
r, workspace := aRepository(t, anImage, map[string]string{"modules/bus/Dockerfile": "ARG BASE\nFROM ${BASE}"})
r.contents["modules/bus/module.json"] = anImage
l := &lending{mirroring: &mirroring{recorded: r, at: "127.0.0.1:5000"}}
if _, err := Build(context.Background(), r.run, l, "https://forge.invalid/catalogue.git", "modules/bus", "", workspace,
nil, Npmrc{}, GitCredential{}, nil); err != nil {
t.Fatal(err)
}
want := []string{
"lend bus/server 127.0.0.1:5000/upstream/docker.io/vendor/server@sha256:" + strings.Repeat("1", 64),
"push bus/server",
}
if strings.Join(l.order, "\n") != strings.Join(want, "\n") {
t.Fatalf("the build did:\n%s\nwant:\n%s", strings.Join(l.order, "\n"), strings.Join(want, "\n"))
}
}
@@ -0,0 +1,40 @@
//go:build integration
package builder
import (
"context"
"strings"
"testing"
)
func TestABuildSaysWhatItMirroredEvenWhenItFails(t *testing.T) {
// A recipe that reaches for an image nobody declared is refused — after the base was copied.
r, workspace := aRepository(t, anImage, map[string]string{
"modules/bus/Dockerfile": "ARG BASE\nFROM ${BASE}\nCOPY --from=vendor/other:1 /x /x"})
r.contents["modules/bus/module.json"] = anImage
r.tree = "aaaa"
m := &mirroring{recorded: r, at: "registry-a:5000"}
got, err := Build(context.Background(), r.run, m, "https://forge.invalid/catalogue.git", "modules/bus", "", workspace,
nil, Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatal("a recipe fetching an undeclared image was built")
}
want := "registry-a:5000/upstream/docker.io/vendor/server@sha256:" + strings.Repeat("1", 64)
if len(got.Mirrored) != 1 || got.Mirrored[0] != want {
t.Fatalf("a failed build said it mirrored %v, want [%s]", got.Mirrored, want)
}
// And a build that works says it too.
r2, workspace2 := aRepository(t, anImage, map[string]string{"modules/bus/Dockerfile": "ARG BASE\nFROM ${BASE}"})
r2.contents["modules/bus/module.json"] = anImage
r2.tree = "aaaa"
ok, err := Build(context.Background(), r2.run, &mirroring{recorded: r2, at: "registry-a:5000"},
"https://forge.invalid/catalogue.git", "modules/bus", "", workspace2, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if len(ok.Mirrored) != 1 || ok.Mirrored[0] != want {
t.Fatalf("a build said it mirrored %v, want [%s]", ok.Mirrored, want)
}
}
-30
View File
@@ -219,36 +219,6 @@ func countPrefix(m map[string][]byte, prefix string) int {
// What a build copied is said whether it worked or not: the copy is in the store either way, and a
// build that failed after copying is the one record that it is there.
func TestABuildSaysWhatItMirroredEvenWhenItFails(t *testing.T) {
// A recipe that reaches for an image nobody declared is refused — after the base was copied.
r, workspace := aRepository(t, anImage, map[string]string{
"modules/bus/Dockerfile": "ARG BASE\nFROM ${BASE}\nCOPY --from=vendor/other:1 /x /x"})
r.contents["modules/bus/module.json"] = anImage
r.tree = "aaaa"
m := &mirroring{recorded: r, at: "registry-a:5000"}
got, err := Build(context.Background(), r.run, m, "https://forge.invalid/catalogue.git", "modules/bus", "", workspace,
nil, Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatal("a recipe fetching an undeclared image was built")
}
want := "registry-a:5000/upstream/docker.io/vendor/server@sha256:" + strings.Repeat("1", 64)
if len(got.Mirrored) != 1 || got.Mirrored[0] != want {
t.Fatalf("a failed build said it mirrored %v, want [%s]", got.Mirrored, want)
}
// And a build that works says it too.
r2, workspace2 := aRepository(t, anImage, map[string]string{"modules/bus/Dockerfile": "ARG BASE\nFROM ${BASE}"})
r2.contents["modules/bus/module.json"] = anImage
r2.tree = "aaaa"
ok, err := Build(context.Background(), r2.run, &mirroring{recorded: r2, at: "registry-a:5000"},
"https://forge.invalid/catalogue.git", "modules/bus", "", workspace2, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if len(ok.Mirrored) != 1 || ok.Mirrored[0] != want {
t.Fatalf("a build said it mirrored %v, want [%s]", ok.Mirrored, want)
}
}
// held puts one image in a repository the old way and answers its index digest and the registry.
func heldUnderAModule(t *testing.T) (*aRegistryOfRepositories, Registry, string, string, map[string][]byte, func()) {
@@ -0,0 +1,135 @@
//go:build integration
package builder
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
)
func TestAnImageBuildGetsTheCredentialInTheContextAndHostNetwork(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch\nCOPY .npmrc ./", "files/x": "y"})
n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm/", Token: "t"}
npmrc := filepath.Join(workspace, "source", ".npmrc")
inContext := false
run := func(ctx context.Context, dir, name string, args ...string) (string, error) {
if name == "docker" && len(args) > 0 && args[0] == "build" {
_, err := os.Stat(npmrc)
inContext = err == nil
}
return r.run(ctx, dir, name, args...)
}
if _, err := Build(context.Background(), run, r,
"https://forge.invalid/meshboard.git", "", "", workspace, nil, n, GitCredential{}, nil); err != nil {
t.Fatalf("the build failed: %v", err)
}
var build string
for _, line := range r.ran {
if strings.HasPrefix(line, "docker build") {
build = line
}
}
if build == "" {
t.Fatal("no docker build ran")
}
if strings.Contains(build, "--secret") {
t.Fatalf("the build used a buildkit secret, which this path avoids: %s", build)
}
if !strings.Contains(build, "--network host") {
t.Fatalf("the build was not given the host network to reach the registry: %s", build)
}
// The .npmrc is written into the build context (the source tree), where a Dockerfile COPYs it, and
// removed with the tree when the build ends: a later check's container mounts the workspace (novox/hq
// issue 462).
if !inContext {
t.Fatal("the credential was not in the build context when the image was built")
}
if _, err := os.Stat(npmrc); !os.IsNotExist(err) {
t.Fatalf("the credential outlives the build in the workspace: %v", err)
}
}
func TestAnImageBuildWithoutACredentialGetsNoHostNetwork(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch", "files/x": "y"})
if _, err := Build(context.Background(), r.run, r,
"https://forge.invalid/meshboard.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil); err != nil {
t.Fatalf("the build failed: %v", err)
}
for _, line := range r.ran {
if strings.HasPrefix(line, "docker build") && (strings.Contains(line, "--secret") || strings.Contains(line, "--network host")) {
t.Fatalf("a build with no credential was still given build-network or a secret: %s", line)
}
}
tree := filepath.Join(workspace, "source")
if _, err := os.Stat(filepath.Join(tree, ".npmrc")); err == nil {
t.Fatal("an .npmrc was written into a build that has no credential")
}
}
func TestAPackageIsBuiltOnAPublicBaseAndPublishedByVersion(t *testing.T) {
r, workspace := aRepository(t, aPackage, map[string]string{
"package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`,
})
n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm/", Token: "t"}
got, err := Build(context.Background(), r.run, r,
"https://forge.invalid/mesh-sdk.git", "", "", workspace, nil, n, GitCredential{}, nil)
if err != nil {
t.Fatalf("the package did not build: %v", err)
}
if len(got.Built) != 1 || got.Built[0].Reference != "@novox/mesh-sdk@0.1.0" {
t.Fatalf("a package is published by name and version, got %+v", got.Built)
}
if len(r.images) != 0 || len(r.archives) != 0 {
t.Fatal("a package was pushed to the artifact store, which is not where packages live")
}
var ran string
for _, line := range r.ran {
if strings.HasPrefix(line, "docker run") {
ran = line
}
}
if ran == "" {
t.Fatal("nothing ran to build the package")
}
if !strings.Contains(ran, "node:22-bookworm-slim") {
t.Fatalf("a package was not built on a public base: %s", ran)
}
if !strings.Contains(ran, ":/root/.npmrc:ro") {
t.Fatalf("the credential was not mounted read-only for the publish: %s", ran)
}
}
func TestAPackageWithNoRegistryIsRefused(t *testing.T) {
r, workspace := aRepository(t, aPackage, map[string]string{
"package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`,
})
_, err := Build(context.Background(), r.run, r,
"https://forge.invalid/mesh-sdk.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatal("a package built with no registry to publish to, silently")
}
}
func TestAnImageThatDoesNotAskForTheCredentialDoesNotGetIt(t *testing.T) {
// A Dockerfile with no .npmrc reference (like the control plane's) must build clean: no .npmrc
// in its context, no host network — so its image stays deterministic and the credential does not
// leak into a build that never resolves a mesh package.
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch\nCOPY . .", "files/x": "y"})
n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm/", Token: "t"}
if _, err := Build(context.Background(), r.run, r,
"https://forge.invalid/meshboard.git", "", "", workspace, nil, n, GitCredential{}, nil); err != nil {
t.Fatalf("the build failed: %v", err)
}
for _, line := range r.ran {
if strings.HasPrefix(line, "docker build") && strings.Contains(line, "--network host") {
t.Fatalf("a build that does not ask for the credential got the host network: %s", line)
}
}
if _, err := os.Stat(filepath.Join(workspace, "source", ".npmrc")); err == nil {
t.Fatal("an .npmrc was written into a build that does not reference it")
}
}
-125
View File
@@ -1,9 +1,6 @@
package builder
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
)
@@ -67,65 +64,6 @@ func TestNpmrcDisabledUntilThereIsARegistry(t *testing.T) {
// COPY in a stage it does not publish — and the build runs on the host network so a RUN resolving the
// registry reaches it where the binding says (novox/hq ADR 0076). Not a buildkit secret, because this
// machine's docker may carry no buildx.
func TestAnImageBuildGetsTheCredentialInTheContextAndHostNetwork(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch\nCOPY .npmrc ./", "files/x": "y"})
n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm/", Token: "t"}
npmrc := filepath.Join(workspace, "source", ".npmrc")
inContext := false
run := func(ctx context.Context, dir, name string, args ...string) (string, error) {
if name == "docker" && len(args) > 0 && args[0] == "build" {
_, err := os.Stat(npmrc)
inContext = err == nil
}
return r.run(ctx, dir, name, args...)
}
if _, err := Build(context.Background(), run, r,
"https://forge.invalid/meshboard.git", "", "", workspace, nil, n, GitCredential{}, nil); err != nil {
t.Fatalf("the build failed: %v", err)
}
var build string
for _, line := range r.ran {
if strings.HasPrefix(line, "docker build") {
build = line
}
}
if build == "" {
t.Fatal("no docker build ran")
}
if strings.Contains(build, "--secret") {
t.Fatalf("the build used a buildkit secret, which this path avoids: %s", build)
}
if !strings.Contains(build, "--network host") {
t.Fatalf("the build was not given the host network to reach the registry: %s", build)
}
// The .npmrc is written into the build context (the source tree), where a Dockerfile COPYs it, and
// removed with the tree when the build ends: a later check's container mounts the workspace (novox/hq
// issue 462).
if !inContext {
t.Fatal("the credential was not in the build context when the image was built")
}
if _, err := os.Stat(npmrc); !os.IsNotExist(err) {
t.Fatalf("the credential outlives the build in the workspace: %v", err)
}
}
func TestAnImageBuildWithoutACredentialGetsNoHostNetwork(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch", "files/x": "y"})
if _, err := Build(context.Background(), r.run, r,
"https://forge.invalid/meshboard.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil); err != nil {
t.Fatalf("the build failed: %v", err)
}
for _, line := range r.ran {
if strings.HasPrefix(line, "docker build") && (strings.Contains(line, "--secret") || strings.Contains(line, "--network host")) {
t.Fatalf("a build with no credential was still given build-network or a secret: %s", line)
}
}
tree := filepath.Join(workspace, "source")
if _, err := os.Stat(filepath.Join(tree, ".npmrc")); err == nil {
t.Fatal("an .npmrc was written into a build that has no credential")
}
}
const aPackage = `{"module":"mesh-sdk","version":"1",
"build":{"artifacts":[{"name":"lib","kind":"package","language":"typescript"}]},
@@ -133,49 +71,6 @@ const aPackage = `{"module":"mesh-sdk","version":"1",
// A package is compiled on a public base and published to the mesh's package registry by version,
// with nothing pushed to the artifact store and the credential mounted, not baked (novox/hq ADR 0076).
func TestAPackageIsBuiltOnAPublicBaseAndPublishedByVersion(t *testing.T) {
r, workspace := aRepository(t, aPackage, map[string]string{
"package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`,
})
n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm/", Token: "t"}
got, err := Build(context.Background(), r.run, r,
"https://forge.invalid/mesh-sdk.git", "", "", workspace, nil, n, GitCredential{}, nil)
if err != nil {
t.Fatalf("the package did not build: %v", err)
}
if len(got.Built) != 1 || got.Built[0].Reference != "@novox/mesh-sdk@0.1.0" {
t.Fatalf("a package is published by name and version, got %+v", got.Built)
}
if len(r.images) != 0 || len(r.archives) != 0 {
t.Fatal("a package was pushed to the artifact store, which is not where packages live")
}
var ran string
for _, line := range r.ran {
if strings.HasPrefix(line, "docker run") {
ran = line
}
}
if ran == "" {
t.Fatal("nothing ran to build the package")
}
if !strings.Contains(ran, "node:22-bookworm-slim") {
t.Fatalf("a package was not built on a public base: %s", ran)
}
if !strings.Contains(ran, ":/root/.npmrc:ro") {
t.Fatalf("the credential was not mounted read-only for the publish: %s", ran)
}
}
func TestAPackageWithNoRegistryIsRefused(t *testing.T) {
r, workspace := aRepository(t, aPackage, map[string]string{
"package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`,
})
_, err := Build(context.Background(), r.run, r,
"https://forge.invalid/mesh-sdk.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatal("a package built with no registry to publish to, silently")
}
}
func TestNpmrcRendersBasicAuthWhenGivenAUserAndPassword(t *testing.T) {
n := Npmrc{
@@ -210,23 +105,3 @@ func TestNpmrcRefusesWhenGivenNeitherTokenNorPassword(t *testing.T) {
t.Fatal("a username with no password rendered an .npmrc")
}
}
func TestAnImageThatDoesNotAskForTheCredentialDoesNotGetIt(t *testing.T) {
// A Dockerfile with no .npmrc reference (like the control plane's) must build clean: no .npmrc
// in its context, no host network — so its image stays deterministic and the credential does not
// leak into a build that never resolves a mesh package.
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch\nCOPY . .", "files/x": "y"})
n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm/", Token: "t"}
if _, err := Build(context.Background(), r.run, r,
"https://forge.invalid/meshboard.git", "", "", workspace, nil, n, GitCredential{}, nil); err != nil {
t.Fatalf("the build failed: %v", err)
}
for _, line := range r.ran {
if strings.HasPrefix(line, "docker build") && strings.Contains(line, "--network host") {
t.Fatalf("a build that does not ask for the credential got the host network: %s", line)
}
}
if _, err := os.Stat(filepath.Join(workspace, "source", ".npmrc")); err == nil {
t.Fatal("an .npmrc was written into a build that does not reference it")
}
}
+2
View File
@@ -1,3 +1,5 @@
//go:build integration
package builder
import (
@@ -0,0 +1,93 @@
//go:build integration
package builder
import (
"context"
"strings"
"testing"
)
func TestABuildNeedingTheRegistryIsRefusedOnAnAgentWithoutOne(t *testing.T) {
none := Npmrc{Machine: "workstation"}
cases := map[string]struct {
manifest string
files map[string]string
held map[string]string
}{
"a package": {aPackage, map[string]string{"package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`}, nil},
"an image naming .npmrc": {withBoth, map[string]string{
"Dockerfile": "FROM scratch\nCOPY .npmrc /root/.npmrc\n", "files/x": "y"}, nil},
"a TypeScript bundle depending on the SDK": {aBundle, map[string]string{
"index.ts": "console.log(1)", "package.json": `{"dependencies":{"@novox/mesh-sdk":"^0.1.0"}}`},
map[string]string{"mesh-tools/build": "registry.invalid/mesh-tools/build@sha256:" + strings.Repeat("b", 64)}},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
r, workspace := aRepository(t, c.manifest, c.files)
var said []string
_, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/m.git", "", "", workspace, c.held, none, GitCredential{},
func(step, message string) { said = append(said, step+": "+message) })
if err == nil {
t.Fatal("built with no package registry")
}
for _, want := range []string{"workstation", "npm-package-registry", "unbound", "gitea"} {
if !strings.Contains(err.Error(), want) {
t.Errorf("the refusal does not say %q: %v", want, err)
}
}
for _, line := range r.ran {
if strings.HasPrefix(line, "docker build") || strings.HasPrefix(line, "docker run") ||
strings.HasPrefix(line, "docker pull") {
t.Errorf("something was built after the refusal: %s", line)
}
}
if len(r.images) != 0 || len(r.archives) != 0 {
t.Errorf("something was published after the refusal: %v %v", r.images, r.archives)
}
if !strings.Contains(strings.Join(said, "\n"), "REFUSED") {
t.Errorf("the build's log does not say it was refused:\n%s", strings.Join(said, "\n"))
}
})
}
// A Go artifact builds on the same agent.
files := aSharedRepository("one", "1")
manifest := `{"module":"ctl","version":"1","build":{"artifacts":[
{"name":"controller","kind":"bundle","language":"go","system":"arch","from":"cmd/ctl","binary":"ctl"}]},
"resources":[{"id":"controller","type":"process","name":"ctl","artifact":"controller","run":["./ctl"]}]}`
r := &recorded{contents: map[string]string{ManifestName: manifest}}
for k, v := range files {
r.contents[k] = v
}
held := map[string]string{"mesh-tools-go/build": "registry.invalid/mesh-tools-go/build@sha256:" + strings.Repeat("b", 64)}
if _, err := Build(context.Background(), onTrunk{recorded: r}.run, r,
"https://forge.invalid/ctl.git", "", "", t.TempDir(), held, none, GitCredential{}, nil); err != nil {
t.Fatalf("a Go artifact did not build on an agent without the registry: %v", err)
}
// And an image that does not name .npmrc.
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch\nCOPY . .", "files/x": "y"})
if _, err := Build(context.Background(), r.run, r,
"https://forge.invalid/meshboard.git", "", "", workspace, nil, none, GitCredential{}, nil); err != nil {
t.Fatalf("an image not naming .npmrc did not build on an agent without the registry: %v", err)
}
}
func TestABuildSaysWhichRegistryItResolvedFrom(t *testing.T) {
for _, from := range []string{FromBinding, FromGenesis} {
r, workspace := aRepository(t, aPackage, map[string]string{
"package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`,
})
n := Npmrc{Scope: "@novox", Registry: "http://127.0.0.1:3000/api/packages/novox/npm/",
Username: "mesh-builder", Password: "p", From: from, Machine: "anchor"}
var said []string
if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/mesh-sdk.git", "", "",
workspace, nil, n, GitCredential{}, func(step, message string) { said = append(said, step+": "+message) }); err != nil {
t.Fatalf("%s: %v", from, err)
}
if !strings.Contains(strings.Join(said, "\n"), "from "+from) {
t.Errorf("the log does not say the registry came from %s:\n%s", from, strings.Join(said, "\n"))
}
}
}
-84
View File
@@ -1,10 +1,8 @@
package builder
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"github.com/novox/mesh-controller/internal/beside"
@@ -17,90 +15,8 @@ import (
// with an `@novox/` dependency each fail before anything is built or published, naming the machine,
// the unbound requirement and the module that provides it. A Go artifact, and an image that does not
// name .npmrc, build there as before.
func TestABuildNeedingTheRegistryIsRefusedOnAnAgentWithoutOne(t *testing.T) {
none := Npmrc{Machine: "workstation"}
cases := map[string]struct {
manifest string
files map[string]string
held map[string]string
}{
"a package": {aPackage, map[string]string{"package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`}, nil},
"an image naming .npmrc": {withBoth, map[string]string{
"Dockerfile": "FROM scratch\nCOPY .npmrc /root/.npmrc\n", "files/x": "y"}, nil},
"a TypeScript bundle depending on the SDK": {aBundle, map[string]string{
"index.ts": "console.log(1)", "package.json": `{"dependencies":{"@novox/mesh-sdk":"^0.1.0"}}`},
map[string]string{"mesh-tools/build": "registry.invalid/mesh-tools/build@sha256:" + strings.Repeat("b", 64)}},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
r, workspace := aRepository(t, c.manifest, c.files)
var said []string
_, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/m.git", "", "", workspace, c.held, none, GitCredential{},
func(step, message string) { said = append(said, step+": "+message) })
if err == nil {
t.Fatal("built with no package registry")
}
for _, want := range []string{"workstation", "npm-package-registry", "unbound", "gitea"} {
if !strings.Contains(err.Error(), want) {
t.Errorf("the refusal does not say %q: %v", want, err)
}
}
for _, line := range r.ran {
if strings.HasPrefix(line, "docker build") || strings.HasPrefix(line, "docker run") ||
strings.HasPrefix(line, "docker pull") {
t.Errorf("something was built after the refusal: %s", line)
}
}
if len(r.images) != 0 || len(r.archives) != 0 {
t.Errorf("something was published after the refusal: %v %v", r.images, r.archives)
}
if !strings.Contains(strings.Join(said, "\n"), "REFUSED") {
t.Errorf("the build's log does not say it was refused:\n%s", strings.Join(said, "\n"))
}
})
}
// A Go artifact builds on the same agent.
files := aSharedRepository("one", "1")
manifest := `{"module":"ctl","version":"1","build":{"artifacts":[
{"name":"controller","kind":"bundle","language":"go","system":"arch","from":"cmd/ctl","binary":"ctl"}]},
"resources":[{"id":"controller","type":"process","name":"ctl","artifact":"controller","run":["./ctl"]}]}`
r := &recorded{contents: map[string]string{ManifestName: manifest}}
for k, v := range files {
r.contents[k] = v
}
held := map[string]string{"mesh-tools-go/build": "registry.invalid/mesh-tools-go/build@sha256:" + strings.Repeat("b", 64)}
if _, err := Build(context.Background(), onTrunk{recorded: r}.run, r,
"https://forge.invalid/ctl.git", "", "", t.TempDir(), held, none, GitCredential{}, nil); err != nil {
t.Fatalf("a Go artifact did not build on an agent without the registry: %v", err)
}
// And an image that does not name .npmrc.
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch\nCOPY . .", "files/x": "y"})
if _, err := Build(context.Background(), r.run, r,
"https://forge.invalid/meshboard.git", "", "", workspace, nil, none, GitCredential{}, nil); err != nil {
t.Fatalf("an image not naming .npmrc did not build on an agent without the registry: %v", err)
}
}
// **The build's log says which registry it resolved from** (novox/hq ADR 0336 §4).
func TestABuildSaysWhichRegistryItResolvedFrom(t *testing.T) {
for _, from := range []string{FromBinding, FromGenesis} {
r, workspace := aRepository(t, aPackage, map[string]string{
"package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`,
})
n := Npmrc{Scope: "@novox", Registry: "http://127.0.0.1:3000/api/packages/novox/npm/",
Username: "mesh-builder", Password: "p", From: from, Machine: "anchor"}
var said []string
if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/mesh-sdk.git", "", "",
workspace, nil, n, GitCredential{}, func(step, message string) { said = append(said, step+": "+message) }); err != nil {
t.Fatalf("%s: %v", from, err)
}
if !strings.Contains(strings.Join(said, "\n"), "from "+from) {
t.Errorf("the log does not say the registry came from %s:\n%s", from, strings.Join(said, "\n"))
}
}
}
// The refusal names the module that provides the registry; it is the catalogue's provider.
func TestTheRegistryABuildIsRefusedForNamesItsProvider(t *testing.T) {
+4 -4
View File
@@ -76,13 +76,13 @@ func TestABuildWaitsForARegistryHeldStillAndGoesOn(t *testing.T) {
waitingFor(t, 5*time.Second)
said := saying(t)
f := &fakeRegistry{}
r := Registry{Address: heldStill(t, f, 400*time.Millisecond)}
r := Registry{Address: heldStill(t, f, 100*time.Millisecond)}
body := []byte("a theme")
sum := sha256.Sum256(body)
digest := "sha256:" + hex.EncodeToString(sum[:])
if _, err := r.PublishArchive(context.Background(), "shell/config", body, digest); err != nil {
t.Fatalf("a registry refusing for 400ms failed the build: %v", err)
t.Fatalf("a registry refusing for 100ms failed the build: %v", err)
}
if string(f.blobs[digest]) != "a theme" {
t.Fatalf("the registry holds %q", f.blobs[digest])
@@ -97,7 +97,7 @@ func TestABuildWaitsForARegistryHeldStillAndGoesOn(t *testing.T) {
}
func TestABuildFailsLoudlyOnARegistryRefusingPastTheBound(t *testing.T) {
waitingFor(t, 300*time.Millisecond)
waitingFor(t, 100*time.Millisecond)
said := saying(t)
f := &fakeRegistry{}
r := Registry{Address: heldStill(t, f, -1)}
@@ -113,7 +113,7 @@ func TestABuildFailsLoudlyOnARegistryRefusingPastTheBound(t *testing.T) {
if !strings.Contains(err.Error(), "refused every connection for") || !strings.Contains(err.Error(), "connection refused") {
t.Errorf("the failure does not say it waited and was refused throughout: %v", err)
}
if waited := time.Since(started); waited < 300*time.Millisecond {
if waited := time.Since(started); waited < 100*time.Millisecond {
t.Errorf("failed after %s, before the bound", waited)
}
if log := strings.Join(said(), "\n"); !strings.Contains(log, "waits for the registry") {
+2
View File
@@ -1,3 +1,5 @@
//go:build integration
package builder
import (
+2
View File
@@ -1,3 +1,5 @@
//go:build integration
package builder
import (
+2
View File
@@ -1,3 +1,5 @@
//go:build integration
package builder
import (
+2
View File
@@ -1,3 +1,5 @@
//go:build integration
package builder
import (
+2
View File
@@ -1,3 +1,5 @@
//go:build integration
package builder
import (
@@ -0,0 +1,91 @@
//go:build integration
package builder
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestAGoBundleBuiltTwiceFromOneBuildSourceIsOneDigest(t *testing.T) {
for _, tool := range []string{"go", "git"} {
if _, err := exec.LookPath(tool); err != nil {
t.Skipf("%s is not on this machine: %v", tool, err)
}
}
chain, err := ToolchainFor("go")
if err != nil {
t.Fatal(err)
}
root := t.TempDir()
repo := filepath.Join(root, "origin")
files := map[string]string{
"go.mod": "module example.com/ctl\n\ngo 1.22\n",
"cmd/ctl/main.go": "package main\n\nfunc main() { println(\"ctl\") }\n",
"README.md": "one\n",
}
for name, body := range files {
if err := os.MkdirAll(filepath.Dir(filepath.Join(repo, name)), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(repo, name), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
git := func(dir string, args ...string) string {
t.Helper()
cmd := exec.Command("git", append([]string{"-c", "user.name=t", "-c", "user.email=t@example.invalid",
"-c", "commit.gpgsign=false", "-c", "init.defaultBranch=main"}, args...)...)
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out)
}
return strings.TrimSpace(string(out))
}
git(repo, "init", "-q")
git(repo, "add", ".")
git(repo, "commit", "-q", "-m", "one")
first := git(repo, "rev-parse", "HEAD")
if err := os.WriteFile(filepath.Join(repo, "README.md"), []byte("two\n"), 0o644); err != nil {
t.Fatal(err)
}
git(repo, "commit", "-q", "-am", "only the README")
second := git(repo, "rev-parse", "HEAD")
build := func(commit, at string) string {
t.Helper()
clone := filepath.Join(root, at)
git(root, "clone", "-q", repo, clone)
git(clone, "checkout", "-q", commit)
out := filepath.Join(clone, Out("server"), "ctl")
line := append([]string(nil), chain.Compile...)
line = append(line, "-ldflags", strings.Join(chain.LinkerFlags, " "), chain.OutputFlag, out, "./cmd/ctl")
cmd := exec.Command(line[0], line[1:]...)
cmd.Dir = clone
cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local", "GOPROXY=off", "GOWORK=off")
if b, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("%s: %v\n%s", strings.Join(line, " "), err, b)
}
body, err := os.ReadFile(out)
if err != nil {
t.Fatal(err)
}
if bytes.Contains(body, []byte(commit)) {
t.Errorf("the binary built at %s carries the commit it was built at", commit[:12])
}
sum := sha256.Sum256(body)
return hex.EncodeToString(sum[:])
}
a, b := build(first, "a"), build(second, "elsewhere/b")
if a != b {
t.Fatalf("one build source, two digests: %s at %s, %s at %s (compiled with %q)",
a[:12], first[:12], b[:12], second[:12], strings.Join(chain.Compile, " "))
}
}
-80
View File
@@ -1,11 +1,7 @@
package builder
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
@@ -19,82 +15,6 @@ import (
//
// The toolchain's own compile line is run here, as the build seat runs it inside the toolchain image: in
// two clones at two paths, of two commits that differ outside the program. One digest is wanted.
func TestAGoBundleBuiltTwiceFromOneBuildSourceIsOneDigest(t *testing.T) {
for _, tool := range []string{"go", "git"} {
if _, err := exec.LookPath(tool); err != nil {
t.Skipf("%s is not on this machine: %v", tool, err)
}
}
chain, err := ToolchainFor("go")
if err != nil {
t.Fatal(err)
}
root := t.TempDir()
repo := filepath.Join(root, "origin")
files := map[string]string{
"go.mod": "module example.com/ctl\n\ngo 1.22\n",
"cmd/ctl/main.go": "package main\n\nfunc main() { println(\"ctl\") }\n",
"README.md": "one\n",
}
for name, body := range files {
if err := os.MkdirAll(filepath.Dir(filepath.Join(repo, name)), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(repo, name), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
git := func(dir string, args ...string) string {
t.Helper()
cmd := exec.Command("git", append([]string{"-c", "user.name=t", "-c", "user.email=t@example.invalid",
"-c", "commit.gpgsign=false", "-c", "init.defaultBranch=main"}, args...)...)
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out)
}
return strings.TrimSpace(string(out))
}
git(repo, "init", "-q")
git(repo, "add", ".")
git(repo, "commit", "-q", "-m", "one")
first := git(repo, "rev-parse", "HEAD")
if err := os.WriteFile(filepath.Join(repo, "README.md"), []byte("two\n"), 0o644); err != nil {
t.Fatal(err)
}
git(repo, "commit", "-q", "-am", "only the README")
second := git(repo, "rev-parse", "HEAD")
build := func(commit, at string) string {
t.Helper()
clone := filepath.Join(root, at)
git(root, "clone", "-q", repo, clone)
git(clone, "checkout", "-q", commit)
out := filepath.Join(clone, Out("server"), "ctl")
line := append([]string(nil), chain.Compile...)
line = append(line, "-ldflags", strings.Join(chain.LinkerFlags, " "), chain.OutputFlag, out, "./cmd/ctl")
cmd := exec.Command(line[0], line[1:]...)
cmd.Dir = clone
cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local", "GOPROXY=off", "GOWORK=off")
if b, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("%s: %v\n%s", strings.Join(line, " "), err, b)
}
body, err := os.ReadFile(out)
if err != nil {
t.Fatal(err)
}
if bytes.Contains(body, []byte(commit)) {
t.Errorf("the binary built at %s carries the commit it was built at", commit[:12])
}
sum := sha256.Sum256(body)
return hex.EncodeToString(sum[:])
}
a, b := build(first, "a"), build(second, "elsewhere/b")
if a != b {
t.Fatalf("one build source, two digests: %s at %s, %s at %s (compiled with %q)",
a[:12], first[:12], b[:12], second[:12], strings.Join(chain.Compile, " "))
}
}
// The toolchain says so itself, so a reader of the compile line sees it without running a build.
func TestTheGoToolchainStampsNoVersionControl(t *testing.T) {
+336
View File
@@ -0,0 +1,336 @@
//go:build integration
package builder
import (
"reflect"
"strings"
"testing"
)
func aCheckBuilt(t *testing.T, scratch *aScratch, tree string) CheckBuilt {
t.Helper()
r, run, workspace := aModuleAt(t, tree)
return aCheckBuiltBy(t, scratch, r, run, workspace)
}
func aCheckBuiltBy(t *testing.T, scratch *aScratch, r *recorded, run Runner, workspace string) CheckBuilt {
t.Helper()
spec := CheckSpec{ID: "build-1", Owner: aPull.Owner, Repo: aPull.Repo, Number: aPull.Number,
Builds: []CheckBuildSpec{{Module: "meshboard", Repository: "https://forge.invalid/meshboard.git", Path: "mod",
Ref: "main"}}}
built := buildForTheMerge(t.Context(), run, spec, CheckBuilding{Publish: r, Scratch: scratch}, workspace, "", "",
CheckVerdict{Head: "abcdefabcdef", Trunk: "123456123456"}, GitCredential{}, nil, logging(nil))
if len(built) != 1 || built[0].NotBuilt != "" || built[0].Key == "" {
t.Fatalf("the check did not build the module for its merge: %+v", built)
}
if !dockerBuilt(r) {
t.Fatal("the check built no image")
}
for repository := range r.images {
if !strings.HasPrefix(repository, ScratchPrefix) {
t.Fatalf("the check published %s outside the scratch namespace", repository)
}
}
for repository := range r.archives {
if !strings.HasPrefix(repository, ScratchPrefix) {
t.Fatalf("the check published %s outside the scratch namespace", repository)
}
}
return built[0]
}
func TestAWalkWhoseKeyIsItsChecksBuildsNothingAndRegistersTheChecksDigest(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
checked := aCheckBuilt(t, scratch, "tree-one")
r, run, workspace := aModuleAt(t, "tree-one")
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if dockerBuilt(r) || len(r.images) > 0 || len(r.archives) > 0 {
t.Fatalf("the walk built again: ran %v, published %v %v", r.ran, r.images, r.archives)
}
if got.Reused == nil || got.Why != "" {
t.Fatalf("the walk did not say it reused the check's build: reused %+v, why %q", got.Reused, got.Why)
}
if got.ReuseKey != checked.Key {
t.Fatalf("the walk's key %s is not the check's %s", got.ReuseKey, checked.Key)
}
if len(scratch.took) != 2 {
t.Fatalf("the walk copied %v, not the check's two artifacts", scratch.took)
}
for _, made := range checked.Made {
registered := strings.Replace(made.Reference, ScratchPrefix, "", 1)
found := false
for _, b := range got.Built {
found = found || (b.Reference == registered && b.Digest == made.Digest)
}
if !found {
t.Fatalf("the walk registers %+v, not the check's %s copied", got.Built, made.Reference)
}
}
if image, _ := got.Manifest.Resources[0]["image"].(string); image != "registry.invalid/meshboard/server@sha256:"+strings.Repeat("a", 64) {
t.Fatalf("the manifest pins %q, not the check's digest in the module's namespace", image)
}
words := got.Reused.Words()
if !strings.HasPrefix(words, "reused from the check of novox/meshboard#7 at head abcdefabcdef (merged into trunk 123456123456)") ||
!strings.Contains(words, "sha256:") {
t.Fatalf("the walk says %q", words)
}
}
func TestARecordNoVerdictNamesIsRefusedAndBuilt(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
checked := aCheckBuilt(t, scratch, "tree-one")
for name, leave := range map[string][]NamedBuild{
"no verdict": nil,
"another digest": func() []NamedBuild {
n := named(checked)
n[0].Digests["server"] = "sha256:" + strings.Repeat("e", 64)
return n
}(),
"another check": func() []NamedBuild { n := named(checked); n[0].Check = "build-9"; return n }(),
} {
t.Run(name, func(t *testing.T) {
scratch.took = nil
r, run, workspace := aModuleAt(t, "tree-one")
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: leave},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if got.Reused != nil || len(scratch.took) > 0 || !dockerBuilt(r) {
t.Fatalf("a record no verdict names was taken: %+v, copied %v", got.Reused, scratch.took)
}
if !strings.Contains(got.Why, "no check verdict of the walk's pull requests at the head the forge merged names it") ||
!strings.Contains(got.Why, "at head abcdefabcdef") {
t.Fatalf("the walk says %q", got.Why)
}
})
}
}
func TestAWalkWhoseKeyDiffersBuildsAndSaysWhy(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
checked := aCheckBuilt(t, scratch, "tree-one")
r, run, workspace := aModuleAt(t, "tree-two")
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if !dockerBuilt(r) || got.Reused != nil {
t.Fatalf("a walk whose source differs did not build: reused %+v", got.Reused)
}
if !strings.Contains(got.Why, "the key differs") || !strings.Contains(got.Why, partTree) {
t.Fatalf("the walk says %q, not which part of the key differs", got.Why)
}
// And a builder of another version builds too, saying so.
withVersion(t, "builder-2")
r, run, workspace = aModuleAt(t, "tree-one")
got, err = BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if !dockerBuilt(r) || !strings.Contains(got.Why, partBuilder) {
t.Fatalf("a newer builder took the older one's build, or did not say why not: %q", got.Why)
}
// No check of the walk's pull requests built it: said.
r, run, workspace = aModuleAt(t, "tree-one")
got, _ = BuildReusing(t.Context(), run, r, Reuse{Scratch: newScratch(), Pulls: []PullRef{aPull}},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if !dockerBuilt(r) || !strings.Contains(got.Why, "no check of novox/meshboard#7 built it") {
t.Fatalf("the walk says %q", got.Why)
}
}
func TestAFailedCopyOfAChecksBuildFallsBackToBuilding(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
checked := aCheckBuilt(t, scratch, "tree-one")
scratch.failTake = true
r, run, workspace := aModuleAt(t, "tree-one")
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatalf("a failed copy failed the walk: %v", err)
}
if !dockerBuilt(r) || got.Reused != nil || len(r.images) == 0 {
t.Fatal("a failed copy did not fall back to a build")
}
if !strings.Contains(got.Why, "copying meshboard's") || !strings.Contains(got.Why, "refused the mount") {
t.Fatalf("the walk says %q", got.Why)
}
for repository := range r.images {
if strings.HasPrefix(repository, ScratchPrefix) {
t.Fatalf("the walk's own build went into the scratch namespace: %s", repository)
}
}
}
func TestOnlyAWalkOfATrunkCommitTakesAChecksBuild(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
checked := aCheckBuilt(t, scratch, "tree-one")
r, run, workspace := aModuleAt(t, "tree-one")
if _, err := Build(t.Context(), run, r, "https://forge.invalid/meshboard.git", "mod", "main", workspace, nil,
Npmrc{}, GitCredential{}, nil); err != nil {
t.Fatal(err)
}
if !dockerBuilt(r) || len(scratch.took) > 0 {
t.Fatal("a build not asked to reuse took a check's build")
}
r, _, workspace = aModuleAt(t, "tree-one") // no trunk said: off it
got, err := BuildReusing(t.Context(), r.run, r, Reuse{Scratch: scratch, Named: named(checked)}, "https://forge.invalid/meshboard.git",
"mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if !dockerBuilt(r) || got.Reused != nil || !strings.Contains(got.Why, "not on its trunk") {
t.Fatalf("a commit off the trunk took a check's build: %q", got.Why)
}
}
func TestACheckThatFailedBuildsNothingForItsMerge(t *testing.T) {
if skipsTheBuilds(CheckVerdict{Gate: &Layer{Verdict: "pass"}, Repo: &Layer{Verdict: "fail"}}) == "" {
t.Fatal("a check whose repository layer failed builds for its merge")
}
if skipsTheBuilds(CheckVerdict{Gate: &Layer{Verdict: "pass"}, Repo: &Layer{Verdict: "warning"}}) != "" {
t.Fatal("a check that passed with a warning builds nothing for its merge")
}
r, run, workspace := aModuleAt(t, "tree-one")
if built := buildForTheMerge(t.Context(), run, CheckSpec{ID: "build-1"}, CheckBuilding{Publish: r, Scratch: newScratch()},
workspace, "", "", CheckVerdict{}, GitCredential{}, nil, logging(nil)); built != nil || len(r.ran) > 0 {
t.Fatalf("a check moving no module built %v, ran %v", built, r.ran)
}
}
func TestACheckBuildsNoModuleWithoutAKeyNorWhatStandsAfterIt(t *testing.T) {
withVersion(t, "builder-1")
r, run, workspace := aModuleAt(t, "tree-one")
r.contents["mod/"+ManifestName] = `{"module":"meshboard","version":"1","build":{"artifacts":[
{"name":"mirror","kind":"upstream","from":"docker.io/library/alpine:3"}]}}`
spec := CheckSpec{ID: "build-1", Owner: "novox", Repo: "meshboard", Number: 7, Builds: []CheckBuildSpec{
{Module: "meshboard", Repository: "https://forge.invalid/meshboard.git", Path: "mod", Tier: 0},
{Module: "after", Repository: "https://forge.invalid/after.git", Tier: 1}}}
built := buildForTheMerge(t.Context(), run, spec, CheckBuilding{Publish: r, Scratch: newScratch()}, workspace, "", "",
CheckVerdict{}, GitCredential{}, nil, logging(nil))
if len(built) != 2 || !strings.Contains(built[0].NotBuilt, "named by tag") ||
!strings.Contains(built[1].NotBuilt, "stands after meshboard") {
t.Fatalf("built %+v", built)
}
if len(r.images) > 0 {
t.Fatalf("a module without a key was published: %v", r.images)
}
}
func aCheckAgain(t *testing.T, scratch *aScratch, tree string, leave []NamedBuild) (CheckBuilt, *recorded) {
t.Helper()
return aCheckOf(t, scratch, aPull, "abcdefabcdef", tree, leave)
}
func aCheckOf(t *testing.T, scratch *aScratch, pull PullRef, head, tree string, leave []NamedBuild) (CheckBuilt, *recorded) {
t.Helper()
r, run, workspace := aModuleAt(t, tree)
spec := CheckSpec{ID: "build-2", Owner: pull.Owner, Repo: pull.Repo, Number: pull.Number,
Builds: []CheckBuildSpec{{Module: "meshboard", Repository: "https://forge.invalid/meshboard.git", Path: "mod",
Ref: "main"}}}
built := buildForTheMerge(t.Context(), run, spec, CheckBuilding{Publish: r, Scratch: scratch, Named: leave},
workspace, "", "", CheckVerdict{Head: head, Trunk: "654321654321"}, GitCredential{}, nil, logging(nil))
if len(built) != 1 || built[0].NotBuilt != "" || built[0].Key == "" {
t.Fatalf("the second check did not answer the module for its merge: %+v", built)
}
return built[0], r
}
func TestASecondCheckWhoseKeyIsUnchangedBuildsNothingAndNamesTheEarlierBuild(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
first := aCheckBuilt(t, scratch, "tree-one")
again, r := aCheckAgain(t, scratch, "tree-one", named(first))
if dockerBuilt(r) || len(r.images) > 0 || len(r.archives) > 0 {
t.Fatalf("the second check built again: ran %v, published %v %v", r.ran, r.images, r.archives)
}
if again.Key != first.Key || again.Kept != "build-1" || !reflect.DeepEqual(again.Made, first.Made) {
t.Fatalf("the second check names %+v, not the first check's build %+v", again, first)
}
// The walk takes it by what the second verdict names.
leave := named(again)
leave[0].Check = again.Kept
w, run, workspace := aModuleAt(t, "tree-one")
got, err := BuildReusing(t.Context(), run, w, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: leave},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil || got.Reused == nil || dockerBuilt(w) {
t.Fatalf("the walk did not take the kept build: %v, why %q", err, got.Why)
}
}
func TestASecondCheckWhoseKeyChangedBuilds(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
first := aCheckBuilt(t, scratch, "tree-one")
again, r := aCheckAgain(t, scratch, "tree-two", named(first))
if !dockerBuilt(r) || again.Kept != "" || again.Key == first.Key {
t.Fatalf("a check whose key changed did not build: %+v", again)
}
}
func TestASecondCheckKeepsNoRecordAVerdictDoesNotName(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
first := aCheckBuilt(t, scratch, "tree-one")
for name, leave := range map[string][]NamedBuild{
"no verdict": nil,
"another digest": func() []NamedBuild {
n := named(first)
n[0].Digests["server"] = "sha256:" + strings.Repeat("e", 64)
return n
}(),
"another key": func() []NamedBuild { n := named(first); n[0].Key = "r1:" + strings.Repeat("f", 64); return n }(),
} {
t.Run(name, func(t *testing.T) {
again, r := aCheckAgain(t, scratch, "tree-one", leave)
if !dockerBuilt(r) || again.Kept != "" {
t.Fatalf("a record no verdict names was kept: %+v", again)
}
})
}
}
func TestACheckKeepsNoBuildOfAnotherHeadOrPullRequest(t *testing.T) {
withVersion(t, "builder-1")
other := PullRef{Owner: "novox", Repo: "meshboard", Number: 8}
for name, c := range map[string]struct {
pull PullRef
head string
}{
"another head": {aPull, "999999999999"},
"another pull request": {other, "abcdefabcdef"},
} {
t.Run(name, func(t *testing.T) {
scratch := newScratch()
first := aCheckBuilt(t, scratch, "tree-one")
leave := named(first)
leave[0].Pull = c.pull // as if its own verdict named it
again, r := aCheckOf(t, scratch, c.pull, c.head, "tree-one", leave)
if !dockerBuilt(r) || again.Kept != "" {
t.Fatalf("a build of %s was kept: %+v", name, again)
}
})
}
}
-313
View File
@@ -6,7 +6,6 @@ import (
"errors"
"net/http"
"net/http/httptest"
"reflect"
"slices"
"strconv"
"strings"
@@ -104,38 +103,8 @@ func dockerBuilt(r *recorded) bool {
var aPull = PullRef{Owner: "novox", Repo: "meshboard", Number: 7}
// aCheckBuilt is the check of aPull building the module from a tree, into the scratch namespace.
func aCheckBuilt(t *testing.T, scratch *aScratch, tree string) CheckBuilt {
t.Helper()
r, run, workspace := aModuleAt(t, tree)
return aCheckBuiltBy(t, scratch, r, run, workspace)
}
// aCheckBuiltBy is aCheckBuilt of the repository given.
func aCheckBuiltBy(t *testing.T, scratch *aScratch, r *recorded, run Runner, workspace string) CheckBuilt {
t.Helper()
spec := CheckSpec{ID: "build-1", Owner: aPull.Owner, Repo: aPull.Repo, Number: aPull.Number,
Builds: []CheckBuildSpec{{Module: "meshboard", Repository: "https://forge.invalid/meshboard.git", Path: "mod",
Ref: "main"}}}
built := buildForTheMerge(t.Context(), run, spec, CheckBuilding{Publish: r, Scratch: scratch}, workspace, "", "",
CheckVerdict{Head: "abcdefabcdef", Trunk: "123456123456"}, GitCredential{}, nil, logging(nil))
if len(built) != 1 || built[0].NotBuilt != "" || built[0].Key == "" {
t.Fatalf("the check did not build the module for its merge: %+v", built)
}
if !dockerBuilt(r) {
t.Fatal("the check built no image")
}
for repository := range r.images {
if !strings.HasPrefix(repository, ScratchPrefix) {
t.Fatalf("the check published %s outside the scratch namespace", repository)
}
}
for repository := range r.archives {
if !strings.HasPrefix(repository, ScratchPrefix) {
t.Fatalf("the check published %s outside the scratch namespace", repository)
}
}
return built[0]
}
// named is the walk's leave naming what a check's verdict says it built, as the controller reads it.
func named(c CheckBuilt) []NamedBuild {
@@ -159,211 +128,21 @@ func withVersion(t *testing.T, v string) {
// A merge whose reuse key equals its check's builds nothing again: the walk copies the check's build by digest
// and registers that digest.
func TestAWalkWhoseKeyIsItsChecksBuildsNothingAndRegistersTheChecksDigest(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
checked := aCheckBuilt(t, scratch, "tree-one")
r, run, workspace := aModuleAt(t, "tree-one")
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if dockerBuilt(r) || len(r.images) > 0 || len(r.archives) > 0 {
t.Fatalf("the walk built again: ran %v, published %v %v", r.ran, r.images, r.archives)
}
if got.Reused == nil || got.Why != "" {
t.Fatalf("the walk did not say it reused the check's build: reused %+v, why %q", got.Reused, got.Why)
}
if got.ReuseKey != checked.Key {
t.Fatalf("the walk's key %s is not the check's %s", got.ReuseKey, checked.Key)
}
if len(scratch.took) != 2 {
t.Fatalf("the walk copied %v, not the check's two artifacts", scratch.took)
}
for _, made := range checked.Made {
registered := strings.Replace(made.Reference, ScratchPrefix, "", 1)
found := false
for _, b := range got.Built {
found = found || (b.Reference == registered && b.Digest == made.Digest)
}
if !found {
t.Fatalf("the walk registers %+v, not the check's %s copied", got.Built, made.Reference)
}
}
if image, _ := got.Manifest.Resources[0]["image"].(string); image != "registry.invalid/meshboard/server@sha256:"+strings.Repeat("a", 64) {
t.Fatalf("the manifest pins %q, not the check's digest in the module's namespace", image)
}
words := got.Reused.Words()
if !strings.HasPrefix(words, "reused from the check of novox/meshboard#7 at head abcdefabcdef (merged into trunk 123456123456)") ||
!strings.Contains(words, "sha256:") {
t.Fatalf("the walk says %q", words)
}
}
// A record no verdict names is refused and built instead, and the walk says so: the store takes writes from anything
// on its network, so a record planted under another merge's key is never registered.
func TestARecordNoVerdictNamesIsRefusedAndBuilt(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
checked := aCheckBuilt(t, scratch, "tree-one")
for name, leave := range map[string][]NamedBuild{
"no verdict": nil,
"another digest": func() []NamedBuild {
n := named(checked)
n[0].Digests["server"] = "sha256:" + strings.Repeat("e", 64)
return n
}(),
"another check": func() []NamedBuild { n := named(checked); n[0].Check = "build-9"; return n }(),
} {
t.Run(name, func(t *testing.T) {
scratch.took = nil
r, run, workspace := aModuleAt(t, "tree-one")
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: leave},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if got.Reused != nil || len(scratch.took) > 0 || !dockerBuilt(r) {
t.Fatalf("a record no verdict names was taken: %+v, copied %v", got.Reused, scratch.took)
}
if !strings.Contains(got.Why, "no check verdict of the walk's pull requests at the head the forge merged names it") ||
!strings.Contains(got.Why, "at head abcdefabcdef") {
t.Fatalf("the walk says %q", got.Why)
}
})
}
}
// A differing key builds, and says which part differs.
func TestAWalkWhoseKeyDiffersBuildsAndSaysWhy(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
checked := aCheckBuilt(t, scratch, "tree-one")
r, run, workspace := aModuleAt(t, "tree-two")
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if !dockerBuilt(r) || got.Reused != nil {
t.Fatalf("a walk whose source differs did not build: reused %+v", got.Reused)
}
if !strings.Contains(got.Why, "the key differs") || !strings.Contains(got.Why, partTree) {
t.Fatalf("the walk says %q, not which part of the key differs", got.Why)
}
// And a builder of another version builds too, saying so.
withVersion(t, "builder-2")
r, run, workspace = aModuleAt(t, "tree-one")
got, err = BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if !dockerBuilt(r) || !strings.Contains(got.Why, partBuilder) {
t.Fatalf("a newer builder took the older one's build, or did not say why not: %q", got.Why)
}
// No check of the walk's pull requests built it: said.
r, run, workspace = aModuleAt(t, "tree-one")
got, _ = BuildReusing(t.Context(), run, r, Reuse{Scratch: newScratch(), Pulls: []PullRef{aPull}},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if !dockerBuilt(r) || !strings.Contains(got.Why, "no check of novox/meshboard#7 built it") {
t.Fatalf("the walk says %q", got.Why)
}
}
// A copy that fails is said and falls back to a build; it never fails the walk.
func TestAFailedCopyOfAChecksBuildFallsBackToBuilding(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
checked := aCheckBuilt(t, scratch, "tree-one")
scratch.failTake = true
r, run, workspace := aModuleAt(t, "tree-one")
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatalf("a failed copy failed the walk: %v", err)
}
if !dockerBuilt(r) || got.Reused != nil || len(r.images) == 0 {
t.Fatal("a failed copy did not fall back to a build")
}
if !strings.Contains(got.Why, "copying meshboard's") || !strings.Contains(got.Why, "refused the mount") {
t.Fatalf("the walk says %q", got.Why)
}
for repository := range r.images {
if strings.HasPrefix(repository, ScratchPrefix) {
t.Fatalf("the walk's own build went into the scratch namespace: %s", repository)
}
}
}
// Only a walk takes a check's build: a plain build — a check's, a dry run's, `build --ref`'s — builds, and a commit
// off the trunk never takes one.
func TestOnlyAWalkOfATrunkCommitTakesAChecksBuild(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
checked := aCheckBuilt(t, scratch, "tree-one")
r, run, workspace := aModuleAt(t, "tree-one")
if _, err := Build(t.Context(), run, r, "https://forge.invalid/meshboard.git", "mod", "main", workspace, nil,
Npmrc{}, GitCredential{}, nil); err != nil {
t.Fatal(err)
}
if !dockerBuilt(r) || len(scratch.took) > 0 {
t.Fatal("a build not asked to reuse took a check's build")
}
r, _, workspace = aModuleAt(t, "tree-one") // no trunk said: off it
got, err := BuildReusing(t.Context(), r.run, r, Reuse{Scratch: scratch, Named: named(checked)}, "https://forge.invalid/meshboard.git",
"mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if !dockerBuilt(r) || got.Reused != nil || !strings.Contains(got.Why, "not on its trunk") {
t.Fatalf("a commit off the trunk took a check's build: %q", got.Why)
}
}
// A check that did not pass builds nothing for its merge, and one asked to build nothing builds nothing.
func TestACheckThatFailedBuildsNothingForItsMerge(t *testing.T) {
if skipsTheBuilds(CheckVerdict{Gate: &Layer{Verdict: "pass"}, Repo: &Layer{Verdict: "fail"}}) == "" {
t.Fatal("a check whose repository layer failed builds for its merge")
}
if skipsTheBuilds(CheckVerdict{Gate: &Layer{Verdict: "pass"}, Repo: &Layer{Verdict: "warning"}}) != "" {
t.Fatal("a check that passed with a warning builds nothing for its merge")
}
r, run, workspace := aModuleAt(t, "tree-one")
if built := buildForTheMerge(t.Context(), run, CheckSpec{ID: "build-1"}, CheckBuilding{Publish: r, Scratch: newScratch()},
workspace, "", "", CheckVerdict{}, GitCredential{}, nil, logging(nil)); built != nil || len(r.ran) > 0 {
t.Fatalf("a check moving no module built %v, ran %v", built, r.ran)
}
}
// A module whose source does not pin its build has no key and is not built by the check; what stands after it
// is not built either.
func TestACheckBuildsNoModuleWithoutAKeyNorWhatStandsAfterIt(t *testing.T) {
withVersion(t, "builder-1")
r, run, workspace := aModuleAt(t, "tree-one")
r.contents["mod/"+ManifestName] = `{"module":"meshboard","version":"1","build":{"artifacts":[
{"name":"mirror","kind":"upstream","from":"docker.io/library/alpine:3"}]}}`
spec := CheckSpec{ID: "build-1", Owner: "novox", Repo: "meshboard", Number: 7, Builds: []CheckBuildSpec{
{Module: "meshboard", Repository: "https://forge.invalid/meshboard.git", Path: "mod", Tier: 0},
{Module: "after", Repository: "https://forge.invalid/after.git", Tier: 1}}}
built := buildForTheMerge(t.Context(), run, spec, CheckBuilding{Publish: r, Scratch: newScratch()}, workspace, "", "",
CheckVerdict{}, GitCredential{}, nil, logging(nil))
if len(built) != 2 || !strings.Contains(built[0].NotBuilt, "named by tag") ||
!strings.Contains(built[1].NotBuilt, "stands after meshboard") {
t.Fatalf("built %+v", built)
}
if len(r.images) > 0 {
t.Fatalf("a module without a key was published: %v", r.images)
}
}
// What the key names: every input a fresh build could differ by.
func TestTheReuseKeyNamesEveryInputOfTheBuild(t *testing.T) {
@@ -642,86 +421,16 @@ func TestABuildTwoChecksShareGoesWithTheLastOfThem(t *testing.T) {
// aCheckAgain is a later check (build-2) of aPull building the module from a tree, with the builds the build seat's
// verdicts name for it (novox/hq issue 703).
func aCheckAgain(t *testing.T, scratch *aScratch, tree string, leave []NamedBuild) (CheckBuilt, *recorded) {
t.Helper()
return aCheckOf(t, scratch, aPull, "abcdefabcdef", tree, leave)
}
// aCheckOf is a later check (build-2) of a pull request at a head, the trunk moved since build-1.
func aCheckOf(t *testing.T, scratch *aScratch, pull PullRef, head, tree string, leave []NamedBuild) (CheckBuilt, *recorded) {
t.Helper()
r, run, workspace := aModuleAt(t, tree)
spec := CheckSpec{ID: "build-2", Owner: pull.Owner, Repo: pull.Repo, Number: pull.Number,
Builds: []CheckBuildSpec{{Module: "meshboard", Repository: "https://forge.invalid/meshboard.git", Path: "mod",
Ref: "main"}}}
built := buildForTheMerge(t.Context(), run, spec, CheckBuilding{Publish: r, Scratch: scratch, Named: leave},
workspace, "", "", CheckVerdict{Head: head, Trunk: "654321654321"}, GitCredential{}, nil, logging(nil))
if len(built) != 1 || built[0].NotBuilt != "" || built[0].Key == "" {
t.Fatalf("the second check did not answer the module for its merge: %+v", built)
}
return built[0], r
}
// A check asked again when only the trunk moved, whose key is unchanged, builds nothing: it keeps the build an
// earlier verdict names and names it by key and digest, so the walk can take it (novox/hq issue 703).
func TestASecondCheckWhoseKeyIsUnchangedBuildsNothingAndNamesTheEarlierBuild(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
first := aCheckBuilt(t, scratch, "tree-one")
again, r := aCheckAgain(t, scratch, "tree-one", named(first))
if dockerBuilt(r) || len(r.images) > 0 || len(r.archives) > 0 {
t.Fatalf("the second check built again: ran %v, published %v %v", r.ran, r.images, r.archives)
}
if again.Key != first.Key || again.Kept != "build-1" || !reflect.DeepEqual(again.Made, first.Made) {
t.Fatalf("the second check names %+v, not the first check's build %+v", again, first)
}
// The walk takes it by what the second verdict names.
leave := named(again)
leave[0].Check = again.Kept
w, run, workspace := aModuleAt(t, "tree-one")
got, err := BuildReusing(t.Context(), run, w, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: leave},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil || got.Reused == nil || dockerBuilt(w) {
t.Fatalf("the walk did not take the kept build: %v, why %q", err, got.Why)
}
}
// A check whose key changed builds, as before.
func TestASecondCheckWhoseKeyChangedBuilds(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
first := aCheckBuilt(t, scratch, "tree-one")
again, r := aCheckAgain(t, scratch, "tree-two", named(first))
if !dockerBuilt(r) || again.Kept != "" || again.Key == first.Key {
t.Fatalf("a check whose key changed did not build: %+v", again)
}
}
// A scratch record no verdict names is never kept: the store takes writes from anything on its network, so a
// check builds rather than name a planted record (novox/hq issue 703, ADR 0350 §2).
func TestASecondCheckKeepsNoRecordAVerdictDoesNotName(t *testing.T) {
withVersion(t, "builder-1")
scratch := newScratch()
first := aCheckBuilt(t, scratch, "tree-one")
for name, leave := range map[string][]NamedBuild{
"no verdict": nil,
"another digest": func() []NamedBuild {
n := named(first)
n[0].Digests["server"] = "sha256:" + strings.Repeat("e", 64)
return n
}(),
"another key": func() []NamedBuild { n := named(first); n[0].Key = "r1:" + strings.Repeat("f", 64); return n }(),
} {
t.Run(name, func(t *testing.T) {
again, r := aCheckAgain(t, scratch, "tree-one", leave)
if !dockerBuilt(r) || again.Kept != "" {
t.Fatalf("a record no verdict names was kept: %+v", again)
}
})
}
}
// A build a later check kept stays when the check that built it goes: its artifacts and its record, which the later
// check's verdict names (novox/hq issue 703, ADR 0350 §5).
@@ -767,25 +476,3 @@ func TestABuildALaterCheckKeptStaysWhenTheCheckThatBuiltItGoes(t *testing.T) {
// Only an earlier check of the same pull request at the same head is kept from: another head of it, or another pull
// request whose verdict names the same key and digests, builds (novox/hq issue 703).
func TestACheckKeepsNoBuildOfAnotherHeadOrPullRequest(t *testing.T) {
withVersion(t, "builder-1")
other := PullRef{Owner: "novox", Repo: "meshboard", Number: 8}
for name, c := range map[string]struct {
pull PullRef
head string
}{
"another head": {aPull, "999999999999"},
"another pull request": {other, "abcdefabcdef"},
} {
t.Run(name, func(t *testing.T) {
scratch := newScratch()
first := aCheckBuilt(t, scratch, "tree-one")
leave := named(first)
leave[0].Pull = c.pull // as if its own verdict named it
again, r := aCheckOf(t, scratch, c.pull, c.head, "tree-one", leave)
if !dockerBuilt(r) || again.Kept != "" {
t.Fatalf("a build of %s was kept: %+v", name, again)
}
})
}
}
+2
View File
@@ -1,3 +1,5 @@
//go:build integration
package builder
import (
@@ -0,0 +1,44 @@
//go:build integration
package builder
import (
"context"
"strings"
"testing"
)
func fingerprintOf(t *testing.T, manifest, tree string, mirrored string) string {
t.Helper()
r, workspace := aRepository(t, manifest, map[string]string{"modules/bus/Dockerfile": "ARG BASE\nFROM ${BASE}"})
r.contents["modules/bus/module.json"] = manifest
r.tree = tree
m := &mirroring{recorded: r, at: mirrored}
got, err := Build(context.Background(), r.run, m, "https://forge.invalid/catalogue.git", "modules/bus", "", workspace,
nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
return got.Source
}
func TestASourceFingerprintNamesWhatABuildWasMadeFrom(t *testing.T) {
one := fingerprintOf(t, anImage, "aaaa", "registry-a:5000")
if !strings.HasPrefix(one, sourcePrefix) {
t.Fatalf("no fingerprint: %q", one)
}
// The same tree and base, the base copied to another registry address: one source.
if again := fingerprintOf(t, anImage, "aaaa", "registry-b:5000"); again != one {
t.Fatalf("one source has two fingerprints: %s %s", one, again)
}
// The module's tree changed: another source.
if changed := fingerprintOf(t, anImage, "bbbb", "registry-a:5000"); changed == one {
t.Fatal("a changed tree kept its fingerprint")
}
// The base moved: another source.
moved := strings.Replace(anImage, "1111111111111111111111111111111111111111111111111111111111111111",
"2222222222222222222222222222222222222222222222222222222222222222", 1)
if changed := fingerprintOf(t, moved, "aaaa", "registry-a:5000"); changed == one {
t.Fatal("a moved base kept its fingerprint")
}
}
-35
View File
@@ -14,20 +14,6 @@ const anImage = `{"module":"bus","version":"1",
"artifacts":[{"name":"server","kind":"image","from":"Dockerfile"}]},
"resources":[{"id":"svc","type":"container","name":"bus","artifact":"server"}]}`
func fingerprintOf(t *testing.T, manifest, tree string, mirrored string) string {
t.Helper()
r, workspace := aRepository(t, manifest, map[string]string{"modules/bus/Dockerfile": "ARG BASE\nFROM ${BASE}"})
r.contents["modules/bus/module.json"] = manifest
r.tree = tree
m := &mirroring{recorded: r, at: mirrored}
got, err := Build(context.Background(), r.run, m, "https://forge.invalid/catalogue.git", "modules/bus", "", workspace,
nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
return got.Source
}
// mirroring is a store that copies a vendor's image into the mesh's registry, at an address a test says.
type mirroring struct {
*recorded
@@ -39,27 +25,6 @@ func (m *mirroring) MirrorImage(_ context.Context, from, repository string) (str
return m.at + "/" + repository + "@" + digest, nil
}
func TestASourceFingerprintNamesWhatABuildWasMadeFrom(t *testing.T) {
one := fingerprintOf(t, anImage, "aaaa", "registry-a:5000")
if !strings.HasPrefix(one, sourcePrefix) {
t.Fatalf("no fingerprint: %q", one)
}
// The same tree and base, the base copied to another registry address: one source.
if again := fingerprintOf(t, anImage, "aaaa", "registry-b:5000"); again != one {
t.Fatalf("one source has two fingerprints: %s %s", one, again)
}
// The module's tree changed: another source.
if changed := fingerprintOf(t, anImage, "bbbb", "registry-a:5000"); changed == one {
t.Fatal("a changed tree kept its fingerprint")
}
// The base moved: another source.
moved := strings.Replace(anImage, "1111111111111111111111111111111111111111111111111111111111111111",
"2222222222222222222222222222222222222222222222222222222222222222", 1)
if changed := fingerprintOf(t, moved, "aaaa", "registry-a:5000"); changed == one {
t.Fatal("a moved base kept its fingerprint")
}
}
func TestABuildTheRegistryDecidesHasNoFingerprint(t *testing.T) {
s := newSourceInputs("app")
s.tree = "aaaa"
@@ -0,0 +1,103 @@
//go:build integration
package builder
import (
"context"
"crypto/sha256"
"encoding/hex"
"strings"
"testing"
"github.com/novox/mesh-controller/internal/catalogue"
)
func TestABuildReadsEachTemplateBackFromItsArchive(t *testing.T) {
r, workspace := aRepository(t, templatedModule, templatedFiles)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "", workspace, nil,
Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if text := contentOf(t, got.Manifest, "conf"); text != templatedFiles["files/meshboard.conf"] {
t.Fatalf("the template's text is %q, not the file's", text)
}
if text := contentOf(t, got.Manifest, "deeper"); text != templatedFiles["files/a/deeper.conf"] {
t.Fatalf("the template's text is %q, not the file's", text)
}
if r.archives["meshboard/files"] == "" {
t.Fatalf("the templates archive was not published: %v", r.archives)
}
if problem := catalogue.HeldTemplateProblem(got.Manifest); problem != "" {
t.Fatal(problem)
}
}
func TestABuildWhoseTemplateIsMissingFailsNamingIt(t *testing.T) {
files := map[string]string{}
for k, v := range templatedFiles {
files[k] = v
}
delete(files, "files/a/deeper.conf")
r, workspace := aRepository(t, templatedModule, files)
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "", workspace, nil,
Npmrc{}, GitCredential{}, nil)
if err == nil || !strings.Contains(err.Error(), "files/a/deeper.conf") {
t.Fatalf("a build whose template is missing did not fail naming it: %v", err)
}
}
func TestAWalkTakingAChecksBuildReadsTheTemplatesBackToo(t *testing.T) {
withVersion(t, "builder-1")
contents := map[string]string{"mod/" + ManifestName: templatedModule}
for k, v := range templatedFiles {
contents["mod/"+k] = v
}
scratch := newScratch()
r, run, workspace := aModuleWith(t, "tree-one", contents)
checked := aCheckBuiltBy(t, scratch, r, run, workspace)
r, run, workspace = aModuleWith(t, "tree-one", contents)
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if got.Reused == nil || len(r.archives) > 0 {
t.Fatalf("the walk built again: reused %+v, why %q, published %v", got.Reused, got.Why, r.archives)
}
if text := contentOf(t, got.Manifest, "conf"); text != templatedFiles["files/meshboard.conf"] {
t.Fatalf("the template's text is %q, not the file's", text)
}
}
func TestABuildReadsEachTemplateBackFromTheTemplatesDirectoryItsManifestNames(t *testing.T) {
files := map[string]string{"files/bin/meshboard-hook": "#!/bin/sh\n", "templates/meshboard.conf": "listen = 8080\n"}
r, workspace := aRepository(t, unpackingModule, files)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "", workspace, nil,
Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if text := contentOf(t, got.Manifest, "conf"); text != files["templates/meshboard.conf"] {
t.Fatalf("the template's text is %q, not the file's", text)
}
if got.Manifest.Templates != "" {
t.Fatalf("the built manifest still names its templates directory: %q", got.Manifest.Templates)
}
// files/ packs exactly as it would with no templates beside it.
alone, err := pack(writeTree(t, t.TempDir(), map[string]string{"bin/meshboard-hook": "#!/bin/sh\n"}))
if err != nil {
t.Fatal(err)
}
sum := sha256.Sum256(alone)
packed := ""
for _, b := range got.Built {
if b.Name == "scripts" {
packed = b.Digest
}
}
if packed != "sha256:"+hex.EncodeToString(sum[:]) {
t.Fatalf("files/ packed to %q, not to what it packs to alone", packed)
}
}
-92
View File
@@ -1,9 +1,6 @@
package builder
import (
"context"
"crypto/sha256"
"encoding/hex"
"os"
"path/filepath"
"strings"
@@ -48,66 +45,8 @@ func contentOf(t *testing.T, m catalogue.Manifest, id string) string {
return ""
}
func TestABuildReadsEachTemplateBackFromItsArchive(t *testing.T) {
r, workspace := aRepository(t, templatedModule, templatedFiles)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "", workspace, nil,
Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if text := contentOf(t, got.Manifest, "conf"); text != templatedFiles["files/meshboard.conf"] {
t.Fatalf("the template's text is %q, not the file's", text)
}
if text := contentOf(t, got.Manifest, "deeper"); text != templatedFiles["files/a/deeper.conf"] {
t.Fatalf("the template's text is %q, not the file's", text)
}
if r.archives["meshboard/files"] == "" {
t.Fatalf("the templates archive was not published: %v", r.archives)
}
if problem := catalogue.HeldTemplateProblem(got.Manifest); problem != "" {
t.Fatal(problem)
}
}
func TestABuildWhoseTemplateIsMissingFailsNamingIt(t *testing.T) {
files := map[string]string{}
for k, v := range templatedFiles {
files[k] = v
}
delete(files, "files/a/deeper.conf")
r, workspace := aRepository(t, templatedModule, files)
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "", workspace, nil,
Npmrc{}, GitCredential{}, nil)
if err == nil || !strings.Contains(err.Error(), "files/a/deeper.conf") {
t.Fatalf("a build whose template is missing did not fail naming it: %v", err)
}
}
// A walk that takes its check's build copies the archive by digest and builds nothing: the templates are read back
// from the tree packed again, which is the archive only when it packs to the digest taken.
func TestAWalkTakingAChecksBuildReadsTheTemplatesBackToo(t *testing.T) {
withVersion(t, "builder-1")
contents := map[string]string{"mod/" + ManifestName: templatedModule}
for k, v := range templatedFiles {
contents["mod/"+k] = v
}
scratch := newScratch()
r, run, workspace := aModuleWith(t, "tree-one", contents)
checked := aCheckBuiltBy(t, scratch, r, run, workspace)
r, run, workspace = aModuleWith(t, "tree-one", contents)
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if got.Reused == nil || len(r.archives) > 0 {
t.Fatalf("the walk built again: reused %+v, why %q, published %v", got.Reused, got.Why, r.archives)
}
if text := contentOf(t, got.Manifest, "conf"); text != templatedFiles["files/meshboard.conf"] {
t.Fatalf("the template's text is %q, not the file's", text)
}
}
// The tree packed again must be the archive taken; text read from anything else is not the archive's.
func TestTemplateTextsAreRefusedWhenTheTreeDoesNotPackToTheDigestTaken(t *testing.T) {
@@ -148,34 +87,3 @@ const unpackingModule = `{"module":"meshboard","version":"1",
"resources":[
{"id":"scripts","type":"archive","path":"/usr/local/lib/meshboard","artifact":"scripts"},
{"id":"conf","type":"file","path":"/etc/meshboard/meshboard.conf","template":"templates/meshboard.conf"}]}`
func TestABuildReadsEachTemplateBackFromTheTemplatesDirectoryItsManifestNames(t *testing.T) {
files := map[string]string{"files/bin/meshboard-hook": "#!/bin/sh\n", "templates/meshboard.conf": "listen = 8080\n"}
r, workspace := aRepository(t, unpackingModule, files)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "", workspace, nil,
Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if text := contentOf(t, got.Manifest, "conf"); text != files["templates/meshboard.conf"] {
t.Fatalf("the template's text is %q, not the file's", text)
}
if got.Manifest.Templates != "" {
t.Fatalf("the built manifest still names its templates directory: %q", got.Manifest.Templates)
}
// files/ packs exactly as it would with no templates beside it.
alone, err := pack(writeTree(t, t.TempDir(), map[string]string{"bin/meshboard-hook": "#!/bin/sh\n"}))
if err != nil {
t.Fatal(err)
}
sum := sha256.Sum256(alone)
packed := ""
for _, b := range got.Built {
if b.Name == "scripts" {
packed = b.Digest
}
}
if packed != "sha256:"+hex.EncodeToString(sum[:]) {
t.Fatalf("files/ packed to %q, not to what it packs to alone", packed)
}
}
+2 -1
View File
@@ -41,7 +41,8 @@ const maxUpstream = 512 << 20
// maxEntries is the most entries an upstream archive may hold: millions of empty ones would fill the build
// seat's inodes and the node's. Betterbird's release holds 64.
const maxEntries = 100_000
// It is a variable so that a test can lower it instead of writing a hundred thousand headers.
var maxEntries = 100_000
// noDowngrade refuses a redirect off https: the digest still guards the bytes, and a fetch in the clear says
// to whoever is on the path what the build seat fetches.
@@ -0,0 +1,180 @@
//go:build integration
package builder
import (
"archive/tar"
"bytes"
"context"
"os/exec"
"path/filepath"
"sort"
"strings"
"testing"
"time"
"github.com/novox/mesh-controller/internal/catalogue"
)
func TestAnArchivePinnedByURLIsFetchedCheckedAndPackedWithoutItsOwnDirectory(t *testing.T) {
body := release(t)
v := &vendor{files: map[string][]byte{"/release.tar.gz": body}}
base := serveVendor(t, v)
r, workspace := aRepository(t, pinnedManifest(base, sumHex(body)), nil)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/catalogue.git", "", "", workspace, nil,
Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
digest, _ := got.Manifest.Resources[0]["digest"].(string)
if !strings.HasPrefix(digest, "sha256:") || r.archives["betterbird/archive"] != digest {
t.Fatalf("the release was not published and pinned as the module's archive: %v, published %v",
got.Manifest.Resources[0], r.archives)
}
packed, err := upstreamArchive(context.Background(), workspace, catalogue.Artifact{Name: "archive", Kind: "archive",
URL: base + "/release.tar.gz", SHA256: sumHex(body), Version: "153.4.0esr-bb10", Within: "betterbird"},
func(string, string, ...any) {})
if err != nil {
t.Fatal(err)
}
if "sha256:"+sumHex(packed) != digest {
t.Fatalf("a second packing of one pin gave another digest: %s and %s", digest, sumHex(packed))
}
entries := entriesOf(t, packed)
want := map[string]string{
"application.ini": "[App]\nVersion=153.4.0\n -rw-r--r--",
"betterbird": "#!/bin/sh\n -rwxr-xr-x",
"defaults/pref/channel-prefs.js": "pref(\"app.update.channel\", \"default\");\n -rw-r--r--",
}
if len(entries) != len(want) {
t.Fatalf("the archive holds %v, want %v", entries, want)
}
for name, w := range want {
if entries[name] != w {
t.Errorf("%s is %q in the archive, want %q", name, entries[name], w)
}
}
}
func TestAnUpstreamFileIsFetchedOnceAndKeptByItsDigest(t *testing.T) {
// The vendor of an add-on serves only its newest version at one address: a pin that was fetched once
// must still build after the address moved on.
body := release(t)
v := &vendor{files: map[string][]byte{"/release.tar.gz": body}}
base := serveVendor(t, v)
r, workspace := aRepository(t, pinnedManifest(base, sumHex(body)), nil)
first, err := Build(context.Background(), r.run, r, "https://forge.invalid/catalogue.git", "", "", workspace, nil,
Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
v.files["/release.tar.gz"] = []byte("the vendor's next release")
second, err := Build(context.Background(), r.run, r, "https://forge.invalid/catalogue.git", "", "", workspace, nil,
Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatalf("a pin fetched before failed to build once the address served something else: %v", err)
}
if v.asked != 1 {
t.Fatalf("the vendor was asked %d times for one pin, want once", v.asked)
}
if first.Manifest.Resources[0]["digest"] != second.Manifest.Resources[0]["digest"] {
t.Fatal("two builds of one pin gave two digests")
}
}
func TestAnUpstreamFileWhoseDownloadDoesNotMatchItsPinFailsAndNamesBothDigests(t *testing.T) {
pinned := release(t)
served := gzipped(t, tarOf(t, []tarEntry{{name: "betterbird/betterbird", body: "not what was reviewed", mode: 0o755}}))
v := &vendor{files: map[string][]byte{"/release.tar.gz": served}}
base := serveVendor(t, v)
r, workspace := aRepository(t, pinnedManifest(base, sumHex(pinned)), nil)
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/catalogue.git", "", "", workspace, nil,
Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatal("a download that is not the pinned file was built")
}
for _, digest := range []string{sumHex(pinned), sumHex(served)} {
if !strings.Contains(err.Error(), "sha256:"+digest) {
t.Errorf("the refusal does not name sha256:%s: %v", digest, err)
}
}
if len(r.archives) != 0 {
t.Fatalf("something was published: %v", r.archives)
}
kept, _ := filepath.Glob(filepath.Join(upstreamDir(workspace), "*"))
if len(kept) != 0 {
t.Fatalf("the refused download was kept: %v", kept)
}
}
func TestAnUpstreamArchiveCompressedWithXzIsRead(t *testing.T) {
// Betterbird publishes its Linux release as .tar.xz; the build seat's image carries xz for it.
if _, err := exec.LookPath("xz"); err != nil {
t.Skip("NOT TESTED here: no xz on this machine (the build seat's image carries it)")
}
plain := tarOf(t, []tarEntry{{name: "betterbird/betterbird", body: "#!/bin/sh\n", mode: 0o755}})
cmd := exec.Command("xz", "-zc")
cmd.Stdin = bytes.NewReader(plain)
compressed, err := cmd.Output()
if err != nil {
t.Fatal(err)
}
v := &vendor{files: map[string][]byte{"/r.tar.xz": compressed}}
base := serveVendor(t, v)
packed, err := upstreamArchive(context.Background(), t.TempDir(), catalogue.Artifact{Name: "archive", Kind: "archive",
URL: base + "/r.tar.xz", SHA256: sumHex(compressed), Version: "1", Within: "betterbird"}, func(string, string, ...any) {})
if err != nil {
t.Fatal(err)
}
var names []string
for n := range entriesOf(t, packed) {
names = append(names, n)
}
sort.Strings(names)
if strings.Join(names, ",") != "betterbird" {
t.Fatalf("the xz release unpacked to %v", names)
}
}
func TestAnUpstreamTarWithAGlobalHeaderNestedWithinAndTrailingDataIsRead(t *testing.T) {
// git archive writes a pax global header; a release may unpack under a/b with a/ listed first; xz may be
// followed by bytes after the tar's end, which must be read before xz is waited for.
var raw bytes.Buffer
w := tar.NewWriter(&raw)
w.WriteHeader(&tar.Header{Typeflag: tar.TypeXGlobalHeader, Name: "pax_global_header", PAXRecords: map[string]string{"comment": "abc"}})
w.WriteHeader(&tar.Header{Name: "a/", Typeflag: tar.TypeDir, Mode: 0o755})
w.WriteHeader(&tar.Header{Name: "a/b/", Typeflag: tar.TypeDir, Mode: 0o755})
w.WriteHeader(&tar.Header{Name: "a/b/f", Typeflag: tar.TypeReg, Mode: 0o644, Size: 1})
w.Write([]byte("x"))
w.Close()
body := gzipped(t, raw.Bytes())
v := &vendor{files: map[string][]byte{"/r.tar.gz": body}}
base := serveVendor(t, v)
packed, err := upstreamArchive(context.Background(), t.TempDir(), catalogue.Artifact{Name: "a", Kind: "archive",
URL: base + "/r.tar.gz", SHA256: sumHex(body), Version: "1", Within: "a/b"}, func(string, string, ...any) {})
if err != nil {
t.Fatal(err)
}
if e := entriesOf(t, packed); len(e) != 1 || e["f"] == "" {
t.Fatalf("%v", e)
}
if _, err := exec.LookPath("xz"); err != nil {
t.Skip("NOT TESTED here: no xz")
}
// A megabyte after the tar's end: more than a pipe holds.
trailing := append(append([]byte{}, raw.Bytes()...), bytes.Repeat([]byte{0}, 1<<20)...)
cmd := exec.Command("xz", "-zc")
cmd.Stdin = bytes.NewReader(trailing)
compressed, err := cmd.Output()
if err != nil {
t.Fatal(err)
}
v.files["/r.tar.xz"] = compressed
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if _, err := upstreamArchive(ctx, t.TempDir(), catalogue.Artifact{Name: "a", Kind: "archive", URL: base + "/r.tar.xz",
SHA256: sumHex(compressed), Version: "1", Within: "a/b"}, func(string, string, ...any) {}); err != nil {
t.Fatal(err)
}
}
+3 -166
View File
@@ -15,13 +15,10 @@ import (
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"testing"
"time"
"github.com/novox/mesh-controller/internal/catalogue"
)
@@ -153,98 +150,6 @@ func pinnedManifest(base, sum string) string {
"resources":[{"id":"installation","type":"archive","path":"/opt/betterbird","artifact":"archive"}]}`
}
func TestAnArchivePinnedByURLIsFetchedCheckedAndPackedWithoutItsOwnDirectory(t *testing.T) {
body := release(t)
v := &vendor{files: map[string][]byte{"/release.tar.gz": body}}
base := serveVendor(t, v)
r, workspace := aRepository(t, pinnedManifest(base, sumHex(body)), nil)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/catalogue.git", "", "", workspace, nil,
Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
digest, _ := got.Manifest.Resources[0]["digest"].(string)
if !strings.HasPrefix(digest, "sha256:") || r.archives["betterbird/archive"] != digest {
t.Fatalf("the release was not published and pinned as the module's archive: %v, published %v",
got.Manifest.Resources[0], r.archives)
}
packed, err := upstreamArchive(context.Background(), workspace, catalogue.Artifact{Name: "archive", Kind: "archive",
URL: base + "/release.tar.gz", SHA256: sumHex(body), Version: "153.4.0esr-bb10", Within: "betterbird"},
func(string, string, ...any) {})
if err != nil {
t.Fatal(err)
}
if "sha256:"+sumHex(packed) != digest {
t.Fatalf("a second packing of one pin gave another digest: %s and %s", digest, sumHex(packed))
}
entries := entriesOf(t, packed)
want := map[string]string{
"application.ini": "[App]\nVersion=153.4.0\n -rw-r--r--",
"betterbird": "#!/bin/sh\n -rwxr-xr-x",
"defaults/pref/channel-prefs.js": "pref(\"app.update.channel\", \"default\");\n -rw-r--r--",
}
if len(entries) != len(want) {
t.Fatalf("the archive holds %v, want %v", entries, want)
}
for name, w := range want {
if entries[name] != w {
t.Errorf("%s is %q in the archive, want %q", name, entries[name], w)
}
}
}
func TestAnUpstreamFileIsFetchedOnceAndKeptByItsDigest(t *testing.T) {
// The vendor of an add-on serves only its newest version at one address: a pin that was fetched once
// must still build after the address moved on.
body := release(t)
v := &vendor{files: map[string][]byte{"/release.tar.gz": body}}
base := serveVendor(t, v)
r, workspace := aRepository(t, pinnedManifest(base, sumHex(body)), nil)
first, err := Build(context.Background(), r.run, r, "https://forge.invalid/catalogue.git", "", "", workspace, nil,
Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
v.files["/release.tar.gz"] = []byte("the vendor's next release")
second, err := Build(context.Background(), r.run, r, "https://forge.invalid/catalogue.git", "", "", workspace, nil,
Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatalf("a pin fetched before failed to build once the address served something else: %v", err)
}
if v.asked != 1 {
t.Fatalf("the vendor was asked %d times for one pin, want once", v.asked)
}
if first.Manifest.Resources[0]["digest"] != second.Manifest.Resources[0]["digest"] {
t.Fatal("two builds of one pin gave two digests")
}
}
func TestAnUpstreamFileWhoseDownloadDoesNotMatchItsPinFailsAndNamesBothDigests(t *testing.T) {
pinned := release(t)
served := gzipped(t, tarOf(t, []tarEntry{{name: "betterbird/betterbird", body: "not what was reviewed", mode: 0o755}}))
v := &vendor{files: map[string][]byte{"/release.tar.gz": served}}
base := serveVendor(t, v)
r, workspace := aRepository(t, pinnedManifest(base, sumHex(pinned)), nil)
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/catalogue.git", "", "", workspace, nil,
Npmrc{}, GitCredential{}, nil)
if err == nil {
t.Fatal("a download that is not the pinned file was built")
}
for _, digest := range []string{sumHex(pinned), sumHex(served)} {
if !strings.Contains(err.Error(), "sha256:"+digest) {
t.Errorf("the refusal does not name sha256:%s: %v", digest, err)
}
}
if len(r.archives) != 0 {
t.Fatalf("something was published: %v", r.archives)
}
kept, _ := filepath.Glob(filepath.Join(upstreamDir(workspace), "*"))
if len(kept) != 0 {
t.Fatalf("the refused download was kept: %v", kept)
}
}
func TestAnUpstreamFileThatIsNotAnArchiveIsKeptWholeUnderItsName(t *testing.T) {
xpi := []byte("PK\x03\x04 an add-on")
v := &vendor{files: map[string][]byte{"/owl.xpi": xpi}}
@@ -282,78 +187,10 @@ func TestAnUpstreamArchiveHoldingALinkOrAnEntryOutsideItsDirectoryIsRefused(t *t
}
}
func TestAnUpstreamArchiveCompressedWithXzIsRead(t *testing.T) {
// Betterbird publishes its Linux release as .tar.xz; the build seat's image carries xz for it.
if _, err := exec.LookPath("xz"); err != nil {
t.Skip("NOT TESTED here: no xz on this machine (the build seat's image carries it)")
}
plain := tarOf(t, []tarEntry{{name: "betterbird/betterbird", body: "#!/bin/sh\n", mode: 0o755}})
cmd := exec.Command("xz", "-zc")
cmd.Stdin = bytes.NewReader(plain)
compressed, err := cmd.Output()
if err != nil {
t.Fatal(err)
}
v := &vendor{files: map[string][]byte{"/r.tar.xz": compressed}}
base := serveVendor(t, v)
packed, err := upstreamArchive(context.Background(), t.TempDir(), catalogue.Artifact{Name: "archive", Kind: "archive",
URL: base + "/r.tar.xz", SHA256: sumHex(compressed), Version: "1", Within: "betterbird"}, func(string, string, ...any) {})
if err != nil {
t.Fatal(err)
}
var names []string
for n := range entriesOf(t, packed) {
names = append(names, n)
}
sort.Strings(names)
if strings.Join(names, ",") != "betterbird" {
t.Fatalf("the xz release unpacked to %v", names)
}
}
func TestAnUpstreamTarWithAGlobalHeaderNestedWithinAndTrailingDataIsRead(t *testing.T) {
// git archive writes a pax global header; a release may unpack under a/b with a/ listed first; xz may be
// followed by bytes after the tar's end, which must be read before xz is waited for.
var raw bytes.Buffer
w := tar.NewWriter(&raw)
w.WriteHeader(&tar.Header{Typeflag: tar.TypeXGlobalHeader, Name: "pax_global_header", PAXRecords: map[string]string{"comment": "abc"}})
w.WriteHeader(&tar.Header{Name: "a/", Typeflag: tar.TypeDir, Mode: 0o755})
w.WriteHeader(&tar.Header{Name: "a/b/", Typeflag: tar.TypeDir, Mode: 0o755})
w.WriteHeader(&tar.Header{Name: "a/b/f", Typeflag: tar.TypeReg, Mode: 0o644, Size: 1})
w.Write([]byte("x"))
w.Close()
body := gzipped(t, raw.Bytes())
v := &vendor{files: map[string][]byte{"/r.tar.gz": body}}
base := serveVendor(t, v)
packed, err := upstreamArchive(context.Background(), t.TempDir(), catalogue.Artifact{Name: "a", Kind: "archive",
URL: base + "/r.tar.gz", SHA256: sumHex(body), Version: "1", Within: "a/b"}, func(string, string, ...any) {})
if err != nil {
t.Fatal(err)
}
if e := entriesOf(t, packed); len(e) != 1 || e["f"] == "" {
t.Fatalf("%v", e)
}
if _, err := exec.LookPath("xz"); err != nil {
t.Skip("NOT TESTED here: no xz")
}
// A megabyte after the tar's end: more than a pipe holds.
trailing := append(append([]byte{}, raw.Bytes()...), bytes.Repeat([]byte{0}, 1<<20)...)
cmd := exec.Command("xz", "-zc")
cmd.Stdin = bytes.NewReader(trailing)
compressed, err := cmd.Output()
if err != nil {
t.Fatal(err)
}
v.files["/r.tar.xz"] = compressed
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if _, err := upstreamArchive(ctx, t.TempDir(), catalogue.Artifact{Name: "a", Kind: "archive", URL: base + "/r.tar.xz",
SHA256: sumHex(compressed), Version: "1", Within: "a/b"}, func(string, string, ...any) {}); err != nil {
t.Fatal(err)
}
}
func TestAnUpstreamArchiveWithTooManyEntriesIsRefused(t *testing.T) {
before := maxEntries
maxEntries = 100
t.Cleanup(func() { maxEntries = before })
var raw bytes.Buffer
w := tar.NewWriter(&raw)
for i := 0; i <= maxEntries; i++ {
@@ -0,0 +1,40 @@
//go:build integration
package builder
import (
"bytes"
"context"
"os"
"os/exec"
"path/filepath"
"testing"
)
func TestAnXzFileOfAFewDeltaBlocksAndTwoStreamsIsRead(t *testing.T) {
// The streams this test builds are ones the reader decodes, so what the walk refuses below is the
// same framing the reader follows.
body := bytes.Repeat([]byte("betterbird "), 300)
whole := tarHolding(t, body)
half := len(whole) / 2
for name, file := range map[string][]byte{
"one stream of delta blocks": xzStream(whole, 512, true, -1),
"two streams with padding": append(append(xzStream(whole[:half], 700, true, -1), 0, 0, 0, 0),
xzStream(whole[half:], 100, false, -1)...),
} {
path := writeTemp(t, file)
if xzPath, err := exec.LookPath("xz"); err == nil {
if out, err := exec.Command(xzPath, "-t", path).CombinedOutput(); err != nil {
t.Fatalf("%s: xz itself refuses the built stream: %v %s", name, err, out)
}
}
into := t.TempDir()
if err := unpackUpstream(context.Background(), path, into, "betterbird"); err != nil {
t.Fatalf("%s: %v", name, err)
}
got, err := os.ReadFile(filepath.Join(into, "data"))
if err != nil || !bytes.Equal(got, body) {
t.Fatalf("%s: unpacked %d bytes, want %d: %v", name, len(got), len(body), err)
}
}
}
-29
View File
@@ -9,7 +9,6 @@ import (
"errors"
"hash/crc32"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
@@ -125,34 +124,6 @@ func writeTemp(t *testing.T, body []byte) string {
return file
}
func TestAnXzFileOfAFewDeltaBlocksAndTwoStreamsIsRead(t *testing.T) {
// The streams this test builds are ones the reader decodes, so what the walk refuses below is the
// same framing the reader follows.
body := bytes.Repeat([]byte("betterbird "), 300)
whole := tarHolding(t, body)
half := len(whole) / 2
for name, file := range map[string][]byte{
"one stream of delta blocks": xzStream(whole, 512, true, -1),
"two streams with padding": append(append(xzStream(whole[:half], 700, true, -1), 0, 0, 0, 0),
xzStream(whole[half:], 100, false, -1)...),
} {
path := writeTemp(t, file)
if xzPath, err := exec.LookPath("xz"); err == nil {
if out, err := exec.Command(xzPath, "-t", path).CombinedOutput(); err != nil {
t.Fatalf("%s: xz itself refuses the built stream: %v %s", name, err, out)
}
}
into := t.TempDir()
if err := unpackUpstream(context.Background(), path, into, "betterbird"); err != nil {
t.Fatalf("%s: %v", name, err)
}
got, err := os.ReadFile(filepath.Join(into, "data"))
if err != nil || !bytes.Equal(got, body) {
t.Fatalf("%s: unpacked %d bytes, want %d: %v", name, len(got), len(body), err)
}
}
}
func TestAnXzFileOfManyDeltaBlocksIsRefusedBeforeItIsDecoded(t *testing.T) {
// Review 68 on pull request 264: `xz --block-size=1 --delta` of a 200 kB tar kept about 67 MiB live in
// the reader, one delta decoder per block. Its index here lists one block, as a file crafted to pass a