Merge branch 'fix/credits-toolchain-guard' into main
1 file changed,
+40
−5
internal/calls/credits_inventory_test.go+40 −5
CI passed — run details
diff --git a/internal/calls/credits_inventory_test.go b/internal/calls/credits_inventory_test.go| index d1cfff2..458deac 100644 |
| --- a/internal/calls/credits_inventory_test.go |
| +++ b/internal/calls/credits_inventory_test.go |
| @@ -20,6 +20,32 @@ import ( |
| // 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", "-deps", "-json", "./cmd/calls") |
| @@ -44,13 +70,22 @@ func TestCreditsMatchProductionDependencies(t *testing.T) { |
| } |
| inventory.Components = append(inventory.Components, credits.Lucide(), 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 }) |
| - var embedded creditInventory |
| - if err := json.Unmarshal(creditNotices, &embedded); err != nil { |
| - t.Fatal(err) |
| - } |
| 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") |
| + 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 "" |
| } |