package calls import ( "bytes" "context" "encoding/json" "os" "os/exec" "path/filepath" "runtime" "sort" "strings" "testing" "time" "amadan.net/oficina/estilo.git/credits" "amadan.net/oficina/estilo.git/credits/catalog" ) // Check the production package graph, not the test binary's dependencies. // A dependency or notice change must update the embedded attribution too. func TestCreditsMatchProductionDependencies(t *testing.T) { // Read the embedded inventory first, and check the toolchain against // it before anything else runs. Everything below depends on this // binary being the Go the notices were generated with: // runtime.Version() goes into the expected inventory, and // runtime.GOROOT()/LICENSE is read out of it. // // Under any other toolchain the failure is real and says nothing // about credits, and it surfaces two different misleading ways. A // Homebrew keg without LICENSE at GOROOT fails the read with a bare // file error that never mentions credits at all. A keg with it gets // past that and fails the comparison instead, whose message sends // you to `make credits` — which would rewrite notices.json to name // whatever toolchain you happen to be on, burying the dependency // inventory the test exists to protect. Both cost a session a while // before this guard was here. var embedded creditInventory if err := json.Unmarshal(creditNotices, &embedded); err != nil { t.Fatal(err) } if want := embeddedGoVersion(embedded); want != "" && want != runtime.Version() { t.Fatalf("credits were generated with %s, this test is running under %s.\n"+ "Run `GOTOOLCHAIN=%s make ci`. Do not run `make credits` to fix this: it would\n"+ "rewrite notices.json to name this toolchain instead of checking the inventory.", want, runtime.Version(), want) } ctx, cancel := context.WithTimeout(t.Context(), 45*time.Second) defer cancel() command := exec.CommandContext(ctx, "go", "list", "-buildvcs=false", "-deps", "-json", "./cmd/calls") command.Dir = filepath.Join("..", "..") for _, entry := range os.Environ() { if !strings.HasPrefix(entry, "GOOS=") && !strings.HasPrefix(entry, "GOARCH=") && !strings.HasPrefix(entry, "CGO_ENABLED=") && !strings.HasPrefix(entry, "GOWORK=") { command.Env = append(command.Env, entry) } } command.Env = append(command.Env, "GOOS=linux", "GOARCH=arm64", "CGO_ENABLED=0", "GOWORK=off") raw, err := command.Output() if err != nil { t.Fatal("production dependency graph unavailable", err) } inventory, err := catalog.Collect(bytes.NewReader(raw), map[string]bool{"amadan.net/oficina/estilo.git": true, "amadan.net/oficina/home/handoff": true, "amadan.net/oficina/home/connect": true}) if err != nil { t.Fatal(err) } goLicense, err := os.ReadFile(filepath.Join(runtime.GOROOT(), "LICENSE")) if err != nil { t.Fatal(err) } inventory.Components = append(inventory.Components, credits.Lucide(), credits.Onest(), credits.Component{Name: "Go", Version: runtime.Version(), SourceURL: "https://go.dev/LICENSE", Notices: []credits.Notice{{Name: "LICENSE", Text: string(goLicense)}}}) sort.Slice(inventory.Components, func(i, j int) bool { return inventory.Components[i].Name < inventory.Components[j].Name }) expected, _ := json.Marshal(creditInventory{Components: inventory.Components, MissingNotices: inventory.MissingNotices}) actual, _ := json.Marshal(embedded) if !bytes.Equal(expected, actual) { t.Fatal("embedded credits differ from production dependencies; run make credits " + "(the toolchain is already ruled out by the check at the top of this test)") } } // embeddedGoVersion is the Go toolchain the embedded notices name, or "" // when they name none — in which case there is nothing to compare against // and the test proceeds exactly as it did before the guard existed. func embeddedGoVersion(inventory creditInventory) string { for _, component := range inventory.Components { if component.Name == "Go" { return component.Version } } return "" }