diff --git a/global_test.go b/global_test.go index 55f88d3..d1248f0 100644 --- a/global_test.go +++ b/global_test.go @@ -36,6 +36,12 @@ func TestGlobal_DefaultNotInitialized(t *testing.T) { _, err = Output("proc-1") assert.ErrorIs(t, err, ErrServiceNotInitialized) + err = Input("proc-1", "test") + assert.ErrorIs(t, err, ErrServiceNotInitialized) + + err = CloseStdin("proc-1") + assert.ErrorIs(t, err, ErrServiceNotInitialized) + assert.Nil(t, List()) assert.Nil(t, Running()) @@ -273,6 +279,30 @@ func TestGlobal_Output(t *testing.T) { assert.Contains(t, output, "global-output") } +func TestGlobal_InputAndCloseStdin(t *testing.T) { + svc, _ := newTestService(t) + + old := defaultService.Swap(svc) + defer func() { + if old != nil { + defaultService.Store(old) + } + }() + + proc, err := Start(context.Background(), "cat") + require.NoError(t, err) + + err = Input(proc.ID, "global-input\n") + require.NoError(t, err) + + err = CloseStdin(proc.ID) + require.NoError(t, err) + + <-proc.Done() + + assert.Contains(t, proc.Output(), "global-input") +} + func TestGlobal_Wait(t *testing.T) { svc, _ := newTestService(t) diff --git a/process_global.go b/process_global.go index bde259a..12a5b54 100644 --- a/process_global.go +++ b/process_global.go @@ -114,6 +114,32 @@ func Output(id string) (string, error) { return svc.Output(id) } +// Input writes data to the stdin of a managed process using the default service. +// +// Example: +// +// _ = process.Input("proc-1", "hello\n") +func Input(id string, input string) error { + svc := Default() + if svc == nil { + return ErrServiceNotInitialized + } + return svc.Input(id, input) +} + +// CloseStdin closes the stdin pipe of a managed process using the default service. +// +// Example: +// +// _ = process.CloseStdin("proc-1") +func CloseStdin(id string) error { + svc := Default() + if svc == nil { + return ErrServiceNotInitialized + } + return svc.CloseStdin(id) +} + // Wait blocks until a managed process exits and returns its final snapshot. // // Example: diff --git a/service.go b/service.go index 83a7fbd..739f964 100644 --- a/service.go +++ b/service.go @@ -536,6 +536,32 @@ func (s *Service) Output(id string) (string, error) { return proc.Output(), nil } +// Input writes data to the stdin of a managed process. +// +// Example: +// +// _ = svc.Input("proc-1", "hello\n") +func (s *Service) Input(id string, input string) error { + proc, err := s.Get(id) + if err != nil { + return err + } + return proc.SendInput(input) +} + +// CloseStdin closes the stdin pipe of a managed process. +// +// Example: +// +// _ = svc.CloseStdin("proc-1") +func (s *Service) CloseStdin(id string) error { + proc, err := s.Get(id) + if err != nil { + return err + } + return proc.CloseStdin() +} + // Wait blocks until a managed process exits and returns its final snapshot. // // Example: diff --git a/service_test.go b/service_test.go index f4f952c..f481352 100644 --- a/service_test.go +++ b/service_test.go @@ -607,6 +607,57 @@ func TestService_Output(t *testing.T) { }) } +func TestService_Input(t *testing.T) { + t.Run("writes to stdin", func(t *testing.T) { + svc, _ := newTestService(t) + + proc, err := svc.Start(context.Background(), "cat") + require.NoError(t, err) + + err = svc.Input(proc.ID, "service-input\n") + require.NoError(t, err) + + err = svc.CloseStdin(proc.ID) + require.NoError(t, err) + + <-proc.Done() + + assert.Contains(t, proc.Output(), "service-input") + }) + + t.Run("error on unknown id", func(t *testing.T) { + svc, _ := newTestService(t) + + err := svc.Input("nonexistent", "test") + assert.ErrorIs(t, err, ErrProcessNotFound) + }) +} + +func TestService_CloseStdin(t *testing.T) { + t.Run("closes stdin pipe", func(t *testing.T) { + svc, _ := newTestService(t) + + proc, err := svc.Start(context.Background(), "cat") + require.NoError(t, err) + + err = svc.CloseStdin(proc.ID) + require.NoError(t, err) + + select { + case <-proc.Done(): + case <-time.After(2 * time.Second): + t.Fatal("cat should exit when stdin is closed") + } + }) + + t.Run("error on unknown id", func(t *testing.T) { + svc, _ := newTestService(t) + + err := svc.CloseStdin("nonexistent") + assert.ErrorIs(t, err, ErrProcessNotFound) + }) +} + func TestService_Wait(t *testing.T) { t.Run("returns final info on success", func(t *testing.T) { svc, _ := newTestService(t)