spdx-headers.sh changed into `git rev-parse --show-toplevel` before stamping. Run from a copy of the repository that has no .git of its own, such as a git archive export under the working tree, git names the enclosing checkout, so the copy stamped the checkout's tracked files instead of its own. The script now takes as its root the directory above its own scripts/ directory. When git does not name that directory as the top of a checkout, it prints a refusal on stderr and exits 2 before it reads any file. Run from its own checkout it behaves as before, which is how `make spdx-headers` and `make sqlc-generate` call it. TestSPDXHeadersStampsOnlyItsOwnCheckout builds a scratch git checkout with one tracked Go file and, under it, an export holding the same files without a .git. Run from the export, the script must exit 2 and leave both Go files unstamped; run from the checkout's own copy, it must stamp the checkout's file and not the export's. At the parent the export case fails: the script exits 0 and stamps the checkout's a.go. The checkout case passes on both sides. Run in the working tree, the new script stamped 0 of 934 eligible files and changed no path. go vet, gofmt, bash -n and make lint pass.
133 lines
3.9 KiB
Go
133 lines
3.9 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
|
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
|
|
|
|
package test
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestSPDXHeadersStampsOnlyItsOwnCheckout runs copies of
|
|
// scripts/spdx-headers.sh in a scratch checkout that holds an export of
|
|
// itself: a copy of the tree with no .git, the shape a drafting tool leaves
|
|
// under the working tree. Run from the export, git names the enclosing
|
|
// checkout, so the script must refuse instead of stamping the checkout's
|
|
// files. Run from the checkout's own copy, it stamps the checkout's tracked
|
|
// files and nothing in the export.
|
|
func TestSPDXHeadersStampsOnlyItsOwnCheckout(t *testing.T) {
|
|
sb := newStampBox(t)
|
|
|
|
t.Run("from an export inside the checkout", func(t *testing.T) {
|
|
code, stderr := sb.run(t, sb.export)
|
|
if code != 2 {
|
|
t.Errorf("exit status %d, want 2 (stderr %q)", code, stderr)
|
|
}
|
|
if !strings.Contains(stderr, "is not the top of a git checkout") {
|
|
t.Errorf("stderr %q, want the refusal", stderr)
|
|
}
|
|
if stamped(t, filepath.Join(sb.checkout, "a.go")) {
|
|
t.Error("the checkout's a.go was stamped from the export")
|
|
}
|
|
if stamped(t, filepath.Join(sb.export, "a.go")) {
|
|
t.Error("the export's a.go was stamped")
|
|
}
|
|
})
|
|
t.Run("from the checkout's own copy", func(t *testing.T) {
|
|
code, stderr := sb.run(t, sb.checkout)
|
|
if code != 0 {
|
|
t.Errorf("exit status %d, want 0 (stderr %q)", code, stderr)
|
|
}
|
|
if !stamped(t, filepath.Join(sb.checkout, "a.go")) {
|
|
t.Error("the checkout's a.go was not stamped")
|
|
}
|
|
if stamped(t, filepath.Join(sb.export, "a.go")) {
|
|
t.Error("the export's a.go was stamped")
|
|
}
|
|
})
|
|
}
|
|
|
|
// stampBox is a scratch git checkout with a copy of the stamper and one
|
|
// tracked Go file, and under it an export: the same two files with no .git.
|
|
type stampBox struct {
|
|
bash, checkout, export string
|
|
env []string
|
|
}
|
|
|
|
func newStampBox(t *testing.T) stampBox {
|
|
t.Helper()
|
|
src, err := os.ReadFile(filepath.Join("..", "scripts", "spdx-headers.sh"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
bash, err := exec.LookPath("bash")
|
|
if err != nil {
|
|
t.Fatalf("bash: %v", err)
|
|
}
|
|
checkout := t.TempDir()
|
|
sb := stampBox{
|
|
bash: bash,
|
|
checkout: checkout,
|
|
export: filepath.Join(checkout, "notebook", "export"),
|
|
env: append(os.Environ(), "GIT_CONFIG_GLOBAL=/dev/null", "GIT_CONFIG_NOSYSTEM=1"),
|
|
}
|
|
for _, dir := range []string{sb.checkout, sb.export} {
|
|
writeFile(t, filepath.Join(dir, "scripts", "spdx-headers.sh"), src, 0o755)
|
|
writeFile(t, filepath.Join(dir, "a.go"), []byte("package a\n"), 0o644)
|
|
}
|
|
for _, args := range [][]string{
|
|
{"-c", "init.defaultBranch=main", "init", "-q"},
|
|
{"add", "a.go", "scripts/spdx-headers.sh"},
|
|
} {
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir, cmd.Env = sb.checkout, sb.env
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("git %v: %v\n%s", args, err, out)
|
|
}
|
|
}
|
|
return sb
|
|
}
|
|
|
|
// run runs the copy of the stamper under dir, from dir, and returns its exit
|
|
// status and standard error.
|
|
func (sb stampBox) run(t *testing.T, dir string) (int, string) {
|
|
t.Helper()
|
|
cmd := exec.Command(sb.bash, filepath.Join(dir, "scripts", "spdx-headers.sh"))
|
|
cmd.Dir, cmd.Env = dir, sb.env
|
|
var stderr bytes.Buffer
|
|
cmd.Stderr = &stderr
|
|
if err := cmd.Run(); err != nil {
|
|
var exit *exec.ExitError
|
|
if !errors.As(err, &exit) {
|
|
t.Fatal(err)
|
|
}
|
|
return exit.ExitCode(), stderr.String()
|
|
}
|
|
return 0, stderr.String()
|
|
}
|
|
|
|
func writeFile(t *testing.T, path string, data []byte, mode os.FileMode) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, data, mode); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// stamped reports whether the file at path opens with a Go SPDX header.
|
|
func stamped(t *testing.T, path string) bool {
|
|
t.Helper()
|
|
got, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return strings.HasPrefix(string(got), "// SPDX-License-Identifier: ")
|
|
}
|