github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/testing/locking.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package testing
     5  
     6  import (
     7  	"errors"
     8  	"sync"
     9  	"time"
    10  )
    11  
    12  // TestLockingFunction verifies that a function obeys a given lock.
    13  //
    14  // Use this as a building block in your own tests for proper locking.
    15  // Parameters are a lock that you expect your function to block on, and
    16  // the function that you want to test for proper locking on that lock.
    17  //
    18  // This helper attempts to verify that the function both obtains and releases
    19  // the lock.  It will panic if the function fails to do either.
    20  // TODO: Support generic sync.Locker instead of just Mutex.
    21  // TODO: This could be a gocheck checker.
    22  // TODO(rog): make this work reliably even for functions that take longer
    23  // than a few µs to execute.
    24  func TestLockingFunction(lock *sync.Mutex, function func()) {
    25  	// We record two events that must happen in the right order.
    26  	// Buffer the channel so that we don't get hung up during attempts
    27  	// to push the events in.
    28  	events := make(chan string, 2)
    29  	// Synchronization channel, to make sure that the function starts
    30  	// trying to run at the point where we're going to make it block.
    31  	proceed := make(chan bool, 1)
    32  
    33  	goroutine := func() {
    34  		proceed <- true
    35  		function()
    36  		events <- "complete function"
    37  	}
    38  
    39  	lock.Lock()
    40  	go goroutine()
    41  	// Make the goroutine start now.  It should block inside "function()."
    42  	// (It's fine, technically even better, if the goroutine started right
    43  	// away, and this channel is buffered specifically so that it can.)
    44  	<-proceed
    45  
    46  	// Give a misbehaved function plenty of rope to hang itself.  We don't
    47  	// want it to block for a microsecond, hand control back to here so we
    48  	// think it's stuck on the lock, and then later continue on its merry
    49  	// lockless journey to finish last, as expected but for the wrong
    50  	// reason.
    51  	for counter := 0; counter < 10; counter++ {
    52  		// TODO: In Go 1.1, use runtime.GoSched instead.
    53  		time.Sleep(0)
    54  	}
    55  
    56  	// Unblock the goroutine.
    57  	events <- "release lock"
    58  	lock.Unlock()
    59  
    60  	// Now that we've released the lock, the function is unblocked.  Read
    61  	// the 2 events.  (This will wait until the function has completed.)
    62  	firstEvent := <-events
    63  	secondEvent := <-events
    64  	if firstEvent != "release lock" || secondEvent != "complete function" {
    65  		panic(errors.New("function did not obey lock"))
    66  	}
    67  
    68  	// Also, the function must have released the lock.
    69  	blankLock := sync.Mutex{}
    70  	if *lock != blankLock {
    71  		panic(errors.New("function did not release lock"))
    72  	}
    73  }