// 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) }