Example #1
0
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
}