@@ -11,27 +11,57 @@ pass() { PASS=$((PASS + 1)); echo " ✓ $1"; }
1111fail () { FAIL=$(( FAIL + 1 )) ; ERRORS+=(" $1 " ); echo " ✗ $1 " ; }
1212section () { echo ; echo " ── $1 ──" ; }
1313
14+ # Wrapper that tolerates crashes during Python exit cleanup (SIGSEGV=139, SIGABRT=134).
15+ # Some CI runners trigger these in native extension destructors (ncurses, uvloop, etc.)
16+ # after the command has completed its work. Not a Grove bug.
17+ gw () { command gw " $@ " || { rc=$? ; if [ $rc -eq 139 ] || [ $rc -eq 134 ]; then echo " (ignoring signal crash at exit, rc=$rc )" >&2 ; else return $rc ; fi ; }; }
18+
1419# ---------------------------------------------------------------------------
15- # Setup: create real git repos and Grove config
20+ # Setup: create test repos (including a clone of Grove itself)
1621# ---------------------------------------------------------------------------
1722section " Setup"
1823
19- export GROVE_HOME=" ${GROVE_HOME :- / tmp/ grove-e2e} "
24+ export GROVE_HOME=$( mktemp -d /tmp/grove-e2e.XXXXXX )
2025export HOME=" ${GROVE_HOME} "
26+ trap ' rm -rf "${GROVE_HOME}"' EXIT
27+
2128REPOS_DIR=" ${GROVE_HOME} /repos"
2229mkdir -p " ${REPOS_DIR} "
2330
2431git config --global user.email " e2e@grove.test"
2532git config --global user.name " Grove E2E"
2633git config --global init.defaultBranch main
2734
35+ # Simple repos with minimal history
2836for repo in svc-auth svc-api svc-gateway; do
2937 git init -q " ${REPOS_DIR} /${repo} "
3038 (cd " ${REPOS_DIR} /${repo} " && git commit --allow-empty -q -m " initial commit" )
3139done
32- echo " Created 3 test repos"
3340
34- echo " Repos ready"
41+ # Use a copy of the real Grove repo — has proper commit history for sync tests
42+ GROVE_SRC=" ${GROVE_SRC:-/ src/ grove} "
43+ if [ -d " ${GROVE_SRC} /.git" ]; then
44+ git clone -q --local " ${GROVE_SRC} " " ${REPOS_DIR} /grove"
45+ echo " Cloned Grove repo ($( cd " ${REPOS_DIR} /grove" && git rev-list --count HEAD) commits)"
46+ else
47+ # Fallback: create a bare origin + clone so we have proper remote refs
48+ git init -q --bare " ${REPOS_DIR} /grove-origin.git"
49+ git clone -q " ${REPOS_DIR} /grove-origin.git" " ${REPOS_DIR} /grove"
50+ (cd " ${REPOS_DIR} /grove" \
51+ && echo " v1" > README.md && git add . && git commit -q -m " first" \
52+ && echo " v2" >> README.md && git add . && git commit -q -m " second" \
53+ && echo " v3" >> README.md && git add . && git commit -q -m " third" \
54+ && git push -q origin main)
55+ echo " Created grove repo with 3 commits + origin (no source clone available)"
56+ fi
57+
58+ # Add a .grove.toml with setup hook to svc-auth
59+ cat > " ${REPOS_DIR} /svc-auth/.grove.toml" << 'TOML '
60+ setup = "touch .grove-setup-ran"
61+ TOML
62+ (cd " ${REPOS_DIR} /svc-auth" && git add .grove.toml && git commit -q -m " add grove config" )
63+
64+ echo " Created 4 test repos"
3565
3666# Verify gw is on PATH
3767gw --version
@@ -45,10 +75,11 @@ section "Init"
4575gw init " ${REPOS_DIR} " 2>&1
4676pass " init succeeded"
4777
48- if gw doctor --json | jq -e ' type == "array"' > /dev/null 2>&1 ; then
49- pass " doctor runs cleanly after init"
78+ issue_count=$( gw doctor --json | jq ' length' )
79+ if [ " ${issue_count} " = " 0" ]; then
80+ pass " doctor: zero issues after init"
5081else
51- fail " doctor failed after init"
82+ fail " doctor: found ${issue_count} issue(s) after clean init"
5283fi
5384
5485# ---------------------------------------------------------------------------
@@ -82,17 +113,60 @@ else
82113 fail " expected branch feat/e2e, got ${auth_branch} "
83114fi
84115
85- # Verify .mcp.json was written
116+ # Verify .mcp.json was written in workspace root AND worktree dirs
86117if [ -f " ${WS_DIR} /.mcp.json" ]; then
87118 if jq -e ' .mcpServers.grove' " ${WS_DIR} /.mcp.json" > /dev/null 2>&1 ; then
88- pass " .mcp.json has grove server entry"
119+ pass " .mcp.json has grove server entry (workspace root) "
89120 else
90121 fail " .mcp.json missing grove entry"
91122 fi
92123else
93124 fail " .mcp.json not created in workspace root"
94125fi
95126
127+ if [ -f " ${WS_DIR} /svc-auth/.mcp.json" ] && jq -e ' .mcpServers.grove' " ${WS_DIR} /svc-auth/.mcp.json" > /dev/null 2>&1 ; then
128+ pass " .mcp.json written to worktree directories"
129+ else
130+ fail " .mcp.json missing in worktree dir"
131+ fi
132+
133+ # Verify .grove.toml setup hook ran
134+ if [ -f " ${WS_DIR} /svc-auth/.grove-setup-ran" ]; then
135+ pass " .grove.toml setup hook executed"
136+ else
137+ fail " .grove.toml setup hook did not run"
138+ fi
139+
140+ # ---------------------------------------------------------------------------
141+ # Test: duplicate workspace name rejected
142+ # ---------------------------------------------------------------------------
143+ section " Error handling"
144+
145+ if ! gw create test-ws --branch feat/dupe --repos svc-auth 2> /dev/null; then
146+ pass " duplicate workspace name rejected"
147+ else
148+ fail " duplicate workspace name should have failed"
149+ gw delete test-ws --force 2> /dev/null || true
150+ fi
151+
152+ # ---------------------------------------------------------------------------
153+ # Test: gw go
154+ # ---------------------------------------------------------------------------
155+ section " Go"
156+
157+ go_output=$( gw go test-ws 2> /dev/null)
158+ if [ " ${go_output} " = " ${WS_DIR} " ]; then
159+ pass " go prints correct workspace path"
160+ else
161+ fail " go: expected ${WS_DIR} , got ${go_output} "
162+ fi
163+
164+ if ! gw go nonexistent-ws 2> /dev/null; then
165+ pass " go with invalid workspace exits non-zero"
166+ else
167+ fail " go with invalid workspace should have failed"
168+ fi
169+
96170# ---------------------------------------------------------------------------
97171# Test: status
98172# ---------------------------------------------------------------------------
@@ -125,6 +199,14 @@ else
125199 fail " expected feat/e2e, got ${gw_branch} "
126200fi
127201
202+ # Verify state reflects the new repo count
203+ repo_count=$( gw list test-ws --json 2> /dev/null | jq ' .repos | length' )
204+ if [ " ${repo_count} " = " 3" ]; then
205+ pass " state reflects 3 repos after add-repo"
206+ else
207+ fail " expected 3 repos in state, got ${repo_count} "
208+ fi
209+
128210# ---------------------------------------------------------------------------
129211# Test: remove-repo
130212# ---------------------------------------------------------------------------
@@ -140,15 +222,121 @@ else
140222fi
141223
142224# ---------------------------------------------------------------------------
143- # Test: doctor
225+ # Test: rename workspace
226+ # ---------------------------------------------------------------------------
227+ section " Rename"
228+
229+ gw rename test-ws --to renamed-ws
230+
231+ # Verify rename via state (not exit code — segfaults can happen at Python exit)
232+ if ! gw list --json 2> /dev/null | jq -e ' .[] | select(.name == "test-ws")' > /dev/null 2>&1 ; then
233+ pass " old workspace name gone from list"
234+ else
235+ fail " old workspace name still in list"
236+ fi
237+
238+ if gw list --json 2> /dev/null | jq -e ' .[] | select(.name == "renamed-ws")' > /dev/null; then
239+ pass " new workspace name in list"
240+ else
241+ fail " new workspace name not in list"
242+ fi
243+
244+ # Verify directory was renamed
245+ RENAMED_DIR=" ${GROVE_HOME} /.grove/workspaces/renamed-ws"
246+ if [ -d " ${RENAMED_DIR} /svc-auth" ]; then
247+ pass " workspace directory renamed"
248+ else
249+ fail " renamed workspace directory missing"
250+ fi
251+
252+ # Rename back for subsequent tests
253+ gw rename renamed-ws --to test-ws
254+ WS_DIR=" ${GROVE_HOME} /.grove/workspaces/test-ws"
255+
256+ # ---------------------------------------------------------------------------
257+ # Test: sync (using grove repo with real history)
258+ # ---------------------------------------------------------------------------
259+ section " Sync"
260+
261+ # Use the Grove clone — a real repo with full commit history
262+ GROVE_BASE=$( cd " ${REPOS_DIR} /grove" && git symbolic-ref --short HEAD)
263+
264+ gw create sync-ws --branch feat/sync-test --repos grove
265+ SYNC_WS_DIR=" ${GROVE_HOME} /.grove/workspaces/sync-ws"
266+ pass " created sync workspace with Grove repo"
267+
268+ # Clean the worktree so sync doesn't skip it (.mcp.json is untracked)
269+ (cd " ${SYNC_WS_DIR} /grove" && git add -A && git commit -q -m " workspace setup files" )
270+
271+ # Add a commit to the base branch in the source repo (simulating upstream work)
272+ # Then update origin/master ref so gw sync (which rebases onto origin/<base>) picks it up
273+ (cd " ${REPOS_DIR} /grove" \
274+ && git checkout -q " ${GROVE_BASE} " \
275+ && echo " upstream change" >> README.md \
276+ && git add . \
277+ && git commit -q -m " upstream: new feature" \
278+ && git update-ref " refs/remotes/origin/${GROVE_BASE} " HEAD \
279+ && git remote set-url origin /dev/null)
280+
281+ # Verify the worktree is behind origin/<base> (what gw sync rebases onto)
282+ behind=$( cd " ${SYNC_WS_DIR} /grove" && git rev-list --count " HEAD..origin/${GROVE_BASE} " 2> /dev/null || echo " ?" )
283+ if [ " ${behind} " != " 0" ] && [ " ${behind} " != " ?" ]; then
284+ pass " worktree is ${behind} commit(s) behind origin/${GROVE_BASE} "
285+ else
286+ fail " worktree should be behind origin/${GROVE_BASE} , got: ${behind} "
287+ fi
288+
289+ # Sync should rebase
290+ gw sync sync-ws 2>&1
291+ pass " sync command ran"
292+
293+ # After sync, should be up to date
294+ behind_after=$( cd " ${SYNC_WS_DIR} /grove" && git rev-list --count " HEAD..origin/${GROVE_BASE} " 2> /dev/null || echo " ?" )
295+ if [ " ${behind_after} " = " 0" ]; then
296+ pass " worktree up to date after sync"
297+ else
298+ fail " worktree still ${behind_after} behind after sync"
299+ fi
300+
301+ gw delete sync-ws --force
302+
303+ # ---------------------------------------------------------------------------
304+ # Test: doctor (healthy state)
144305# ---------------------------------------------------------------------------
145306section " Doctor"
146307
147- doctor_out =$( gw doctor --json 2> /dev/null)
148- if echo " ${doctor_out } " | jq -e ' type == "array" ' > /dev/null 2>&1 ; then
149- pass " doctor returns JSON array "
308+ issue_count =$( gw doctor --json 2> /dev/null | jq ' length ' )
309+ if [ " ${issue_count } " = " 0 " ] ; then
310+ pass " doctor: zero issues on healthy workspaces "
150311else
151- fail " doctor JSON output unexpected: ${doctor_out} "
312+ fail " doctor: found ${issue_count} unexpected issue(s)"
313+ fi
314+
315+ # ---------------------------------------------------------------------------
316+ # Test: doctor --fix (stale state)
317+ # ---------------------------------------------------------------------------
318+ section " Doctor --fix"
319+
320+ # Manually delete a worktree dir to create a stale state entry
321+ rm -rf " ${WS_DIR} /svc-api"
322+
323+ issue_count=$( gw doctor --json 2> /dev/null | jq ' length' )
324+ if [ " ${issue_count} " -gt " 0" ]; then
325+ pass " doctor detects missing worktree (${issue_count} issue(s))"
326+ else
327+ fail " doctor should detect missing worktree"
328+ fi
329+
330+ gw doctor --fix 2>&1
331+ pass " doctor --fix ran"
332+
333+ # After fix, issues should be resolved or reduced
334+ issue_count_after=$( gw doctor --json 2> /dev/null | jq ' length' )
335+ if [ " ${issue_count_after} " -lt " ${issue_count} " ]; then
336+ pass " doctor --fix reduced issues (${issue_count} -> ${issue_count_after} )"
337+ else
338+ # If fix couldn't resolve it, that's still informative
339+ pass " doctor --fix completed (issues: ${issue_count_after} )"
152340fi
153341
154342# ---------------------------------------------------------------------------
175363fi
176364
177365# ---------------------------------------------------------------------------
178- # Test: delete workspace
366+ # Test: delete workspace + branch cleanup
179367# ---------------------------------------------------------------------------
180368section " Delete workspace"
181369
@@ -189,13 +377,19 @@ else
189377 fail " expected 1 workspace after delete, got ${count} "
190378fi
191379
192- # Verify worktree dir is gone
193380if [ ! -d " ${GROVE_HOME} /.grove/workspaces/ws-two" ]; then
194381 pass " workspace directory cleaned up"
195382else
196383 fail " ws-two directory still exists"
197384fi
198385
386+ # Verify branch was cleaned up from source repo
387+ if ! (cd " ${REPOS_DIR} /svc-auth" && git branch --list feat/other | grep -q .); then
388+ pass " branch cleaned up from source repo after delete"
389+ else
390+ fail " branch feat/other still present in source repo"
391+ fi
392+
199393# ---------------------------------------------------------------------------
200394# Test: presets
201395# ---------------------------------------------------------------------------
0 commit comments