1 // Copyright 2017 Google Inc. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package build 16 17 import ( 18 "fmt" 19 "io/ioutil" 20 "os" 21 "os/exec" 22 "path/filepath" 23 "syscall" 24 "testing" 25 26 "android/soong/ui/logger" 27 ) 28 29 // some util methods and data structures that aren't directly part of a test 30 func makeLockDir() (path string, err error) { 31 return ioutil.TempDir("", "soong_lock_test") 32 } 33 func lockOrFail(t *testing.T) (lock fileLock) { 34 lockDir, err := makeLockDir() 35 var lockPointer *fileLock 36 if err == nil { 37 lockPointer, err = newLock(lockDir) 38 } 39 if err != nil { 40 os.RemoveAll(lockDir) 41 t.Fatalf("Failed to create lock: %v", err) 42 } 43 44 return *lockPointer 45 } 46 func removeTestLock(fileLock fileLock) { 47 lockdir := filepath.Dir(fileLock.File.Name()) 48 os.RemoveAll(lockdir) 49 } 50 51 // countWaiter only exists for the purposes of testing lockSynchronous 52 type countWaiter struct { 53 numWaitsElapsed int 54 maxNumWaits int 55 } 56 57 func newCountWaiter(count int) (waiter *countWaiter) { 58 return &countWaiter{0, count} 59 } 60 61 func (c *countWaiter) wait() { 62 c.numWaitsElapsed++ 63 } 64 func (c *countWaiter) checkDeadline() (done bool, remainder string) { 65 numWaitsRemaining := c.maxNumWaits - c.numWaitsElapsed 66 if numWaitsRemaining < 1 { 67 return true, "" 68 } 69 return false, fmt.Sprintf("%v waits remain", numWaitsRemaining) 70 } 71 func (c countWaiter) summarize() (summary string) { 72 return fmt.Sprintf("waiting %v times", c.maxNumWaits) 73 } 74 75 // countLock only exists for the purposes of testing lockSynchronous 76 type countLock struct { 77 nextIndex int 78 successIndex int 79 } 80 81 var _ lockable = (*countLock)(nil) 82 83 // returns a countLock that succeeds on iteration <index> 84 func testLockCountingTo(index int) (lock *countLock) { 85 return &countLock{nextIndex: 0, successIndex: index} 86 } 87 func (c *countLock) description() (message string) { 88 return fmt.Sprintf("counter that counts from %v to %v", c.nextIndex, c.successIndex) 89 } 90 func (c *countLock) tryLock() (err error) { 91 currentIndex := c.nextIndex 92 c.nextIndex++ 93 if currentIndex == c.successIndex { 94 return nil 95 } 96 return fmt.Errorf("Lock busy: %s", c.description()) 97 } 98 func (c *countLock) Unlock() (err error) { 99 if c.nextIndex == c.successIndex { 100 return nil 101 } 102 return fmt.Errorf("Not locked: %s", c.description()) 103 } 104 105 // end of util methods 106 107 // start of tests 108 109 // simple test 110 func TestGetLock(t *testing.T) { 111 lockfile := lockOrFail(t) 112 defer removeTestLock(lockfile) 113 } 114 115 // a more complicated test that spans multiple processes 116 var lockPathVariable = "LOCK_PATH" 117 var successStatus = 0 118 var unexpectedError = 1 119 var busyStatus = 2 120 121 func TestTrylock(t *testing.T) { 122 lockpath := os.Getenv(lockPathVariable) 123 if len(lockpath) < 1 { 124 checkTrylockMainProcess(t) 125 } else { 126 getLockAndExit(lockpath) 127 } 128 } 129 130 // the portion of TestTrylock that runs in the main process 131 func checkTrylockMainProcess(t *testing.T) { 132 var err error 133 lockfile := lockOrFail(t) 134 defer removeTestLock(lockfile) 135 lockdir := filepath.Dir(lockfile.File.Name()) 136 otherAcquired, message, err := forkAndGetLock(lockdir) 137 if err != nil { 138 t.Fatalf("Unexpected error in subprocess trying to lock uncontested fileLock: %v. Subprocess output: %q", err, message) 139 } 140 if !otherAcquired { 141 t.Fatalf("Subprocess failed to lock uncontested fileLock. Subprocess output: %q", message) 142 } 143 144 err = lockfile.tryLock() 145 if err != nil { 146 t.Fatalf("Failed to lock fileLock: %v", err) 147 } 148 149 reacquired, message, err := forkAndGetLock(filepath.Dir(lockfile.File.Name())) 150 if err != nil { 151 t.Fatal(err) 152 } 153 if reacquired { 154 t.Fatalf("Permitted locking fileLock twice. Subprocess output: %q", message) 155 } 156 157 err = lockfile.Unlock() 158 if err != nil { 159 t.Fatalf("Error unlocking fileLock: %v", err) 160 } 161 162 reacquired, message, err = forkAndGetLock(filepath.Dir(lockfile.File.Name())) 163 if err != nil { 164 t.Fatal(err) 165 } 166 if !reacquired { 167 t.Fatalf("Subprocess failed to acquire lock after it was released by the main process. Subprocess output: %q", message) 168 } 169 } 170 func forkAndGetLock(lockDir string) (acquired bool, subprocessOutput []byte, err error) { 171 cmd := exec.Command(os.Args[0], "-test.run=TestTrylock") 172 cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", lockPathVariable, lockDir)) 173 subprocessOutput, err = cmd.CombinedOutput() 174 exitStatus := successStatus 175 if exitError, ok := err.(*exec.ExitError); ok { 176 if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok { 177 exitStatus = waitStatus.ExitStatus() 178 } 179 } 180 if exitStatus == successStatus { 181 return true, subprocessOutput, nil 182 } else if exitStatus == busyStatus { 183 return false, subprocessOutput, nil 184 } else { 185 return false, subprocessOutput, fmt.Errorf("Unexpected status %v", exitStatus) 186 } 187 } 188 189 // This function runs in a different process. See TestTrylock 190 func getLockAndExit(lockpath string) { 191 fmt.Printf("Will lock path %q\n", lockpath) 192 lockfile, err := newLock(lockpath) 193 exitStatus := unexpectedError 194 if err == nil { 195 err = lockfile.tryLock() 196 if err == nil { 197 exitStatus = successStatus 198 } else { 199 exitStatus = busyStatus 200 } 201 } 202 fmt.Printf("Tried to lock path %s. Received error %v. Exiting with status %v\n", lockpath, err, exitStatus) 203 os.Exit(exitStatus) 204 } 205 206 func TestLockFirstTrySucceeds(t *testing.T) { 207 noopLogger := logger.New(ioutil.Discard) 208 lock := testLockCountingTo(0) 209 waiter := newCountWaiter(0) 210 err := lockSynchronous(lock, waiter, noopLogger) 211 if err != nil { 212 t.Fatal(err) 213 } 214 if waiter.numWaitsElapsed != 0 { 215 t.Fatalf("Incorrect number of waits elapsed; expected 0, got %v", waiter.numWaitsElapsed) 216 } 217 } 218 func TestLockThirdTrySucceeds(t *testing.T) { 219 noopLogger := logger.New(ioutil.Discard) 220 lock := testLockCountingTo(2) 221 waiter := newCountWaiter(2) 222 err := lockSynchronous(lock, waiter, noopLogger) 223 if err != nil { 224 t.Fatal(err) 225 } 226 if waiter.numWaitsElapsed != 2 { 227 t.Fatalf("Incorrect number of waits elapsed; expected 2, got %v", waiter.numWaitsElapsed) 228 } 229 } 230 func TestLockTimedOut(t *testing.T) { 231 noopLogger := logger.New(ioutil.Discard) 232 lock := testLockCountingTo(3) 233 waiter := newCountWaiter(2) 234 err := lockSynchronous(lock, waiter, noopLogger) 235 if err == nil { 236 t.Fatalf("Appeared to have acquired lock on iteration %v which should not be available until iteration %v", waiter.numWaitsElapsed, lock.successIndex) 237 } 238 if waiter.numWaitsElapsed != waiter.maxNumWaits { 239 t.Fatalf("Waited an incorrect number of times; expected %v, got %v", waiter.maxNumWaits, waiter.numWaitsElapsed) 240 } 241 } 242