Browse Source

sync: add tests

pull/2421/head
Nia Waldvogel 3 years ago
committed by Ron Evans
parent
commit
38305399a3
  1. 1
      Makefile
  2. 83
      src/sync/cond_test.go
  3. 205
      src/sync/mutex_test.go
  4. 62
      src/sync/once_test.go
  5. 35
      src/sync/waitgroup_test.go

1
Makefile

@ -234,6 +234,7 @@ TEST_PACKAGES = \
os \ os \
path \ path \
reflect \ reflect \
sync \
testing \ testing \
testing/iotest \ testing/iotest \
text/scanner \ text/scanner \

83
src/sync/cond_test.go

@ -0,0 +1,83 @@
package sync_test
import (
"sync"
"sync/atomic"
"testing"
)
// TestCondSignal tests waiting on a Cond and notifying it with Signal.
func TestCondSignal(t *testing.T) {
// Create a Cond with a normal mutex.
cond := sync.Cond{
L: &sync.Mutex{},
}
cond.L.Lock()
// Start a goroutine to signal us once we wait.
var signaled uint32
go func() {
// Wait for the test goroutine to wait.
cond.L.Lock()
defer cond.L.Unlock()
// Send a signal to the test goroutine.
atomic.StoreUint32(&signaled, 1)
cond.Signal()
}()
// Wait for a signal.
// This will unlock the mutex, and allow the spawned goroutine to run.
cond.Wait()
if atomic.LoadUint32(&signaled) == 0 {
t.Error("wait returned before a signal was sent")
}
}
func TestCondBroadcast(t *testing.T) {
// Create a Cond with an RWMutex.
var mu sync.RWMutex
cond := sync.Cond{
L: mu.RLocker(),
}
// Start goroutines to wait for the broadcast.
var wg sync.WaitGroup
const n = 5
for i := 0; i < n; i++ {
wg.Add(1)
mu.RLock()
go func() {
defer wg.Done()
cond.Wait()
}()
}
// Wait for all goroutines to start waiting.
mu.Lock()
// Broadcast to all of the waiting goroutines.
cond.Broadcast()
// Wait for all spawned goroutines to process the broadcast.
mu.Unlock()
wg.Wait()
}
// TestCondUnlockNotify verifies that a signal is processed even if it happens during the mutex unlock in Wait.
func TestCondUnlockNotify(t *testing.T) {
// Create a Cond that signals itself when waiting.
var cond sync.Cond
cond.L = fakeLocker{cond.Signal}
cond.Wait()
}
// fakeLocker is a fake sync.Locker where unlock calls an arbitrary function.
type fakeLocker struct {
unlock func()
}
func (l fakeLocker) Lock() {}
func (l fakeLocker) Unlock() { l.unlock() }

205
src/sync/mutex_test.go

