Unverified Commit 689bdb3c authored by Francesco Banconi's avatar Francesco Banconi Committed by GitHub

Merge pull request #39 from rogpeppe/006-almost-zero-value

make methods concurrent-safe
parents 63d675f4 12994bed
......@@ -5,12 +5,10 @@ go:
- "1.8"
- "1.9"
- "1.10"
- "1.11"
- "1.11.x"
- "1.12.x"
- 1.x
- master
script:
- go get -v github.com/rogpeppe/godeps
- $GOPATH/bin/godeps -u dependencies.tsv
- GO111MODULE=on go test ./...
- GO111MODULE=on go test -v ./...
- GO111MODULE=on go test -race ./...
github.com/google/go-cmp git 6f77996f0c42f7b84e5a2b252227263f93432e9b 2019-03-12T03:24:27Z
github.com/kr/pretty git 73f6ac0b30a98e433b289500d779f50c1a6f0712 2018-05-06T08:33:45Z
github.com/kr/text git e2ffdb16a802fe2bb95e2e35ff34f0e53aeef34f 2018-05-06T08:24:08Z
......@@ -5,6 +5,7 @@ package quicktest
import (
"fmt"
"strings"
"sync"
"testing"
)
......@@ -32,11 +33,21 @@ import (
// c.Setenv("HOME", "/non-existent")
// c.Assert(os.Getenv("HOME"), qt.Equals, "/non-existent")
// })
//
// A value of C that's has a non-nil TB field but is otherwise zero is valid.
// So:
//
// c := &qt.C{TB: t}
//
// is valid a way to create a C value; it's exactly the same as:
//
// c := qt.New(t)
//
// Methods on C may be called concurrently, assuming the underlying
// `testing.TB` implementation also allows that.
func New(t testing.TB) *C {
return &C{
TB: t,
deferred: func() {},
format: Format,
TB: t,
}
}
......@@ -46,6 +57,7 @@ func New(t testing.TB) *C {
type C struct {
testing.TB
mu sync.Mutex
deferred func()
format formatFunc
}
......@@ -54,9 +66,13 @@ type C struct {
// called. Deferred functions will be called in last added, first called
// order.
func (c *C) Defer(f func()) {
c.mu.Lock()
defer c.mu.Unlock()
oldDeferred := c.deferred
c.deferred = func() {
defer oldDeferred()
if oldDeferred != nil {
defer oldDeferred()
}
f()
}
}
......@@ -68,21 +84,25 @@ func (c *C) Defer(f func()) {
// When a test function is called by Run, Done will be called
// automatically on the C value passed into it.
func (c *C) Done() {
// Note: we need to use defer in case the deferred function panics
// or Goexits.
defer func() {
c.deferred = func() {}
}()
c.deferred()
c.mu.Lock()
deferred := c.deferred
c.deferred = nil
c.mu.Unlock()
if deferred != nil {
deferred()
}
}
// AddCleanup is the old name for Defer.
//
// Deprecated: this will be removed in a subsequent version.
func (c *C) AddCleanup(f func()) {
c.Defer(f)
}
// Cleanup is the old name for Done.
//
// Deprecated: this will be removed in a subsequent version.
func (c *C) Cleanup() {
c.Done()
......@@ -93,7 +113,17 @@ func (c *C) Cleanup() {
// Any subsequent subtests invoked with c.Run will also use this function by
// default.
func (c *C) SetFormat(format func(interface{}) string) {
c.mu.Lock()
c.format = format
c.mu.Unlock()
}
// getFormat returns the format function
// safely acquired under lock.
func (c *C) getFormat() func(interface{}) string {
c.mu.Lock()
defer c.mu.Unlock()
return c.format
}
// Check runs the given check and continues execution in case of failure.
......@@ -147,7 +177,7 @@ func (c *C) Run(name string, f func(c *C)) bool {
return r.Run(name, func(t *testing.T) {
c2 := New(t)
defer c2.Done()
c2.SetFormat(c.format)
c2.SetFormat(c.getFormat())
f(c2)
})
}
......@@ -174,7 +204,11 @@ func (c *C) check(fail func(...interface{}), checker Checker, got interface{}, a
rp := reportParams{
got: got,
args: args,
format: c.format,
format: c.getFormat(),
}
if rp.format == nil {
// No format set; use the default: Format.
rp.format = Format
}
note := func(key string, value interface{}) {
rp.notes = append(rp.notes, note{
......
package quicktest_test
import (
"sync"
"sync/atomic"
"testing"
qt "github.com/frankban/quicktest"
)
func TestConcurrentMethods(t *testing.T) {
// This test is designed to be run with the race
// detector enabled. It checks that C methods
// are safe to call concurrently.
// N holds the number of iterations to run any given
// operation concurrently with the others.
const N = 100
var x, y int32
c := qt.New(dummyT{})
var wg sync.WaitGroup
// start calls f in two goroutines, each
// running it N times.
// All the goroutines get started before we actually
// start them running, so that the race detector
// has a better chance of catching issues.
gogogo := make(chan struct{})
start := func(f func()) {
repeat := func() {
defer wg.Done()
<-gogogo
for i := 0; i < N; i++ {
f()
}
}
wg.Add(2)
go repeat()
go repeat()
}
start(func() {
c.Defer(func() {
atomic.AddInt32(&x, 1)
})
c.Defer(func() {
atomic.AddInt32(&y, 1)
})
})
start(func() {
c.Done()
})
start(func() {
c.SetFormat(func(v interface{}) string {
return "x"
})
})
start(func() {
// Do an assert to exercise the formatter.
c.Check(true, qt.Equals, false)
})
start(func() {
c.Run("", func(c *qt.C) {})
})
close(gogogo)
wg.Wait()
c.Done()
// Check that all the defer functions ran OK.
if x != N*2 || y != N*2 {
t.Fatalf("unexpected x, y counts; got %d, %d; want %d, %d", x, y, N*2, N*2)
}
}
// dummyT implements the testing.TB methods
// required for TestConcurentMethods.
type dummyT struct {
testing.TB
}
func (dummyT) Error(...interface{}) {}
func (dummyT) Run(name string, f func(t *testing.T)) bool {
return false
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment