package broadcaster import ( "bytes" "errors" "strings" "testing" ) type dummyWriter struct { buffer bytes.Buffer failOnWrite bool } func (dw *dummyWriter) Write(p []byte) (n int, err error) { if dw.failOnWrite { return 0, errors.New("Fake fail") } return dw.buffer.Write(p) } func (dw *dummyWriter) String() string { return dw.buffer.String() } func (dw *dummyWriter) Close() error { return nil } func TestUnbuffered(t *testing.T) { writer := new(Unbuffered) // Test 1: Both bufferA and bufferB should contain "foo" bufferA := &dummyWriter{} writer.Add(bufferA) bufferB := &dummyWriter{} writer.Add(bufferB) writer.Write([]byte("foo")) if bufferA.String() != "foo" { t.Errorf("Buffer contains %v", bufferA.String()) } if bufferB.String() != "foo" { t.Errorf("Buffer contains %v", bufferB.String()) } // Test2: bufferA and bufferB should contain "foobar", // while bufferC should only contain "bar" bufferC := &dummyWriter{} writer.Add(bufferC) writer.Write([]byte("bar")) if bufferA.String() != "foobar" { t.Errorf("Buffer contains %v", bufferA.String()) } if bufferB.String() != "foobar" { t.Errorf("Buffer contains %v", bufferB.String()) } if bufferC.String() != "bar" { t.Errorf("Buffer contains %v", bufferC.String()) } // Test3: Test eviction on failure bufferA.failOnWrite = true writer.Write([]byte("fail")) if bufferA.String() != "foobar" { t.Errorf("Buffer contains %v", bufferA.String()) } if bufferC.String() != "barfail" { t.Errorf("Buffer contains %v", bufferC.String()) } // Even though we reset the flag, no more writes should go in there bufferA.failOnWrite = false writer.Write([]byte("test")) if bufferA.String() != "foobar" { t.Errorf("Buffer contains %v", bufferA.String()) } if bufferC.String() != "barfailtest" { t.Errorf("Buffer contains %v", bufferC.String()) } // Test4: Test eviction on multiple simultaneous failures bufferB.failOnWrite = true bufferC.failOnWrite = true bufferD := &dummyWriter{} writer.Add(bufferD) writer.Write([]byte("yo")) writer.Write([]byte("ink")) if strings.Contains(bufferB.String(), "yoink") { t.Errorf("bufferB received write. contents: %q", bufferB) } if strings.Contains(bufferC.String(), "yoink") { t.Errorf("bufferC received write. contents: %q", bufferC) } if g, w := bufferD.String(), "yoink"; g != w { t.Errorf("bufferD = %q, want %q", g, w) } writer.Clean() } type devNullCloser int func (d devNullCloser) Close() error { return nil } func (d devNullCloser) Write(buf []byte) (int, error) { return len(buf), nil } // This test checks for races. It is only useful when run with the race detector. func TestRaceUnbuffered(t *testing.T) { writer := new(Unbuffered) c := make(chan bool) go func() { writer.Add(devNullCloser(0)) c <- true }() writer.Write([]byte("hello")) <-c } func BenchmarkUnbuffered(b *testing.B) { writer := new(Unbuffered) setUpWriter := func() { for i := 0; i < 100; i++ { writer.Add(devNullCloser(0)) writer.Add(devNullCloser(0)) writer.Add(devNullCloser(0)) } } testLine := "Line that thinks that it is log line from docker" var buf bytes.Buffer for i := 0; i < 100; i++ { buf.Write([]byte(testLine + "\n")) } // line without eol buf.Write([]byte(testLine)) testText := buf.Bytes() b.SetBytes(int64(5 * len(testText))) b.ResetTimer() for i := 0; i < b.N; i++ { b.StopTimer() setUpWriter() b.StartTimer() for j := 0; j < 5; j++ { if _, err := writer.Write(testText); err != nil { b.Fatal(err) } } b.StopTimer() writer.Clean() b.StartTimer() } }