Files
zero/internal/release/release_test.go
Anandan f1502f0bfe Add local control browser and terminal tools (#319)
* Add local control browser and terminal tools

Add configurable local browser, desktop, and terminal control helpers with packaged helper discovery for install and release flows.

Improve sandbox scope classification for local control commands and shared temporary paths.

Add structured browser actions, terminal session line input/key normalization/read support, and artifact capture for local-control outputs.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/localcontrol ./internal/tools ./internal/cli ./internal/config -run 'Test.*LocalControl|TestTerminal|TestBrowser|TestDesktop|TestCapture'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/localcontrol ./internal/tools ./internal/cli ./internal/config ./internal/agent

Tested: tuistory smoke launched bash, sent interactive input, waited for expected output, and captured a no-cursor snapshot

Tested: git diff --cached --check

* Fix npm wrapper helper manifest test on macOS

Canonicalize expected helper paths in the npm wrapper manifest test so macOS /var and /private/var temp path aliases compare correctly.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/npmwrapper

Tested: git diff --check

* Harden local control review fixes

Respect resolved local-control config before tool listing and filter validation while preserving stream-json usage error ordering.

Harden browser, desktop, and artifact tool validation; guard Linux-only app launch behavior; clear stale helper manifests from the npm wrapper.

Make helper packaging lockfile-based with npm ci and add installer/package tests for helper staging.

Tested: focused local-control/tool/CLI/release/install/sandbox suite

Tested: isolated full go test ./...

Tested: macOS and Windows compile-only checks for touched packages
2026-06-26 08:22:56 +05:30

660 lines
21 KiB
Go

package release
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"context"
"crypto/sha256"
"encoding/hex"
"io"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
func TestSHA256FileHashesArchiveBytes(t *testing.T) {
dir := t.TempDir()
archivePath := filepath.Join(dir, "zero-v0.1.0-linux-x64.tar.gz")
archiveBytes := []byte("zero archive bytes")
if err := os.WriteFile(archivePath, archiveBytes, 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
sum := sha256.Sum256(archiveBytes)
expected := hex.EncodeToString(sum[:])
got, err := SHA256File(archivePath)
if err != nil {
t.Fatalf("SHA256File returned error: %v", err)
}
if got != expected {
t.Fatalf("SHA256File = %q, want %q", got, expected)
}
}
func TestWriteAndVerifyReleaseChecksums(t *testing.T) {
dir := t.TempDir()
archiveName := "zero-v0.1.0-linux-x64.tar.gz"
archivePath := filepath.Join(dir, archiveName)
if err := os.WriteFile(archivePath, []byte("zero archive bytes"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
written, err := WriteSHA256Checksum(archivePath)
if err != nil {
t.Fatalf("WriteSHA256Checksum returned error: %v", err)
}
checksumBytes, err := os.ReadFile(written.ChecksumPath)
if err != nil {
t.Fatalf("ReadFile checksum: %v", err)
}
if !strings.HasSuffix(string(checksumBytes), " "+archiveName+"\n") {
t.Fatalf("checksum text = %q, want shasum-compatible archive name", string(checksumBytes))
}
verifiedFile, err := VerifySHA256Checksum(written.ChecksumPath)
if err != nil {
t.Fatalf("VerifySHA256Checksum returned error: %v", err)
}
if verifiedFile.ArchiveName != archiveName || verifiedFile.ExpectedChecksum != verifiedFile.ActualChecksum {
t.Fatalf("verified checksum = %#v", verifiedFile)
}
verifiedRelease, err := VerifyReleaseChecksums(VerifyOptions{ReleaseDir: dir})
if err != nil {
t.Fatalf("VerifyReleaseChecksums returned error: %v", err)
}
if len(verifiedRelease) != 1 || verifiedRelease[0].ArchiveName != archiveName {
t.Fatalf("verified release = %#v, want %s", verifiedRelease, archiveName)
}
}
func TestChecksumParsingRejectsMalformedAndUnsafeNames(t *testing.T) {
if _, err := ParseSHA256Checksum("not a checksum"); err == nil || !strings.Contains(err.Error(), "checksum file must contain") {
t.Fatalf("ParseSHA256Checksum malformed error = %v", err)
}
if _, err := FormatSHA256Checksum("abc", "zero.tar.gz"); err == nil || !strings.Contains(err.Error(), "64 hexadecimal") {
t.Fatalf("FormatSHA256Checksum invalid checksum error = %v", err)
}
if _, err := ParseSHA256Checksum(strings.Repeat("a", 64) + " ../zero.tar.gz\n"); err == nil || !strings.Contains(err.Error(), "same-directory") {
t.Fatalf("ParseSHA256Checksum unsafe path error = %v", err)
}
if _, err := ParseSHA256Checksum(strings.Repeat("a", 64) + " zero.tar.gz\n" + strings.Repeat("b", 64) + " other.tar.gz\n"); err == nil || !strings.Contains(err.Error(), "exactly one checksum line") {
t.Fatalf("ParseSHA256Checksum multi-line error = %v", err)
}
}
func TestVerifyChecksumDetectsArchiveChanges(t *testing.T) {
dir := t.TempDir()
archivePath := filepath.Join(dir, "zero-v0.1.0-linux-x64.tar.gz")
if err := os.WriteFile(archivePath, []byte("original bytes"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
written, err := WriteSHA256Checksum(archivePath)
if err != nil {
t.Fatalf("WriteSHA256Checksum returned error: %v", err)
}
if err := os.WriteFile(archivePath, []byte("changed bytes"), 0o644); err != nil {
t.Fatalf("WriteFile changed archive: %v", err)
}
_, err = VerifySHA256Checksum(written.ChecksumPath)
if err == nil || !strings.Contains(err.Error(), "checksum mismatch") {
t.Fatalf("VerifySHA256Checksum error = %v, want mismatch", err)
}
}
func TestVerifyReleaseChecksumsRequiresMatchingFiles(t *testing.T) {
dir := t.TempDir()
archivePath := filepath.Join(dir, "zero-v0.1.0-linux-x64.tar.gz")
if err := os.WriteFile(archivePath, []byte("archive bytes"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
_, err := VerifyReleaseChecksums(VerifyOptions{ReleaseDir: dir})
if err == nil || !strings.Contains(err.Error(), "missing checksum file") {
t.Fatalf("VerifyReleaseChecksums error = %v, want missing checksum", err)
}
if _, err := WriteSHA256Checksum(archivePath); err != nil {
t.Fatalf("WriteSHA256Checksum returned error: %v", err)
}
strayChecksum := filepath.Join(dir, "zero-v0.1.0-macos-arm64.tar.gz.sha256")
if err := os.WriteFile(strayChecksum, []byte(strings.Repeat("a", 64)+" zero-v0.1.0-macos-arm64.tar.gz\n"), 0o644); err != nil {
t.Fatalf("WriteFile stray checksum: %v", err)
}
_, err = VerifyReleaseChecksums(VerifyOptions{ReleaseDir: dir})
if err == nil || !strings.Contains(err.Error(), "unexpected checksum file") {
t.Fatalf("VerifyReleaseChecksums error = %v, want unexpected checksum", err)
}
}
func TestVerifyReleaseChecksumsRejectsEmptyReleaseDir(t *testing.T) {
_, err := VerifyReleaseChecksums(VerifyOptions{ReleaseDir: t.TempDir()})
if err == nil || !strings.Contains(err.Error(), "no release archives found") {
t.Fatalf("VerifyReleaseChecksums error = %v, want no archives", err)
}
}
func TestReleaseArchiveNamesMatchInstallerContracts(t *testing.T) {
tests := []struct {
name string
version string
goos string
goarch string
packageName string
archiveName string
}{
{
name: "linux amd64",
version: "0.1.0",
goos: "linux",
goarch: "amd64",
packageName: "zero-v0.1.0-linux-x64",
archiveName: "zero-v0.1.0-linux-x64.tar.gz",
},
{
name: "macos arm64",
version: "0.1.0",
goos: "darwin",
goarch: "arm64",
packageName: "zero-v0.1.0-macos-arm64",
archiveName: "zero-v0.1.0-macos-arm64.tar.gz",
},
{
name: "windows amd64",
version: "0.1.0",
goos: "windows",
goarch: "amd64",
packageName: "zero-v0.1.0-windows-x64",
archiveName: "zero-v0.1.0-windows-x64.zip",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
packageName, err := ReleasePackageName(tt.version, tt.goos, tt.goarch)
if err != nil {
t.Fatalf("ReleasePackageName returned error: %v", err)
}
archiveName, err := ReleaseArchiveName(tt.version, tt.goos, tt.goarch)
if err != nil {
t.Fatalf("ReleaseArchiveName returned error: %v", err)
}
if packageName != tt.packageName || archiveName != tt.archiveName {
t.Fatalf("package/archive = %q/%q, want %q/%q", packageName, archiveName, tt.packageName, tt.archiveName)
}
})
}
}
func TestBuildHelpersMatchScriptContracts(t *testing.T) {
root := t.TempDir()
mustWriteFile(t, filepath.Join(root, "package.json"), `{"version":"0.1.0"}`)
version, err := PackageVersion(root)
if err != nil {
t.Fatalf("PackageVersion returned error: %v", err)
}
if version != "0.1.0" {
t.Fatalf("PackageVersion = %q, want 0.1.0", version)
}
if got := DefaultBuildOutput(root, "windows"); got != filepath.Join(root, "zero.exe") {
t.Fatalf("DefaultBuildOutput(windows) = %q", got)
}
if got := DefaultBuildOutput(root, "linux"); got != filepath.Join(root, "zero") {
t.Fatalf("DefaultBuildOutput(linux) = %q", got)
}
if got := WindowsSandboxCommandRunnerArtifactName("windows"); got != "zero-windows-command-runner.exe" {
t.Fatalf("WindowsSandboxCommandRunnerArtifactName(windows) = %q", got)
}
if got := WindowsSandboxSetupArtifactName("windows"); got != "zero-windows-sandbox-setup.exe" {
t.Fatalf("WindowsSandboxSetupArtifactName(windows) = %q", got)
}
if got := BuildLdflags(version); !strings.Contains(got, "-X github.com/Gitlawb/zero/internal/cli.version=0.1.0") {
t.Fatalf("BuildLdflags = %q", got)
}
}
func TestSmokeRejectsMissingDefaultArtifact(t *testing.T) {
root := t.TempDir()
mustWriteFile(t, filepath.Join(root, "package.json"), `{"version":"0.1.0"}`)
_, err := Smoke(context.Background(), SmokeOptions{RootDir: root, GOOS: "linux"})
if err == nil || !strings.Contains(err.Error(), "build artifact not found: zero") {
t.Fatalf("Smoke error = %v, want missing artifact", err)
}
}
func TestPackageRejectsCrossTargetBecauseItSmokesTheBinary(t *testing.T) {
root := t.TempDir()
if err := os.WriteFile(filepath.Join(root, "package.json"), []byte(`{"version":"0.1.0"}`), 0o644); err != nil {
t.Fatalf("WriteFile package.json: %v", err)
}
goos := "linux"
if runtime.GOOS == "linux" {
goos = "darwin"
}
_, err := Package(context.Background(), PackageOptions{
RootDir: root,
GOOS: goos,
GOARCH: runtime.GOARCH,
})
if err == nil || !strings.Contains(err.Error(), "target must match host platform") {
t.Fatalf("Package error = %v, want host-target mismatch", err)
}
}
func TestResolvePackageDirsRejectsDangerousDeleteTargets(t *testing.T) {
root := t.TempDir()
home := t.TempDir()
tests := []struct {
name string
releaseDir string
stagingDir string
want string
}{
{
name: "repo root release dir",
releaseDir: ".",
stagingDir: "dist/package",
want: "inside",
},
{
name: "dist root release dir",
releaseDir: "dist",
stagingDir: "dist/package",
want: "inside",
},
{
name: "outside absolute release dir",
releaseDir: home,
stagingDir: "dist/package",
want: "inside",
},
{
name: "same release and staging dir",
releaseDir: "dist/release",
stagingDir: "dist/release",
want: "overlap",
},
{
name: "staging contains release dir",
releaseDir: "dist/package/release",
stagingDir: "dist/package",
want: "overlap",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, _, err := resolvePackageDirs(root, tt.releaseDir, tt.stagingDir)
if err == nil || !strings.Contains(err.Error(), tt.want) {
t.Fatalf("resolvePackageDirs error = %v, want %q", err, tt.want)
}
})
}
}
func TestPackageRejectsDangerousDirsBeforeDeleting(t *testing.T) {
root := t.TempDir()
mustWriteFile(t, filepath.Join(root, "package.json"), `{"version":"0.1.0"}`)
markerPath := filepath.Join(root, "DO_NOT_DELETE")
mustWriteFile(t, markerPath, "marker")
_, err := Package(context.Background(), PackageOptions{
RootDir: root,
ReleaseDir: ".",
})
if err == nil || !strings.Contains(err.Error(), "inside") {
t.Fatalf("Package error = %v, want unsafe path rejection", err)
}
if _, statErr := os.Stat(markerPath); statErr != nil {
t.Fatalf("Package removed marker before rejecting unsafe dir: %v", statErr)
}
}
func TestResolvePackageDirsAcceptsDistSubdirs(t *testing.T) {
root := t.TempDir()
releaseDir, stagingDir, err := resolvePackageDirs(root, "dist/custom-release", "dist/custom-package")
if err != nil {
t.Fatalf("resolvePackageDirs returned error: %v", err)
}
if releaseDir != filepath.Join(root, "dist", "custom-release") || stagingDir != filepath.Join(root, "dist", "custom-package") {
t.Fatalf("release/staging dirs = %q/%q", releaseDir, stagingDir)
}
}
func TestCreateArchivesWithRootPackageFiles(t *testing.T) {
t.Run("tar gz", func(t *testing.T) {
stagingDir := packageStagingFixture(t, "zero")
archivePath := filepath.Join(t.TempDir(), "zero-v0.1.0-linux-x64.tar.gz")
if err := createArchive(stagingDir, archivePath, "linux"); err != nil {
t.Fatalf("createArchive returned error: %v", err)
}
names := tarArchiveNames(t, archivePath)
for _, want := range []string{"zero", "README.md", "bin/zero.js", "VERSION"} {
if !names[want] {
t.Fatalf("tar archive missing %s: %#v", want, names)
}
}
})
t.Run("zip", func(t *testing.T) {
stagingDir := packageStagingFixture(t, "zero.exe")
archivePath := filepath.Join(t.TempDir(), "zero-v0.1.0-windows-x64.zip")
if err := createArchive(stagingDir, archivePath, "windows"); err != nil {
t.Fatalf("createArchive returned error: %v", err)
}
names := zipArchiveNames(t, archivePath)
for _, want := range []string{"zero.exe", "README.md", "bin/zero.js", "VERSION"} {
if !names[want] {
t.Fatalf("zip archive missing %s: %#v", want, names)
}
}
})
}
func TestCreateTarArchivePreservesSymlinkTargets(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Unix symlink archive behavior")
}
stagingDir := packageStagingFixture(t, "zero")
linkPath := filepath.Join(stagingDir, "helpers", "node_modules", ".bin", "agent-browser")
if err := os.MkdirAll(filepath.Dir(linkPath), 0o755); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
linkTarget := "../agent-browser/bin/agent-browser.js"
if err := os.Symlink(linkTarget, linkPath); err != nil {
t.Fatalf("Symlink: %v", err)
}
archivePath := filepath.Join(t.TempDir(), "zero-v0.1.0-linux-x64.tar.gz")
if err := createArchive(stagingDir, archivePath, "linux"); err != nil {
t.Fatalf("createArchive returned error: %v", err)
}
header := tarArchiveHeaders(t, archivePath)["helpers/node_modules/.bin/agent-browser"]
if header == nil {
t.Fatal("archive missing symlink header")
}
if header.Typeflag != tar.TypeSymlink || header.Linkname != linkTarget {
t.Fatalf("symlink header type/link = %v/%q, want %v/%q", header.Typeflag, header.Linkname, tar.TypeSymlink, linkTarget)
}
}
func TestCopyPackageFilesStagesLinuxSandboxHelper(t *testing.T) {
root := t.TempDir()
staging := t.TempDir()
artifact := filepath.Join(root, "zero")
helper := filepath.Join(root, "zero-linux-sandbox")
seccomp := filepath.Join(root, "zero-seccomp")
for path, content := range map[string]string{
artifact: "zero",
helper: "helper",
seccomp: "seccomp",
filepath.Join(root, "README.md"): "readme",
filepath.Join(root, "package.json"): `{"version":"0.1.0"}`,
filepath.Join(root, "bin", "zero.js"): "wrapper",
} {
mustWriteFile(t, path, content)
}
if err := copyPackageFiles(root, staging, artifact, filepath.Join(staging, "zero"), "linux", "0.1.0", map[string]string{
"zero-linux-sandbox": helper,
"zero-seccomp": seccomp,
}); err != nil {
t.Fatalf("copyPackageFiles: %v", err)
}
if _, err := os.Stat(filepath.Join(staging, "zero-linux-sandbox")); err != nil {
t.Fatalf("staged helper missing: %v", err)
}
if _, err := os.Stat(filepath.Join(staging, "zero-seccomp")); err != nil {
t.Fatalf("staged seccomp compatibility helper missing: %v", err)
}
}
func TestCopyPackageFilesStagesWindowsSandboxHelpers(t *testing.T) {
root := t.TempDir()
staging := t.TempDir()
artifact := filepath.Join(root, "zero.exe")
runner := filepath.Join(root, "zero-windows-command-runner.exe")
setup := filepath.Join(root, "zero-windows-sandbox-setup.exe")
for path, content := range map[string]string{
artifact: "zero",
runner: "runner",
setup: "setup",
filepath.Join(root, "README.md"): "readme",
filepath.Join(root, "package.json"): `{"version":"0.1.0"}`,
filepath.Join(root, "bin", "zero.js"): "wrapper",
} {
mustWriteFile(t, path, content)
}
if err := copyPackageFiles(root, staging, artifact, filepath.Join(staging, "zero.exe"), "windows", "0.1.0", map[string]string{
"zero-windows-command-runner.exe": runner,
"zero-windows-sandbox-setup.exe": setup,
}); err != nil {
t.Fatalf("copyPackageFiles: %v", err)
}
if _, err := os.Stat(filepath.Join(staging, "zero-windows-command-runner.exe")); err != nil {
t.Fatalf("staged runner missing: %v", err)
}
if _, err := os.Stat(filepath.Join(staging, "zero-windows-sandbox-setup.exe")); err != nil {
t.Fatalf("staged setup helper missing: %v", err)
}
}
func TestStageLocalControlHelpersUsesPackageDependencies(t *testing.T) {
root := t.TempDir()
helpers := filepath.Join(t.TempDir(), "helpers")
mustWriteFile(t, filepath.Join(root, "package.json"), `{
"version": "0.1.0",
"dependencies": {
"agent-browser": "0.30.1",
"tuistory": "0.10.0"
}
}`)
mustWriteFile(t, filepath.Join(root, "package-lock.json"), `{
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"agent-browser": "0.30.1",
"tuistory": "0.10.0"
}
},
"node_modules/agent-browser": {
"version": "0.30.1"
},
"node_modules/tuistory": {
"version": "0.10.0"
}
}
}`)
fakeBin := t.TempDir()
writeFakeNPM(t, fakeBin)
t.Setenv("PATH", fakeBin+string(os.PathListSeparator)+os.Getenv("PATH"))
if err := stageLocalControlHelpers(context.Background(), root, helpers); err != nil {
t.Fatalf("stageLocalControlHelpers: %v", err)
}
manifestBytes, err := os.ReadFile(filepath.Join(helpers, "package.json"))
if err != nil {
t.Fatalf("ReadFile helper package.json: %v", err)
}
manifest := string(manifestBytes)
for _, want := range []string{`"agent-browser": "0.30.1"`, `"tuistory": "0.10.0"`} {
if !strings.Contains(manifest, want) {
t.Fatalf("helper package.json missing %s:\n%s", want, manifest)
}
}
if _, err := os.Stat(filepath.Join(helpers, "package-lock.json")); err != nil {
t.Fatalf("helper package-lock.json was not copied: %v", err)
}
for _, name := range localControlHelperPackages {
found := false
for _, shimName := range localControlHelperShimNames(name, runtime.GOOS) {
if _, err := os.Stat(filepath.Join(helpers, "node_modules", ".bin", shimName)); err == nil {
found = true
break
}
}
if !found {
t.Fatalf("missing shim for %s", name)
}
}
}
func TestStageLocalControlHelpersRequiresPackageLock(t *testing.T) {
root := t.TempDir()
helpers := filepath.Join(t.TempDir(), "helpers")
mustWriteFile(t, filepath.Join(root, "package.json"), `{
"version": "0.1.0",
"dependencies": {
"agent-browser": "0.30.1",
"tuistory": "0.10.0"
}
}`)
err := stageLocalControlHelpers(context.Background(), root, helpers)
if err == nil || !strings.Contains(err.Error(), "package-lock.json is required") {
t.Fatalf("stageLocalControlHelpers error = %v, want package-lock requirement", err)
}
}
func TestLocalControlHelperDependenciesRequiresConfiguredPackages(t *testing.T) {
root := t.TempDir()
mustWriteFile(t, filepath.Join(root, "package.json"), `{"version":"0.1.0","dependencies":{"agent-browser":"0.30.1"}}`)
_, err := localControlHelperDependencies(root)
if err == nil || !strings.Contains(err.Error(), `dependency "tuistory"`) {
t.Fatalf("localControlHelperDependencies error = %v, want missing tuistory", err)
}
}
func packageStagingFixture(t *testing.T, binaryName string) string {
t.Helper()
dir := t.TempDir()
files := map[string]string{
binaryName: "binary",
"README.md": "readme",
"bin/zero.js": "wrapper",
"VERSION": "0.1.0\n",
}
for name, content := range files {
path := filepath.Join(dir, name)
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("WriteFile(%s): %v", name, err)
}
}
return dir
}
func writeFakeNPM(t *testing.T, dir string) {
t.Helper()
if runtime.GOOS == "windows" {
path := filepath.Join(dir, "npm.cmd")
content := `@echo off
if not "%1"=="ci" (
echo expected npm ci 1>&2
exit /b 2
)
mkdir node_modules 2>NUL
mkdir node_modules\.bin 2>NUL
echo @echo off> node_modules\.bin\agent-browser.cmd
echo @echo off> node_modules\.bin\tuistory.cmd
exit /b 0
`
mustWriteFile(t, path, content)
return
}
path := filepath.Join(dir, "npm")
content := `#!/usr/bin/env sh
set -eu
if [ "${1:-}" != "ci" ]; then
echo "expected npm ci" >&2
exit 2
fi
mkdir -p node_modules/.bin
for name in agent-browser tuistory; do
printf '#!/usr/bin/env sh\n' > "node_modules/.bin/$name"
chmod 755 "node_modules/.bin/$name"
done
`
mustWriteFile(t, path, content)
if err := os.Chmod(path, 0o755); err != nil {
t.Fatalf("Chmod fake npm: %v", err)
}
}
func mustWriteFile(t *testing.T, path string, content string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("MkdirAll(%s): %v", path, err)
}
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("WriteFile(%s): %v", path, err)
}
}
func tarArchiveNames(t *testing.T, archivePath string) map[string]bool {
t.Helper()
headers := tarArchiveHeaders(t, archivePath)
names := map[string]bool{}
for name := range headers {
names[name] = true
}
return names
}
func tarArchiveHeaders(t *testing.T, archivePath string) map[string]*tar.Header {
t.Helper()
file, err := os.Open(archivePath)
if err != nil {
t.Fatalf("Open tar archive: %v", err)
}
defer func() {
_ = file.Close()
}()
gzipReader, err := gzip.NewReader(file)
if err != nil {
t.Fatalf("NewReader gzip: %v", err)
}
defer func() {
_ = gzipReader.Close()
}()
reader := tar.NewReader(gzipReader)
headers := map[string]*tar.Header{}
for {
header, err := reader.Next()
if err != nil {
if err == io.EOF {
break
}
t.Fatalf("tar Next: %v", err)
}
copied := *header
headers[header.Name] = &copied
}
return headers
}
func zipArchiveNames(t *testing.T, archivePath string) map[string]bool {
t.Helper()
reader, err := zip.OpenReader(archivePath)
if err != nil {
t.Fatalf("OpenReader zip: %v", err)
}
defer func() {
_ = reader.Close()
}()
names := map[string]bool{}
for _, file := range reader.File {
names[file.Name] = true
}
return names
}