mirror of
https://github.com/taigrr/bitcask
synced 2025-01-18 04:03:17 -08:00
* add failing test case to highlight the race condition on bug note : the test "TestLock" is non deterministic, its outcome depends on the sequence of instructions yielded by the go scheduler on each run. There are two values, "goroutines" and "succesfulLockCount", which can be edited to see how the test performs. With the committed value, resp "20" and "50", I had a 100% failure on my local machine, running linux (Ubuntu 20.04). Sample test output : $ go test . -run TestLock --- FAIL: TestLock (0.17s) lock_test.go:91: [runner 14] lockCounter was > 1 on 5 occasions, max seen value was 2 lock_test.go:91: [runner 03] lockCounter was > 1 on 2 occasions, max seen value was 3 lock_test.go:91: [runner 02] lockCounter was > 1 on 3 occasions, max seen value was 3 lock_test.go:91: [runner 00] lockCounter was > 1 on 1 occasions, max seen value was 2 lock_test.go:91: [runner 12] lockCounter was > 1 on 7 occasions, max seen value was 3 lock_test.go:91: [runner 01] lockCounter was > 1 on 8 occasions, max seen value was 2 lock_test.go:91: [runner 04] lockCounter was > 1 on 6 occasions, max seen value was 4 lock_test.go:91: [runner 13] lockCounter was > 1 on 1 occasions, max seen value was 2 lock_test.go:91: [runner 17] lockCounter was > 1 on 4 occasions, max seen value was 2 lock_test.go:91: [runner 10] lockCounter was > 1 on 3 occasions, max seen value was 2 lock_test.go:91: [runner 08] lockCounter was > 1 on 6 occasions, max seen value was 2 lock_test.go:91: [runner 09] lockCounter was > 1 on 4 occasions, max seen value was 2 lock_test.go:91: [runner 05] lockCounter was > 1 on 1 occasions, max seen value was 2 lock_test.go:91: [runner 19] lockCounter was > 1 on 3 occasions, max seen value was 3 lock_test.go:91: [runner 07] lockCounter was > 1 on 4 occasions, max seen value was 3 lock_test.go:91: [runner 11] lockCounter was > 1 on 9 occasions, max seen value was 2 lock_test.go:91: [runner 15] lockCounter was > 1 on 1 occasions, max seen value was 3 lock_test.go:91: [runner 16] lockCounter was > 1 on 1 occasions, max seen value was 3 FAIL FAIL github.com/prologic/bitcask 0.176s FAIL * flock: create a wrapper module, local to bitcask, around gofrs.Flock the racy TestLock has been moved to bitcask/flock * flock: add test for expected regular locking behavior * flock: replace gofrs/flock with local implementation * update go.sum * Add build constraint for flock_unix.go Co-authored-by: James Mills <prologic@shortcircuit.net.au>
122 lines
2.7 KiB
Go
122 lines
2.7 KiB
Go
package flock
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// WARNING : this test will delete the file located at "testLockPath". Choose an adequate temporary file name.
|
|
const testLockPath = "/tmp/bitcask_unit_test_lock" // file path to use for the lock
|
|
|
|
func TestTryLock(t *testing.T) {
|
|
// test that basic locking functionnalities are consistent
|
|
|
|
// make sure there is no present lock when startng this test
|
|
os.Remove(testLockPath)
|
|
|
|
assert := assert.New(t)
|
|
|
|
lock1 := New(testLockPath)
|
|
lock2 := New(testLockPath)
|
|
|
|
// 1- take the first lock
|
|
locked1, err := lock1.TryLock()
|
|
assert.True(locked1)
|
|
assert.NoError(err)
|
|
|
|
// 2- check that the second lock cannot acquire the lock
|
|
locked2, err := lock2.TryLock()
|
|
assert.False(locked2)
|
|
assert.Error(err)
|
|
|
|
// 3- release the first lock
|
|
err = lock1.Unlock()
|
|
assert.NoError(err)
|
|
|
|
// 4- check that the second lock can acquire and then release the lock without error
|
|
locked2, err = lock2.TryLock()
|
|
assert.True(locked2)
|
|
assert.NoError(err)
|
|
|
|
err = lock2.Unlock()
|
|
assert.NoError(err)
|
|
}
|
|
|
|
func TestLock(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
// make sure there is no present lock when startng this test
|
|
os.Remove(testLockPath)
|
|
|
|
syncChan := make(chan bool)
|
|
|
|
// main goroutine: take lock on testPath
|
|
lock := New(testLockPath)
|
|
|
|
err := lock.Lock()
|
|
assert.NoError(err)
|
|
|
|
go func() {
|
|
// sub routine:
|
|
lock := New(testLockPath)
|
|
|
|
// before entering the block '.Lock()' call, signal we are about to do it
|
|
// see below : the main goroutine will wait for a small delay before releasing the lock
|
|
syncChan <- true
|
|
// '.Lock()' should ultimately return without error :
|
|
err := lock.Lock()
|
|
assert.NoError(err)
|
|
|
|
err = lock.Unlock()
|
|
assert.NoError(err)
|
|
|
|
close(syncChan)
|
|
}()
|
|
|
|
// wait for the "ready" signal from the sub routine,
|
|
<-syncChan
|
|
|
|
// after that signal wait for a small delay before releasing the lock
|
|
<-time.After(100 * time.Microsecond)
|
|
err = lock.Unlock()
|
|
assert.NoError(err)
|
|
|
|
// wait for the sub routine to finish
|
|
<-syncChan
|
|
}
|
|
|
|
func TestErrorConditions(t *testing.T) {
|
|
// error conditions implemented in this version :
|
|
// - you can't release a lock you do not hold
|
|
// - you can't lock twice the same lock
|
|
|
|
// -- setup
|
|
assert := assert.New(t)
|
|
|
|
// make sure there is no present lock when startng this test
|
|
os.Remove(testLockPath)
|
|
|
|
lock := New(testLockPath)
|
|
|
|
// -- run tests :
|
|
|
|
err := lock.Unlock()
|
|
assert.Error(err, "you can't release a lock you do not hold")
|
|
|
|
// take the lock once:
|
|
lock.TryLock()
|
|
|
|
locked, err := lock.TryLock()
|
|
assert.False(locked)
|
|
assert.Error(err, "you can't lock twice the same lock (using .TryLock())")
|
|
|
|
err = lock.Lock()
|
|
assert.Error(err, "you can't lock twice the same lock (using .Lock())")
|
|
|
|
// -- teardown
|
|
lock.Unlock()
|
|
}
|