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