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