-
-
Notifications
You must be signed in to change notification settings - Fork 2
Make tests independent of the machine they run on #217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,22 @@ func TestRealGitRunner_TagsAtHead(t *testing.T) { | |
| assert.NoError(t, err) | ||
| } | ||
|
|
||
| // git runs a command with the developer's global and system config ignored. | ||
| // Inheriting them made these tests fail on real machines: commit.gpgsign=true | ||
| // aborts the commit with exit 128, and a global core.hooksPath with a failing | ||
| // pre-commit hook does the same. | ||
| func git(t *testing.T, args ...string) { | ||
| t.Helper() | ||
| cmd := exec.Command("git", args...) //nolint:gosec // args are literals from this test file | ||
| cmd.Env = append(os.Environ(), | ||
| "GIT_CONFIG_GLOBAL=/dev/null", | ||
| "GIT_CONFIG_SYSTEM=/dev/null", | ||
|
Comment on lines
+48
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '1,20p' pkg/gitcheck/runner_test.go
rg -n --glob 'go.mod' --glob '*.yml' --glob '*.yaml' \
'//go:build|GOOS|windows|darwin|linux|unix' . || trueRepository: vertti/preflight Length of output: 491 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- pkg/gitcheck/runner_test.go ---'
cat -n pkg/gitcheck/runner_test.go
printf '%s\n' '--- module and nearby package files ---'
cat go.mod
git ls-files 'pkg/gitcheck/*'
printf '%s\n' '--- build constraints and platform references ---'
rg -n --glob '*.go' --glob 'go.mod' --glob '*.yml' --glob '*.yaml' \
'//go:build|// \+build|GOOS|windows|darwin|linux|unix|os.DevNull|GIT_CONFIG_GLOBAL|GIT_CONFIG_SYSTEM|GIT_DIR|GIT_WORK_TREE|GIT_INDEX_FILE' .Repository: vertti/preflight Length of output: 8786 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path("pkg/gitcheck/runner_test.go")
text = p.read_text()
lines = text.splitlines()
print("package:", next((x for x in lines if x.startswith("package ")), "<none>")
print("leading build constraints:")
for line in lines[:12]:
print(repr(line))
print("uses os.DevNull:", "os.DevNull" in text)
print("uses /dev/null:", "/dev/null" in text)
print("clears Git repository selectors:")
for name in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"):
print(name, any(name in line for line in lines))
PYRepository: vertti/preflight Length of output: 253 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- pkg/gitcheck/runner.go ---'
cat -n pkg/gitcheck/runner.go
printf '%s\n' '--- gitcheck tests and call sites ---'
cat -n pkg/gitcheck/check_test.go
rg -n 'RealGitRunner|exec.Command|cmd\.Env|GIT_' pkg/gitcheck '*.go' 2>/dev/null || true
printf '%s\n' '--- repository test/build configuration ---'
rg -n --hidden --glob '!*.sum' --glob '!vendor/**' \
'GOOS|go test|go vet|windows|unix' .github Makefile* README.md 2>/dev/null || trueRepository: vertti/preflight Length of output: 13198 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path("pkg/gitcheck/runner_test.go")
lines = p.read_text().splitlines()
print("leading build constraints:")
for line in lines[:12]:
print(repr(line))
print("uses os.DevNull:", any("os.DevNull" in line for line in lines))
print("uses /dev/null:", any("/dev/null" in line for line in lines))
for name in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"):
print(f"{name} referenced:", any(name in line for line in lines))
PYRepository: vertti/preflight Length of output: 530 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Read-only semantic probe for the proposed platform-neutral value.
cat > /tmp/probe.go <<'EOF'
package main
import (
"fmt"
"os"
"runtime"
)
func main() {
fmt.Printf("GOOS=%s os.DevNull=%q\n", runtime.GOOS, os.DevNull)
}
EOF
go run /tmp/probe.goRepository: vertti/preflight Length of output: 188 🏁 Script executed: #!/bin/bash
set -euo pipefail
cat > /tmp/probe.go <<'EOF'
package main
import (
"fmt"
"os"
"runtime"
)
func main() {
fmt.Printf("GOOS=%s os.DevNull=%q\n", runtime.GOOS, os.DevNull)
}
EOF
go run /tmp/probe.goRepository: vertti/preflight Length of output: 188 Use 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| "GIT_TERMINAL_PROMPT=0", | ||
|
Comment on lines
+47
to
+50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
env | rg '^GIT_(DIR|WORK_TREE|INDEX_FILE|OBJECT_DIRECTORY|ALTERNATE_OBJECT_DIRECTORIES)=' || true
rg -n -C 3 'exec\.Command\("git"|cmd\.Env|GIT_(DIR|WORK_TREE|INDEX_FILE)' \
pkg/gitcheck/runner_test.go pkg/gitcheck/runner.go || trueRepository: vertti/preflight Length of output: 2213 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- runner_test.go outline ---'
ast-grep outline pkg/gitcheck/runner_test.go || true
printf '%s\n' '--- runner.go outline ---'
ast-grep outline pkg/gitcheck/runner.go || true
printf '%s\n' '--- runner_test.go relevant sections ---'
cat -n pkg/gitcheck/runner_test.go | sed -n '1,115p'
printf '%s\n' '--- runner.go relevant sections ---'
cat -n pkg/gitcheck/runner.go | sed -n '1,105p'
printf '%s\n' '--- build constraints and related environment setup ---'
rg -n -C 3 '//go:build|// \+build|t\.Setenv|os\.Setenv|GIT_(DIR|WORK_TREE|INDEX_FILE|OBJECT_DIRECTORY|ALTERNATE_OBJECT_DIRECTORIES)|Chdir|cmd\.Dir' \
pkg/gitcheck .github 2>/dev/null || trueRepository: vertti/preflight Length of output: 8820 🌐 Web query:
💡 Result: Git uses the GIT_DIR, GIT_WORK_TREE, and GIT_INDEX_FILE environment variables to control how it locates and interacts with a repository [1][2]. GIT_DIR The GIT_DIR variable specifies the path to the repository, which is the directory containing the Git metadata (typically named.git) [3][1]. When GIT_DIR is set, Git bypasses its standard repository discovery process—which otherwise searches for a.git directory in the current working directory and its parent directories [3][1]. Setting this variable explicitly defines the base of the repository [1][4]. GIT_WORK_TREE The GIT_WORK_TREE variable specifies the root path of the working tree [1][2]. It is used in conjunction with GIT_DIR (or the --git-dir command-line option) to define where your project files are located [1][5]. If GIT_DIR is set but GIT_WORK_TREE (or the --work-tree command-line option and core.worktree configuration) is not, Git defaults to assuming that the current working directory is the root of the working tree [3][1]. GIT_INDEX_FILE The GIT_INDEX_FILE variable specifies the path to an alternate index file [1][2]. By default, Git uses the index file located at $GIT_DIR/index [1][2]. Setting this variable allows you to target a different index, which is often used in specialized operations or when manipulating multiple index states [1]. Summary of Repository Selection Under normal circumstances, Git performs repository discovery by walking up the directory tree to find a.git subdirectory, which identifies both the repository location and the top level of the working tree [3][1]. Setting GIT_DIR disables this automatic discovery [1][6]. If you provide a GIT_DIR path that is not at the top level of your project, you must also provide the correct path to the working tree via GIT_WORK_TREE to ensure Git operates on the intended files [1][7]. Command-line options like --git-dir and --work-tree serve as functional equivalents to these environment variables and take precedence over them [1][4]. Citations:
🌐 Web query:
💡 Result: In Go's os/exec package, the behavior of the Env field in the Cmd struct is as follows: 1. Inheritance Behavior: If the Env field is nil (its default value), the new process inherits the current process's environment [1][2]. 2. Duplicate Environment Variables: If you provide a non-nil slice to Env, and that slice contains duplicate keys (e.g., FOO=bar, FOO=baz), the system uses only the last value provided for each key [1][3]. This applies regardless of whether you are overriding the entire environment or appending to it [1][4]. 3. Combining with Current Environment: If you want to keep the current environment variables while adding or overriding specific ones, you must explicitly populate the Env slice, typically by appending to a copy of the current environment [4]: cmd:= exec.Command("prog") cmd.Env = append(os.Environ, "KEY=value") // Inherits current environment plus new/overridden values Because Go deduplicates these variables by keeping the last occurrence, placing your new or overridden values at the end of the slice ensures they take precedence over any inherited values [1][3]. Note: On Windows, there is a special case where the SYSTEMROOT variable is automatically added if it is missing and not explicitly set to an empty string [1][2]. Additionally, the Dir field may also influence the PWD environment variable on Unix systems [1][3]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
import os
import subprocess
from pathlib import Path
source = Path("pkg/gitcheck/runner_test.go").read_text()
required = [
'"GIT_CONFIG_GLOBAL=/dev/null"',
'"GIT_CONFIG_SYSTEM=/dev/null"',
'"GIT_TERMINAL_PROMPT=0"',
]
assert all(item in source for item in required)
inherited = dict(os.environ)
inherited.update({
"GIT_DIR": "/external/repository/.git",
"GIT_WORK_TREE": "/external/repository",
"GIT_INDEX_FILE": "/external/repository/.git/index",
})
child_env = [f"{key}={value}" for key, value in inherited.items()]
child_env += [
"GIT_CONFIG_GLOBAL=/dev/null",
"GIT_CONFIG_SYSTEM=/dev/null",
"GIT_TERMINAL_PROMPT=0",
]
result = subprocess.run(
["/usr/bin/env"],
env=dict(item.split("=", 1) for item in child_env),
text=True,
capture_output=True,
check=True,
)
child = dict(line.split("=", 1) for line in result.stdout.splitlines() if "=" in line)
for key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"):
assert child[key] == inherited[key]
print("constructed child environment preserves all three repository-selection variables")
root = subprocess.check_output(
["git", "rev-parse", "--show-toplevel"],
text=True,
).strip()
probe_env = dict(os.environ)
probe_env["GIT_DIR"] = str(Path(root) / ".git")
probe_env.pop("GIT_WORK_TREE", None)
probe_env.pop("GIT_INDEX_FILE", None)
selected = subprocess.check_output(
["git", "rev-parse", "--show-toplevel"],
cwd="/tmp",
env=probe_env,
text=True,
).strip()
assert selected == root
print(f"inherited GIT_DIR selects {selected} even when git runs from /tmp")
PYRepository: vertti/preflight Length of output: 324 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
import os
import subprocess
from pathlib import Path
root = subprocess.check_output(
["git", "rev-parse", "--show-toplevel"],
text=True,
).strip()
git_dir = subprocess.check_output(
["git", "rev-parse", "--git-dir"],
text=True,
).strip()
if not os.path.isabs(git_dir):
git_dir = str(Path(root, git_dir).resolve())
probe_env = dict(os.environ)
probe_env["GIT_DIR"] = git_dir
probe_env["GIT_WORK_TREE"] = root
probe_env.pop("GIT_INDEX_FILE", None)
selected = subprocess.check_output(
["git", "rev-parse", "--show-toplevel"],
cwd="/tmp",
env=probe_env,
text=True,
).strip()
assert selected == root, (root, selected)
print(f"GIT_DIR and GIT_WORK_TREE select {selected} when git runs from /tmp")
PYRepository: vertti/preflight Length of output: 230 Isolate Git repository variables in test commands. If the test process inherits 🤖 Prompt for AI Agents |
||
| ) | ||
| out, err := cmd.CombinedOutput() | ||
| require.NoError(t, err, "git %v: %s", args, out) | ||
| } | ||
|
|
||
| func TestRealGitRunner_IsGitRepo_NotRepo(t *testing.T) { | ||
| tmpDir := t.TempDir() | ||
| oldWd, err := os.Getwd() | ||
|
|
@@ -60,13 +76,13 @@ func TestRealGitRunner_TagsAtHead_WithTag(t *testing.T) { | |
| require.NoError(t, os.Chdir(tmpDir)) | ||
|
|
||
| // Initialize git repo with a commit and tag | ||
| require.NoError(t, exec.Command("git", "init").Run()) | ||
| require.NoError(t, exec.Command("git", "config", "user.email", "test@test.com").Run()) | ||
| require.NoError(t, exec.Command("git", "config", "user.name", "Test").Run()) | ||
| git(t, "init") | ||
| git(t, "config", "user.email", "test@test.com") | ||
| git(t, "config", "user.name", "Test") | ||
| require.NoError(t, os.WriteFile("test.txt", []byte("test"), 0o600)) | ||
| require.NoError(t, exec.Command("git", "add", "test.txt").Run()) | ||
| require.NoError(t, exec.Command("git", "commit", "-m", "initial").Run()) | ||
| require.NoError(t, exec.Command("git", "tag", "v1.0.0").Run()) | ||
| git(t, "add", "test.txt") | ||
| git(t, "commit", "-m", "initial") | ||
| git(t, "tag", "v1.0.0") | ||
|
|
||
| runner := &RealGitRunner{} | ||
| tags, err := runner.TagsAtHead() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ import ( | |
| "path/filepath" | ||
| "reflect" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestFindFile_ExplicitPath(t *testing.T) { | ||
|
|
@@ -51,54 +53,65 @@ func TestFindFile_TraverseUp(t *testing.T) { | |
| } | ||
| } | ||
|
|
||
| // A repo root is a boundary: a .preflight above it belongs to something else. | ||
| // The .preflight must live only in the parent, or FindFile returns on its first | ||
| // iteration and the .git logic is never reached — which is what the previous | ||
| // version of this test did, so it passed with that logic deleted. | ||
| func TestFindFile_StopAtGit(t *testing.T) { | ||
| tmpDir := t.TempDir() | ||
|
|
||
| projectDir := filepath.Join(tmpDir, "project") | ||
| gitDir := filepath.Join(projectDir, ".git") | ||
| if err := os.MkdirAll(gitDir, 0o700); err != nil { | ||
| t.Fatalf("failed to create directories: %v", err) | ||
| } | ||
| require.NoError(t, os.MkdirAll(filepath.Join(projectDir, ".git"), 0o700)) | ||
| require.NoError(t, os.WriteFile(filepath.Join(tmpDir, ".preflight"), []byte("env HOME\n"), 0o600)) | ||
|
|
||
| preflightPath := filepath.Join(tmpDir, ".preflight") | ||
| if err := os.WriteFile(preflightPath, []byte("test"), 0o600); err != nil { | ||
| t.Fatalf("failed to create test file: %v", err) | ||
| } | ||
| _, err := FindFile(projectDir, "") | ||
| require.Error(t, err, ".git is a boundary: the parent's .preflight is not ours") | ||
| } | ||
|
|
||
| projectPreflight := filepath.Join(projectDir, ".preflight") | ||
| if err := os.WriteFile(projectPreflight, []byte("test"), 0o600); err != nil { | ||
| t.Fatalf("failed to create test file: %v", err) | ||
| } | ||
| // The mirror image: without a .git boundary the parent's file is found, which | ||
| // pins that the error above comes from the boundary and not from the walk | ||
| // failing generally. | ||
| func TestFindFile_WalksUpWithoutGitBoundary(t *testing.T) { | ||
| tmpDir := t.TempDir() | ||
| projectDir := filepath.Join(tmpDir, "project") | ||
| require.NoError(t, os.MkdirAll(projectDir, 0o700)) | ||
| parentPreflight := filepath.Join(tmpDir, ".preflight") | ||
| require.NoError(t, os.WriteFile(parentPreflight, []byte("env HOME\n"), 0o600)) | ||
|
|
||
| found, err := FindFile(projectDir, "") | ||
| if err != nil { | ||
| t.Fatalf("FindFile failed: %v", err) | ||
| } | ||
| if found != projectPreflight { | ||
| t.Errorf("expected %q, got %q", projectPreflight, found) | ||
| } | ||
| require.NoError(t, err) | ||
| require.Equal(t, parentPreflight, found) | ||
| } | ||
|
|
||
| // The search stops at $HOME. This uses a temporary HOME rather than the real | ||
| // one: the previous version created and RemoveAll'd ~/test_preflight on the | ||
| // developer's machine, and failed outright for anyone who has a global | ||
| // ~/.preflight — which is a supported location this package exists to find. | ||
| func TestFindFile_StopAtHome(t *testing.T) { | ||
| homeDir, err := os.UserHomeDir() | ||
| if err != nil { | ||
| t.Fatalf("failed to get home directory: %v", err) | ||
| } | ||
| home := t.TempDir() | ||
| t.Setenv("HOME", home) | ||
| t.Setenv("USERPROFILE", home) // os.UserHomeDir on Windows | ||
|
|
||
| testDir := filepath.Join(homeDir, "test_preflight") | ||
| if err := os.MkdirAll(testDir, 0o700); err != nil { | ||
| t.Fatalf("failed to create test directory: %v", err) | ||
| } | ||
| defer func() { | ||
| if err := os.RemoveAll(testDir); err != nil { | ||
| t.Errorf("failed to clean up test directory: %v", err) | ||
| } | ||
| }() | ||
| testDir := filepath.Join(home, "project") | ||
| require.NoError(t, os.MkdirAll(testDir, 0o700)) | ||
|
|
||
| _, err = FindFile(testDir, "") | ||
| if err == nil { | ||
| t.Error("expected error when .preflight not found") | ||
| } | ||
| _, err := FindFile(testDir, "") | ||
| require.Error(t, err, "no .preflight anywhere up to HOME") | ||
| } | ||
|
|
||
| func TestFindFile_StopsBeforeLeavingHome(t *testing.T) { | ||
| home := t.TempDir() | ||
| t.Setenv("HOME", home) | ||
| t.Setenv("USERPROFILE", home) | ||
|
|
||
| // A .preflight above HOME must not be picked up. | ||
| require.NoError(t, os.WriteFile(filepath.Join(filepath.Dir(home), ".preflight"), []byte("env HOME\n"), 0o600)) | ||
| t.Cleanup(func() { _ = os.Remove(filepath.Join(filepath.Dir(home), ".preflight")) }) | ||
|
Comment on lines
+102
to
+108
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Keep the fixture inside the test-owned temporary directory. Line 107 writes Create Proposed fix-func TestFindFile_StopsBeforeLeavingHome(t *testing.T) {
- home := t.TempDir()
+func TestFindFile_StopsBeforeLeavingHome(t *testing.T) {
+ root := t.TempDir()
+ home := filepath.Join(root, "home")
+ require.NoError(t, os.MkdirAll(home, 0o700))
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
// A .preflight above HOME must not be picked up.
- require.NoError(t, os.WriteFile(filepath.Join(filepath.Dir(home), ".preflight"), []byte("env HOME\n"), 0o600))
- t.Cleanup(func() { _ = os.Remove(filepath.Join(filepath.Dir(home), ".preflight")) })
+ require.NoError(t, os.WriteFile(filepath.Join(root, ".preflight"), []byte("env HOME\n"), 0o600))🤖 Prompt for AI Agents |
||
|
|
||
| testDir := filepath.Join(home, "project") | ||
| require.NoError(t, os.MkdirAll(testDir, 0o700)) | ||
|
|
||
| _, err := FindFile(testDir, "") | ||
| require.Error(t, err, "search must stop at HOME, not walk past it") | ||
| } | ||
|
|
||
| func TestParseFile(t *testing.T) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: vertti/preflight
Length of output: 40168
🏁 Script executed:
Repository: vertti/preflight
Length of output: 12363
Ensure the test reaches the tag-mismatch branch.
Create a temporary repository with a tag at
HEADand a different--tag-matchpattern, or assertno tag matches patternin the command output. Without a tag atHEAD, the check returns earlier withno tags.🤖 Prompt for AI Agents