Derive module release identity and accept full Git tags
This commit is contained in:
178
internal/releaseidentity/identity_test.go
Normal file
178
internal/releaseidentity/identity_test.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package releaseidentity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDeriveUsesModuleDeclarationAndPhysicalLayout(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
subdir string
|
||||
modulePath string
|
||||
version string
|
||||
wantTag string
|
||||
wantNote string
|
||||
}{
|
||||
{name: "root v1", subdir: ".", modulePath: "example.com/root", version: "v1.2.3", wantTag: "v1.2.3", wantNote: "docs/releases/v1.2.3.md"},
|
||||
{name: "nested", subdir: "integrations/check", modulePath: "example.com/check", version: "v1.2.3", wantTag: "integrations/check/v1.2.3", wantNote: "integrations/check/docs/releases/v1.2.3.md"},
|
||||
{name: "root major path", subdir: ".", modulePath: "example.com/root/v2", version: "v2.3.4", wantTag: "v2.3.4", wantNote: "docs/releases/v2.3.4.md"},
|
||||
{name: "root physical major directory", subdir: "v2", modulePath: "example.com/root/v2", version: "v2.3.4", wantTag: "v2.3.4", wantNote: "v2/docs/releases/v2.3.4.md"},
|
||||
{name: "physical major directory", subdir: "integrations/check/v2", modulePath: "example.com/check/v2", version: "v2.3.4", wantTag: "integrations/check/v2.3.4", wantNote: "integrations/check/v2/docs/releases/v2.3.4.md"},
|
||||
{name: "logical major without physical directory", subdir: "integrations/check", modulePath: "example.com/check/v2", version: "v2.3.4", wantTag: "integrations/check/v2.3.4", wantNote: "integrations/check/docs/releases/v2.3.4.md"},
|
||||
{name: "ordinary directory named v2", subdir: "integrations/v2", modulePath: "example.com/check", version: "v1.2.3", wantTag: "integrations/v2/v1.2.3", wantNote: "integrations/v2/docs/releases/v1.2.3.md"},
|
||||
{name: "gopkg special major", subdir: "integrations/example.v2", modulePath: "gopkg.in/example.v2", version: "v2.3.4", wantTag: "integrations/example.v2/v2.3.4", wantNote: "integrations/example.v2/docs/releases/v2.3.4.md"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
selection := Selection{RepositoryDir: "/repo", ModuleDir: filepath.Join("/repo", filepath.FromSlash(tt.subdir)), ModuleSubdir: tt.subdir}
|
||||
if tt.subdir == "." {
|
||||
selection.ModuleDir = "/repo"
|
||||
}
|
||||
got, err := Derive(selection, []byte("module "+tt.modulePath+"\n"), tt.version)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Tag != tt.wantTag || got.NotePath != tt.wantNote {
|
||||
t.Fatalf("identity tag/note = %q, %q; want %q, %q", got.Tag, got.NotePath, tt.wantTag, tt.wantNote)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveRejectsInvalidModuleAndVersionIdentity(t *testing.T) {
|
||||
selection := Selection{RepositoryDir: "/repo", ModuleDir: "/repo", ModuleSubdir: "."}
|
||||
for _, tt := range []struct {
|
||||
name, declaration, version string
|
||||
}{
|
||||
{name: "missing module directive", declaration: "go 1.26\n", version: "v1.2.3"},
|
||||
{name: "malformed module path", declaration: "module example.com/../bad\n", version: "v1.2.3"},
|
||||
{name: "incompatible major", declaration: "module example.com/service/v2\n", version: "v1.2.3"},
|
||||
{name: "unstable version", declaration: "module example.com/service\n", version: "v1.2.3-rc.1"},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if _, err := Derive(selection, []byte(tt.declaration), tt.version); err == nil {
|
||||
t.Fatal("Derive() succeeded for invalid identity")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveSelectionNormalizesAndRejectsEscape(t *testing.T) {
|
||||
repository := t.TempDir()
|
||||
moduleDir := filepath.Join(repository, "integrations", "one")
|
||||
if err := os.MkdirAll(moduleDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
selection, err := ResolveSelection(repository, "integrations/one/../one")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if selection.ModuleSubdir != "integrations/one" || selection.ModuleDir != moduleDir {
|
||||
t.Fatalf("normalized selection = %#v", selection)
|
||||
}
|
||||
root, err := ResolveSelection(repository, "")
|
||||
if err != nil || root.ModuleSubdir != "." || root.ModuleDir != repository {
|
||||
t.Fatalf("default root selection = %#v, error = %v", root, err)
|
||||
}
|
||||
if _, err := ResolveSelection(repository, "../outside"); err == nil {
|
||||
t.Fatal("ResolveSelection() accepted a path outside the repository")
|
||||
}
|
||||
if _, err := ResolveSelection(repository, moduleDir); err == nil {
|
||||
t.Fatal("ResolveSelection() accepted an absolute module path")
|
||||
}
|
||||
outside := t.TempDir()
|
||||
link := filepath.Join(repository, "linked")
|
||||
if err := os.Symlink(outside, link); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ResolveSelection(repository, "linked"); err == nil {
|
||||
t.Fatal("ResolveSelection() accepted a symlink that resolves outside the repository")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommittedAndWorkingTreeDeclarationsUseTheirOwnSources(t *testing.T) {
|
||||
repository := t.TempDir()
|
||||
gitIdentityRun(t, repository, "init", "-q")
|
||||
gitIdentityRun(t, repository, "config", "user.name", "ReleaseKit test")
|
||||
gitIdentityRun(t, repository, "config", "user.email", "releasekit@example.invalid")
|
||||
moduleDir := filepath.Join(repository, "nested", "mod")
|
||||
if err := os.MkdirAll(moduleDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
declaration := filepath.Join(moduleDir, "go.mod")
|
||||
if err := os.WriteFile(declaration, []byte("module example.com/committed\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gitIdentityRun(t, repository, "add", ".")
|
||||
gitIdentityRun(t, repository, "commit", "-qm", "add module")
|
||||
commit := strings.TrimSpace(gitIdentityOutput(t, repository, "rev-parse", "HEAD"))
|
||||
if err := os.WriteFile(declaration, []byte("module example.com/working\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
committed, err := FromCommitted(context.Background(), repository, "nested/mod", "v1.2.3", commit)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
working, err := FromWorkingTree(context.Background(), repository, "nested/mod", "v1.2.3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if committed.ModulePath != "example.com/committed" || committed.Tag != "nested/mod/v1.2.3" {
|
||||
t.Fatalf("committed identity = %#v", committed)
|
||||
}
|
||||
if working.ModulePath != "example.com/working" || working.Tag != "nested/mod/v1.2.3" {
|
||||
t.Fatalf("working identity = %#v", working)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectedNestedModuleDoesNotRequireRootDeclaration(t *testing.T) {
|
||||
repository := t.TempDir()
|
||||
moduleDir := filepath.Join(repository, "nested")
|
||||
if err := os.MkdirAll(moduleDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(moduleDir, "go.mod"), []byte("module example.com/nested\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
identity, err := FromWorkingTree(context.Background(), repository, "nested", "v1.2.3")
|
||||
if err != nil || identity.ModulePath != "example.com/nested" {
|
||||
t.Fatalf("nested identity = %#v, error = %v", identity, err)
|
||||
}
|
||||
if _, err := FromWorkingTree(context.Background(), repository, ".", "v1.2.3"); err == nil {
|
||||
t.Fatal("root selection silently fell back to a nested module declaration")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadersPreserveCancellation(t *testing.T) {
|
||||
repository := t.TempDir()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
if _, err := FromWorkingTree(ctx, repository, ".", "v1.2.3"); err == nil || !strings.Contains(err.Error(), context.Canceled.Error()) {
|
||||
t.Fatalf("working-tree error = %v, want cancellation", err)
|
||||
}
|
||||
}
|
||||
|
||||
func gitIdentityRun(t *testing.T, dir string, args ...string) {
|
||||
t.Helper()
|
||||
command := exec.Command("git", args...)
|
||||
command.Dir = dir
|
||||
if output, err := command.CombinedOutput(); err != nil {
|
||||
t.Fatalf("git %v: %v\n%s", args, err, output)
|
||||
}
|
||||
}
|
||||
|
||||
func gitIdentityOutput(t *testing.T, dir string, args ...string) string {
|
||||
t.Helper()
|
||||
command := exec.Command("git", args...)
|
||||
command.Dir = dir
|
||||
output, err := command.Output()
|
||||
if err != nil {
|
||||
t.Fatalf("git %v: %v", args, err)
|
||||
}
|
||||
return string(output)
|
||||
}
|
||||
Reference in New Issue
Block a user