First of the steps in novox/hq ADR 0142, and it ships alone: a new manifest word reaches the builder and the controller one release before any manifest uses it. A toolchain deliberately accepts nothing from the module — anything a module could override there it would be writing a Dockerfile to override — and yet a compiled binary is per operating system, pinned at link time so a host refuses to touch a machine it was not built for (ADR 0005). The way out is that the target belongs to the artifact: one artifact per system, one build each, recipe still the mesh's. A bundle in a language that compiles to a binary must name a system, or it would be built for whatever the build machine happened to be — which reads as portable and is not. A bundle in a language that runs anywhere may not name one, because a system that decides nothing reads as though it did. The list is the host's own names, not a compiler's: the difference between two of them is a C library rather than a kernel. Nothing declares a system yet, and no toolchain compiles to a binary yet, so this changes no build.
83 lines
3.3 KiB
Go
83 lines
3.3 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// bundleFor is a manifest whose one artifact is a bundle in the given language and system.
|
|
func bundleFor(language, system string) Manifest {
|
|
return Manifest{Module: "a-component", Build: &Build{Artifacts: []Artifact{
|
|
{Name: "binary", Kind: ArtifactBundle, Language: language, System: system},
|
|
}}}
|
|
}
|
|
|
|
func problemsOf(t *testing.T, m Manifest) string {
|
|
t.Helper()
|
|
return strings.Join(m.Build.problems(m.Module), "\n")
|
|
}
|
|
|
|
// **A language that compiles to a binary must say which system.**
|
|
//
|
|
// A binary is pinned to one operating system at link time, so a host refuses to touch a machine it
|
|
// was not built for. An artifact that says nothing would be compiled for whatever the build machine
|
|
// happened to be — which reads as portable and is not, and is the fault this check exists for.
|
|
func TestABinaryMustSayWhichSystemItIsFor(t *testing.T) {
|
|
got := problemsOf(t, bundleFor("go", ""))
|
|
if !strings.Contains(got, "says no system") {
|
|
t.Fatalf("a compiled bundle with no system was accepted:\n%s", got)
|
|
}
|
|
// And the refusal names what it could have said, so a reader is one edit from right.
|
|
for _, system := range []string{"alpine", "android", "arch"} {
|
|
if !strings.Contains(got, system) {
|
|
t.Fatalf("the refusal does not name %q as a choice:\n%s", system, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestABinaryThatNamesASystemIsAccepted(t *testing.T) {
|
|
if got := problemsOf(t, bundleFor("go", "arch")); got != "" {
|
|
t.Fatalf("a compiled bundle naming a system was refused:\n%s", got)
|
|
}
|
|
}
|
|
|
|
// A system the mesh does not build for is refused where it is written. These are the host's own
|
|
// names, not a compiler's: the difference between two of them is a C library rather than a kernel,
|
|
// so a value that looks like an operating system to a toolchain is still wrong here.
|
|
func TestASystemTheMeshDoesNotBuildForIsRefused(t *testing.T) {
|
|
for _, wrong := range []string{"linux", "debian", "darwin"} {
|
|
got := problemsOf(t, bundleFor("go", wrong))
|
|
if !strings.Contains(got, "and a system is") {
|
|
t.Fatalf("%q was accepted as a system:\n%s", wrong, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// **And a language that runs anywhere must not name one.** A system that decides nothing reads as
|
|
// though it did, which is the same fault as a restriction that restricts nothing (novox/hq ADR 0045).
|
|
func TestAPortableBundleMayNotNameASystem(t *testing.T) {
|
|
got := problemsOf(t, bundleFor("typescript", "arch"))
|
|
if !strings.Contains(got, "runs anywhere") {
|
|
t.Fatalf("a portable bundle was allowed to name a system:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestAPortableBundleNamingNoSystemIsAccepted(t *testing.T) {
|
|
if got := problemsOf(t, bundleFor("typescript", "")); got != "" {
|
|
t.Fatalf("an ordinary bundle was refused:\n%s", got)
|
|
}
|
|
}
|
|
|
|
// One component, one artifact per system: the shape the mesh's own binaries are declared in, and the
|
|
// reason the target is the artifact's rather than the recipe's.
|
|
func TestOneArtifactPerSystemIsAccepted(t *testing.T) {
|
|
m := Manifest{Module: "the-host", Build: &Build{Artifacts: []Artifact{
|
|
{Name: "arch", Kind: ArtifactBundle, Language: "go", System: "arch"},
|
|
{Name: "alpine", Kind: ArtifactBundle, Language: "go", System: "alpine"},
|
|
{Name: "android", Kind: ArtifactBundle, Language: "go", System: "android"},
|
|
}}}
|
|
if got := problemsOf(t, m); got != "" {
|
|
t.Fatalf("one artifact per system was refused:\n%s", got)
|
|
}
|
|
}
|