Source file src/cmd/go/internal/lockedfile/internal/filelock/filelock_test.go

     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  //go:build !js && !plan9 && !wasip1
     6  
     7  package filelock_test
     8  
     9  import (
    10  	"fmt"
    11  	"internal/testenv"
    12  	"os"
    13  	"path/filepath"
    14  	"runtime"
    15  	"testing"
    16  	"time"
    17  
    18  	"cmd/go/internal/lockedfile/internal/filelock"
    19  )
    20  
    21  func lock(t *testing.T, f *os.File) {
    22  	t.Helper()
    23  	err := filelock.Lock(f)
    24  	t.Logf("Lock(fd %d) = %v", f.Fd(), err)
    25  	if err != nil {
    26  		t.Fail()
    27  	}
    28  }
    29  
    30  func rLock(t *testing.T, f *os.File) {
    31  	t.Helper()
    32  	err := filelock.RLock(f)
    33  	t.Logf("RLock(fd %d) = %v", f.Fd(), err)
    34  	if err != nil {
    35  		t.Fail()
    36  	}
    37  }
    38  
    39  func unlock(t *testing.T, f *os.File) {
    40  	t.Helper()
    41  	err := filelock.Unlock(f)
    42  	t.Logf("Unlock(fd %d) = %v", f.Fd(), err)
    43  	if err != nil {
    44  		t.Fail()
    45  	}
    46  }
    47  
    48  func mustTempFile(t *testing.T) (f *os.File, remove func()) {
    49  	t.Helper()
    50  
    51  	base := filepath.Base(t.Name())
    52  	f, err := os.CreateTemp("", base)
    53  	if err != nil {
    54  		t.Fatalf(`os.CreateTemp("", %q) = %v`, base, err)
    55  	}
    56  	t.Logf("fd %d = %s", f.Fd(), f.Name())
    57  
    58  	return f, func() {
    59  		f.Close()
    60  		os.Remove(f.Name())
    61  	}
    62  }
    63  
    64  func mustOpen(t *testing.T, name string) *os.File {
    65  	t.Helper()
    66  
    67  	f, err := os.OpenFile(name, os.O_RDWR, 0)
    68  	if err != nil {
    69  		t.Fatalf("os.OpenFile(%q) = %v", name, err)
    70  	}
    71  
    72  	t.Logf("fd %d = os.OpenFile(%q)", f.Fd(), name)
    73  	return f
    74  }
    75  
    76  const (
    77  	quiescent            = 10 * time.Millisecond
    78  	probablyStillBlocked = 10 * time.Second
    79  )
    80  
    81  func mustBlock(t *testing.T, op string, f *os.File) (wait func(*testing.T)) {
    82  	t.Helper()
    83  
    84  	desc := fmt.Sprintf("%s(fd %d)", op, f.Fd())
    85  
    86  	done := make(chan struct{})
    87  	go func() {
    88  		t.Helper()
    89  		switch op {
    90  		case "Lock":
    91  			lock(t, f)
    92  		case "RLock":
    93  			rLock(t, f)
    94  		default:
    95  			panic("invalid op: " + op)
    96  		}
    97  		close(done)
    98  	}()
    99  
   100  	select {
   101  	case <-done:
   102  		t.Fatalf("%s unexpectedly did not block", desc)
   103  		return nil
   104  
   105  	case <-time.After(quiescent):
   106  		t.Logf("%s is blocked (as expected)", desc)
   107  		return func(t *testing.T) {
   108  			t.Helper()
   109  			select {
   110  			case <-time.After(probablyStillBlocked):
   111  				t.Fatalf("%s is unexpectedly still blocked", desc)
   112  			case <-done:
   113  			}
   114  		}
   115  	}
   116  }
   117  
   118  func TestLockExcludesLock(t *testing.T) {
   119  	t.Parallel()
   120  
   121  	f, remove := mustTempFile(t)
   122  	defer remove()
   123  
   124  	other := mustOpen(t, f.Name())
   125  	defer other.Close()
   126  
   127  	lock(t, f)
   128  	lockOther := mustBlock(t, "Lock", other)
   129  	unlock(t, f)
   130  	lockOther(t)
   131  	unlock(t, other)
   132  }
   133  
   134  func TestLockExcludesRLock(t *testing.T) {
   135  	t.Parallel()
   136  
   137  	f, remove := mustTempFile(t)
   138  	defer remove()
   139  
   140  	other := mustOpen(t, f.Name())
   141  	defer other.Close()
   142  
   143  	lock(t, f)
   144  	rLockOther := mustBlock(t, "RLock", other)
   145  	unlock(t, f)
   146  	rLockOther(t)
   147  	unlock(t, other)
   148  }
   149  
   150  func TestRLockExcludesOnlyLock(t *testing.T) {
   151  	t.Parallel()
   152  
   153  	f, remove := mustTempFile(t)
   154  	defer remove()
   155  	rLock(t, f)
   156  
   157  	f2 := mustOpen(t, f.Name())
   158  	defer f2.Close()
   159  
   160  	doUnlockTF := false
   161  	switch runtime.GOOS {
   162  	case "aix", "solaris":
   163  		// When using POSIX locks (as on Solaris), we can't safely read-lock the
   164  		// same inode through two different descriptors at the same time: when the
   165  		// first descriptor is closed, the second descriptor would still be open but
   166  		// silently unlocked. So a second RLock must block instead of proceeding.
   167  		lockF2 := mustBlock(t, "RLock", f2)
   168  		unlock(t, f)
   169  		lockF2(t)
   170  	default:
   171  		rLock(t, f2)
   172  		doUnlockTF = true
   173  	}
   174  
   175  	other := mustOpen(t, f.Name())
   176  	defer other.Close()
   177  	lockOther := mustBlock(t, "Lock", other)
   178  
   179  	unlock(t, f2)
   180  	if doUnlockTF {
   181  		unlock(t, f)
   182  	}
   183  	lockOther(t)
   184  	unlock(t, other)
   185  }
   186  
   187  func TestLockNotDroppedByExecCommand(t *testing.T) {
   188  	testenv.MustHaveExec(t)
   189  
   190  	f, remove := mustTempFile(t)
   191  	defer remove()
   192  
   193  	lock(t, f)
   194  
   195  	other := mustOpen(t, f.Name())
   196  	defer other.Close()
   197  
   198  	// Some kinds of file locks are dropped when a duplicated or forked file
   199  	// descriptor is unlocked. Double-check that the approach used by os/exec does
   200  	// not accidentally drop locks.
   201  	cmd := testenv.Command(t, os.Args[0], "-test.run=^$")
   202  	if err := cmd.Run(); err != nil {
   203  		t.Fatalf("exec failed: %v", err)
   204  	}
   205  
   206  	lockOther := mustBlock(t, "Lock", other)
   207  	unlock(t, f)
   208  	lockOther(t)
   209  	unlock(t, other)
   210  }
   211  

View as plain text