func TestRunClosedInput(t *testing.T) { done := make(chan struct{}) in := make(chan redgreen.RunSpec) out := redgreen.Run(done, in) // Signal that there will be no more input. close(in) // Ensure that out is eventually closed. mustBeClosedTimeout(out, time.Second, t) }
func TestRunDoneBlockedOnOut(t *testing.T) { var outWasOpen, outWasClosed bool test := func() { done := make(chan struct{}) in := make(chan redgreen.RunSpec) out := redgreen.Run(done, in) // Send a single command but don't receive from out, so that // Run's goroutine will be blocked on sending to out. in <- redgreen.RunSpec{Command: []string{"true"}} // Signal that we are done. close(done) // Ensure that out is eventually closed. timeout := time.Second select { case _, isOpen := <-out: // Since we receive from out here, we can observe that // the receive operation either succeed or not with // equal probability. if isOpen { outWasOpen = true } else { outWasClosed = true } case <-time.After(timeout): t.Fatalf("receive from channel timed out") } mustBeClosedTimeout(out, timeout, t) } var runs int for !(outWasOpen && outWasClosed) { // 2 or 3 runs should be enough. The probability of needing more // than 16 runs to observe both outWasOpen and outWasClosed is // (1/2)^16 ≈ 0.001 if runs > 16 { t.Fatalf("failed to observe both conditions: outWasOpen=%v, outWasClosed=%v", outWasOpen, outWasClosed) } test() runs++ } t.Logf("#runs: %d", runs) }
func TestRunDoneBlockedOnIn(t *testing.T) { done := make(chan struct{}) in := make(chan redgreen.RunSpec) out := redgreen.Run(done, in) // Test the execution of a single command. in <- redgreen.RunSpec{Command: []string{"true"}} if res := <-out; res.Error != nil { t.Fatalf("got %#v, want nil", res.Error) } // There is no more input, Run's goroutine should be blocked on // receiving from in. // Signal that we are done. close(done) // Ensure that out is eventually closed. mustBeClosedTimeout(out, time.Second, t) }
func do() error { // Initialize and defer termination of termbox. if !debug { if err := termbox.Init(); err != nil { return err } defer termbox.Close() termbox.HideCursor() termbox.SetOutputMode(termbox.Output256) } // wg waits for all goroutines started by this function to return. var wg sync.WaitGroup defer wg.Wait() // Closing done signals all goroutines to terminate. done := make(chan struct{}) defer close(done) w, err := redgreen.Watch(done, ".", 200*time.Millisecond) if err != nil { return err } runSpec := redgreen.RunSpec{Command: testCommand, Timeout: timeout} run := make(chan redgreen.RunSpec, 1) res := redgreen.Run(done, run) // Trigger an initial run of the test command. run <- runSpec // Run tests every time a file is created/removed/modified. wg.Add(1) go func() { defer wg.Done() for range w { run <- runSpec } }() state := make(chan redgreen.State) wg.Add(1) go func() { defer wg.Done() redgreen.Render(done, state) }() s := redgreen.State{Debug: debug} var mu sync.RWMutex // synchronizes access to s. // Render initial state. state <- s // Render after every test command result. wg.Add(1) go func() { defer wg.Done() for r := range res { mu.Lock() s.Results = append(s.Results, r) mu.Unlock() mu.RLock() state <- s mu.RUnlock() } }() if debug { // Wait for Ctrl-C. ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt) <-ch } else { // Block until Esc is pressed. for { e := termbox.PollEvent() if e.Type == termbox.EventKey && e.Key == termbox.KeyEsc { break } if e.Type == termbox.EventResize { mu.RLock() state <- s mu.RUnlock() } } } return nil }