Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions cmd/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ import (
"github.com/spf13/cobra"
)

// TestMain clears user config and pager env vars so host settings don't break cmd tests.
func TestMain(m *testing.M) {
for _, env := range os.Environ() {
if k, _, ok := strings.Cut(env, "="); ok {
if strings.HasPrefix(k, "DIFFM_") && !strings.HasPrefix(k, "DIFFM_TEST_") {
_ = os.Unsetenv(k)
continue
}
switch k {
case "PAGER", "GIT_PAGER", "COLUMNS", "NO_COLOR", "CLICOLOR", "CLICOLOR_FORCE",
"GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE":
_ = os.Unsetenv(k)
}
}
}
os.Exit(m.Run())
}

func TestRootCmdFlags(t *testing.T) {
flags := []struct {
name string
Expand Down
8 changes: 8 additions & 0 deletions internal/git/git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import (
"testing"
)

// TestMain clears Git pointer env vars so the host repo state doesn't leak into tests.
func TestMain(m *testing.M) {
for _, k := range []string{"GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"} {
_ = os.Unsetenv(k)
}
os.Exit(m.Run())
}

func TestIsGitRepository(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
Expand Down
8 changes: 8 additions & 0 deletions internal/pager/pager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import (
"testing"
)

// TestMain clears pager env vars so host settings don't change test expectations.
func TestMain(m *testing.M) {
for _, k := range []string{"DIFFM_NO_PAGER", "GIT_PAGER", "PAGER"} {
_ = os.Unsetenv(k)
}
os.Exit(m.Run())
}

func TestPager_Disabled(t *testing.T) {
p, w := Start(true)
if p != nil {
Expand Down
68 changes: 67 additions & 1 deletion tests/e2e/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"os/exec"
"path/filepath"
"runtime"
"slices"
"strings"
"testing"

Expand Down Expand Up @@ -43,9 +44,27 @@ func TestMain(m *testing.M) {
panic("failed to build diffm: " + string(out) + ": " + err.Error())
}

sanitizeTestEnv()
os.Exit(m.Run())
}

// sanitizeTestEnv strips user config and pager env vars so shell settings don't bleed into e2e runs.
func sanitizeTestEnv() {
for _, env := range os.Environ() {
if k, _, ok := strings.Cut(env, "="); ok {
if strings.HasPrefix(k, "DIFFM_") && !strings.HasPrefix(k, "DIFFM_TEST_") {
_ = os.Unsetenv(k)
continue
}
switch k {
case "PAGER", "GIT_PAGER", "COLUMNS", "NO_COLOR", "CLICOLOR", "CLICOLOR_FORCE",
"GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE":
_ = os.Unsetenv(k)
}
}
}
}

// testdataDir returns the absolute path to tests/testdata/.
func testdataDir(t *testing.T) string {
t.Helper()
Expand Down Expand Up @@ -81,9 +100,27 @@ func fixtureFiles(t *testing.T, name string) (string, string) {
return oldPath, newPath
}

// runDiffm runs the diffm binary with the given args.
// isolatedEnv strips Git repo pointers and blocks host git configs so subprocesses run clean.
func isolatedEnv() []string {
env := slices.DeleteFunc(os.Environ(), func(kv string) bool {
k, _, ok := strings.Cut(kv, "=")
if !ok {
return true
}
switch k {
case "GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_CONFIG_GLOBAL", "GIT_CONFIG_NOSYSTEM":
return true
default:
return false
}
})
return append(env, "GIT_CONFIG_GLOBAL="+os.DevNull, "GIT_CONFIG_NOSYSTEM=1")
}

// runDiffm runs the diffm binary with the given args in an isolated environment.
func runDiffm(args ...string) (stdout, stderr string, err error) {
cmd := exec.Command(binaryPath, args...)
cmd.Env = isolatedEnv()
var outBuf, errBuf strings.Builder
cmd.Stdout = &outBuf
cmd.Stderr = &errBuf
Expand Down Expand Up @@ -536,6 +573,35 @@ func TestCLI_EnvVarDefaults(t *testing.T) {
}
}

func TestCLI_SubprocessEnvironmentIsolation(t *testing.T) {
t.Setenv("GIT_DIR", "/invalid/git/dir")
t.Setenv("GIT_WORK_TREE", "/invalid/work/tree")
t.Setenv("GIT_INDEX_FILE", "/invalid/index/file")

env := isolatedEnv()
for _, kv := range env {
k, _, _ := strings.Cut(kv, "=")
if k == "GIT_DIR" || k == "GIT_WORK_TREE" || k == "GIT_INDEX_FILE" {
t.Errorf("isolatedEnv leaked %s", k)
}
}
if !slices.Contains(env, "GIT_CONFIG_GLOBAL="+os.DevNull) {
t.Errorf("isolatedEnv missing GIT_CONFIG_GLOBAL=%s", os.DevNull)
}
if !slices.Contains(env, "GIT_CONFIG_NOSYSTEM=1") {
t.Error("isolatedEnv missing GIT_CONFIG_NOSYSTEM=1")
}

oldPath, newPath := fixtureFiles(t, sampleFixture(t))
stdout, stderr, err := runDiffm(oldPath, newPath)
if err != nil {
t.Fatalf("runDiffm failed under poisoned Git env: %v\nstderr: %s", err, stderr)
}
if stdout == "" {
t.Fatal("expected non-empty diff output under isolated env")
}
}

func TestCLI_GitMode_TypoFailFast(t *testing.T) {
// In the current git repo, diffm with a typo file should fail fast
_, stderr, err := runDiffm("HEAD", "nonexistent_typo_file_12345.go")
Expand Down
Loading