Files
jschoubben e3748bc06f One module, several languages, each bundle packed alone
A module is one piece of software and may still carry a daemon in one language,
tools in another and a package in a third. The first cut compiled every bundle
into the toolchain's single output directory, so two of them would have
overwritten each other and then been packed together — one artifact containing
both, published twice.

So output is a property of the artifact, not of the toolchain, and the toolchain
says how it is told where to write rather than where it writes. Under a directory
named for the build rather than beside the source, so a pack never sweeps up the
module's own working files.

A second toolchain is declared so the multi-language path is exercised rather
than asserted — a list with one entry cannot fail the way a list with four will.

And the fake compiler in the tests now writes where it was TOLD to. One that
always wrote to a fixed place would have passed whether or not each artifact got
its own directory, which is the whole of what these tests are for.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-15 02:09:06 +02:00

90 lines
3.2 KiB
Go

package builder
import (
"strings"
"testing"
)
// A language the mesh builds resolves to the toolchain that builds it.
func TestADeclaredLanguageSelectsItsToolchain(t *testing.T) {
chain, err := ToolchainFor("typescript")
if err != nil {
t.Fatalf("typescript is not buildable: %v", err)
}
if chain.Base == "" || chain.Artifact == "" {
t.Fatalf("a toolchain names no base to compile in: %+v", chain)
}
if len(chain.Compile) == 0 {
t.Fatalf("a toolchain says nothing about how to compile: %+v", chain)
}
}
// Case and stray whitespace are a module author's slip, not a different language.
func TestALanguageIsMatchedLoosely(t *testing.T) {
for _, said := range []string{"TypeScript", " typescript ", "TYPESCRIPT"} {
if _, err := ToolchainFor(said); err != nil {
t.Fatalf("%q was refused: %v", said, err)
}
}
}
// **A refusal says what would have worked.** A module author who names a language the mesh does
// not build is one word away from a language it does, and a bare "unsupported" makes them go
// looking for a list that exists in one place in the source.
func TestAnUnknownLanguageSaysWhatIsBuildable(t *testing.T) {
_, err := ToolchainFor("cobol")
if err == nil {
t.Fatal("a language nothing can build was accepted")
}
for _, want := range Languages() {
if !strings.Contains(err.Error(), want) {
t.Fatalf("the refusal does not mention %q, which would have worked: %v", want, err)
}
}
}
// And saying nothing is its own message: the mesh chooses the compiler, so a bundle that names no
// language has not asked for anything in particular — which is a mistake rather than a default.
func TestABundleMustSayWhatItIsWrittenIn(t *testing.T) {
_, err := ToolchainFor("")
if err == nil {
t.Fatal("a bundle with no language was accepted, so the mesh guessed a compiler")
}
if !strings.Contains(err.Error(), "must say") {
t.Fatalf("the refusal does not say a language is required: %v", err)
}
}
// **One module, several languages, and each bundle packed alone.**
//
// A module is one piece of software (ADR 0040) and may still carry a daemon in one language, tools
// in another and a package in a third. Compiling them into one output directory would have them
// overwrite each other and then be packed together, so output is a property of the artifact rather
// than of the toolchain.
func TestTwoBundlesInOneModuleDoNotShareAnOutputDirectory(t *testing.T) {
first, second := Out("daemon"), Out("tools")
if first == second {
t.Fatalf("two artifacts compile into the same place (%q), so one would overwrite the "+
"other and both would be packed together", first)
}
for _, out := range []string{first, second} {
if strings.HasPrefix(out, "/") || strings.Contains(out, "..") {
t.Fatalf("%q leaves the module's own directory", out)
}
}
}
// And the mesh can say what it builds, which is what a refusal quotes.
func TestTheMeshSaysWhichLanguagesItBuilds(t *testing.T) {
spoken := Languages()
if len(spoken) < 2 {
t.Fatalf("only %v — this test exists to keep the multi-language path real rather than "+
"theoretical", spoken)
}
for _, language := range spoken {
if _, err := ToolchainFor(language); err != nil {
t.Fatalf("%q is listed as buildable and has no toolchain: %v", language, err)
}
}
}