github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/vfs/vfs_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  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"runtime"
    14  	"sort"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/cockroachdb/errors"
    19  	"github.com/cockroachdb/errors/oserror"
    20  	"github.com/stretchr/testify/require"
    21  	"github.com/zuoyebang/bitalostable/internal/datadriven"
    22  )
    23  
    24  func normalizeError(err error) error {
    25  	// It is OS-specific which errors match IsExist, IsNotExist, and
    26  	// IsPermission, with OS-specific error messages. We normalize to the
    27  	// oserror.Err* errors which have standard error messages across
    28  	// platforms.
    29  	switch {
    30  	case oserror.IsExist(err):
    31  		return oserror.ErrExist
    32  	case oserror.IsNotExist(err):
    33  		return oserror.ErrNotExist
    34  	case oserror.IsPermission(err):
    35  		return oserror.ErrPermission
    36  	}
    37  	return err
    38  }
    39  
    40  type loggingFS struct {
    41  	FS
    42  	base    string
    43  	w       io.Writer
    44  	linkErr error
    45  }
    46  
    47  func (fs loggingFS) stripBase(path string) string {
    48  	if strings.HasPrefix(path, fs.base+"/") {
    49  		return path[len(fs.base)+1:]
    50  	}
    51  	return path
    52  }
    53  
    54  func (fs loggingFS) Create(name string) (File, error) {
    55  	f, err := fs.FS.Create(name)
    56  	fmt.Fprintf(fs.w, "create: %s [%v]\n", fs.stripBase(name), normalizeError(err))
    57  	return loggingFile{f, fs.PathBase(name), fs.w}, err
    58  }
    59  
    60  func (fs loggingFS) Link(oldname, newname string) error {
    61  	err := fs.linkErr
    62  	if err == nil {
    63  		err = fs.FS.Link(oldname, newname)
    64  	}
    65  	fmt.Fprintf(fs.w, "link: %s -> %s [%v]\n",
    66  		fs.stripBase(oldname), fs.stripBase(newname), normalizeError(err))
    67  	return err
    68  }
    69  
    70  func (fs loggingFS) ReuseForWrite(oldname, newname string) (File, error) {
    71  	f, err := fs.FS.ReuseForWrite(oldname, newname)
    72  	if err == nil {
    73  		f = loggingFile{f, fs.PathBase(newname), fs.w}
    74  	}
    75  	fmt.Fprintf(fs.w, "reuseForWrite: %s -> %s [%v]\n",
    76  		fs.stripBase(oldname), fs.stripBase(newname), normalizeError(err))
    77  	return f, err
    78  }
    79  
    80  func (fs loggingFS) MkdirAll(dir string, perm os.FileMode) error {
    81  	err := fs.FS.MkdirAll(dir, perm)
    82  	fmt.Fprintf(fs.w, "mkdir: %s [%v]\n", fs.stripBase(dir), normalizeError(err))
    83  	return err
    84  }
    85  
    86  func (fs loggingFS) Open(name string, opts ...OpenOption) (File, error) {
    87  	f, err := fs.FS.Open(name, opts...)
    88  	fmt.Fprintf(fs.w, "open: %s [%v]\n", fs.stripBase(name), normalizeError(err))
    89  	return loggingFile{f, fs.stripBase(name), fs.w}, err
    90  }
    91  
    92  func (fs loggingFS) Remove(name string) error {
    93  	err := fs.FS.Remove(name)
    94  	fmt.Fprintf(fs.w, "remove: %s [%v]\n", fs.stripBase(name), normalizeError(err))
    95  	return err
    96  }
    97  
    98  func (fs loggingFS) RemoveAll(name string) error {
    99  	err := fs.FS.RemoveAll(name)
   100  	fmt.Fprintf(fs.w, "remove-all: %s [%v]\n", fs.stripBase(name), normalizeError(err))
   101  	return err
   102  }
   103  
   104  type loggingFile struct {
   105  	File
   106  	name string
   107  	w    io.Writer
   108  }
   109  
   110  func (f loggingFile) Close() error {
   111  	err := f.File.Close()
   112  	fmt.Fprintf(f.w, "close: %s [%v]\n", f.name, err)
   113  	return err
   114  }
   115  
   116  func (f loggingFile) Sync() error {
   117  	err := f.File.Sync()
   118  	fmt.Fprintf(f.w, "sync: %s [%v]\n", f.name, err)
   119  	return err
   120  }
   121  
   122  func runTestVFS(t *testing.T, baseFS FS, dir string) {
   123  	var buf bytes.Buffer
   124  	fs := loggingFS{FS: baseFS, base: dir, w: &buf}
   125  
   126  	datadriven.RunTest(t, "testdata/vfs", func(td *datadriven.TestData) string {
   127  		switch td.Cmd {
   128  		case "define":
   129  			buf.Reset()
   130  
   131  			for _, arg := range td.CmdArgs {
   132  				switch arg.Key {
   133  				case "linkErr":
   134  					if len(arg.Vals) != 1 {
   135  						return fmt.Sprintf("%s: %s expected 1 value", td.Cmd, arg.Key)
   136  					}
   137  					switch arg.Vals[0] {
   138  					case "ErrExist":
   139  						fs.linkErr = oserror.ErrExist
   140  					case "ErrNotExist":
   141  						fs.linkErr = oserror.ErrNotExist
   142  					case "ErrPermission":
   143  						fs.linkErr = oserror.ErrPermission
   144  					default:
   145  						fs.linkErr = errors.New(arg.Vals[0])
   146  					}
   147  				default:
   148  					return fmt.Sprintf("%s: unknown arg: %s", td.Cmd, arg.Key)
   149  				}
   150  			}
   151  
   152  			for _, line := range strings.Split(td.Input, "\n") {
   153  				parts := strings.Fields(line)
   154  				if len(parts) == 0 {
   155  					return "<op> [<args>]"
   156  				}
   157  
   158  				switch parts[0] {
   159  				case "clone":
   160  					if len(parts) != 3 {
   161  						return "clone <src> <dest>"
   162  					}
   163  					_, _ = Clone(fs, fs, fs.PathJoin(dir, parts[1]), fs.PathJoin(dir, parts[2]))
   164  
   165  				case "create":
   166  					if len(parts) != 2 {
   167  						return "create <name>"
   168  					}
   169  					f, _ := fs.Create(fs.PathJoin(dir, parts[1]))
   170  					f.Close()
   171  
   172  				case "link":
   173  					if len(parts) != 3 {
   174  						return "link <oldname> <newname>"
   175  					}
   176  					_ = fs.Link(fs.PathJoin(dir, parts[1]), fs.PathJoin(dir, parts[2]))
   177  
   178  				case "link-or-copy":
   179  					if len(parts) != 3 {
   180  						return "link-or-copy <oldname> <newname>"
   181  					}
   182  					_ = LinkOrCopy(fs, fs.PathJoin(dir, parts[1]), fs.PathJoin(dir, parts[2]))
   183  
   184  				case "reuseForWrite":
   185  					if len(parts) != 3 {
   186  						return "reuseForWrite <oldname> <newname>"
   187  					}
   188  					_, _ = fs.ReuseForWrite(fs.PathJoin(dir, parts[1]), fs.PathJoin(dir, parts[2]))
   189  
   190  				case "list":
   191  					if len(parts) != 2 {
   192  						return "list <dir>"
   193  					}
   194  					paths, _ := fs.List(fs.PathJoin(dir, parts[1]))
   195  					sort.Strings(paths)
   196  					for _, p := range paths {
   197  						fmt.Fprintln(&buf, p)
   198  					}
   199  
   200  				case "mkdir":
   201  					if len(parts) != 2 {
   202  						return "mkdir <dir>"
   203  					}
   204  					_ = fs.MkdirAll(fs.PathJoin(dir, parts[1]), 0755)
   205  
   206  				case "remove":
   207  					if len(parts) != 2 {
   208  						return "remove <name>"
   209  					}
   210  					_ = fs.Remove(fs.PathJoin(dir, parts[1]))
   211  
   212  				case "remove-all":
   213  					if len(parts) != 2 {
   214  						return "remove-all <name>"
   215  					}
   216  					_ = fs.RemoveAll(fs.PathJoin(dir, parts[1]))
   217  				}
   218  			}
   219  
   220  			return buf.String()
   221  
   222  		default:
   223  			return fmt.Sprintf("unknown command: %s", td.Cmd)
   224  		}
   225  	})
   226  }
   227  
   228  func TestVFS(t *testing.T) {
   229  	t.Run("mem", func(t *testing.T) {
   230  		runTestVFS(t, NewMem(), "")
   231  	})
   232  	if runtime.GOOS != "windows" {
   233  		t.Run("disk", func(t *testing.T) {
   234  			dir, err := ioutil.TempDir("", "test-vfs")
   235  			require.NoError(t, err)
   236  			defer func() {
   237  				_ = os.RemoveAll(dir)
   238  			}()
   239  			runTestVFS(t, Default, dir)
   240  		})
   241  	}
   242  }
   243  
   244  func TestVFSGetDiskUsage(t *testing.T) {
   245  	dir, err := ioutil.TempDir("", "test-free-space")
   246  	require.NoError(t, err)
   247  	defer func() {
   248  		_ = os.RemoveAll(dir)
   249  	}()
   250  	_, err = Default.GetDiskUsage(dir)
   251  	require.Nil(t, err)
   252  }
   253  
   254  func TestVFSCreateLinkSemantics(t *testing.T) {
   255  	dir, err := ioutil.TempDir("", "test-create-link")
   256  	require.NoError(t, err)
   257  	defer func() { _ = os.RemoveAll(dir) }()
   258  
   259  	for _, fs := range []FS{Default, NewMem()} {
   260  		t.Run(fmt.Sprintf("%T", fs), func(t *testing.T) {
   261  			writeFile := func(path, contents string) {
   262  				path = fs.PathJoin(dir, path)
   263  				f, err := fs.Create(path)
   264  				require.NoError(t, err)
   265  				_, err = f.Write([]byte(contents))
   266  				require.NoError(t, err)
   267  				require.NoError(t, f.Close())
   268  			}
   269  			readFile := func(path string) string {
   270  				path = fs.PathJoin(dir, path)
   271  				f, err := fs.Open(path)
   272  				require.NoError(t, err)
   273  				b, err := ioutil.ReadAll(f)
   274  				require.NoError(t, err)
   275  				require.NoError(t, f.Close())
   276  				return string(b)
   277  			}
   278  			require.NoError(t, fs.MkdirAll(dir, 0755))
   279  
   280  			// Write a file 'foo' and create a hardlink at 'bar'.
   281  			writeFile("foo", "foo")
   282  			require.NoError(t, fs.Link(fs.PathJoin(dir, "foo"), fs.PathJoin(dir, "bar")))
   283  
   284  			// Both files should contain equal contents, because they're backed by
   285  			// the same inode.
   286  			require.Equal(t, "foo", readFile("foo"))
   287  			require.Equal(t, "foo", readFile("bar"))
   288  
   289  			// Calling Create on 'bar' must NOT truncate 'foo'. It should create a
   290  			// new file at path 'bar' with a new inode.
   291  			writeFile("bar", "bar")
   292  
   293  			require.Equal(t, "foo", readFile("foo"))
   294  			require.Equal(t, "bar", readFile("bar"))
   295  		})
   296  	}
   297  }
   298  
   299  // TestVFSRootDirName ensures that opening the root directory on both the
   300  // Default and MemFS works and returns a File which has the name of the
   301  // path separator for the FS (always sep for MemFS).
   302  func TestVFSRootDirName(t *testing.T) {
   303  	for _, fs := range []FS{Default, NewMem()} {
   304  		rootDir, err := fs.Open("/")
   305  		require.NoError(t, err)
   306  		fi, err := rootDir.Stat()
   307  		require.NoError(t, err)
   308  
   309  		exp := sep
   310  		if fs == Default {
   311  			exp = string(os.PathSeparator)
   312  		}
   313  		require.Equal(t, exp, fi.Name())
   314  	}
   315  }