Vectrum
Sign In
93 lines · 2.5 KB · Go
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)
	}
}