Skip to content

refactor: e2e tests in go#3

Merged
krystxf merged 5 commits into
mainfrom
refactor/e2e-tests-in-go
Mar 12, 2026
Merged

refactor: e2e tests in go#3
krystxf merged 5 commits into
mainfrom
refactor/e2e-tests-in-go

Conversation

@krystxf

@krystxf krystxf commented Mar 11, 2026

Copy link
Copy Markdown
Owner

No description provided.

@krystxf krystxf self-assigned this Mar 11, 2026
@coderabbitai

coderabbitai Bot commented Mar 11, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The pull request migrates end-to-end testing from shell scripts to Go's native testing framework. It introduces a comprehensive Go test suite with ~40 test scenarios, removes all Bash test runners and helpers, updates CI and build configurations to execute tests via go test, and refactors terminal detection from golang.org/x/term to github.com/mattn/go-isatty.

Changes

Cohort / File(s) Summary
E2E Test Framework Migration
e2e/e2e_test.go
New comprehensive Go test suite with TestMain bootstrap, 40+ test cases covering create/remove/list/init/help operations, and helper utilities for filesystem assertions, subprocess execution, config management, and repository initialization.
Removed Bash Test Infrastructure
e2e/helpers.sh, e2e/run.sh, e2e/setup.sh, e2e/test_*.sh
Deleted 15 shell test scripts and helper library providing Bash-based assertions and test runners. Functionality now integrated into Go test suite.
CI and Build Configuration
.github/workflows/ci.yml, Makefile
Updated CI job and Makefile targets to execute e2e tests via go test ./e2e/... instead of shell scripts; added Go setup step and docker-based e2e test target; added git default branch configuration.
Documentation Update
README.md
Added E2E Testing section documenting docker-based test execution via make e2e-docker with prerequisites and process description.
Dependency Updates
go.mod
Promoted github.com/mattn/go-isatty and github.com/charmbracelet/x/ansi from indirect to direct dependencies; removed golang.org/x/term direct dependency.
Terminal Detection Refactoring
internal/cmd/create.go, internal/cmd/rm.go
Replaced golang.org/x/term.IsTerminal() with github.com/mattn/go-isatty.IsTerminal() for interactive terminal detection in worktree creation and removal flows.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

  • go app letsgooooo #1: Directly inverse relationship — original PR introduced the shell-based e2e test infrastructure (helpers.sh, run.sh, test_*.sh, and shell CI runner) that this PR replaces with Go testing framework.
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning No description was provided by the author, making it impossible to evaluate relevance to the changeset. Add a description explaining the rationale, impact, and details of converting e2e tests from shell scripts to Go.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor: e2e tests in go' accurately describes the main change: converting shell-based e2e tests to Go-based tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/e2e-tests-in-go

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@krystxf krystxf marked this pull request as ready for review March 12, 2026 00:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/e2e_test.go`:
- Around line 380-384: The test currently ignores the error from the wtw call
(wtw(t, repo, "create", "hook-fail-test") //nolint:errcheck), which allows hook
failures to be silently swallowed; change the test to capture the returned error
(or command result) from wtw and assert it indicates failure (e.g.,
require.Error/NotNil or check non‑zero exit code) before asserting filesystem
side effects; update references in this test to use the captured error variable
and assert the command failed for the "create" invocation that targets
"hook-fail-test" so hook failures are enforced.
- Around line 23-45: TestMain currently lets the real $HOME leak into the e2e
suite; create an isolated temp home directory at the start of TestMain (e.g.,
via os.MkdirTemp), set os.Setenv("HOME", tmpHome) before any tests/run or before
writeGlobalConfig is ever called so all subprocesses inherit it, and defer
cleanup: remove the temp dir and restore the original HOME after m.Run; this
ensures writeGlobalConfig and any wtw/git subprocesses operate against the
isolated HOME during tests.
- Around line 195-206: In TestCreate, the test hardcodes checkout to "main"
which is host-dependent; capture the repository's initial branch immediately
after initRepo (e.g., call/get the current branch name into a variable like
initialBranch) before switching to "feature-one", and then use that variable
(initialBranch) instead of the literal "main" when checking out back; update
references in TestCreate (and any helper usages) to use the captured initial
branch so the test works regardless of the git default branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 54c7eabc-b4d7-405e-8e76-3150374261d1

📥 Commits

Reviewing files that changed from the base of the PR and between 22ed211 and 20480d7.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (32)
  • .github/workflows/ci.yml
  • Makefile
  • README.md
  • e2e/e2e_test.go
  • e2e/helpers.sh
  • e2e/run.sh
  • e2e/setup.sh
  • e2e/test_create.sh
  • e2e/test_create_duplicate.sh
  • e2e/test_create_new_branch.sh
  • e2e/test_create_second.sh
  • e2e/test_custom_naming.sh
  • e2e/test_defaults.sh
  • e2e/test_force_create.sh
  • e2e/test_force_rm.sh
  • e2e/test_help.sh
  • e2e/test_hooks.sh
  • e2e/test_hooks_fail.sh
  • e2e/test_init_local.sh
  • e2e/test_init_naming.sh
  • e2e/test_list.sh
  • e2e/test_no_sync.sh
  • e2e/test_not_git_repo.sh
  • e2e/test_rm.sh
  • e2e/test_rm_custom_naming.sh
  • e2e/test_rm_force.sh
  • e2e/test_rm_force_custom_naming.sh
  • e2e/test_rm_nonexistent.sh
  • e2e/test_sync.sh
  • go.mod
  • internal/cmd/create.go
  • internal/cmd/rm.go
💤 Files with no reviewable changes (25)
  • e2e/setup.sh
  • e2e/test_no_sync.sh
  • e2e/test_force_rm.sh
  • e2e/test_not_git_repo.sh
  • e2e/test_rm_nonexistent.sh
  • e2e/run.sh
  • e2e/test_force_create.sh
  • e2e/test_create_duplicate.sh
  • e2e/test_hooks_fail.sh
  • e2e/test_sync.sh
  • e2e/test_rm.sh
  • e2e/test_defaults.sh
  • e2e/test_rm_custom_naming.sh
  • e2e/helpers.sh
  • e2e/test_rm_force_custom_naming.sh
  • e2e/test_create_new_branch.sh
  • e2e/test_init_naming.sh
  • e2e/test_create.sh
  • e2e/test_rm_force.sh
  • e2e/test_create_second.sh
  • e2e/test_help.sh
  • e2e/test_list.sh
  • e2e/test_hooks.sh
  • e2e/test_init_local.sh
  • e2e/test_custom_naming.sh

Comment thread e2e/e2e_test.go
Comment on lines +23 to +45
func TestMain(m *testing.M) {
bin := os.Getenv("WTW")
if bin == "" {
bin = "../wtw"
}
if !filepath.IsAbs(bin) {
_, src, _, _ := runtime.Caller(0)
repoRoot := filepath.Dir(filepath.Dir(src))
bin = filepath.Join(repoRoot, bin)
}
abs, err := filepath.Abs(bin)
if err != nil {
panic(err)
}
wtwBin = abs

code := m.Run()
if code == 0 {
fmt.Printf("\n%s%s✓ All e2e tests passed%s\n", colorBold, colorGreen, colorReset)
} else {
fmt.Printf("\n%s%s✗ E2E tests failed%s\n", colorBold, colorRed, colorReset)
}
os.Exit(code)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Isolate $HOME for the E2E suite.

writeGlobalConfig later writes to $HOME/.config/worktree-workflow/config.json, and every wtw/git subprocess inherits this environment. As written, go test ./e2e/... will overwrite the developer’s real config and leak ambient home-state into the suite.

Proposed fix
 func TestMain(m *testing.M) {
+	testHome, err := os.MkdirTemp("", "wtw-e2e-home-*")
+	if err != nil {
+		panic(err)
+	}
+	if err := os.Setenv("HOME", testHome); err != nil {
+		panic(err)
+	}
+	if err := os.Setenv("XDG_CONFIG_HOME", filepath.Join(testHome, ".config")); err != nil {
+		panic(err)
+	}
+
 	bin := os.Getenv("WTW")
 	if bin == "" {
 		bin = "../wtw"
 	}
 	if !filepath.IsAbs(bin) {
@@
 	wtwBin = abs
 
 	code := m.Run()
+	_ = os.RemoveAll(testHome)
 	if code == 0 {
 		fmt.Printf("\n%s%s✓ All e2e tests passed%s\n", colorBold, colorGreen, colorReset)
 	} else {
 		fmt.Printf("\n%s%s✗ E2E tests failed%s\n", colorBold, colorRed, colorReset)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func TestMain(m *testing.M) {
bin := os.Getenv("WTW")
if bin == "" {
bin = "../wtw"
}
if !filepath.IsAbs(bin) {
_, src, _, _ := runtime.Caller(0)
repoRoot := filepath.Dir(filepath.Dir(src))
bin = filepath.Join(repoRoot, bin)
}
abs, err := filepath.Abs(bin)
if err != nil {
panic(err)
}
wtwBin = abs
code := m.Run()
if code == 0 {
fmt.Printf("\n%s%s✓ All e2e tests passed%s\n", colorBold, colorGreen, colorReset)
} else {
fmt.Printf("\n%s%s✗ E2E tests failed%s\n", colorBold, colorRed, colorReset)
}
os.Exit(code)
func TestMain(m *testing.M) {
testHome, err := os.MkdirTemp("", "wtw-e2e-home-*")
if err != nil {
panic(err)
}
if err := os.Setenv("HOME", testHome); err != nil {
panic(err)
}
if err := os.Setenv("XDG_CONFIG_HOME", filepath.Join(testHome, ".config")); err != nil {
panic(err)
}
bin := os.Getenv("WTW")
if bin == "" {
bin = "../wtw"
}
if !filepath.IsAbs(bin) {
_, src, _, _ := runtime.Caller(0)
repoRoot := filepath.Dir(filepath.Dir(src))
bin = filepath.Join(repoRoot, bin)
}
abs, err := filepath.Abs(bin)
if err != nil {
panic(err)
}
wtwBin = abs
code := m.Run()
_ = os.RemoveAll(testHome)
if code == 0 {
fmt.Printf("\n%s%s✓ All e2e tests passed%s\n", colorBold, colorGreen, colorReset)
} else {
fmt.Printf("\n%s%s✗ E2E tests failed%s\n", colorBold, colorRed, colorReset)
}
os.Exit(code)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/e2e_test.go` around lines 23 - 45, TestMain currently lets the real $HOME
leak into the e2e suite; create an isolated temp home directory at the start of
TestMain (e.g., via os.MkdirTemp), set os.Setenv("HOME", tmpHome) before any
tests/run or before writeGlobalConfig is ever called so all subprocesses inherit
it, and defer cleanup: remove the temp dir and restore the original HOME after
m.Run; this ensures writeGlobalConfig and any wtw/git subprocesses operate
against the isolated HOME during tests.

