// Copyright 2015 Tim Heckman. All rights reserved.
// Copyright 2018 The Gofrs. All rights reserved.
// Use of this source code is governed by the BSD 3-Clause
// license that can be found in the LICENSE file.
package flock_test
import (
"context"
"io/ioutil"
"os"
"runtime"
"testing"
"time"
"github.com/gofrs/flock"
. "gopkg.in/check.v1"
)
type TestSuite struct {
path string
flock *flock.Flock
}
var _ = Suite(&TestSuite{})
func Test(t *testing.T) { TestingT(t) }
func (t *TestSuite) SetUpTest(c *C) {
tmpFile, err := ioutil.TempFile(os.TempDir(), "go-flock-")
c.Assert(err, IsNil)
c.Assert(tmpFile, Not(IsNil))
t.path = tmpFile.Name()
defer os.Remove(t.path)
tmpFile.Close()
t.flock = flock.New(t.path)
}
func (t *TestSuite) TearDownTest(c *C) {
t.flock.Unlock()
os.Remove(t.path)
}
func (t *TestSuite) TestNew(c *C) {
var f *flock.Flock
f = flock.New(t.path)
c.Assert(f, Not(IsNil))
c.Check(f.Path(), Equals, t.path)
c.Check(f.Locked(), Equals, false)
c.Check(f.RLocked(), Equals, false)
}
func (t *TestSuite) TestFlock_Path(c *C) {
var path string
path = t.flock.Path()
c.Check(path, Equals, t.path)
}
func (t *TestSuite) TestFlock_Locked(c *C) {
var locked bool
locked = t.flock.Locked()
c.Check(locked, Equals, false)
}
func (t *TestSuite) TestFlock_RLocked(c *C) {
var locked bool
locked = t.flock.RLocked()
c.Check(locked, Equals, false)
}
func (t *TestSuite) TestFlock_String(c *C) {
var str string
str = t.flock.String()
c.Assert(str, Equals, t.path)
}
func (t *TestSuite) TestFlock_TryLock(c *C) {
c.Assert(t.flock.Locked(), Equals, false)
c.Assert(t.flock.RLocked(), Equals, false)
var locked bool
var err error
locked, err = t.flock.TryLock()
c.Assert(err, IsNil)
c.Check(locked, Equals, true)
c.Check(t.flock.Locked(), Equals, true)
c.Check(t.flock.RLocked(), Equals, false)
locked, err = t.flock.TryLock()
c.Assert(err, IsNil)
c.Check(locked, Equals, true)
// make sure we just return false with no error in cases
// where we would have been blocked
locked, err = flock.New(t.path).TryLock()
c.Assert(err, IsNil)
c.Check(locked, Equals, false)
}
func (t *TestSuite) TestFlock_TryRLock(c *C) {
c.Assert(t.flock.Locked(), Equals, false)
c.Assert(t.flock.RLocked(), Equals, false)
var locked bool
var err error
locked, err = t.flock.TryRLock()
c.Assert(err, IsNil)
c.Check(locked, Equals, true)
c.Check(t.flock.Locked(), Equals, false)
c.Check(t.flock.RLocked(), Equals, true)
locked, err = t.flock.TryRLock()
c.Assert(err, IsNil)
c.Check(locked, Equals, true)
// shared lock should not block.
flock2 := flock.New(t.path)
locked, err = flock2.TryRLock()
c.Assert(err, IsNil)
if runtime.GOOS == "aix" {
// When using POSIX locks, we can't safely read-lock the same
// inode through two different descriptors at the same time:
// when the first descriptor is closed, the second descriptor
// would still be open but silently unlocked. So a second
// TryRLock must return false.
c.Check(locked, Equals, false)
} else {
c.Check(locked, Equals, true)
}
// make sure we just return false with no error in cases
// where we would have been blocked
t.flock.Unlock()
flock2.Unlock()
t.flock.Lock()
locked, err = flock.New(t.path).TryRLock()
c.Assert(err, IsNil)
c.Check(locked, Equals, false)
}
func (t *TestSuite) TestFlock_TryLockContext(c *C) {
// happy path
ctx, cancel := context.WithCancel(context.Background())
locked, err := t.flock.TryLockContext(ctx, time.Second)
c.Assert(err, IsNil)
c.Check(locked, Equals, true)
// context already canceled
cancel()
locked, err = flock.New(t.path).TryLockContext(ctx, time.Second)
c.Assert(err, Equals, context.Canceled)
c.Check(locked, Equals, false)
// timeout
ctx, cancel = context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
locked, err = flock.New(t.path).TryLockContext(ctx, time.Second)
c.Assert(err, Equals, context.DeadlineExceeded)
c.Check(locked, Equals, false)
}
func (t *TestSuite) TestFlock_TryRLockContext(c *C) {
// happy path
ctx, cancel := context.WithCancel(context.Background())
locked, err := t.flock.TryRLockContext(ctx, time.Second)
c.Assert(err, IsNil)
c.Check(locked, Equals, true)
// context already canceled
cancel()
locked, err = flock.New(t.path).TryRLockContext(ctx, time.Second)
c.Assert(err, Equals, context.Canceled)
c.Check(locked, Equals, false)
// timeout
t.flock.Unlock()
t.flock.Lock()
ctx, cancel = context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
locked, err = flock.New(t.path).TryRLockContext(ctx, time.Second)
c.Assert(err, Equals, context.DeadlineExceeded)
c.Check(locked, Equals, false)
}
func (t *TestSuite) TestFlock_Unlock(c *C) {
var err error
err = t.flock.Unlock()
c.Assert(err, IsNil)
// get a lock for us to unlock
locked, err := t.flock.TryLock()
c.Assert(err, IsNil)
c.Assert(locked, Equals, true)
c.Assert(t.flock.Locked(), Equals, true)
c.Check(t.flock.RLocked(), Equals, false)
_, err = os.Stat(t.path)
c.Assert(os.IsNotExist(err), Equals, false)
err = t.flock.Unlock()
c.Assert(err, IsNil)
c.Check(t.flock.Locked(), Equals, false)
c.Check(t.flock.RLocked(), Equals, false)
}
func (t *TestSuite) TestFlock_Lock(c *C) {
c.Assert(t.flock.Locked(), Equals, false)
c.Check(t.flock.RLocked(), Equals, false)
var err error
err = t.flock.Lock()
c.Assert(err, IsNil)
c.Check(t.flock.Locked(), Equals, true)
c.Check(t.flock.RLocked(), Equals, false)
// test that the short-circuit works
err = t.flock.Lock()
c.Assert(err, IsNil)
//
// Test that Lock() is a blocking call
//
ch := make(chan error, 2)
gf := flock.New(t.path)
defer gf.Unlock()
go func(ch chan<- error) {
ch <- nil
ch <- gf.Lock()
close(ch)
}(ch)
errCh, ok := <-ch
c.Assert(ok, Equals, true)
c.Assert(errCh, IsNil)
err = t.flock.Unlock()
c.Assert(err, IsNil)
errCh, ok = <-ch
c.Assert(ok, Equals, true)
c.Assert(errCh, IsNil)
c.Check(t.flock.Locked(), Equals, false)
c.Check(t.flock.RLocked(), Equals, false)
c.Check(gf.Locked(), Equals, true)
c.Check(gf.RLocked(), Equals, false)
}
func (t *TestSuite) TestFlock_RLock(c *C) {
c.Assert(t.flock.Locked(), Equals, false)
c.Check(t.flock.RLocked(), Equals, false)
var err error
err = t.flock.RLock()
c.Assert(err, IsNil)
c.Check(t.flock.Locked(), Equals, false)
c.Check(t.flock.RLocked(), Equals, true)
// test that the short-circuit works
err = t.flock.RLock()
c.Assert(err, IsNil)
//
// Test that RLock() is a blocking call
//
ch := make(chan error, 2)
gf := flock.New(t.path)
defer gf.Unlock()
go func(ch chan<- error) {
ch <- nil
ch <- gf.RLock()
close(ch)
}(ch)
errCh, ok := <-ch
c.Assert(ok, Equals, true)
c.Assert(errCh, IsNil)
err = t.flock.Unlock()
c.Assert(err, IsNil)
errCh, ok = <-ch
c.Assert(ok, Equals, true)
c.Assert(errCh, IsNil)
c.Check(t.flock.Locked(), Equals, false)
c.Check(t.flock.RLocked(), Equals, false)
c.Check(gf.Locked(), Equals, false)
c.Check(gf.RLocked(), Equals, true)
}