github.com/gernest/nezuko@v0.1.2/internal/lockedfile/lockedfile_test.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // js and nacl do not support inter-process file locking. 6 // +build !js,!nacl 7 8 package lockedfile_test 9 10 import ( 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "testing" 15 "time" 16 17 "github.com/gernest/nezuko/internal/lockedfile" 18 ) 19 20 func mustTempDir(t *testing.T) (dir string, remove func()) { 21 t.Helper() 22 23 dir, err := ioutil.TempDir("", filepath.Base(t.Name())) 24 if err != nil { 25 t.Fatal(err) 26 } 27 return dir, func() { os.RemoveAll(dir) } 28 } 29 30 const ( 31 quiescent = 10 * time.Millisecond 32 probablyStillBlocked = 10 * time.Second 33 ) 34 35 func mustBlock(t *testing.T, desc string, f func()) (wait func(*testing.T)) { 36 t.Helper() 37 38 done := make(chan struct{}) 39 go func() { 40 f() 41 close(done) 42 }() 43 44 select { 45 case <-done: 46 t.Fatalf("%s unexpectedly did not block", desc) 47 return nil 48 49 case <-time.After(quiescent): 50 return func(t *testing.T) { 51 t.Helper() 52 select { 53 case <-time.After(probablyStillBlocked): 54 t.Fatalf("%s is unexpectedly still blocked after %v", desc, probablyStillBlocked) 55 case <-done: 56 } 57 } 58 } 59 } 60 61 func TestMutexExcludes(t *testing.T) { 62 t.Parallel() 63 64 dir, remove := mustTempDir(t) 65 defer remove() 66 67 path := filepath.Join(dir, "lock") 68 69 mu := lockedfile.MutexAt(path) 70 t.Logf("mu := MutexAt(_)") 71 72 unlock, err := mu.Lock() 73 if err != nil { 74 t.Fatalf("mu.Lock: %v", err) 75 } 76 t.Logf("unlock, _ := mu.Lock()") 77 78 mu2 := lockedfile.MutexAt(mu.Path) 79 t.Logf("mu2 := MutexAt(mu.Path)") 80 81 wait := mustBlock(t, "mu2.Lock()", func() { 82 unlock2, err := mu2.Lock() 83 if err != nil { 84 t.Errorf("mu2.Lock: %v", err) 85 return 86 } 87 t.Logf("unlock2, _ := mu2.Lock()") 88 t.Logf("unlock2()") 89 unlock2() 90 }) 91 92 t.Logf("unlock()") 93 unlock() 94 wait(t) 95 } 96 97 func TestReadWaitsForLock(t *testing.T) { 98 t.Parallel() 99 100 dir, remove := mustTempDir(t) 101 defer remove() 102 103 path := filepath.Join(dir, "timestamp.txt") 104 105 f, err := lockedfile.Create(path) 106 if err != nil { 107 t.Fatalf("Create: %v", err) 108 } 109 defer f.Close() 110 111 const ( 112 part1 = "part 1\n" 113 part2 = "part 2\n" 114 ) 115 _, err = f.WriteString(part1) 116 if err != nil { 117 t.Fatalf("WriteString: %v", err) 118 } 119 t.Logf("WriteString(%q) = <nil>", part1) 120 121 wait := mustBlock(t, "Read", func() { 122 b, err := lockedfile.Read(path) 123 if err != nil { 124 t.Errorf("Read: %v", err) 125 return 126 } 127 128 const want = part1 + part2 129 got := string(b) 130 if got == want { 131 t.Logf("Read(_) = %q", got) 132 } else { 133 t.Errorf("Read(_) = %q, _; want %q", got, want) 134 } 135 }) 136 137 _, err = f.WriteString(part2) 138 if err != nil { 139 t.Errorf("WriteString: %v", err) 140 } else { 141 t.Logf("WriteString(%q) = <nil>", part2) 142 } 143 f.Close() 144 145 wait(t) 146 } 147 148 func TestCanLockExistingFile(t *testing.T) { 149 t.Parallel() 150 151 dir, remove := mustTempDir(t) 152 defer remove() 153 path := filepath.Join(dir, "existing.txt") 154 155 if err := ioutil.WriteFile(path, []byte("ok"), 0777); err != nil { 156 t.Fatalf("ioutil.WriteFile: %v", err) 157 } 158 159 f, err := lockedfile.Edit(path) 160 if err != nil { 161 t.Fatalf("first Edit: %v", err) 162 } 163 164 wait := mustBlock(t, "Edit", func() { 165 other, err := lockedfile.Edit(path) 166 if err != nil { 167 t.Errorf("second Edit: %v", err) 168 } 169 other.Close() 170 }) 171 172 f.Close() 173 wait(t) 174 }