@ -0,0 +1,205 @@
package sync_test
import (
"runtime"
"sync"
"sync/atomic"
"testing"
)
// TestMutexUncontended tests locking and unlocking a Mutex that is not shared with any other goroutines.
func TestMutexUncontended(t *testing.T) {
var mu sync.Mutex
// Lock and unlock the mutex a few times.
for i := 0; i < 3; i++ {
mu.Lock()
mu.Unlock()
}
}
// TestMutexConcurrent tests a mutex concurrently from multiple goroutines.
// It will fail if multiple goroutines hold the lock simultaneously.
func TestMutexConcurrent(t *testing.T) {
var mu sync.Mutex
var active uint
var completed uint
ok := true
const n = 10
for i := 0; i < n; i++ {
j := i
go func() {
// Delay a bit.
for k := j; k > 0; k-- {
runtime.Gosched()
}
mu.Lock()
// Increment the active counter.
active++
if active > 1 {
// Multiple things are holding the lock at the same time.
ok = false
} else {
// Delay a bit.
for k := j; k < n; k++ {
runtime.Gosched()
}
}
// Decrement the active counter.
active--
// This is completed.
completed++
mu.Unlock()
}()
}
// Wait for everything to finish.
var done bool
for !done {
// Wait a bit for other things to run.
runtime.Gosched()
// Acquire the lock and check whether everything has completed.
mu.Lock()
done = completed == n
mu.Unlock()
}
if !ok {
t.Error("lock held concurrently")
}
}
// TestRWMutexUncontended tests locking and unlocking an RWMutex that is not shared with any other goroutines.
func TestRWMutexUncontended(t *testing.T) {
var mu sync.RWMutex
// Lock the mutex exclusively and then unlock it.
mu.Lock()
mu.Unlock()
// Acuire several read locks.
const n = 5
for i := 0; i < n; i++ {
mu.RLock()
}
// Release all of the read locks.
for i := 0; i < n; i++ {
mu.RUnlock()
}
// Re-acquire the lock exclusively.
mu.Lock()
mu.Unlock()
}
// TestRWMutexWriteToRead tests the transition from a write lock to a read lock while contended.
func TestRWMutexWriteToRead(t *testing.T) {
// Create a new RWMutex and acquire a write lock.
var mu sync.RWMutex
mu.Lock()
const n = 3
var readAcquires uint32
var completed uint32
var unlocked uint32
var bad uint32
for i := 0; i < n; i++ {
go func() {
// Acquire a read lock.
mu.RLock()
// Verify that the write lock is supposed to be released by now.
if atomic.LoadUint32(&unlocked) == 0 {
// The write lock is still being held.
atomic.AddUint32(&bad, 1)
}
// Add ourselves to the read lock counter.
atomic.AddUint32(&readAcquires, 1)
// Wait for everything to hold the read lock simultaneously.
for atomic.LoadUint32(&readAcquires) < n {
runtime.Gosched()
}
// Notify of completion.
atomic.AddUint32(&completed, 1)
// Release the read lock.
mu.RUnlock()
}()
}
// Wait a bit for the goroutines to block.
for i := 0; i < 3*n; i++ {
runtime.Gosched()
}
// Release the write lock so that the goroutines acquire read locks.
atomic.StoreUint32(&unlocked, 1)
mu.Unlock()
// Wait for everything to complete.
for atomic.LoadUint32(&completed) < n {
runtime.Gosched()
}
// Acquire another write lock.
mu.Lock()
if bad != 0 {
t.Error("read lock acquired while write-locked")
}
}
// TestRWMutexWriteToRead tests the transition from a read lock to a write lock while contended.
func TestRWMutexReadToWrite(t *testing.T) {
// Create a new RWMutex and read-lock it several times.
const n = 3
var mu sync.RWMutex
var readers uint32
for i := 0; i < n; i++ {
mu.RLock()
readers++
}
// Start a goroutine to acquire a write lock.
result := ^uint32(0)
go func() {
// Acquire a write lock.
mu.Lock()
// Check for active readers.
readers := atomic.LoadUint32(&readers)
mu.Unlock()
// Report the number of active readers.
atomic.StoreUint32(&result, readers)
}()
// Release the read locks.
for i := 0; i < n; i++ {
runtime.Gosched()
atomic.AddUint32(&readers, ^uint32(0))
mu.RUnlock()
}
// Wait for a result.
var res uint32
for res == ^uint32(0) {
runtime.Gosched()
res = atomic.LoadUint32(&result)
}
if res != 0 {
t.Errorf("write lock acquired while %d readers were active", res)
}
}

62
src/sync/once_test.go

@ -0,0 +1,62 @@
package sync_test
import (
"sync"
"testing"
)
// TestOnceUncontended tests Once on a single goroutine.
func TestOnceUncontended(t *testing.T) {
var once sync.Once
{
var ran bool
once.Do(func() {
ran = true
})
if !ran {
t.Error("first call to Do did not run")
}
}
{
var ran bool
once.Do(func() {
ran = true
})
if ran {
t.Error("second call to Do ran")
}
}
}
// TestOnceConcurrent tests multiple concurrent invocations of sync.Once.
func TestOnceConcurrent(t *testing.T) {
var once sync.Once
var mu sync.Mutex
mu.Lock()
var ran bool
var ranTwice bool
once.Do(func() {
ran = true
// Start a goroutine and (approximately) wait for it to enter the call to Do.
var startWait sync.Mutex
startWait.Lock()
go func() {
startWait.Unlock()
once.Do(func() {
ranTwice = true
})
mu.Unlock()
}()
startWait.Lock()
})
if !ran {
t.Error("first call to Do did not run")
}
// Wait for the goroutine to finish.
mu.Lock()
if ranTwice {
t.Error("second concurrent call to Once also ran")
}
}

35
src/sync/waitgroup_test.go

@ -0,0 +1,35 @@
package sync_test
import (
"sync"
"testing"
)
// TestWaitGroupUncontended tests the wait group from a single goroutine.
func TestWaitGroupUncontended(t *testing.T) {
// Check that a single add-and-done works.
var wg sync.WaitGroup
wg.Add(1)
wg.Done()
wg.Wait()
// Check that mixing positive and negative counts works.
wg.Add(10)
wg.Add(-8)
wg.Add(-1)
wg.Add(0)
wg.Done()
wg.Wait()
}
// TestWaitGroup tests the typical usage of WaitGroup.
func TestWaitGroup(t *testing.T) {
const n = 5
var wg sync.WaitGroup
wg.Add(n)
for i := 0; i < n; i++ {
go wg.Done()
}
wg.Wait()
}
Loading…
Cancel
Save