diff --git a/main.go b/main.go index e5c90f3..f47b90d 100644 --- a/main.go +++ b/main.go @@ -13,14 +13,14 @@ import ( ) const ( - FLAG_ROOT = "root" - ENV_ROOT = "FMUTEX_ROOT" - FLAG_ID = "id" - FLAG_SILENT = "s" - FLAG_PULSE = "pulse" - FLAG_REFRESH = "refresh" - FLAG_LIMIT = "limit" - FLAG_TIMEOUT = "timeout" + FlagRoot = "root" + EnvRoot = "FMUTEX_ROOT" + FlagId = "id" + FlagSilent = "s" + FlagPulse = "pulse" + FlagRefresh = "refresh" + FlagLimit = "limit" + FlagTimeout = "timeout" ) var cmn = struct { // Common flags @@ -28,7 +28,7 @@ var cmn = struct { // Common flags Id string Silent bool }{ - Root: ifEmptyStr(os.Getenv(ENV_ROOT), os.TempDir()), + Root: ifEmptyStr(os.Getenv(EnvRoot), os.TempDir()), Silent: false, } @@ -44,10 +44,10 @@ var lck = struct { // Lock flags } const ( - CMD_LOCK = "lock" - CMD_RELEASE = "release" - CMD_UNLOCK = "unlock" // An alias to CMD_RELEASE - CMD_TEST = "test" + CmdLock = "lock" + CmdRelease = "release" + CmdUnlock = "unlock" // An alias to CmdRelease + CmdTest = "test" ) var ( @@ -63,28 +63,28 @@ func init() { log.SetPrefix(fmt.Sprintf("%s: ", getProg(os.Args))) flag.Usage = usage - flag.StringVar(&cmn.Root, FLAG_ROOT, cmn.Root, "root directory for mutex(es)") - flag.StringVar(&cmn.Id, FLAG_ID, cmn.Id, "mutex id") - flag.BoolVar(&cmn.Silent, FLAG_SILENT, cmn.Silent, "silent execution") + flag.StringVar(&cmn.Root, FlagRoot, cmn.Root, "root directory for mutex(es)") + flag.StringVar(&cmn.Id, FlagId, cmn.Id, "mutex id") + flag.BoolVar(&cmn.Silent, FlagSilent, cmn.Silent, "silent execution") - cmdLock = flag.NewFlagSet(CMD_LOCK, flag.ExitOnError) - cmdLock.DurationVar(&lck.Pulse, FLAG_PULSE, lck.Pulse, "determines frequency of locking attempts") - cmdLock.DurationVar(&lck.Refresh, FLAG_REFRESH, lck.Refresh, "determines frequency of saving current timestamp in a locking file") - cmdLock.DurationVar(&lck.Limit, FLAG_LIMIT, lck.Limit, "determines how long takes to consider given mutex as \"dead\"") - cmdLock.DurationVar(&lck.Timeout, FLAG_TIMEOUT, lck.Timeout, "locking timeout (if > 0)") + cmdLock = flag.NewFlagSet(CmdLock, flag.ExitOnError) + cmdLock.DurationVar(&lck.Pulse, FlagPulse, lck.Pulse, "determines frequency of locking attempts") + cmdLock.DurationVar(&lck.Refresh, FlagRefresh, lck.Refresh, "determines frequency of saving current timestamp in a locking file") + cmdLock.DurationVar(&lck.Limit, FlagLimit, lck.Limit, "determines how long takes to consider given mutex as \"dead\"") + cmdLock.DurationVar(&lck.Timeout, FlagTimeout, lck.Timeout, "locking timeout (if > 0)") - cmdRelease = flag.NewFlagSet(CMD_RELEASE, flag.ExitOnError) - cmdTest = flag.NewFlagSet(CMD_TEST, flag.ExitOnError) + cmdRelease = flag.NewFlagSet(CmdRelease, flag.ExitOnError) + cmdTest = flag.NewFlagSet(CmdTest, flag.ExitOnError) cmdAll, cmdNames = mkCommands(cmdLock, cmdRelease, cmdTest) - flag.Parse() } func main() { + flag.Parse() if isEmptyStr(cmn.Id) { - log.Fatalf("Flag -%s is required.", FLAG_ID) + log.Fatalf("Flag -%s is required.", FlagId) } if flag.NArg() < 1 { @@ -95,19 +95,19 @@ func main() { log.SetOutput(ioutil.Discard) } switch flag.Arg(0) { - case CMD_LOCK: + case CmdLock: cmdLock.Parse(flag.Args()[1:]) doLock() if !cmn.Silent { fmt.Println("LOCKED") } - case CMD_RELEASE, CMD_UNLOCK: + case CmdRelease, CmdUnlock: cmdRelease.Parse(flag.Args()[1:]) doUnlock() if !cmn.Silent { fmt.Println("RELEASED") } - case CMD_TEST: + case CmdTest: cmdTest.Parse(flag.Args()[1:]) os.Exit(doTest()) @@ -146,7 +146,7 @@ func doUnlock() { func newMutex() *mutex.Mutex { result, err := mutex.NewMutexExt(cmn.Root, cmn.Id, lck.Pulse, lck.Refresh, lck.Limit) if err != nil { - log.Fatalf("Cannot create mutex \"%s\": %v", result.Id(), err) + log.Fatalf("Cannot create mutex \"%s\": %v", cmn.Id, err) } return result } diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..1d1fc0d --- /dev/null +++ b/main_test.go @@ -0,0 +1,97 @@ +package main + +import ( + "fmt" + "os" + "path" + "testing" +) + +func temporaryCatalog(t *testing.T) string { + tempDir, err := os.MkdirTemp("", "temp-*.dir") + if err != nil { + t.Fatalf("error creating temporary directory: %v", err) + } + t.Cleanup(func() { + if err := os.RemoveAll(tempDir); err != nil { + t.Errorf("error removing temporary directory: %v", err) + } + }) + return tempDir +} + +func TestIsEmptyStr(t *testing.T) { + cases := [][]string{ + {"", " ", " \t "}, + {"A", " a "}, + } + + for i, c := range cases { + expected := i == 0 + for _, testCase := range c { + if got := isEmptyStr(testCase); got != expected { + t.Fatalf("wrong value of isEmpty(\"%s\") => %v instead of %v", testCase, got, expected) + } + } + } +} + +func TestIfEmptyStr(t *testing.T) { + cases := []struct { + s string + r string + }{ + {"", "abcd"}, + {"xyz", "xyz"}, + {" ", "abcd"}, + } + + for _, c := range cases { + expected := c.r + if got := ifEmptyStr(c.s, "abcd"); got != expected { + t.Fatalf("wrong value of ifEmpty(\"%s\", \"abcd\") => %v instead of \"%s\"", c.s, got, expected) + } + } +} + +func lockName() string { + lockFile := fmt.Sprintf("%s-mutex.lck", cmn.Id) + return path.Join(cmn.Root, cmn.Id, lockFile) +} + +func TestTest(t *testing.T) { + cmn.Root = temporaryCatalog(t) + cmn.Id = "test-test" + doLock() + expected := 0 + if got := doTest(); got != expected { + t.Fatalf("wrong value of doTest() => %d instead of %d", got, expected) + } + doUnlock() + expected = 1 + if got := doTest(); got != expected { + t.Fatalf("wrong value of doTest() => %d instead of %d", got, expected) + } +} + +func TestLock(t *testing.T) { + cmn.Root = temporaryCatalog(t) + cmn.Id = "test-lock" + defer doUnlock() + doLock() + expected := lockName() + if _, err := os.Stat(expected); err != nil { + t.Fatalf("wrong result of doLock(): %v", err) + } +} + +func TestUnlock(t *testing.T) { + cmn.Root = temporaryCatalog(t) + cmn.Id = "test-unlock" + doLock() + doUnlock() + lockFile := lockName() + if _, err := os.Stat(lockFile); err == nil { + t.Fatalf("wrong result of doUnlock(): lock file still exists: %s", lockFile) + } +} diff --git a/mutex/mutex.go b/mutex/mutex.go index 397e42e..dc7afae 100644 --- a/mutex/mutex.go +++ b/mutex/mutex.go @@ -128,7 +128,6 @@ func NewMutex(root string, lockId string) (*Mutex, error) { func NewMutexExt(root string, lockId string, pulse time.Duration, refresh time.Duration, deadTimeout time.Duration) (*Mutex, error) { if !filepath.IsAbs(root) { var err error - //return nil, fmt.Errorf("root (%s) is NOT an absolute absolute path", root) if root, err = filepath.Abs(root); err != nil { return nil, err } diff --git a/mutex/mutex_test.go b/mutex/mutex_test.go index 6842499..6e58cd5 100644 --- a/mutex/mutex_test.go +++ b/mutex/mutex_test.go @@ -2,7 +2,9 @@ package mutex import ( "fmt" + "log" "os" + "path" "path/filepath" "sync" "testing" @@ -22,13 +24,18 @@ func temporaryCatalog(t *testing.T) string { return tempDir } +func newTestMutex(root string, id string) *Mutex { + result, err := NewMutex(root, id) + if err != nil { + log.Fatalf("Cannot create mutex \"%s\": %v", id, err) + } + return result +} + func TestSimpleMutex(t *testing.T) { const mutexId = "simple-test-mutex" mutexRoot := temporaryCatalog(t) - mx, err := NewMutex(mutexRoot, mutexId) - if err != nil { - t.Fatalf("cannot create the mutex: %v", err) - } + mx := newTestMutex(mutexRoot, mutexId) value := 0 mx.Lock() go func(v *int) { @@ -84,10 +91,7 @@ func TestSimpleMutexN(t *testing.T) { func TestLockPath(t *testing.T) { const mutexId = "simple-test-mutex" mutexRoot := temporaryCatalog(t) - mx, err := NewMutex(mutexRoot, mutexId) - if err != nil { - t.Fatalf("cannot create the mutex: %v", err) - } + mx := newTestMutex(mutexRoot, mutexId) want := filepath.Join(mutexRoot, mutexId, fmt.Sprintf("%s-mutex.lck", mutexId)) got := mx.LockPath() @@ -99,10 +103,7 @@ func TestLockPath(t *testing.T) { func TestId(t *testing.T) { const mutexId = "simple-test-mutex" mutexRoot := temporaryCatalog(t) - mx, err := NewMutex(mutexRoot, mutexId) - if err != nil { - t.Fatalf("cannot create the mutex: %v", err) - } + mx := newTestMutex(mutexRoot, mutexId) want := mutexId got := mx.Id() @@ -114,10 +115,7 @@ func TestId(t *testing.T) { func TestWhen(t *testing.T) { const mutexId = "simple-test-mutex" mutexRoot := temporaryCatalog(t) - mx, err := NewMutex(mutexRoot, mutexId) - if err != nil { - t.Fatalf("cannot create the mutex: %v", err) - } + mx := newTestMutex(mutexRoot, mutexId) mx.Lock() defer mx.Unlock() if file, err := os.Create(mx.LockPath()); err != nil { @@ -133,3 +131,74 @@ func TestWhen(t *testing.T) { } } } + +func TestTry1(t *testing.T) { + const mutexId = "try1-test-mutex" + mutexRoot := temporaryCatalog(t) + mx1 := newTestMutex(mutexRoot, mutexId) + mx1.Lock() + go func() { + defer mx1.Unlock() + time.Sleep(3 * time.Second) + }() + mx2 := newTestMutex(mutexRoot, mutexId) + defer mx2.Unlock() + if err := mx2.TryLock(5 * time.Second); err != nil { + t.Fatalf("TryLock failed (%v), but should succeed.", err) + } +} + +func TestTry2(t *testing.T) { + const mutexId = "try2-test-mutex" + mutexRoot := temporaryCatalog(t) + mx1 := newTestMutex(mutexRoot, mutexId) + mx1.Lock() + go func() { + defer mx1.Unlock() + time.Sleep(3 * time.Second) + }() + mx2 := newTestMutex(mutexRoot, mutexId) + defer mx2.Unlock() + if err := mx2.TryLock(1 * time.Second); err == nil { + t.Fatal("TryLock succeed but should failed.") + } +} + +func TestMutexDefaults(t *testing.T) { + const mutexId = "mutex-defaults" + mutexRoot := temporaryCatalog(t) + zero := time.Duration(0) + if mx, err := NewMutexExt(mutexRoot, mutexId, zero, zero, zero); err != nil { + t.Fatal(err) + } else { + expected := DefaultPulse + if got := mx.pulse; got != expected { + t.Fatalf("Wrong pulse default: got: %v, expected: %v", got, expected) + } + expected = DefaultRefresh + if got := mx.refresh; got != expected { + t.Fatalf("Wrong pulse refresh: got: %v, expected: %v", got, expected) + } + } +} + +func TestMutexRoot(t *testing.T) { + const mutexId = "mutex-root" + cwd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + defer os.Chdir(cwd) + tmpdir := temporaryCatalog(t) + if err := os.Chdir(tmpdir); err != nil { + t.Fatal(err) + } + mutexRoot := "./here" + if mx, err := NewMutex(mutexRoot, mutexId); err != nil { + t.Fatal(err) + } else { + if !path.IsAbs(mx.directory) { + t.Fatalf("Wrong lock directory - should be absolute (%s)", mx.directory) + } + } +}