package main import ( "os" "path/filepath" "slices" "testing" "golang.org/x/mod/modfile" ) // The lockstep notes in go.mod are load-bearing prose: they are the only record // of why the pairs exist. `go mod tidy` re-sorts requires and can leave a note // stranded above the wrong line, which reads fine in a diff and is wrong. func TestNoteAttached(t *testing.T) { const attached = `module example.com/x go 1.27.0 require ( // NOTE: modernc sqlite has a fragile dependency chain: // https://github.com/juanfont/headscale/issues/2188 modernc.org/sqlite v1.52.0 pgregory.net/rapid v1.3.0 ) ` // The note is still in the file, but now documents the wrong module. const detached = `module example.com/x go 1.27.0 require ( // NOTE: modernc sqlite has a fragile dependency chain: // https://github.com/juanfont/headscale/issues/2188 pgregory.net/rapid v1.3.0 modernc.org/sqlite v1.52.0 ) ` tests := []struct { name string content string want bool }{ {name: "attached", content: attached, want: true}, {name: "detached", content: detached, want: false}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { f, err := modfile.Parse("go.mod", []byte(test.content), nil) if err != nil { t.Fatalf("parsing fixture: %v", err) } if got := noteAttached(f, "modernc.org/sqlite", "issues/2188"); got != test.want { t.Errorf("noteAttached = %v, want %v", got, test.want) } }) } } func TestRequiredVersion(t *testing.T) { const goMod = `module modernc.org/sqlite go 1.25.0 require ( modernc.org/libc v1.75.6 modernc.org/mathutil v1.7.1 ) ` f, err := modfile.Parse("go.mod", []byte(goMod), nil) if err != nil { t.Fatalf("parsing fixture: %v", err) } tests := []struct { name string dep string want string found bool }{ {name: "present", dep: "modernc.org/libc", want: "v1.75.6", found: true}, {name: "absent", dep: "gvisor.dev/gvisor", found: false}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, ok := requiredVersion(f, test.dep) if ok != test.found { t.Fatalf("requiredVersion found = %v, want %v", ok, test.found) } if got != test.want { t.Errorf("requiredVersion = %q, want %q", got, test.want) } }) } } // The repository's own go.mod is the case that actually matters: the notes must // survive whatever the last tidy did to the require blocks. func TestRepoLockstepNotesAttached(t *testing.T) { b, err := modfile.Parse("../../go.mod", mustRead(t, "../../go.mod"), nil) if err != nil { t.Fatalf("parsing go.mod: %v", err) } for _, note := range lockstepNotes { if !noteAttached(b, note.module, note.needle) { t.Errorf("go.mod: note %q is not attached to %s", note.needle, note.module) } } if len(b.Tool) == 0 { t.Error("go.mod: tool block is missing") } } // A tool's module is indirect in go.mod, but it still has to move: go generate // runs it against whatever Go the flake ships. func TestDirectRequirementsIncludesTools(t *testing.T) { const content = `module example.com/x go 1.27.0 require ( github.com/go-chi/chi/v5 v5.4.0 golang.org/x/net v0.58.0 // indirect golang.org/x/tools v0.49.0 // indirect tailscale.com v1.100.0 ) tool ( golang.org/x/tools/cmd/stringer tailscale.com/cmd/viewer ) ` dir := t.TempDir() err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(content), 0o600) if err != nil { t.Fatal(err) } got, err := directRequirements(&repo{Root: dir}) if err != nil { t.Fatal(err) } want := []string{"github.com/go-chi/chi/v5", "golang.org/x/tools"} if !slices.Equal(got, want) { t.Errorf("directRequirements = %v, want %v", got, want) } }