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 }