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

     1  // Copyright 2012 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
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/cockroachdb/datadriven"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  func runTestCases(t *testing.T, testCases []string, fs *MemFS) {
    21  	var f File
    22  	for _, tc := range testCases {
    23  		s := strings.Split(tc, " ")[1:]
    24  
    25  		saveF := s[0] == "f" && s[1] == "="
    26  		if saveF {
    27  			s = s[2:]
    28  		}
    29  
    30  		fails := s[len(s)-1] == "fails"
    31  		if fails {
    32  			s = s[:len(s)-1]
    33  		}
    34  
    35  		var (
    36  			fi  os.FileInfo
    37  			g   File
    38  			err error
    39  		)
    40  		switch s[0] {
    41  		case "create":
    42  			g, err = fs.Create(s[1])
    43  		case "link":
    44  			err = fs.Link(s[1], s[2])
    45  		case "open":
    46  			g, err = fs.Open(s[1])
    47  		case "openDir":
    48  			g, err = fs.OpenDir(s[1])
    49  		case "mkdirall":
    50  			err = fs.MkdirAll(s[1], 0755)
    51  		case "remove":
    52  			err = fs.Remove(s[1])
    53  		case "rename":
    54  			err = fs.Rename(s[1], s[2])
    55  		case "reuseForWrite":
    56  			g, err = fs.ReuseForWrite(s[1], s[2])
    57  		case "resetToSynced":
    58  			fs.ResetToSyncedState()
    59  		case "ignoreSyncs":
    60  			fs.SetIgnoreSyncs(true)
    61  		case "stopIgnoringSyncs":
    62  			fs.SetIgnoreSyncs(false)
    63  		case "f.write":
    64  			_, err = f.Write([]byte(s[1]))
    65  		case "f.sync":
    66  			err = f.Sync()
    67  		case "f.read":
    68  			n, _ := strconv.Atoi(s[1])
    69  			buf := make([]byte, n)
    70  			_, err = io.ReadFull(f, buf)
    71  			if err != nil {
    72  				break
    73  			}
    74  			if got, want := string(buf), s[3]; got != want {
    75  				t.Fatalf("%q: got %q, want %q", tc, got, want)
    76  			}
    77  		case "f.readat":
    78  			n, _ := strconv.Atoi(s[1])
    79  			off, _ := strconv.Atoi(s[2])
    80  			buf := make([]byte, n)
    81  			_, err = f.ReadAt(buf, int64(off))
    82  			if err != nil {
    83  				break
    84  			}
    85  			if got, want := string(buf), s[4]; got != want {
    86  				t.Fatalf("%q: got %q, want %q", tc, got, want)
    87  			}
    88  		case "f.close":
    89  			f, err = nil, f.Close()
    90  		case "f.stat.name":
    91  			fi, err = f.Stat()
    92  			if err != nil {
    93  				break
    94  			}
    95  			if got, want := fi.Name(), s[2]; got != want {
    96  				t.Fatalf("%q: got %q, want %q", tc, got, want)
    97  			}
    98  		default:
    99  			t.Fatalf("bad test case: %q", tc)
   100  		}
   101  
   102  		if saveF {
   103  			f, g = g, nil
   104  		} else if g != nil {
   105  			g.Close()
   106  		}
   107  
   108  		if fails {
   109  			if err == nil {
   110  				t.Fatalf("%q: got nil error, want non-nil", tc)
   111  			}
   112  		} else {
   113  			if err != nil {
   114  				t.Fatalf("%q: %v", tc, err)
   115  			}
   116  		}
   117  	}
   118  
   119  	// Both "" and "/" are allowed to be used to refer to the root of the FS
   120  	// for the purposes of cloning.
   121  	checkClonedIsEquivalent(t, fs, "")
   122  	checkClonedIsEquivalent(t, fs, "/")
   123  }
   124  
   125  // Test that the FS can be cloned and that the clone serializes identically.
   126  func checkClonedIsEquivalent(t *testing.T, fs *MemFS, path string) {
   127  	t.Helper()
   128  	clone := NewMem()
   129  	cloned, err := Clone(fs, clone, path, path)
   130  	require.NoError(t, err)
   131  	require.True(t, cloned)
   132  	require.Equal(t, fs.String(), clone.String())
   133  }
   134  
   135  func TestBasics(t *testing.T) {
   136  	fs := NewMem()
   137  	testCases := []string{
   138  		// Create a top-level file.
   139  		"1a: create /foo",
   140  		// Create a child of that file. It should fail, since /foo is not a directory.
   141  		"2a: create /foo/x fails",
   142  		// Create a third-level file. It should fail, since /bar has not been created.
   143  		// Similarly, opening that file should fail.
   144  		"3a: create /bar/baz/y fails",
   145  		"3b: open /bar/baz/y fails",
   146  		// Make the /bar/baz directory; create a third-level file. Creation should now succeed.
   147  		"4a: mkdirall /bar/baz",
   148  		"4b: f = create /bar/baz/y",
   149  		"4c: f.stat.name == y",
   150  		// Write some data; read it back.
   151  		"5a: f.write abcde",
   152  		"5b: f.close",
   153  		"5c: f = open /bar/baz/y",
   154  		"5d: f.read 5 == abcde",
   155  		"5e: f.readat 2 1 == bc",
   156  		"5f: f.close",
   157  		// Link /bar/baz/y to /bar/baz/z. We should be able to read from both files
   158  		// and remove them independently.
   159  		"6a: link /bar/baz/y /bar/baz/z",
   160  		"6b: f = open /bar/baz/z",
   161  		"6c: f.read 5 == abcde",
   162  		"6d: f.close",
   163  		"6e: remove /bar/baz/z",
   164  		"6f: f = open /bar/baz/y",
   165  		"6g: f.read 5 == abcde",
   166  		"6h: f.close",
   167  		// Remove the file twice. The first should succeed, the second should fail.
   168  		"7a: remove /bar/baz/y",
   169  		"7b: remove /bar/baz/y fails",
   170  		"7c: open /bar/baz/y fails",
   171  		// Rename /foo to /goo. Trying to open /foo should succeed before the rename and
   172  		// fail afterwards, and vice versa for /goo.
   173  		"8a: open /foo",
   174  		"8b: open /goo fails",
   175  		"8c: rename /foo /goo",
   176  		"8d: open /foo fails",
   177  		"8e: open /goo",
   178  		// Create /bar/baz/z and rename /bar/baz to /bar/caz.
   179  		"9a: create /bar/baz/z",
   180  		"9b: open /bar/baz/z",
   181  		"9c: open /bar/caz/z fails",
   182  		"9d: rename /bar/baz /bar/caz",
   183  		"9e: open /bar/baz/z fails",
   184  		"9f: open /bar/caz/z",
   185  		// ReuseForWrite
   186  		"10a: reuseForWrite /bar/caz/z /bar/z",
   187  		"10b: open /bar/caz/z fails",
   188  		"10c: open /bar/z",
   189  		// Opening the root directory works.
   190  		"11a: f = open /",
   191  		"11b: f.stat.name == /",
   192  	}
   193  	runTestCases(t, testCases, fs)
   194  }
   195  
   196  func TestList(t *testing.T) {
   197  	fs := NewMem()
   198  
   199  	dirnames := []string{
   200  		"/bar",
   201  		"/foo/2",
   202  	}
   203  	for _, dirname := range dirnames {
   204  		err := fs.MkdirAll(dirname, 0755)
   205  		if err != nil {
   206  			t.Fatalf("MkdirAll %q: %v", dirname, err)
   207  		}
   208  	}
   209  
   210  	filenames := []string{
   211  		"/a",
   212  		"/bar/baz",
   213  		"/foo/0",
   214  		"/foo/1",
   215  		"/foo/2/a",
   216  		"/foo/2/b",
   217  		"/foo/3",
   218  		"/foot",
   219  	}
   220  	for _, filename := range filenames {
   221  		f, err := fs.Create(filename)
   222  		if err != nil {
   223  			t.Fatalf("Create %q: %v", filename, err)
   224  		}
   225  		if err := f.Close(); err != nil {
   226  			t.Fatalf("Close %q: %v", filename, err)
   227  		}
   228  	}
   229  
   230  	{
   231  		got := fs.String()
   232  		const want = `          /
   233         0    a
   234              bar/
   235         0      baz
   236              foo/
   237         0      0
   238         0      1
   239                2/
   240         0        a
   241         0        b
   242         0      3
   243         0    foot
   244  `
   245  		if got != want {
   246  			t.Fatalf("String:\n----got----\n%s----want----\n%s", got, want)
   247  		}
   248  	}
   249  
   250  	testCases := []string{
   251  		"/:a bar foo foot",
   252  		"/bar:baz",
   253  		"/bar/:baz",
   254  		"/baz:",
   255  		"/baz/:",
   256  		"/foo:0 1 2 3",
   257  		"/foo/:0 1 2 3",
   258  		"/foo/1:",
   259  		"/foo/1/:",
   260  		"/foo/2:a b",
   261  		"/foo/2/:a b",
   262  		"/foot:",
   263  		"/foot/:",
   264  	}
   265  	for _, tc := range testCases {
   266  		s := strings.Split(tc, ":")
   267  		list, _ := fs.List(s[0])
   268  		sort.Strings(list)
   269  		got := strings.Join(list, " ")
   270  		want := s[1]
   271  		if got != want {
   272  			t.Errorf("List %q: got %q, want %q", s[0], got, want)
   273  		}
   274  	}
   275  }
   276  
   277  func TestMemFile(t *testing.T) {
   278  	want := "foo"
   279  	f := NewMemFile([]byte(want))
   280  	buf, err := io.ReadAll(f)
   281  	if err != nil {
   282  		t.Fatalf("%v", err)
   283  	}
   284  	if got := string(buf); got != want {
   285  		t.Fatalf("got %q, want %q", got, want)
   286  	}
   287  }
   288  
   289  func TestStrictFS(t *testing.T) {
   290  	fs := NewStrictMem()
   291  	testCases := []string{
   292  		// Created file disappears if directory is not synced.
   293  		"1a: create /foo",
   294  		"1b: open /foo",
   295  		"1c: resetToSynced",
   296  		"1d: open /foo fails",
   297  
   298  		// Create directory and a file in it and write and read from it.
   299  		"2a: mkdirall /bar",
   300  		"2b: f = create /bar/y",
   301  		"2c: f.stat.name == y",
   302  		// Write some data; read it back.
   303  		"2d: f.write abcde",
   304  		"2e: f.close",
   305  		"2f: f = open /bar/y",
   306  		"2g: f.read 5 == abcde",
   307  		"2h: f.close",
   308  		"2i: open /bar",
   309  
   310  		// Resetting causes both the directory and file to disappear.
   311  		"3a: resetToSynced",
   312  		"3b: openDir /bar fails",
   313  		"3c: open /bar/y fails",
   314  
   315  		// Create the directory and file again. Link the file to another file in the same dir,
   316  		// and to a file in the root dir. Sync the root dir. After reset, the created dir and the
   317  		// file in the root dir are the only ones visible.
   318  		"4a: mkdirall /bar",
   319  		"4b: create /bar/y",
   320  		"4c: f = openDir /",
   321  		"4d: f.sync",
   322  		"4e: f.close",
   323  		"4f: link /bar/y /bar/z",
   324  		"4g: link /bar/y /z",
   325  		"4h: f = openDir /",
   326  		"4i: f.sync",
   327  		"4j: f.close",
   328  		"4k: resetToSynced",
   329  		"4l: openDir /bar",
   330  		"4m: open /bar/y fails",
   331  		"4n: open /bar/z fails",
   332  		"4o: open /z",
   333  
   334  		// Create the file in the directory again and this time sync /bar directory. The file is
   335  		// preserved after reset.
   336  		"5a: create /bar/y",
   337  		"5b: f = openDir /bar",
   338  		"5c: f.sync",
   339  		"5d: f.close",
   340  		"5e: resetToSynced",
   341  		"5f: openDir /bar",
   342  		"5g: open /bar/y",
   343  
   344  		// Unsynced data in the file is lost on reset.
   345  		"5a: f = create /bar/y",
   346  		"5b: f.write a",
   347  		"5c: f.sync",
   348  		"5d: f.write b",
   349  		"5e: f.close",
   350  		"5f: f = openDir /bar",
   351  		"5g: f.sync",
   352  		"5h: f.close",
   353  		"5i: resetToSynced",
   354  		"5j: f = open /bar/y",
   355  		"5k: f.read 1 = a",
   356  		"5l: f.read 1 fails",
   357  		"5m: f.close",
   358  
   359  		// reuseForWrite works correctly in strict mode in that unsynced data does not overwrite
   360  		// previous contents when a reset happens.
   361  		"6a: f = create /z",
   362  		"6b: f.write abcdefgh",
   363  		"6c: f.sync",
   364  		"6d: f.close",
   365  		"6e: f = reuseForWrite /z /y",
   366  		"6f: f.write x",
   367  		"6g: f.sync",
   368  		"6h: f.write y", // will be lost
   369  		"6i: f.close",
   370  		"6j: f = openDir /",
   371  		"6k: f.sync",
   372  		"6l: f.close",
   373  		"6m: resetToSynced",
   374  		"6n: f = open /y",
   375  		"6o: f.read 8 = xbcdefgh",
   376  		"6p: f.close",
   377  
   378  		// Ignore syncs.
   379  		"7a: f = create /z",
   380  		"7b: f.write a",
   381  		"7c: f.sync",
   382  		"7d: ignoreSyncs",
   383  		"7e: f.write b",
   384  		"7f: f.sync",
   385  		"7g: f.close",
   386  		"7h: stopIgnoringSyncs",
   387  		"7e: f = openDir /",
   388  		"7f: f.sync",
   389  		"7g: f.close",
   390  		"7h: resetToSynced",
   391  		"7i: f = open /z",
   392  		"7j: f.read 1 = a",
   393  		"7k: f.read 1 fails",
   394  		"7l: f.close",
   395  	}
   396  	runTestCases(t, testCases, fs)
   397  }
   398  
   399  func TestMemFSLock(t *testing.T) {
   400  	filesystems := map[string]FS{}
   401  	fileLocks := map[string]io.Closer{}
   402  
   403  	datadriven.RunTest(t, "testdata/memfs_lock", func(t *testing.T, td *datadriven.TestData) string {
   404  		switch td.Cmd {
   405  		case "mkfs":
   406  			for _, arg := range td.CmdArgs {
   407  				filesystems[arg.String()] = NewMem()
   408  			}
   409  			return "OK"
   410  
   411  		// lock fs=<filesystem-name> handle=<handle> path=<path>
   412  		case "lock":
   413  			var filesystemName string
   414  			var path string
   415  			var handle string
   416  			td.ScanArgs(t, "fs", &filesystemName)
   417  			td.ScanArgs(t, "path", &path)
   418  			td.ScanArgs(t, "handle", &handle)
   419  			fs := filesystems[filesystemName]
   420  			if fs == nil {
   421  				return fmt.Sprintf("filesystem %q doesn't exist", filesystemName)
   422  			}
   423  			l, err := fs.Lock(path)
   424  			if err != nil {
   425  				return err.Error()
   426  			}
   427  			fileLocks[handle] = l
   428  			return "OK"
   429  
   430  		// mkdirall fs=<filesystem-name> path=<path>
   431  		case "mkdirall":
   432  			var filesystemName string
   433  			var path string
   434  			td.ScanArgs(t, "fs", &filesystemName)
   435  			td.ScanArgs(t, "path", &path)
   436  			fs := filesystems[filesystemName]
   437  			if fs == nil {
   438  				return fmt.Sprintf("filesystem %q doesn't exist", filesystemName)
   439  			}
   440  			err := fs.MkdirAll(path, 0755)
   441  			if err != nil {
   442  				return err.Error()
   443  			}
   444  			return "OK"
   445  
   446  		// close handle=<handle>
   447  		case "close":
   448  			var handle string
   449  			td.ScanArgs(t, "handle", &handle)
   450  			err := fileLocks[handle].Close()
   451  			delete(fileLocks, handle)
   452  			if err != nil {
   453  				return err.Error()
   454  			}
   455  			return "OK"
   456  		default:
   457  			return fmt.Sprintf("unrecognized command %q", td.Cmd)
   458  		}
   459  	})
   460  }