package tools import ( "context" "strings" "sync" "testing" "time" ) func TestACommandsOutputArrivesBeforeItFinishes(t *testing.T) { // The whole point: a two-minute test run should not be a blank row for two // minutes. The command prints, then sleeps; if nothing is streaming, the // first chunk cannot arrive until the sleep is over. first := make(chan string, 1) var mu sync.Mutex var all strings.Builder ctx := WithProgress(context.Background(), func(chunk string) { mu.Lock() all.WriteString(chunk) mu.Unlock() select { case first <- chunk: default: } }) done := make(chan string, 1) go func() { report, _ := RunCommand(ctx, t.TempDir(), []string{"bash", "-c", "echo early; sleep 1"}, 10*time.Second) done <- report }() select { case chunk := <-first: if !strings.Contains(chunk, "early") { t.Errorf("first chunk = %q", chunk) } case <-done: t.Fatal("the output only arrived once the command had finished") case <-time.After(5 * time.Second): t.Fatal("no output arrived at all") } report := <-done // The report is unaffected: streaming is an addition, not a replacement. if !strings.Contains(report, "early") { t.Errorf("report lost the output: %q", report) } mu.Lock() defer mu.Unlock() if !strings.Contains(all.String(), "early") { t.Errorf("streamed output = %q", all.String()) } } func TestBothStreamsReachTheSameSink(t *testing.T) { var mu sync.Mutex var got strings.Builder ctx := WithProgress(context.Background(), func(chunk string) { mu.Lock() defer mu.Unlock() got.WriteString(chunk) }) RunCommand(ctx, t.TempDir(), []string{"bash", "-c", "echo out; echo err >&2"}, 10*time.Second) mu.Lock() defer mu.Unlock() // A reader watching a build wants what a terminal would have shown. for _, want := range []string{"out", "err"} { if !strings.Contains(got.String(), want) { t.Errorf("the sink never saw %q: %q", want, got.String()) } } } func TestWithNobodyWatchingNothingChanges(t *testing.T) { if p := ProgressFrom(context.Background()); p != nil { t.Fatal("a bare context should carry no sink") } // And a nil sink must not be installed, so ProgressFrom stays the cheap // "is anyone watching" check that tools branch on. if p := ProgressFrom(WithProgress(context.Background(), nil)); p != nil { t.Error("a nil sink was installed") } report, failed := RunCommand(context.Background(), t.TempDir(), []string{"bash", "-c", "echo hello"}, 10*time.Second) if failed || !strings.Contains(report, "hello") { t.Errorf("report = %q, failed = %v", report, failed) } }