Home | History | Annotate | Download | only in build
      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