package compose import ( "errors" "os" "testing" "time" ) func TestTerminationKillsTheTreeEvenWhenTheDirectChildIsAlreadyReaped(t *testing.T) { done := make(chan error, 1) done <- nil treeFailure := errors.New("tree termination failed") treeCalls := 0 directCalls := 0 err := terminateProcessTree(done, func() error { treeCalls++ return treeFailure }, func() error { directCalls++ return os.ErrProcessDone }, time.Second) if treeCalls != 1 { t.Fatalf("tree termination calls = %d, want 1", treeCalls) } if directCalls != 1 { t.Fatalf("direct termination calls = %d, want 1 fallback after tree failure", directCalls) } if !errors.Is(err, treeFailure) || !errors.Is(err, ErrProcessReap) { t.Fatalf("terminateProcessTree() error = %v, want tree failure and ErrProcessReap", err) } if errors.Is(err, os.ErrProcessDone) { t.Fatalf("terminateProcessTree() error = %v, must treat an already-reaped direct child as benign", err) } } func TestTerminationWaitsForTheOriginalProcessAfterTreeTermination(t *testing.T) { done := make(chan error, 1) done <- nil directCalls := 0 err := terminateProcessTree(done, func() error { return nil }, func() error { directCalls++ return errors.New("direct fallback should not run after a successful tree termination") }, time.Second) if err != nil { t.Fatalf("terminateProcessTree() error = %v, want nil", err) } if directCalls != 0 { t.Fatalf("direct termination calls = %d, want 0", directCalls) } } func TestTerminationJoinsTreeDirectAndReapFailures(t *testing.T) { done := make(chan error) treeFailure := errors.New("tree termination failed") directFailure := errors.New("direct termination failed") err := terminateProcessTree(done, func() error { return treeFailure }, func() error { return directFailure }, 0) for _, want := range []error{treeFailure, directFailure, ErrProcessReap} { if !errors.Is(err, want) { t.Fatalf("terminateProcessTree() error = %v, want %v", err, want) } } } func TestTerminationReportsReapFailureWhenTheTreeTerminatesButTheProcessNeverReaps(t *testing.T) { done := make(chan error) err := terminateProcessTree(done, func() error { return nil }, func() error { return errors.New("direct fallback should not run") }, 0) if !errors.Is(err, ErrProcessReap) { t.Fatalf("terminateProcessTree() error = %v, want ErrProcessReap", err) } }