@@ -415,6 +415,232 @@ describeIntegration("Runtime integration tests", () => {
415415 expect ( content ) . toBe ( "nested" ) ;
416416 } ) ;
417417 } ) ;
418+
419+ describe ( "Git operations" , ( ) => {
420+ test . concurrent ( "can initialize a git repository" , async ( ) => {
421+ const runtime = createRuntime ( ) ;
422+ await using workspace = await TestWorkspace . create ( runtime , type ) ;
423+
424+ // Initialize git repo
425+ const result = await execBuffered ( runtime , "git init" , {
426+ cwd : workspace . path ,
427+ timeout : 30 ,
428+ } ) ;
429+
430+ expect ( result . exitCode ) . toBe ( 0 ) ;
431+
432+ // Verify .git directory exists
433+ const stat = await runtime . stat ( `${ workspace . path } /.git` ) ;
434+ expect ( stat . isDirectory ) . toBe ( true ) ;
435+ } ) ;
436+
437+ test . concurrent ( "can create commits" , async ( ) => {
438+ const runtime = createRuntime ( ) ;
439+ await using workspace = await TestWorkspace . create ( runtime , type ) ;
440+
441+ // Initialize git and configure user
442+ await execBuffered (
443+ runtime ,
444+ `git init && git config user.email "test@example.com" && git config user.name "Test User"` ,
445+ { cwd : workspace . path , timeout : 30 }
446+ ) ;
447+
448+ // Create a file and commit
449+ await writeFileString ( runtime , `${ workspace . path } /test.txt` , "initial content" ) ;
450+ await execBuffered ( runtime , `git add test.txt && git commit -m "Initial commit"` , {
451+ cwd : workspace . path ,
452+ timeout : 30 ,
453+ } ) ;
454+
455+ // Verify commit exists
456+ const logResult = await execBuffered ( runtime , "git log --oneline" , {
457+ cwd : workspace . path ,
458+ timeout : 30 ,
459+ } ) ;
460+
461+ expect ( logResult . stdout ) . toContain ( "Initial commit" ) ;
462+ } ) ;
463+
464+ test . concurrent ( "can create and checkout branches" , async ( ) => {
465+ const runtime = createRuntime ( ) ;
466+ await using workspace = await TestWorkspace . create ( runtime , type ) ;
467+
468+ // Setup git repo
469+ await execBuffered (
470+ runtime ,
471+ `git init && git config user.email "test@example.com" && git config user.name "Test"` ,
472+ { cwd : workspace . path , timeout : 30 }
473+ ) ;
474+
475+ // Create initial commit
476+ await writeFileString ( runtime , `${ workspace . path } /file.txt` , "content" ) ;
477+ await execBuffered ( runtime , `git add file.txt && git commit -m "init"` , {
478+ cwd : workspace . path ,
479+ timeout : 30 ,
480+ } ) ;
481+
482+ // Create and checkout new branch
483+ await execBuffered ( runtime , "git checkout -b feature-branch" , {
484+ cwd : workspace . path ,
485+ timeout : 30 ,
486+ } ) ;
487+
488+ // Verify branch
489+ const branchResult = await execBuffered ( runtime , "git branch --show-current" , {
490+ cwd : workspace . path ,
491+ timeout : 30 ,
492+ } ) ;
493+
494+ expect ( branchResult . stdout . trim ( ) ) . toBe ( "feature-branch" ) ;
495+ } ) ;
496+
497+ test . concurrent ( "can handle git status in dirty workspace" , async ( ) => {
498+ const runtime = createRuntime ( ) ;
499+ await using workspace = await TestWorkspace . create ( runtime , type ) ;
500+
501+ // Setup git repo with commit
502+ await execBuffered (
503+ runtime ,
504+ `git init && git config user.email "test@example.com" && git config user.name "Test"` ,
505+ { cwd : workspace . path , timeout : 30 }
506+ ) ;
507+ await writeFileString ( runtime , `${ workspace . path } /file.txt` , "original" ) ;
508+ await execBuffered ( runtime , `git add file.txt && git commit -m "init"` , {
509+ cwd : workspace . path ,
510+ timeout : 30 ,
511+ } ) ;
512+
513+ // Make changes
514+ await writeFileString ( runtime , `${ workspace . path } /file.txt` , "modified" ) ;
515+
516+ // Check status
517+ const statusResult = await execBuffered ( runtime , "git status --short" , {
518+ cwd : workspace . path ,
519+ timeout : 30 ,
520+ } ) ;
521+
522+ expect ( statusResult . stdout ) . toContain ( "M file.txt" ) ;
523+ } ) ;
524+ } ) ;
525+
526+ describe ( "Environment and shell behavior" , ( ) => {
527+ test . concurrent ( "preserves multi-line output formatting" , async ( ) => {
528+ const runtime = createRuntime ( ) ;
529+ await using workspace = await TestWorkspace . create ( runtime , type ) ;
530+
531+ const result = await execBuffered ( runtime , 'echo "line1\nline2\nline3"' , {
532+ cwd : workspace . path ,
533+ timeout : 30 ,
534+ } ) ;
535+
536+ expect ( result . stdout ) . toContain ( "line1" ) ;
537+ expect ( result . stdout ) . toContain ( "line2" ) ;
538+ expect ( result . stdout ) . toContain ( "line3" ) ;
539+ } ) ;
540+
541+ test . concurrent ( "handles commands with pipes" , async ( ) => {
542+ const runtime = createRuntime ( ) ;
543+ await using workspace = await TestWorkspace . create ( runtime , type ) ;
544+
545+ await writeFileString ( runtime , `${ workspace . path } /test.txt` , "line1\nline2\nline3" ) ;
546+
547+ const result = await execBuffered ( runtime , "cat test.txt | grep line2" , {
548+ cwd : workspace . path ,
549+ timeout : 30 ,
550+ } ) ;
551+
552+ expect ( result . stdout . trim ( ) ) . toBe ( "line2" ) ;
553+ } ) ;
554+
555+ test . concurrent ( "handles command substitution" , async ( ) => {
556+ const runtime = createRuntime ( ) ;
557+ await using workspace = await TestWorkspace . create ( runtime , type ) ;
558+
559+ const result = await execBuffered ( runtime , 'echo "Current dir: $(basename $(pwd))"' , {
560+ cwd : workspace . path ,
561+ timeout : 30 ,
562+ } ) ;
563+
564+ expect ( result . stdout ) . toContain ( "Current dir:" ) ;
565+ } ) ;
566+
567+ test . concurrent ( "handles large stdout output" , async ( ) => {
568+ const runtime = createRuntime ( ) ;
569+ await using workspace = await TestWorkspace . create ( runtime , type ) ;
570+
571+ // Generate large output (1000 lines)
572+ const result = await execBuffered ( runtime , "seq 1 1000" , {
573+ cwd : workspace . path ,
574+ timeout : 30 ,
575+ } ) ;
576+
577+ const lines = result . stdout . trim ( ) . split ( "\n" ) ;
578+ expect ( lines . length ) . toBe ( 1000 ) ;
579+ expect ( lines [ 0 ] ) . toBe ( "1" ) ;
580+ expect ( lines [ 999 ] ) . toBe ( "1000" ) ;
581+ } ) ;
582+
583+ test . concurrent ( "handles commands that produce no output but take time" , async ( ) => {
584+ const runtime = createRuntime ( ) ;
585+ await using workspace = await TestWorkspace . create ( runtime , type ) ;
586+
587+ const result = await execBuffered ( runtime , "sleep 0.1" , {
588+ cwd : workspace . path ,
589+ timeout : 30 ,
590+ } ) ;
591+
592+ expect ( result . exitCode ) . toBe ( 0 ) ;
593+ expect ( result . stdout ) . toBe ( "" ) ;
594+ expect ( result . duration ) . toBeGreaterThanOrEqual ( 100 ) ;
595+ } ) ;
596+ } ) ;
597+
598+ describe ( "Error handling" , ( ) => {
599+ test . concurrent ( "handles command not found" , async ( ) => {
600+ const runtime = createRuntime ( ) ;
601+ await using workspace = await TestWorkspace . create ( runtime , type ) ;
602+
603+ const result = await execBuffered ( runtime , "nonexistentcommand" , {
604+ cwd : workspace . path ,
605+ timeout : 30 ,
606+ } ) ;
607+
608+ expect ( result . exitCode ) . not . toBe ( 0 ) ;
609+ expect ( result . stderr . toLowerCase ( ) ) . toContain ( "not found" ) ;
610+ } ) ;
611+
612+ test . concurrent ( "handles syntax errors in bash" , async ( ) => {
613+ const runtime = createRuntime ( ) ;
614+ await using workspace = await TestWorkspace . create ( runtime , type ) ;
615+
616+ const result = await execBuffered ( runtime , "if true; then echo 'missing fi'" , {
617+ cwd : workspace . path ,
618+ timeout : 30 ,
619+ } ) ;
620+
621+ expect ( result . exitCode ) . not . toBe ( 0 ) ;
622+ } ) ;
623+
624+ test . concurrent ( "handles permission denied errors" , async ( ) => {
625+ const runtime = createRuntime ( ) ;
626+ await using workspace = await TestWorkspace . create ( runtime , type ) ;
627+
628+ // Create file without execute permission and try to execute it
629+ await writeFileString ( runtime , `${ workspace . path } /script.sh` , "#!/bin/sh\necho test" ) ;
630+ await execBuffered ( runtime , "chmod 644 script.sh" , {
631+ cwd : workspace . path ,
632+ timeout : 30 ,
633+ } ) ;
634+
635+ const result = await execBuffered ( runtime , "./script.sh" , {
636+ cwd : workspace . path ,
637+ timeout : 30 ,
638+ } ) ;
639+
640+ expect ( result . exitCode ) . not . toBe ( 0 ) ;
641+ expect ( result . stderr . toLowerCase ( ) ) . toContain ( "permission denied" ) ;
642+ } ) ;
643+ } ) ;
418644 }
419645 ) ;
420646} ) ;
0 commit comments