added serialisedWriter
diff --git a/gocheck.go b/gocheck.go
index c04ab2b..e6ab66c 100644
--- a/gocheck.go
+++ b/gocheck.go
@@ -57,7 +57,7 @@
method *methodType
kind funcKind
status funcStatus
- logb *bytes.Buffer
+ logb logger
logw io.Writer
done chan *C
reason string
@@ -121,6 +121,12 @@
return path
}
+type logger interface {
+ io.Writer
+ io.WriterTo
+ fmt.Stringer
+}
+
// -----------------------------------------------------------------------
// Low-level logging functions.
@@ -584,13 +590,13 @@
// Create a call object with the given suite method, and fork a
// goroutine with the provided dispatcher for running it.
-func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, logb *bytes.Buffer, dispatcher func(c *C)) *C {
+func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, logb logger, dispatcher func(c *C)) *C {
var logw io.Writer
if runner.output.Stream {
logw = runner.output
}
if logb == nil {
- logb = bytes.NewBuffer(nil)
+ logb = &serialisedLogger{}
}
c := &C{
method: method,
@@ -610,8 +616,32 @@
return c
}
+// serialisedLogger serialises calls to the underlying buffer.
+type serialisedLogger struct {
+ sync.Mutex
+ b bytes.Buffer
+}
+
+func (l *serialisedLogger) Write(buf []byte) (int, error) {
+ l.Lock()
+ defer l.Unlock()
+ return l.b.Write(buf)
+}
+
+func (l *serialisedLogger) WriteTo(w io.Writer) (int64, error) {
+ l.Lock()
+ defer l.Unlock()
+ return l.b.WriteTo(w)
+}
+
+func (l *serialisedLogger) String() string {
+ l.Lock()
+ defer l.Unlock()
+ return l.b.String()
+}
+
// Same as forkCall(), but wait for call to finish before returning.
-func (runner *suiteRunner) runFunc(method *methodType, kind funcKind, logb *bytes.Buffer, dispatcher func(c *C)) *C {
+func (runner *suiteRunner) runFunc(method *methodType, kind funcKind, logb logger, dispatcher func(c *C)) *C {
c := runner.forkCall(method, kind, logb, dispatcher)
<-c.done
return c
@@ -654,7 +684,7 @@
// goroutine like all suite methods, but this method will not return
// while the fixture goroutine is not done, because the fixture must be
// run in a desired order.
-func (runner *suiteRunner) runFixture(method *methodType, logb *bytes.Buffer) *C {
+func (runner *suiteRunner) runFixture(method *methodType, logb logger) *C {
if method != nil {
c := runner.runFunc(method, fixtureKd, logb, func(c *C) {
c.ResetTimer()
@@ -670,7 +700,7 @@
// Run the fixture method with runFixture(), but panic with a fixturePanic{}
// in case the fixture method panics. This makes it easier to track the
// fixture panic together with other call panics within forkTest().
-func (runner *suiteRunner) runFixtureWithPanic(method *methodType, logb *bytes.Buffer, skipped *bool) *C {
+func (runner *suiteRunner) runFixtureWithPanic(method *methodType, logb logger, skipped *bool) *C {
if *skipped {
return nil
}
diff --git a/helpers_test.go b/helpers_test.go
index 0469426..55436c3 100644
--- a/helpers_test.go
+++ b/helpers_test.go
@@ -7,6 +7,8 @@
"launchpad.net/gocheck"
"os"
"reflect"
+ "runtime"
+ "sync"
)
var helpersS = gocheck.Suite(&HelpersS{})
@@ -126,7 +128,7 @@
testHelperFailure(c, "Check(1, checker, 2, msg)", false, false, log,
func() interface{} {
// Nice leading comment.
- return c.Check(1, checker, 2) // Hello there
+ return c.Check(1, checker, 2) // Hello there
})
}
@@ -379,7 +381,6 @@
})
}
-
// -----------------------------------------------------------------------
// MakeDir() tests.
@@ -429,6 +430,23 @@
return false
}
+// Concurrent logging should not corrupt the underling buffer.
+// Use go test -race to detect the race in this test.
+func (s *HelpersS) TestConcurrentLogging(c *gocheck.C) {
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))
+ var wg sync.WaitGroup
+ wg.Add(1)
+ for i, n := 0, runtime.NumCPU() * 2 ; i < n ; i++ {
+ go func(i int) {
+ wg.Wait()
+ for j := 0 ; j < 30 ; j++ {
+ c.Logf("Worker %d: line %d", i, j)
+ }
+ }(i)
+ }
+ wg.Done()
+}
+
// -----------------------------------------------------------------------
// A couple of helper functions to test helper functions. :-)