github.com/gernest/nezuko@v0.1.2/internal/lockedfile/lockedfile_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  // js and nacl do not support inter-process file locking.
     6  // +build !js,!nacl
     7  
     8  package lockedfile_test
     9  
    10  import (
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/gernest/nezuko/internal/lockedfile"
    18  )
    19  
    20  func mustTempDir(t *testing.T) (dir string, remove func()) {
    21  	t.Helper()
    22  
    23  	dir, err := ioutil.TempDir("", filepath.Base(t.Name()))
    24  	if err != nil {
    25  		t.Fatal(err)
    26  	}
    27  	return dir, func() { os.RemoveAll(dir) }
    28  }
    29  
    30  const (
    31  	quiescent            = 10 * time.Millisecond
    32  	probablyStillBlocked = 10 * time.Second
    33  )
    34  
    35  func mustBlock(t *testing.T, desc string, f func()) (wait func(*testing.T)) {
    36  	t.Helper()
    37  
    38  	done := make(chan struct{})
    39  	go func() {
    40  		f()
    41  		close(done)
    42  	}()
    43  
    44  	select {
    45  	case <-done:
    46  		t.Fatalf("%s unexpectedly did not block", desc)
    47  		return nil
    48  
    49  	case <-time.After(quiescent):
    50  		return func(t *testing.T) {
    51  			t.Helper()
    52  			select {
    53  			case <-time.After(probablyStillBlocked):
    54  				t.Fatalf("%s is unexpectedly still blocked after %v", desc, probablyStillBlocked)
    55  			case <-done:
    56  			}
    57  		}
    58  	}
    59  }
    60  
    61  func TestMutexExcludes(t *testing.T) {
    62  	t.Parallel()
    63  
    64  	dir, remove := mustTempDir(t)
    65  	defer remove()
    66  
    67  	path := filepath.Join(dir, "lock")
    68  
    69  	mu := lockedfile.MutexAt(path)
    70  	t.Logf("mu := MutexAt(_)")
    71  
    72  	unlock, err := mu.Lock()
    73  	if err != nil {
    74  		t.Fatalf("mu.Lock: %v", err)
    75  	}
    76  	t.Logf("unlock, _  := mu.Lock()")
    77  
    78  	mu2 := lockedfile.MutexAt(mu.Path)
    79  	t.Logf("mu2 := MutexAt(mu.Path)")
    80  
    81  	wait := mustBlock(t, "mu2.Lock()", func() {
    82  		unlock2, err := mu2.Lock()
    83  		if err != nil {
    84  			t.Errorf("mu2.Lock: %v", err)
    85  			return
    86  		}
    87  		t.Logf("unlock2, _ := mu2.Lock()")
    88  		t.Logf("unlock2()")
    89  		unlock2()
    90  	})
    91  
    92  	t.Logf("unlock()")
    93  	unlock()
    94  	wait(t)
    95  }
    96  
    97  func TestReadWaitsForLock(t *testing.T) {
    98  	t.Parallel()
    99  
   100  	dir, remove := mustTempDir(t)
   101  	defer remove()
   102  
   103  	path := filepath.Join(dir, "timestamp.txt")
   104  
   105  	f, err := lockedfile.Create(path)
   106  	if err != nil {
   107  		t.Fatalf("Create: %v", err)
   108  	}
   109  	defer f.Close()
   110  
   111  	const (
   112  		part1 = "part 1\n"
   113  		part2 = "part 2\n"
   114  	)
   115  	_, err = f.WriteString(part1)
   116  	if err != nil {
   117  		t.Fatalf("WriteString: %v", err)
   118  	}
   119  	t.Logf("WriteString(%q) = <nil>", part1)
   120  
   121  	wait := mustBlock(t, "Read", func() {
   122  		b, err := lockedfile.Read(path)
   123  		if err != nil {
   124  			t.Errorf("Read: %v", err)
   125  			return
   126  		}
   127  
   128  		const want = part1 + part2
   129  		got := string(b)
   130  		if got == want {
   131  			t.Logf("Read(_) = %q", got)
   132  		} else {
   133  			t.Errorf("Read(_) = %q, _; want %q", got, want)
   134  		}
   135  	})
   136  
   137  	_, err = f.WriteString(part2)
   138  	if err != nil {
   139  		t.Errorf("WriteString: %v", err)
   140  	} else {
   141  		t.Logf("WriteString(%q) = <nil>", part2)
   142  	}
   143  	f.Close()
   144  
   145  	wait(t)
   146  }
   147  
   148  func TestCanLockExistingFile(t *testing.T) {
   149  	t.Parallel()
   150  
   151  	dir, remove := mustTempDir(t)
   152  	defer remove()
   153  	path := filepath.Join(dir, "existing.txt")
   154  
   155  	if err := ioutil.WriteFile(path, []byte("ok"), 0777); err != nil {
   156  		t.Fatalf("ioutil.WriteFile: %v", err)
   157  	}
   158  
   159  	f, err := lockedfile.Edit(path)
   160  	if err != nil {
   161  		t.Fatalf("first Edit: %v", err)
   162  	}
   163  
   164  	wait := mustBlock(t, "Edit", func() {
   165  		other, err := lockedfile.Edit(path)
   166  		if err != nil {
   167  			t.Errorf("second Edit: %v", err)
   168  		}
   169  		other.Close()
   170  	})
   171  
   172  	f.Close()
   173  	wait(t)
   174  }