package app_test import ( "context" "errors" "mxl-pattern-generator/internal/app" "strings" "testing" "time" ) func TestRunConcurrentCancelsSiblingAndWaitsForCleanup(t *testing.T) { wantErr := errors.New("writer failed") peerStarted := make(chan struct{}) peerStopped := make(chan struct{}) err := app.RunConcurrent(context.Background(), app.Runner{ Name: "video", Run: func(ctx context.Context) error { <-peerStarted return wantErr }, }, app.Runner{ Name: "audio", Run: func(ctx context.Context) error { close(peerStarted) <-ctx.Done() close(peerStopped) return nil }, }, ) if !errors.Is(err, wantErr) { t.Fatalf("error = %v, want wrapped %v", err, wantErr) } if !strings.Contains(err.Error(), "video flow") { t.Fatalf("error = %q, want runner name", err) } select { case <-peerStopped: default: t.Fatal("runConcurrent returned before the sibling completed cleanup") } } func TestRunConcurrentParentCancellationIsGraceful(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) started := make(chan struct{}) done := make(chan error, 1) go func() { done <- app.RunConcurrent(ctx, app.Runner{ Name: "video", Run: func(ctx context.Context) error { close(started) <-ctx.Done() return nil }, }) }() <-started cancel() select { case err := <-done: if err != nil { t.Fatalf("runConcurrent: %v", err) } case <-time.After(time.Second): t.Fatal("runConcurrent did not stop after parent cancellation") } }