@@ -193,9 +193,13 @@ proc test_file_creation_isolation {} {
193193
194194 catch wait result
195195
196- # Check file was in overlay (before we discarded it)
197- # Note: file will be gone after discard, so we can't verify this way
198- # The test passes if merge-overlay detected and handled it
196+ # Verify overlay is now empty (confirms only one file was created and discarded)
197+ set overlay_files [glob -nocomplain -directory $overlay_upper *]
198+ if {[llength $overlay_files] == 0} {
199+ log_success "Overlay empty after discard (only one file was created)"
200+ } else {
201+ log_failure "Overlay not empty - unexpected files remain: $overlay_files"
202+ }
199203
200204 if {![file exists "$repo_dir/$test_file"]} {
201205 log_success "Original repository not modified"
@@ -207,7 +211,7 @@ proc test_file_creation_isolation {} {
207211
208212# Test 3: Protected directory access
209213proc test_protected_directories {} {
210- global repo_dir
214+ global repo_dir overlay_upper
211215
212216 start_test "Protected directories are read-only"
213217
@@ -244,6 +248,14 @@ proc test_protected_directories {} {
244248
245249 catch wait
246250
251+ # Verify overlay is empty (protected dirs shouldn't create overlay files)
252+ set overlay_files [glob -nocomplain -directory $overlay_upper *]
253+ if {[llength $overlay_files] == 0} {
254+ log_success "Overlay empty (protected directory write blocked)"
255+ } else {
256+ log_failure "Overlay not empty - unexpected files: $overlay_files"
257+ }
258+
247259 # Verify .git/config wasn't actually modified
248260 if {[catch {exec grep -c "# test" "$repo_dir/.git/config"} result] || $result eq "0"} {
249261 log_success ".git/config remained unmodified"
@@ -425,7 +437,179 @@ proc test_network_access {} {
425437 catch wait
426438}
427439
428- # Test 8: Overlay cleanup on empty session
440+ # Test 8: Comprehensive multi-file merge workflow
441+ proc test_multi_file_merge_workflow {} {
442+ global repo_dir overlay_upper
443+
444+ start_test "Multi-file merge: NEW, MODIFIED, DELETED with diff/accept/skip/discard"
445+
446+ # Pre-create files: one to modify, one to delete
447+ set existing_file "to-be-deleted.txt"
448+ set modify_file "to-be-modified.txt"
449+ exec sh -c "echo 'will be deleted' > $repo_dir/$existing_file"
450+ exec sh -c "echo 'original content' > $repo_dir/$modify_file"
451+
452+ # Create multiple files, modify one, delete one in a single session
453+ set timestamp [clock seconds]
454+ spawn ./agent-dev bash -c "
455+ echo 'content for file1' > merge-test1-$timestamp.txt
456+ echo 'content for file2' > merge-test2-$timestamp.txt
457+ echo 'content for file3' > merge-test3-$timestamp.txt
458+ echo 'modified content' > $modify_file
459+ rm $existing_file
460+ echo 'Session complete: 3 new, 1 modified, 1 deleted'
461+ "
462+
463+ expect {
464+ "Session complete: 3 new, 1 modified, 1 deleted" {
465+ log_success "Multiple changes made in sandbox (3 NEW, 1 MODIFIED, 1 DELETED)"
466+ }
467+ timeout {
468+ log_failure "Timeout creating changes in sandbox"
469+ expect eof
470+ catch wait
471+ return
472+ }
473+ }
474+
475+ # Verify overlay has changes (3 new files + 1 modified + deletion marker)
476+ # Note: Modern overlayfs uses character devices (0/0) for deletions, not .wh. files
477+ set overlay_items [glob -nocomplain -directory $overlay_upper *]
478+
479+ if {[llength $overlay_items] >= 4} {
480+ log_success "Overlay contains changes (found [llength $overlay_items] items)"
481+ } else {
482+ log_failure "Expected at least 4 items in overlay, found [llength $overlay_items]"
483+ }
484+
485+ # Handle all merge-overlay interactions
486+ # Use a simpler approach - just respond to each prompt as it comes
487+ expect {
488+ -re "Found (\\d+) file" {
489+ set num_files $expect_out(1,string)
490+ log_info "Merge-overlay will process $num_files files"
491+ }
492+ timeout {
493+ log_failure "Timeout waiting for file count"
494+ }
495+ }
496+
497+ # Handle each file interactively
498+ while {1} {
499+ expect {
500+ -re "NEW.*merge-test1-$timestamp\\.txt.*Action" {
501+ send "d\r"
502+ log_success "Requested diff for NEW file 1"
503+ exp_continue
504+ }
505+ -re "File does not exist in repository.*Action" {
506+ send "y\r"
507+ log_success "Diff warning shown, accepting NEW file 1"
508+ exp_continue
509+ }
510+ -re "✓ Accepted" {
511+ exp_continue
512+ }
513+ -re "NEW.*merge-test2-$timestamp\\.txt.*Action" {
514+ send "n\r"
515+ log_success "Skipping NEW file 2"
516+ exp_continue
517+ }
518+ -re "○ Skipped" {
519+ exp_continue
520+ }
521+ -re "NEW.*merge-test3-$timestamp\\.txt.*Action" {
522+ send "r\r"
523+ log_success "Discarding NEW file 3"
524+ exp_continue
525+ }
526+ -re "✓ Discarded" {
527+ exp_continue
528+ }
529+ -re "MODIFIED.*$modify_file.*Action" {
530+ send "d\r"
531+ log_success "Requested diff for MODIFIED file"
532+ exp_continue
533+ }
534+ -re "Diff:.*$modify_file.*Action" {
535+ send "y\r"
536+ log_success "Diff shown, accepting MODIFIED file"
537+ exp_continue
538+ }
539+ -re "DELETED.*$existing_file.*Action" {
540+ send "y\r"
541+ log_success "Accept DELETED file"
542+ exp_continue
543+ }
544+ -re "(File does not exist|already.*deleted).*Action" {
545+ send "y\r"
546+ log_success "Diff handled, accepting DELETED file"
547+ exp_continue
548+ }
549+ -re "✓ Deleted from repository" {
550+ exp_continue
551+ }
552+ eof {
553+ log_success "Merge-overlay completed all files"
554+ break
555+ }
556+ timeout {
557+ log_failure "Timeout during merge-overlay interaction"
558+ break
559+ }
560+ }
561+ }
562+
563+ catch wait
564+
565+ # Verify final state
566+ # File 1 should be in repo
567+ if {[file exists "$repo_dir/merge-test1-$timestamp.txt"]} {
568+ log_success "Accepted NEW file correctly in repository"
569+ } else {
570+ log_failure "Accepted NEW file not found in repository"
571+ }
572+
573+ # File 2 should still be in overlay
574+ if {[file exists "$overlay_upper/merge-test2-$timestamp.txt"]} {
575+ log_success "Skipped NEW file correctly remains in overlay"
576+ } else {
577+ log_failure "Skipped NEW file not found in overlay"
578+ }
579+
580+ # File 3 should not be in repo or overlay
581+ if {![file exists "$repo_dir/merge-test3-$timestamp.txt"] && ![file exists "$overlay_upper/merge-test3-$timestamp.txt"]} {
582+ log_success "Discarded NEW file correctly removed"
583+ } else {
584+ log_failure "Discarded NEW file found where it shouldn't be"
585+ }
586+
587+ # Modified file should have new content
588+ if {[file exists "$repo_dir/$modify_file"]} {
589+ set content [exec cat "$repo_dir/$modify_file"]
590+ if {$content eq "modified content"} {
591+ log_success "MODIFIED file has updated content in repository"
592+ } else {
593+ log_failure "MODIFIED file content incorrect: $content"
594+ }
595+ } else {
596+ log_failure "MODIFIED file not found in repository"
597+ }
598+
599+ # Deleted file should not exist in repo
600+ if {![file exists "$repo_dir/$existing_file"]} {
601+ log_success "DELETED file correctly removed from repository"
602+ } else {
603+ log_failure "DELETED file still exists in repository"
604+ }
605+
606+ # Cleanup: remove skipped file from overlay and test files from repo
607+ exec rm -f "$overlay_upper/merge-test2-$timestamp.txt"
608+ exec rm -f "$repo_dir/merge-test1-$timestamp.txt"
609+ exec rm -f "$repo_dir/$modify_file"
610+ }
611+
612+ # Test 9: Overlay cleanup on empty session
429613proc test_empty_overlay_handling {} {
430614 global overlay_upper
431615
@@ -483,6 +667,7 @@ proc run_all_tests {} {
483667 test_home_isolation
484668 test_working_directory
485669 test_network_access
670+ test_multi_file_merge_workflow
486671 test_empty_overlay_handling
487672
488673 cleanup_test_environment
0 commit comments