package releasecheck import ( "bytes" "os" "os/exec" "path/filepath" "runtime" "sort" "strings" "testing" ) func TestAssetBuilderRejectsUnsafeDestinations(t *testing.T) { repoRoot := releaseCheckRepoRoot(t) builder := filepath.Join(repoRoot, "scripts", "build-release-assets.sh") nonempty := t.TempDir() if err := os.WriteFile(filepath.Join(nonempty, "existing"), []byte("x"), 0o644); err != nil { t.Fatal(err) } notDirectory := filepath.Join(t.TempDir(), "not-a-directory") if err := os.WriteFile(notDirectory, []byte("x"), 0o644); err != nil { t.Fatal(err) } linkTarget := t.TempDir() symlink := filepath.Join(t.TempDir(), "linked-output") if err := os.Symlink(linkTarget, symlink); err != nil { t.Fatal(err) } for _, args := range [][]string{ nil, {"v1.2", t.TempDir()}, {"1.2.3", t.TempDir()}, {"v01.2.3", t.TempDir()}, {"v1.02.3", t.TempDir()}, {"v1.2.03", t.TempDir()}, {"v1.2.3-rc.1", t.TempDir()}, {"v1.2.3+build", t.TempDir()}, {"v1.2.3", "relative-output"}, {"v1.2.3", "/"}, {"v1.2.3", repoRoot}, {"v1.2.3", nonempty}, {"v1.2.3", notDirectory}, {"v1.2.3", symlink}, } { command := exec.Command("sh", append([]string{builder}, args...)...) command.Dir = t.TempDir() output, err := command.CombinedOutput() if err == nil { t.Fatalf("asset builder %q succeeded", args) } if !strings.Contains(string(output), "build-release-assets:") { t.Fatalf("asset builder %q output = %q", args, output) } } } func TestAssetBuilderBuildsNamedAssetsAndChecksEmbeddedVersion(t *testing.T) { repoRoot := releaseCheckRepoRoot(t) builder := filepath.Join(repoRoot, "scripts", "build-release-assets.sh") binDir := t.TempDir() writeFakeGo(t, filepath.Join(binDir, "go")) outputDir := filepath.Join(t.TempDir(), "assets") command := exec.Command("sh", builder, "v1.2.3", outputDir) command.Dir = t.TempDir() command.Env = append(os.Environ(), "PATH="+binDir+string(os.PathListSeparator)+os.Getenv("PATH")) output, err := command.CombinedOutput() if err != nil { t.Fatalf("asset builder error = %v\n%s", err, output) } entries, err := os.ReadDir(outputDir) if err != nil { t.Fatal(err) } got := make([]string, 0, len(entries)) for _, entry := range entries { got = append(got, entry.Name()) } sort.Strings(got) want := []string{ "narratio-v1.2.3-darwin-amd64", "narratio-v1.2.3-darwin-arm64", "narratio-v1.2.3-linux-amd64", "narratio-v1.2.3-linux-arm64", "narratio-v1.2.3-windows-amd64.exe", "narratio-v1.2.3-windows-arm64.exe", } if !slicesEqual(got, want) { t.Fatalf("asset names = %#v, want %#v", got, want) } version := exec.Command(filepath.Join(outputDir, "narratio-v1.2.3-linux-amd64"), "version") var versionOutput bytes.Buffer version.Stdout = &versionOutput if err := version.Run(); err != nil { t.Fatal(err) } if got := versionOutput.String(); got != "narratio v1.2.3\n" { t.Fatalf("embedded version output = %q", got) } } func releaseCheckRepoRoot(t *testing.T) string { t.Helper() _, file, _, ok := runtime.Caller(0) if !ok { t.Fatal("runtime.Caller() failed") } return filepath.Clean(filepath.Join(filepath.Dir(file), "..", "..")) } func writeFakeGo(t *testing.T, path string) { t.Helper() const fake = `#!/bin/sh set -eu if [ "$1" = env ]; then case $2 in GOOS) printf '%s\n' linux ;; GOARCH) printf '%s\n' amd64 ;; *) exit 1 ;; esac exit 0 fi output= version= while [ "$#" -gt 0 ]; do case $1 in -o) shift output=$1 ;; -ldflags) shift case $1 in *gitea.maximumdirect.net/eric/narratio/internal/buildinfo.Version=*) version=${1##*=} ;; esac ;; esac shift done [ -n "$output" ] [ -n "$version" ] printf '#!/bin/sh\nprintf "narratio %%s\\n" "%s"\n' "$version" > "$output" chmod +x "$output" ` if err := os.WriteFile(path, []byte(fake), 0o755); err != nil { t.Fatal(err) } } func slicesEqual(left, right []string) bool { if len(left) != len(right) { return false } for index := range left { if left[index] != right[index] { return false } } return true }