diff --git a/actions.go b/actions.go index 2181c6c..8723afc 100644 --- a/actions.go +++ b/actions.go @@ -194,7 +194,7 @@ type ActionProcessExited struct { ID string ExitCode int Duration time.Duration - Error error // Reserved for future exit metadata; currently left unset by the service + Error error // Set for failed starts, non-zero exits, or killed processes. } // ActionProcessKilled is broadcast when a process is terminated. diff --git a/service.go b/service.go index 9c4ca09..2a7fb55 100644 --- a/service.go +++ b/service.go @@ -215,6 +215,7 @@ func (s *Service) StartWithOptions(ctx context.Context, opts RunOptions) (*Proce // Start the process if err := cmd.Start(); err != nil { + startErr := coreerr.E("Service.StartWithOptions", "failed to start process", err) proc.mu.Lock() proc.Status = StatusFailed proc.ExitCode = -1 @@ -232,10 +233,10 @@ func (s *Service) StartWithOptions(ctx context.Context, opts RunOptions) (*Proce ID: id, ExitCode: -1, Duration: proc.Duration, - Error: nil, + Error: startErr, }) } - return proc, coreerr.E("Service.StartWithOptions", "failed to start process", err) + return proc, startErr } proc.mu.Lock() @@ -291,7 +292,7 @@ func (s *Service) StartWithOptions(ctx context.Context, opts RunOptions) (*Proce err := cmd.Wait() duration := time.Since(proc.StartedAt) - status, exitCode, _, signalName := classifyProcessExit(err) + status, exitCode, exitErr, signalName := classifyProcessExit(err) proc.mu.Lock() proc.Duration = duration @@ -309,7 +310,7 @@ func (s *Service) StartWithOptions(ctx context.Context, opts RunOptions) (*Proce ID: id, ExitCode: exitCode, Duration: duration, - Error: nil, + Error: exitErr, } if c := s.coreApp(); c != nil { diff --git a/service_test.go b/service_test.go index d531687..42d135b 100644 --- a/service_test.go +++ b/service_test.go @@ -337,7 +337,8 @@ func TestService_Actions(t *testing.T) { defer mu.Unlock() assert.Len(t, exited, 1) assert.Equal(t, proc.ID, exited[0].ID) - assert.Nil(t, exited[0].Error) + require.Error(t, exited[0].Error) + assert.Contains(t, exited[0].Error.Error(), "process was killed") assert.Equal(t, StatusKilled, proc.Status) }) @@ -370,7 +371,42 @@ func TestService_Actions(t *testing.T) { defer mu.Unlock() require.Len(t, exited, 1) assert.Equal(t, -1, exited[0].ExitCode) - assert.Nil(t, exited[0].Error) + require.Error(t, exited[0].Error) + assert.Contains(t, exited[0].Error.Error(), "failed to start process") + }) + + t.Run("broadcasts exited error on non-zero exit", func(t *testing.T) { + c := framework.New() + + factory := NewService(Options{}) + raw, err := factory(c) + require.NoError(t, err) + svc := raw.(*Service) + + var exited []ActionProcessExited + var mu sync.Mutex + + c.RegisterAction(func(cc *framework.Core, msg framework.Message) framework.Result { + mu.Lock() + defer mu.Unlock() + if m, ok := msg.(ActionProcessExited); ok { + exited = append(exited, m) + } + return framework.Result{OK: true} + }) + + proc, err := svc.Start(context.Background(), "sh", "-c", "exit 7") + require.NoError(t, err) + + <-proc.Done() + time.Sleep(10 * time.Millisecond) + + mu.Lock() + defer mu.Unlock() + require.Len(t, exited, 1) + assert.Equal(t, 7, exited[0].ExitCode) + require.Error(t, exited[0].Error) + assert.Contains(t, exited[0].Error.Error(), "process exited with code 7") }) }