Comment thread e2e/e2e_test.go
Comment on lines +195 to +206
func TestCreate(t *testing.T) {
tmp := t.TempDir()
repo := filepath.Join(tmp, "test-project")
initRepo(t, repo, "feature-one")

gitCmd(t, repo, "checkout", "feature-one")
if err := os.WriteFile(filepath.Join(repo, "feature.js"), []byte("feature one code"), 0o644); err != nil {
t.Fatal(err)
}
gitCmd(t, repo, "add", "feature.js")
gitCmd(t, repo, "commit", "-m", "add feature one")
gitCmd(t, repo, "checkout", "main")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*.go" -o -name "Makefile" -o -name "ci.yml" | head -20

Repository: krystxf/worktree-workflow

Length of output: 532


🏁 Script executed:

cd . && git ls-files | grep -E "(e2e_test\.go|Makefile|ci\.yml)" | head -10

Repository: krystxf/worktree-workflow

Length of output: 132


🏁 Script executed:

ls -la

Repository: krystxf/worktree-workflow

Length of output: 1291


🏁 Script executed:

find . -type f \( -name "*.go" -o -name "Makefile" \) | grep -E "(e2e|Makefile)" | head -20

Repository: krystxf/worktree-workflow

Length of output: 97


🏁 Script executed:

cat -n e2e/e2e_test.go | head -250

Repository: krystxf/worktree-workflow

Length of output: 7956


🏁 Script executed:

cat -n Makefile

Repository: krystxf/worktree-workflow

Length of output: 973


🏁 Script executed:

cat -n .github/workflows/ci.yml | head -120

Repository: krystxf/worktree-workflow

Length of output: 3128


🏁 Script executed:

grep -n "checkout.*main" e2e/e2e_test.go

Repository: krystxf/worktree-workflow

Length of output: 109


🏁 Script executed:

grep -n "currentBranch" e2e/e2e_test.go

Repository: krystxf/worktree-workflow

Length of output: 233


🏁 Script executed:

cat -n e2e/e2e_test.go | sed -n '195,230p'

Repository: krystxf/worktree-workflow

Length of output: 1470


Replace hardcoded main in TestCreate with captured initial branch.

The hardcoded main at line 206 is host-dependent because initRepo does not explicitly set the default branch. While CI (.github/workflows/ci.yml line 99) and make e2e-docker (Makefile line 28) force repos to start on main via git config --global init.defaultBranch main, plain make e2e does not, making the test fail on hosts with a different default branch.

Capture the starting branch before switching away, as shown below:

Suggested fix
 func TestCreate(t *testing.T) {
 	tmp := t.TempDir()
 	repo := filepath.Join(tmp, "test-project")
 	initRepo(t, repo, "feature-one")
+	baseBranch := currentBranch(t, repo)
 
 	gitCmd(t, repo, "checkout", "feature-one")
 	if err := os.WriteFile(filepath.Join(repo, "feature.js"), []byte("feature one code"), 0o644); err != nil {
 		t.Fatal(err)
 	}
 	gitCmd(t, repo, "add", "feature.js")
 	gitCmd(t, repo, "commit", "-m", "add feature one")
-	gitCmd(t, repo, "checkout", "main")
+	gitCmd(t, repo, "checkout", baseBranch)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/e2e_test.go` around lines 195 - 206, In TestCreate, the test hardcodes
checkout to "main" which is host-dependent; capture the repository's initial
branch immediately after initRepo (e.g., call/get the current branch name into a
variable like initialBranch) before switching to "feature-one", and then use
that variable (initialBranch) instead of the literal "main" when checking out
back; update references in TestCreate (and any helper usages) to use the
captured initial branch so the test works regardless of the git default branch.

Comment thread e2e/e2e_test.go
Comment on lines +380 to +384
wtw(t, repo, "create", "hook-fail-test") //nolint:errcheck

worktree := filepath.Join(tmp, "test-project--worktrees", "test-project--hook-fail-test")
assertFileExists(t, filepath.Join(worktree, "before-fail.txt"))
assertFileNotExists(t, filepath.Join(worktree, "after-fail.txt"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Assert that hook failures return a non-zero command result.

Ignoring err weakens the migrated coverage: this test still passes if create starts swallowing hook failures but simply stops running later hooks. Assert the command failed, not just the filesystem side effects.

Proposed fix
-	wtw(t, repo, "create", "hook-fail-test") //nolint:errcheck
+	out, err := wtw(t, repo, "create", "hook-fail-test")
+	if err == nil {
+		t.Fatalf("expected create to fail when a post_copy_hook exits non-zero\n%s", out)
+	}
 
 	worktree := filepath.Join(tmp, "test-project--worktrees", "test-project--hook-fail-test")
 	assertFileExists(t, filepath.Join(worktree, "before-fail.txt"))
 	assertFileNotExists(t, filepath.Join(worktree, "after-fail.txt"))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
wtw(t, repo, "create", "hook-fail-test") //nolint:errcheck
worktree := filepath.Join(tmp, "test-project--worktrees", "test-project--hook-fail-test")
assertFileExists(t, filepath.Join(worktree, "before-fail.txt"))
assertFileNotExists(t, filepath.Join(worktree, "after-fail.txt"))
out, err := wtw(t, repo, "create", "hook-fail-test")
if err == nil {
t.Fatalf("expected create to fail when a post_copy_hook exits non-zero\n%s", out)
}
worktree := filepath.Join(tmp, "test-project--worktrees", "test-project--hook-fail-test")
assertFileExists(t, filepath.Join(worktree, "before-fail.txt"))
assertFileNotExists(t, filepath.Join(worktree, "after-fail.txt"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/e2e_test.go` around lines 380 - 384, The test currently ignores the error
from the wtw call (wtw(t, repo, "create", "hook-fail-test") //nolint:errcheck),
which allows hook failures to be silently swallowed; change the test to capture
the returned error (or command result) from wtw and assert it indicates failure
(e.g., require.Error/NotNil or check non‑zero exit code) before asserting
filesystem side effects; update references in this test to use the captured
error variable and assert the command failed for the "create" invocation that
targets "hook-fail-test" so hook failures are enforced.

@krystxf krystxf merged commit 9a7415b into main Mar 12, 2026
9 checks passed
@krystxf krystxf deleted the refactor/e2e-tests-in-go branch March 12, 2026 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant