github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/vfs/file_lock_test.go (about)

     1  // Copyright 2014 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package vfs_test
     6  
     7  import (
     8  	"bytes"
     9  	"flag"
    10  	"os"
    11  	"os/exec"
    12  	"testing"
    13  
    14  	"github.com/cockroachdb/pebble/vfs"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  var lockFilename = flag.String("lockfile", "", "File to lock. A non-empty value implies a child process.")
    19  
    20  func spawn(prog, filename string) ([]byte, error) {
    21  	return exec.Command(prog, "-lockfile", filename, "-test.v",
    22  		"-test.run=TestLock$").CombinedOutput()
    23  }
    24  
    25  // TestLock locks a file, spawns a second process that attempts to grab the
    26  // lock to verify it fails.
    27  // Then it closes the lock, and spawns a third copy to verify it can be
    28  // relocked.
    29  func TestLock(t *testing.T) {
    30  	child := *lockFilename != ""
    31  	var filename string
    32  	if child {
    33  		filename = *lockFilename
    34  	} else {
    35  		f, err := os.CreateTemp("", "golang-pebble-db-testlock-")
    36  		require.NoError(t, err)
    37  
    38  		filename = f.Name()
    39  		// NB: On Windows, locking will fail if the file is already open by the
    40  		// current process, so we close the lockfile here.
    41  		require.NoError(t, f.Close())
    42  		defer os.Remove(filename)
    43  	}
    44  
    45  	// Avoid truncating an existing, non-empty file.
    46  	fi, err := os.Stat(filename)
    47  	if err == nil && fi.Size() != 0 {
    48  		t.Fatalf("The file %s is not empty", filename)
    49  	}
    50  
    51  	t.Logf("Locking: %s", filename)
    52  	lock, err := vfs.Default.Lock(filename)
    53  	if err != nil {
    54  		t.Fatalf("Could not lock %s: %v", filename, err)
    55  	}
    56  
    57  	if !child {
    58  		t.Logf("Spawning child, should fail to grab lock.")
    59  		out, err := spawn(os.Args[0], filename)
    60  		if err == nil {
    61  			t.Fatalf("Attempt to grab open lock should have failed.\n%s", out)
    62  		}
    63  		if !bytes.Contains(out, []byte("Could not lock")) {
    64  			t.Fatalf("Child failed with unexpected output: %s", out)
    65  		}
    66  		t.Logf("Child failed to grab lock as expected.")
    67  	}
    68  
    69  	t.Logf("Unlocking %s", filename)
    70  	if err := lock.Close(); err != nil {
    71  		t.Fatalf("Could not unlock %s: %v", filename, err)
    72  	}
    73  
    74  	if !child {
    75  		t.Logf("Spawning child, should successfully grab lock.")
    76  		if out, err := spawn(os.Args[0], filename); err != nil {
    77  			t.Fatalf("Attempt to re-open lock should have succeeded: %v\n%s",
    78  				err, out)
    79  		}
    80  		t.Logf("Child grabbed lock.")
    81  	}
    82  }
    83  
    84  func TestLockSameProcess(t *testing.T) {
    85  	f, err := os.CreateTemp("", "pebble-testlocksameprocess-")
    86  	require.NoError(t, err)
    87  	filename := f.Name()
    88  
    89  	// NB: On Windows, locking will fail if the file is already open by the
    90  	// current process, so we close the lockfile here.
    91  	require.NoError(t, f.Close())
    92  	defer os.Remove(filename)
    93  
    94  	lock1, err := vfs.Default.Lock(filename)
    95  	require.NoError(t, err)
    96  
    97  	// Locking the file again from within the same process should fail.
    98  	// On Unix, Lock should detect the file in the global map of
    99  	// process-locked files.
   100  	// On Windows, locking will fail since the file is already open by the
   101  	// current process.
   102  	_, err = vfs.Default.Lock(filename)
   103  	require.Error(t, err)
   104  
   105  	require.NoError(t, lock1.Close())
   106  }