go4.org@v0.0.0-20230225012048-214862532bf5/lock/lock_test.go (about) 1 /* 2 Copyright 2013 The Go Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package lock 18 19 import ( 20 "bufio" 21 "errors" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "os" 26 "os/exec" 27 "path/filepath" 28 "strconv" 29 "testing" 30 ) 31 32 func TestLock(t *testing.T) { 33 testLock(t, false) 34 } 35 36 func TestLockPortable(t *testing.T) { 37 testLock(t, true) 38 } 39 40 func TestLockInChild(t *testing.T) { 41 f := os.Getenv("TEST_LOCK_FILE") 42 if f == "" { 43 // not child 44 return 45 } 46 lock := Lock 47 if v, _ := strconv.ParseBool(os.Getenv("TEST_LOCK_PORTABLE")); v { 48 lock = lockPortable 49 } 50 51 var lk io.Closer 52 for scan := bufio.NewScanner(os.Stdin); scan.Scan(); { 53 var err error 54 switch scan.Text() { 55 case "lock": 56 lk, err = lock(f) 57 case "unlock": 58 err = lk.Close() 59 lk = nil 60 case "exit": 61 // Simulate a crash, or at least not unlocking the lock. 62 os.Exit(0) 63 default: 64 err = fmt.Errorf("unexpected child command %q", scan.Text()) 65 } 66 if err != nil { 67 fmt.Println(err) 68 } else { 69 fmt.Println("") 70 } 71 } 72 } 73 74 func testLock(t *testing.T, portable bool) { 75 lock := Lock 76 if portable { 77 lock = lockPortable 78 } 79 t.Logf("test lock, portable %v", portable) 80 81 td, err := ioutil.TempDir("", "") 82 if err != nil { 83 t.Fatal(err) 84 } 85 defer os.RemoveAll(td) 86 87 path := filepath.Join(td, "foo.lock") 88 89 proc := newChildProc(t, path, portable) 90 defer proc.kill() 91 92 t.Logf("First lock in child") 93 if err := proc.do("lock"); err != nil { 94 t.Fatalf("first lock in child process: %v", err) 95 } 96 97 t.Logf("Crash child") 98 if err := proc.do("exit"); err != nil { 99 t.Fatalf("crash in child process: %v", err) 100 } 101 102 proc = newChildProc(t, path, portable) 103 defer proc.kill() 104 105 t.Logf("Locking+unlocking in child...") 106 if err := proc.do("lock"); err != nil { 107 t.Fatalf("lock in child process after crashing child: %v", err) 108 } 109 if err := proc.do("unlock"); err != nil { 110 t.Fatalf("lock in child process after crashing child: %v", err) 111 } 112 113 t.Logf("Locking in parent...") 114 lk1, err := lock(path) 115 if err != nil { 116 t.Fatal(err) 117 } 118 119 t.Logf("Again in parent...") 120 _, err = lock(path) 121 if err == nil { 122 t.Fatal("expected second lock to fail") 123 } 124 125 t.Logf("Locking in child...") 126 if err := proc.do("lock"); err == nil { 127 t.Fatalf("expected lock in child process to fail") 128 } 129 130 t.Logf("Unlocking lock in parent") 131 if err := lk1.Close(); err != nil { 132 t.Fatal(err) 133 } 134 135 t.Logf("Trying lock again in child...") 136 if err := proc.do("lock"); err != nil { 137 t.Fatal(err) 138 } 139 if err := proc.do("unlock"); err != nil { 140 t.Fatal(err) 141 } 142 143 lk3, err := lock(path) 144 if err != nil { 145 t.Fatal(err) 146 } 147 lk3.Close() 148 } 149 150 type childLockCmd struct { 151 op string 152 reply chan<- error 153 } 154 155 type childProc struct { 156 proc *os.Process 157 c chan childLockCmd 158 } 159 160 func (c *childProc) kill() { 161 c.proc.Kill() 162 } 163 164 func (c *childProc) do(op string) error { 165 reply := make(chan error) 166 c.c <- childLockCmd{ 167 op: op, 168 reply: reply, 169 } 170 return <-reply 171 } 172 173 func newChildProc(t *testing.T, path string, portable bool) *childProc { 174 cmd := exec.Command(os.Args[0], "-test.run=LockInChild$") 175 cmd.Env = []string{"TEST_LOCK_FILE=" + path} 176 toChild, err := cmd.StdinPipe() 177 if err != nil { 178 t.Fatalf("cannot make pipe: %v", err) 179 } 180 fromChild, err := cmd.StdoutPipe() 181 if err != nil { 182 t.Fatalf("cannot make pipe: %v", err) 183 } 184 cmd.Stderr = os.Stderr 185 if portable { 186 cmd.Env = append(cmd.Env, "TEST_LOCK_PORTABLE=1") 187 } 188 if err := cmd.Start(); err != nil { 189 t.Fatalf("cannot start child: %v", err) 190 } 191 cmdChan := make(chan childLockCmd) 192 go func() { 193 defer fromChild.Close() 194 defer toChild.Close() 195 inScan := bufio.NewScanner(fromChild) 196 for c := range cmdChan { 197 fmt.Fprintln(toChild, c.op) 198 ok := inScan.Scan() 199 if c.op == "exit" { 200 if ok { 201 c.reply <- errors.New("child did not exit") 202 } else { 203 cmd.Wait() 204 c.reply <- nil 205 } 206 break 207 } 208 if !ok { 209 panic("child exited early") 210 } 211 if errText := inScan.Text(); errText != "" { 212 c.reply <- errors.New(errText) 213 } else { 214 c.reply <- nil 215 } 216 } 217 }() 218 return &childProc{ 219 c: cmdChan, 220 proc: cmd.Process, 221 } 222 }