@@ -3,6 +3,7 @@ package orchestrator_test
33import (
44 "context"
55 "encoding/json"
6+ "fmt"
67 "net"
78 "testing"
89 "time"
@@ -14,13 +15,18 @@ import (
1415)
1516
1617type fakeSdkExec struct {
17- startService func (ctx context.Context , taskRunID string , socket string , mode orchestrator.Mode ) (cleanupFunc func () error , err error )
18+ startService func (ctx context.Context , taskRunID string , socket string , mode orchestrator.Mode ) (cleanupFunc func () error , processDone <- chan error , err error )
1819}
1920
20- func (f * fakeSdkExec ) StartService (ctx context.Context , taskRunID string , socket string , mode orchestrator.Mode ) (orchestrator.CleanupFunc , error ) {
21+ func (f * fakeSdkExec ) StartService (ctx context.Context , taskRunID string , socket string , mode orchestrator.Mode ) (orchestrator.CleanupFunc , <- chan error , error ) {
2122 return f .startService (ctx , taskRunID , socket , mode )
2223}
2324
25+ // neverExits returns a process-done channel that never fires, simulating a long-running process.
26+ func neverExits () <- chan error {
27+ return make (chan error )
28+ }
29+
2430type fakeServerFactory struct {
2531 newHandler func (socket net.Listener , input taskserver.GetInput200JSONResponse , getSubtaskResultFunc taskserver.GetSubtaskResultFunc , startSubtaskFunc taskserver.StartSubtaskFunc ) * taskserver.ServerHandler
2632}
@@ -75,15 +81,15 @@ func TestStartTask(t *testing.T) {
7581 ctx ,
7682 s ,
7783 & fakeSdkExec {
78- startService : func (ctx context.Context , taskRunID string , socket string , mode orchestrator.Mode ) (func () error , error ) {
84+ startService : func (ctx context.Context , taskRunID string , socket string , mode orchestrator.Mode ) (func () error , <- chan error , error ) {
7985 if startCount == 0 {
8086 require .Equal (t , mode , orchestrator .ModeRegister )
8187 startCount ++
8288 } else {
8389 require .Equal (t , mode , orchestrator .ModeRun )
8490 }
8591
86- return cleanupFunc , nil
92+ return cleanupFunc , neverExits (), nil
8793 },
8894 },
8995 socketTracker ,
@@ -162,8 +168,8 @@ func TestStartTaskWithSubtask(t *testing.T) {
162168 ctx ,
163169 s ,
164170 & fakeSdkExec {
165- startService : func (ctx context.Context , taskRunID string , socket string , mode orchestrator.Mode ) (func () error , error ) {
166- return cleanupFunc , nil
171+ startService : func (ctx context.Context , taskRunID string , socket string , mode orchestrator.Mode ) (func () error , <- chan error , error ) {
172+ return cleanupFunc , neverExits (), nil
167173 },
168174 },
169175 socketTracker ,
@@ -244,3 +250,113 @@ func TestStartTaskWithSubtask(t *testing.T) {
244250 return taskRun .(taskserver.PostGetSubtaskResult200JSONResponse ).Complete != nil
245251 }, time .Second * 2 , time .Millisecond * 10 )
246252}
253+
254+ func TestPopulateTasksProcessExits (t * testing.T ) {
255+ ctx := context .Background ()
256+
257+ s := store .NewTaskStore ()
258+
259+ postTasksChan := make (chan taskserver.PostRegisterTasksRequestObject )
260+
261+ socketTracker , err := orchestrator .NewSocketTracker (ctx )
262+ require .NoError (t , err )
263+
264+ coordinator := orchestrator .NewCoordinator (
265+ ctx ,
266+ s ,
267+ & fakeSdkExec {
268+ startService : func (ctx context.Context , taskRunID string , socket string , mode orchestrator.Mode ) (func () error , <- chan error , error ) {
269+ processDone := make (chan error , 1 )
270+ processDone <- fmt .Errorf ("exit status 1" )
271+ return func () error { return nil }, processDone , nil
272+ },
273+ },
274+ socketTracker ,
275+ & fakeServerFactory {
276+ newHandler : func (socket net.Listener , input taskserver.GetInput200JSONResponse , getSubtaskResultFunc taskserver.GetSubtaskResultFunc , startSubtaskFunc taskserver.StartSubtaskFunc ) * taskserver.ServerHandler {
277+ return & taskserver.ServerHandler {
278+ Socket : socket ,
279+ Input : input ,
280+ Channels : taskserver.ServerChannels {
281+ PostCallback : make (chan taskserver.PostCallbackRequestObject ),
282+ PostTasks : postTasksChan ,
283+ },
284+ }
285+ },
286+ },
287+ & noopStatusReporter {},
288+ )
289+
290+ _ , err = coordinator .PopulateTasks (ctx )
291+ require .Error (t , err )
292+ require .Contains (t , err .Error (), "start command exited before registering tasks" )
293+ require .Contains (t , err .Error (), "exit status 1" )
294+ require .Contains (t , err .Error (), "--debug" )
295+ }
296+
297+ func TestStartTaskProcessExits (t * testing.T ) {
298+ ctx := context .Background ()
299+
300+ s := store .NewTaskStore ()
301+
302+ tasks := []taskserver.Task {
303+ {
304+ Name : "test-task" ,
305+ },
306+ }
307+
308+ postTasksChan := make (chan taskserver.PostRegisterTasksRequestObject , 1 )
309+ postTasksChan <- taskserver.PostRegisterTasksRequestObject {
310+ Body : & taskserver.PostRegisterTasksJSONRequestBody {
311+ Tasks : tasks ,
312+ },
313+ }
314+
315+ socketTracker , err := orchestrator .NewSocketTracker (ctx )
316+ require .NoError (t , err )
317+
318+ startCount := 0
319+ coordinator := orchestrator .NewCoordinator (
320+ ctx ,
321+ s ,
322+ & fakeSdkExec {
323+ startService : func (ctx context.Context , taskRunID string , socket string , mode orchestrator.Mode ) (func () error , <- chan error , error ) {
324+ startCount ++
325+ if startCount == 1 {
326+ // Registration succeeds normally
327+ return func () error { return nil }, neverExits (), nil
328+ }
329+ // Task run: process exits immediately
330+ processDone := make (chan error , 1 )
331+ processDone <- fmt .Errorf ("exit status 1" )
332+ return func () error { return nil }, processDone , nil
333+ },
334+ },
335+ socketTracker ,
336+ & fakeServerFactory {
337+ newHandler : func (socket net.Listener , input taskserver.GetInput200JSONResponse , getSubtaskResultFunc taskserver.GetSubtaskResultFunc , startSubtaskFunc taskserver.StartSubtaskFunc ) * taskserver.ServerHandler {
338+ return & taskserver.ServerHandler {
339+ Socket : socket ,
340+ Input : input ,
341+ Channels : taskserver.ServerChannels {
342+ PostCallback : make (chan taskserver.PostCallbackRequestObject ),
343+ PostTasks : postTasksChan ,
344+ },
345+ }
346+ },
347+ },
348+ & noopStatusReporter {},
349+ )
350+
351+ taskRun , err := coordinator .StartTask (ctx , "fake-workflow/test-task" , []byte {}, nil )
352+ require .NoError (t , err )
353+
354+ // The background goroutine should detect the process exit and fail the task run
355+ require .Eventually (t , func () bool {
356+ tr := s .GetTaskRun (taskRun .ID )
357+ return tr .Status == store .TaskRunStatusFailed
358+ }, time .Second * 2 , time .Millisecond * 10 )
359+
360+ tr := s .GetTaskRun (taskRun .ID )
361+ require .Contains (t , * tr .Error , "start command exited before completing the task" )
362+ }
0 commit comments