func TestMergeOutput(t *testing.T) { var tasksStarted, tasksDone sync.WaitGroup tasksDone.Add(2) tasksStarted.Add(2) t1 := New("foo", "", nil, nil, devNull) t1exited := make(chan struct{}) t1.RestartDelay = 0 // don't slow the test down for no good reason t1.Finished = func(ok bool) bool { // we expect each of these cases to happen exactly once if !ok { tasksDone.Done() } else { close(t1exited) } return ok } go t1.run(func(t *Task) taskStateFn { defer tasksStarted.Done() t.initLogging(bytes.NewBuffer([]byte{})) t.cmd = newFakeProcess() return taskRunning }) t2 := New("bar", "", nil, nil, devNull) t2exited := make(chan struct{}) t2.RestartDelay = 0 // don't slow the test down for no good reason t2.Finished = func(ok bool) bool { // we expect each of these cases to happen exactly once if !ok { tasksDone.Done() } else { close(t2exited) } return ok } go t2.run(func(t *Task) taskStateFn { defer tasksStarted.Done() t.initLogging(bytes.NewBuffer([]byte{})) t.cmd = newFakeProcess() return taskRunning }) shouldQuit := make(chan struct{}) te := MergeOutput([]*Task{t1, t2}, shouldQuit) tasksStarted.Wait() tasksStarted.Add(2) // recycle the barrier // kill each task once, let it restart; make sure that we get the completion status? t1.cmd.(*fakeProcess).exit(1) t2.cmd.(*fakeProcess).exit(2) codes := map[int]struct{}{} for i := 0; i < 2; i++ { switch tc := <-te.Completion(); tc.code { case 1, 2: codes[tc.code] = struct{}{} default: if tc.err != nil { t.Errorf("unexpected task completion error: %v", tc.err) } else { t.Errorf("unexpected task completion code: %d", tc.code) } } } te.Close() // we're not going to read any other completion or error events if len(codes) != 2 { t.Fatalf("expected each task process to exit once") } // each task invokes Finished() once <-t1exited <-t2exited log.Infoln("each task process has completed one round") tasksStarted.Wait() // tasks will auto-restart their exited procs // assert that the tasks are not dead; TODO(jdef) not sure that these checks are useful select { case <-t1.done: t.Fatalf("t1 is unexpectedly dead") default: } select { case <-t2.done: t.Fatalf("t2 is unexpectedly dead") default: } log.Infoln("firing quit signal") close(shouldQuit) // fire shouldQuit, and everything should terminate gracefully log.Infoln("waiting for tasks to die") tasksDone.Wait() // our tasks should die log.Infoln("waiting for merge to complete") <-te.Done() // wait for the merge to complete }