github.com/zuoyebang/bitalosdb@v1.1.1-0.20240516111551-79a8c4d8ce20/internal/vfs/file_lock_test.go (about)

     1  // Copyright 2021 The Bitalosdb author(hustxrb@163.com) and other contributors.
     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 vfs_test
    16  
    17  import (
    18  	"bytes"
    19  	"flag"
    20  	"io/ioutil"
    21  	"os"
    22  	"os/exec"
    23  	"testing"
    24  
    25  	"github.com/stretchr/testify/require"
    26  	"github.com/zuoyebang/bitalosdb/internal/vfs"
    27  )
    28  
    29  var lockFilename = flag.String("lockfile", "", "File to lock. A non-empty value implies a child process.")
    30  
    31  func spawn(prog, filename string) ([]byte, error) {
    32  	return exec.Command(prog, "-lockfile", filename, "-test.v",
    33  		"-test.run=TestLock$").CombinedOutput()
    34  }
    35  
    36  // TestLock locks a file, spawns a second process that attempts to grab the
    37  // lock to verify it fails.
    38  // Then it closes the lock, and spawns a third copy to verify it can be
    39  // relocked.
    40  func TestLock(t *testing.T) {
    41  	child := *lockFilename != ""
    42  	var filename string
    43  	if child {
    44  		filename = *lockFilename
    45  	} else {
    46  		f, err := ioutil.TempFile("", "golang-bitalosdb-db-testlock-")
    47  		require.NoError(t, err)
    48  
    49  		filename = f.Name()
    50  		// NB: On Windows, locking will fail if the file is already open by the
    51  		// current process, so we close the lockfile here.
    52  		require.NoError(t, f.Close())
    53  		defer os.Remove(filename)
    54  	}
    55  
    56  	// Avoid truncating an existing, non-empty file.
    57  	fi, err := os.Stat(filename)
    58  	if err == nil && fi.Size() != 0 {
    59  		t.Fatalf("The file %s is not empty", filename)
    60  	}
    61  
    62  	t.Logf("Locking: %s", filename)
    63  	lock, err := vfs.Default.Lock(filename)
    64  	if err != nil {
    65  		t.Fatalf("Could not lock %s: %v", filename, err)
    66  	}
    67  
    68  	if !child {
    69  		t.Logf("Spawning child, should fail to grab lock.")
    70  		out, err := spawn(os.Args[0], filename)
    71  		if err == nil {
    72  			t.Fatalf("Attempt to grab open lock should have failed.\n%s", out)
    73  		}
    74  		if !bytes.Contains(out, []byte("Could not lock")) {
    75  			t.Fatalf("Child failed with unexpected output: %s", out)
    76  		}
    77  		t.Logf("Child failed to grab lock as expected.")
    78  	}
    79  
    80  	t.Logf("Unlocking %s", filename)
    81  	if err := lock.Close(); err != nil {
    82  		t.Fatalf("Could not unlock %s: %v", filename, err)
    83  	}
    84  
    85  	if !child {
    86  		t.Logf("Spawning child, should successfully grab lock.")
    87  		if out, err := spawn(os.Args[0], filename); err != nil {
    88  			t.Fatalf("Attempt to re-open lock should have succeeded: %v\n%s",
    89  				err, out)
    90  		}
    91  		t.Logf("Child grabbed lock.")
    92  	}
    93  }
    94  
    95  func TestLockSameProcess(t *testing.T) {
    96  	f, err := ioutil.TempFile("", "bitalosdb-testlocksameprocess-")
    97  	require.NoError(t, err)
    98  	filename := f.Name()
    99  
   100  	// NB: On Windows, locking will fail if the file is already open by the
   101  	// current process, so we close the lockfile here.
   102  	require.NoError(t, f.Close())
   103  	defer os.Remove(filename)
   104  
   105  	lock1, err := vfs.Default.Lock(filename)
   106  	require.NoError(t, err)
   107  
   108  	// Locking the file again from within the same process should fail.
   109  	// On Unix, Lock should detect the file in the global map of
   110  	// process-locked files.
   111  	// On Windows, locking will fail since the file is already open by the
   112  	// current process.
   113  	_, err = vfs.Default.Lock(filename)
   114  	require.Error(t, err)
   115  
   116  	require.NoError(t, lock1.Close())
   117  }