@@ -6,6 +6,7 @@ import pMap from "p-map"
66import pWaitFor from "p-wait-for"
77import { execa , parseCommandString } from "execa"
88import { build , filesystem , GluegunPrompt , GluegunToolbox } from "gluegun"
9+ import psTree from "ps-tree"
910
1011import {
1112 type ExerciseLanguage ,
@@ -36,8 +37,9 @@ import { getExercises } from "./exercises.js"
3637type TaskResult = { success : boolean ; retry : boolean }
3738type TaskPromise = Promise < TaskResult >
3839
39- const TASK_TIMEOUT = 10 * 60 * 1_000
40- const UNIT_TEST_TIMEOUT = 60 * 1_000
40+ const TASK_START_DELAY = 10 * 1_000
41+ const TASK_TIMEOUT = 5 * 60 * 1_000
42+ const UNIT_TEST_TIMEOUT = 2 * 60 * 1_000
4143
4244const testCommands : Record < ExerciseLanguage , { commands : string [ ] ; timeout ?: number ; cwd ?: string } > = {
4345 go : { commands : [ "go test" ] } , // timeout 15s bash -c "cd '$dir' && go test > /dev/null 2>&1"
@@ -98,13 +100,11 @@ const run = async (toolbox: GluegunToolbox) => {
98100 throw new Error ( "No tasks found." )
99101 }
100102
101- console . log ( await execa ( { cwd : exercisesPath } ) `git config user.name "Roo Code"` )
102- console . log ( await execa ( { cwd :
exercisesPath } ) `git config user.email "[email protected] "` ) 103- console . log ( await execa ( { cwd : exercisesPath } ) `git checkout -f` )
104- console . log ( await execa ( { cwd : exercisesPath } ) `git clean -fd` )
105- console . log (
106- await execa ( { cwd : exercisesPath } ) `git checkout -b runs/${ run . id } -${ crypto . randomUUID ( ) . slice ( 0 , 8 ) } main` ,
107- )
103+ await execa ( { cwd : exercisesPath } ) `git config user.name "Roo Code"`
104+ await execa ( { cwd :
exercisesPath } ) `git config user.email "[email protected] "` 105+ await execa ( { cwd : exercisesPath } ) `git checkout -f`
106+ await execa ( { cwd : exercisesPath } ) `git clean -fd`
107+ await execa ( { cwd : exercisesPath } ) `git checkout -b runs/${ run . id } -${ crypto . randomUUID ( ) . slice ( 0 , 8 ) } main`
108108
109109 fs . writeFileSync (
110110 path . resolve ( exercisesPath , "settings.json" ) ,
@@ -145,11 +145,11 @@ const run = async (toolbox: GluegunToolbox) => {
145145 }
146146 }
147147
148- let delay = 0
148+ let delay = TASK_START_DELAY
149149
150150 for ( const task of tasks ) {
151151 const promise = processTask ( task , delay )
152- delay = delay + 5_000
152+ delay = delay + TASK_START_DELAY
153153 runningPromises . push ( promise )
154154 promise . then ( ( ) => processTaskResult ( task , promise ) )
155155
@@ -162,10 +162,10 @@ const run = async (toolbox: GluegunToolbox) => {
162162 await Promise . all ( runningPromises )
163163
164164 const result = await finishRun ( run . id )
165- console . log ( " [cli#run]" , result )
165+ console . log ( ` ${ Date . now ( ) } [cli#run]` , result )
166166
167- console . log ( await execa ( { cwd : exercisesPath } ) `git add .` )
168- console . log ( await execa ( { cwd : exercisesPath } ) `git commit -m ${ `Run #${ run . id } ` } --no-verify` )
167+ await execa ( { cwd : exercisesPath } ) `git add .`
168+ await execa ( { cwd : exercisesPath } ) `git commit -m ${ `Run #${ run . id } ` } --no-verify`
169169}
170170
171171const runExercise = async ( { run, task, server } : { run : Run ; task : Task ; server : IpcServer } ) : TaskPromise => {
@@ -180,9 +180,7 @@ const runExercise = async ({ run, task, server }: { run: Run; task: Task; server
180180 // Don't await execa and store result as subprocess.
181181 // subprocess.stdout.pipe(process.stdout)
182182
183- // Sleep for a random amount of time before opening a new VSCode window.
184- await new Promise ( ( resolve ) => setTimeout ( resolve , 1_000 + Math . random ( ) * 5_000 ) )
185- console . log ( `Opening new VS Code window at ${ workspacePath } ` )
183+ console . log ( `${ Date . now ( ) } [cli#runExercise] Opening new VS Code window at ${ workspacePath } ` )
186184
187185 await execa ( {
188186 env : {
@@ -192,15 +190,15 @@ const runExercise = async ({ run, task, server }: { run: Run; task: Task; server
192190 } ) `code --disable-workspace-trust -n ${ workspacePath } `
193191
194192 // Give VSCode some time to spawn before connecting to its unix socket.
195- await new Promise ( ( resolve ) => setTimeout ( resolve , 1_000 + Math . random ( ) * 4_000 ) )
196- console . log ( `Connecting to ${ taskSocketPath } ` )
193+ await new Promise ( ( resolve ) => setTimeout ( resolve , 3_000 ) )
194+ console . log ( `${ Date . now ( ) } [cli#runExercise] Connecting to ${ taskSocketPath } ` )
197195 const client = new IpcClient ( taskSocketPath )
198196
199197 try {
200198 await pWaitFor ( ( ) => client . isReady , { interval : 250 , timeout : 5_000 } )
201199 // eslint-disable-next-line @typescript-eslint/no-unused-vars
202200 } catch ( error ) {
203- console . log ( `[cli#runExercise | ${ language } / ${ exercise } ] unable to connect` )
201+ console . log ( `${ Date . now ( ) } [cli#runExercise | ${ language } / ${ exercise } ] unable to connect` )
204202 client . disconnect ( )
205203 return { success : false , retry : false }
206204 }
@@ -220,16 +218,20 @@ const runExercise = async ({ run, task, server }: { run: Run; task: Task; server
220218 client . on ( IpcMessageType . TaskEvent , async ( taskEvent ) => {
221219 const { eventName, payload } = taskEvent
222220
223- server . broadcast ( {
224- type : IpcMessageType . TaskEvent ,
225- origin : IpcOrigin . Server ,
226- relayClientId : client . clientId ! ,
227- data : { ...taskEvent , taskId : task . id } ,
228- } )
221+ if ( taskEvent . eventName !== RooCodeEventName . Message ) {
222+ server . broadcast ( {
223+ type : IpcMessageType . TaskEvent ,
224+ origin : IpcOrigin . Server ,
225+ relayClientId : client . clientId ! ,
226+ data : { ...taskEvent , taskId : task . id } ,
227+ } )
228+ }
229229
230230 if ( ! ignoreEvents . includes ( eventName ) ) {
231- console . log ( `[cli#runExercise | ${ language } / ${ exercise } ] taskEvent -> ${ eventName } ` )
232- console . log ( payload )
231+ console . log (
232+ `${ Date . now ( ) } [cli#runExercise | ${ language } / ${ exercise } ] taskEvent -> ${ eventName } ` ,
233+ payload ,
234+ )
233235 }
234236
235237 if ( eventName === RooCodeEventName . TaskStarted ) {
@@ -279,11 +281,11 @@ const runExercise = async ({ run, task, server }: { run: Run; task: Task; server
279281 } )
280282
281283 client . on ( IpcMessageType . Disconnect , async ( ) => {
282- console . log ( `[cli#runExercise | ${ language } / ${ exercise } ] disconnect` )
284+ console . log ( `${ Date . now ( ) } [cli#runExercise | ${ language } / ${ exercise } ] disconnect` )
283285 isClientDisconnected = true
284286 } )
285287
286- console . log ( `[cli#runExercise | ${ language } / ${ exercise } ] starting task` )
288+ console . log ( `${ Date . now ( ) } [cli#runExercise | ${ language } / ${ exercise } ] starting task` )
287289
288290 client . sendMessage ( {
289291 type : IpcMessageType . TaskCommand ,
@@ -307,7 +309,7 @@ const runExercise = async ({ run, task, server }: { run: Run; task: Task; server
307309 await pWaitFor ( ( ) => ! ! taskFinishedAt || isClientDisconnected , { interval : 1_000 , timeout : TASK_TIMEOUT } )
308310 // eslint-disable-next-line @typescript-eslint/no-unused-vars
309311 } catch ( error ) {
310- console . log ( `[cli#runExercise | ${ language } / ${ exercise } ] time limit reached` )
312+ console . log ( `${ Date . now ( ) } [cli#runExercise | ${ language } / ${ exercise } ] time limit reached` )
311313
312314 // Cancel the task.
313315 if ( rooTaskId && ! isClientDisconnected ) {
@@ -351,17 +353,73 @@ const runUnitTest = async ({ task }: { task: Task }) => {
351353 let passed = true
352354
353355 for ( const command of commands ) {
354- const timeout = cmd . timeout ?? UNIT_TEST_TIMEOUT
355-
356356 try {
357- const result = await execa ( { cwd, shell : true , reject : false , timeout } ) `${ command } `
357+ console . log (
358+ `${ Date . now ( ) } [cli#runUnitTest | ${ task . language } / ${ task . exercise } ] running "${ command . join ( " " ) } "` ,
359+ )
360+
361+ const subprocess = execa ( { cwd, shell : true , reject : false } ) `${ command } `
362+
363+ const timeout = setTimeout ( async ( ) => {
364+ const descendants = await new Promise < number [ ] > ( ( resolve , reject ) => {
365+ psTree ( subprocess . pid ! , ( err , children ) => {
366+ if ( err ) {
367+ reject ( err )
368+ }
369+
370+ resolve ( children . map ( ( p ) => parseInt ( p . PID ) ) )
371+ } )
372+ } )
373+
374+ console . log (
375+ `${ Date . now ( ) } [cli#runUnitTest | ${ task . language } / ${ task . exercise } ] "${ command . join ( " " ) } ": unit tests timed out, killing ${ subprocess . pid } + ${ JSON . stringify ( descendants ) } ` ,
376+ )
377+
378+ if ( descendants . length > 0 ) {
379+ for ( const descendant of descendants ) {
380+ try {
381+ console . log (
382+ `${ Date . now ( ) } [cli#runUnitTest | ${ task . language } / ${ task . exercise } ] killing ${ descendant } ` ,
383+ )
384+
385+ await execa `kill -9 ${ descendant } `
386+ } catch ( error ) {
387+ console . error (
388+ `${ Date . now ( ) } [cli#runUnitTest | ${ task . language } / ${ task . exercise } ] Error killing descendant processes:` ,
389+ error ,
390+ )
391+ }
392+ }
393+ }
394+
395+ console . log (
396+ `${ Date . now ( ) } [cli#runUnitTest | ${ task . language } / ${ task . exercise } ] killing ${ subprocess . pid } ` ,
397+ )
398+
399+ try {
400+ await execa `kill -9 ${ subprocess . pid ! } `
401+ } catch ( error ) {
402+ console . error (
403+ `${ Date . now ( ) } [cli#runUnitTest | ${ task . language } / ${ task . exercise } ] Error killing process:` ,
404+ error ,
405+ )
406+ }
407+ } , UNIT_TEST_TIMEOUT )
408+
409+ const result = await subprocess
410+
411+ console . log (
412+ `${ Date . now ( ) } [cli#runUnitTest | ${ task . language } / ${ task . exercise } ] "${ command . join ( " " ) } " result -> ${ JSON . stringify ( result ) } ` ,
413+ )
414+
415+ clearTimeout ( timeout )
358416
359417 if ( result . failed ) {
360418 passed = false
361419 break
362420 }
363421 } catch ( error ) {
364- console . log ( " [cli#runUnitTest]" , error )
422+ console . log ( ` ${ Date . now ( ) } [cli#runUnitTest | ${ task . language } / ${ task . exercise } ]` , error )
365423 passed = false
366424 break
367425 }
0 commit comments