github.com/avfs/avfs@v0.33.1-0.20240303173310-c6ba67c33eb7/test/test_suite.go (about)

     1  //
     2  //  Copyright 2020 The AVFS authors
     3  //
     4  //  Licensed under the Apache License, Version 2.0 (the "License");
     5  //  you may not use this file except in compliance with the License.
     6  //  You may obtain a copy of the License at
     7  //
     8  //  	http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  //  Unless required by applicable law or agreed to in writing, software
    11  //  distributed under the License is distributed on an "AS IS" BASIS,
    12  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  //  See the License for the specific language governing permissions and
    14  //  limitations under the License.
    15  //
    16  
    17  package test
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io/fs"
    23  	"os"
    24  	"path/filepath"
    25  	"reflect"
    26  	"runtime"
    27  	"strings"
    28  	"testing"
    29  
    30  	"github.com/avfs/avfs"
    31  	"github.com/avfs/avfs/vfs/osfs"
    32  )
    33  
    34  // NewSuiteFS creates a new test suite for a file system.
    35  func NewSuiteFS(tb testing.TB, vfsSetup, vfsTest avfs.VFSBase) *Suite {
    36  	if vfsSetup == nil {
    37  		tb.Skip("NewSuiteFS : vfsSetup must not be nil, skipping tests")
    38  	}
    39  
    40  	ts := newSuite(tb, vfsSetup, vfsTest, nil)
    41  
    42  	vfs := ts.VFSTest()
    43  	tb.Logf("VFS: Type=%s OSType=%s UMask=%03o Idm=%s Features=%s",
    44  		vfs.Type(), vfs.OSType(), vfs.UMask(), vfs.Idm().Type(), vfs.Features())
    45  
    46  	return ts
    47  }
    48  
    49  // NewSuiteIdm creates a new test suite for an identity manager.
    50  func NewSuiteIdm(tb testing.TB, idm avfs.IdentityMgr) *Suite {
    51  	if idm == nil {
    52  		tb.Skip("NewSuiteIdm : vfsSetup must not be nil, skipping tests")
    53  	}
    54  
    55  	ts := newSuite(tb, nil, nil, idm)
    56  
    57  	tb.Logf("Idm: Type=%s OSType=%s Features=%s", idm.Type(), idm.OSType(), idm.Features())
    58  
    59  	return ts
    60  }
    61  
    62  // newSuite creates a new test suite.
    63  func newSuite(tb testing.TB, vfsSetup, vfsTest avfs.VFSBase, idm avfs.IdentityMgr) *Suite {
    64  	if vfsSetup == nil {
    65  		vfsSetup = osfs.New()
    66  	}
    67  
    68  	if vfsTest == nil {
    69  		vfsTest = vfsSetup
    70  	}
    71  
    72  	if idm == nil {
    73  		idm = vfsSetup.Idm()
    74  	}
    75  
    76  	vfs := vfsTest
    77  
    78  	if vfs.OSType() != avfs.CurrentOSType() {
    79  		tb.Skipf("NewSuite : Current OSType = %s is different from %s OSType = %s, skipping tests",
    80  			avfs.CurrentOSType(), vfs.Type(), vfs.OSType())
    81  	}
    82  
    83  	initUser := vfs.User()
    84  	canTestPerm := vfs.OSType() != avfs.OsWindows && initUser.IsAdmin() &&
    85  		vfs.HasFeature(avfs.FeatIdentityMgr) && !vfs.HasFeature(avfs.FeatReadOnlyIdm)
    86  
    87  	ts := &Suite{
    88  		vfsSetup:    vfsSetup,
    89  		vfsTest:     vfsTest,
    90  		idm:         idm,
    91  		initUser:    initUser,
    92  		testDataDir: testDataDir(),
    93  		maxRace:     100,
    94  		canTestPerm: canTestPerm,
    95  	}
    96  
    97  	ts.groups = ts.CreateGroups(tb, "")
    98  	ts.users = ts.CreateUsers(tb, "")
    99  
   100  	return ts
   101  }
   102  
   103  // AssertInvalid asserts that the error is fs.ErrInvalid.
   104  func AssertInvalid(tb testing.TB, err error, msgAndArgs ...any) bool {
   105  	if err != fs.ErrInvalid {
   106  		tb.Helper()
   107  		tb.Errorf("error : want error to be %v, got %v\n%s", fs.ErrInvalid, err, formatArgs(msgAndArgs))
   108  
   109  		return false
   110  	}
   111  
   112  	return true
   113  }
   114  
   115  // AssertNoError asserts that there is no error (err == nil).
   116  func AssertNoError(tb testing.TB, err error, msgAndArgs ...any) bool {
   117  	if err != nil {
   118  		tb.Helper()
   119  		tb.Errorf("error : want error to be nil, got %v\n%s", err, formatArgs(msgAndArgs))
   120  
   121  		return false
   122  	}
   123  
   124  	return true
   125  }
   126  
   127  // AssertPanic checks that function f panics.
   128  func AssertPanic(tb testing.TB, funcName string, f func()) {
   129  	tb.Helper()
   130  
   131  	defer func() {
   132  		if r := recover(); r == nil {
   133  			tb.Errorf("%s : want function to panic, not panicking", funcName)
   134  		}
   135  	}()
   136  
   137  	f()
   138  }
   139  
   140  // changeDir changes the current directory for the tests.
   141  func (ts *Suite) changeDir(tb testing.TB, dir string) {
   142  	vfs := ts.vfsTest
   143  
   144  	err := vfs.Chdir(dir)
   145  	RequireNoError(tb, err, "Chdir %s", dir)
   146  }
   147  
   148  // closedFile returns a closed avfs.File.
   149  func (ts *Suite) closedFile(tb testing.TB, testDir string) (f avfs.File, fileName string) {
   150  	fileName = ts.emptyFile(tb, testDir)
   151  
   152  	vfs := ts.vfsTest
   153  
   154  	f, err := vfs.OpenFile(fileName, os.O_RDONLY, 0)
   155  	RequireNoError(tb, err, "OpenFile %s", fileName)
   156  
   157  	err = f.Close()
   158  	RequireNoError(tb, err, "Close %s", fileName)
   159  
   160  	return f, fileName
   161  }
   162  
   163  // createDir creates a directory for the tests.
   164  func (ts *Suite) createDir(tb testing.TB, dirName string, mode fs.FileMode) {
   165  	vfs := ts.vfsSetup
   166  
   167  	err := vfs.MkdirAll(dirName, mode)
   168  	RequireNoError(tb, err, "MkdirAll %s", dirName)
   169  
   170  	err = vfs.Chmod(dirName, mode)
   171  	RequireNoError(tb, err, "Chmod %s", dirName)
   172  }
   173  
   174  // createFile creates an empty file for the tests.
   175  func (ts *Suite) createFile(tb testing.TB, fileName string, mode fs.FileMode) {
   176  	vfs := ts.vfsSetup
   177  
   178  	err := vfs.WriteFile(fileName, nil, mode)
   179  	RequireNoError(tb, err, "WriteFile %s", fileName)
   180  }
   181  
   182  // createRootDir creates tests root directory.
   183  func (ts *Suite) createRootDir(tb testing.TB) {
   184  	vfs := ts.vfsSetup
   185  	rootDir := ""
   186  
   187  	if _, ok := tb.(*testing.B); ok && vfs.HasFeature(avfs.FeatRealFS) {
   188  		// run Benches on real disks, /tmp is usually an in memory file system.
   189  		rootDir = vfs.Join(avfs.HomeDirUser(vfs, "", vfs.User()), "tmp")
   190  		ts.createDir(tb, rootDir, avfs.DefaultDirPerm)
   191  	}
   192  
   193  	rootDir, err := vfs.MkdirTemp(rootDir, "avfs")
   194  	RequireNoError(tb, err, "MkdirTemp %s", rootDir)
   195  
   196  	// Make rootDir accessible by anyone.
   197  	err = vfs.Chmod(rootDir, avfs.DefaultDirPerm)
   198  	RequireNoError(tb, err, "Chmod %s", rootDir)
   199  
   200  	// Ensure rootDir does not include symbolic links.
   201  	if vfs.HasFeature(avfs.FeatSymlink) {
   202  		rootDir, err = vfs.EvalSymlinks(rootDir)
   203  		RequireNoError(tb, err, "EvalSymlinks %s", rootDir)
   204  	}
   205  
   206  	ts.rootDir = rootDir
   207  }
   208  
   209  // emptyFile returns an empty file name.
   210  func (ts *Suite) emptyFile(tb testing.TB, testDir string) string {
   211  	const emptyFile = "emptyFile"
   212  
   213  	vfs := ts.vfsSetup
   214  	fileName := vfs.Join(testDir, emptyFile)
   215  
   216  	_, err := vfs.Stat(fileName)
   217  	if errors.Is(err, fs.ErrNotExist) {
   218  		f, err := vfs.Create(fileName)
   219  		RequireNoError(tb, err, "Create %s", fileName)
   220  
   221  		err = f.Close()
   222  		RequireNoError(tb, err, "Close %s", fileName)
   223  	}
   224  
   225  	return fileName
   226  }
   227  
   228  // existingDir returns an existing directory.
   229  func (ts *Suite) existingDir(tb testing.TB, testDir string) string {
   230  	vfs := ts.vfsSetup
   231  
   232  	dirName, err := vfs.MkdirTemp(testDir, "existingDir")
   233  	RequireNoError(tb, err, "MkdirTemp %s", testDir)
   234  
   235  	_, err = vfs.Stat(dirName)
   236  	if errors.Is(err, fs.ErrNotExist) {
   237  		tb.Fatalf("Stat %s : want error to be nil, got %v", dirName, err)
   238  	}
   239  
   240  	return dirName
   241  }
   242  
   243  // existingFile returns an existing file name with the given content.
   244  func (ts *Suite) existingFile(tb testing.TB, testDir string, content []byte) string {
   245  	vfs := ts.vfsSetup
   246  
   247  	f, err := vfs.CreateTemp(testDir, defaultFile)
   248  	RequireNoError(tb, err, "CreateTemp %s", testDir)
   249  
   250  	fileName := f.Name()
   251  
   252  	_, err = f.Write(content)
   253  	RequireNoError(tb, err, "Write %s", fileName)
   254  
   255  	err = f.Close()
   256  	RequireNoError(tb, err, "Close %s", fileName)
   257  
   258  	return fileName
   259  }
   260  
   261  // funcName returns the name of a function or a method.
   262  // It returns an empty string if not available.
   263  func funcName(i any) string {
   264  	v := reflect.ValueOf(i)
   265  	if v.Kind() != reflect.Func {
   266  		return ""
   267  	}
   268  
   269  	pc := v.Pointer()
   270  	if pc == 0 {
   271  		return ""
   272  	}
   273  
   274  	fpc := runtime.FuncForPC(pc)
   275  	if fpc == nil {
   276  		return ""
   277  	}
   278  
   279  	fn := fpc.Name()
   280  
   281  	end := strings.LastIndex(fn, "-")
   282  	if end == -1 {
   283  		end = len(fn)
   284  	}
   285  
   286  	start := strings.LastIndex(fn[:end], ".")
   287  	if start == -1 {
   288  		return fn[:end]
   289  	}
   290  
   291  	return fn[start+1 : end]
   292  }
   293  
   294  // formatArgs formats a list of optional arguments to a string, the first argument being a format string.
   295  func formatArgs(msgAndArgs []any) string {
   296  	na := len(msgAndArgs)
   297  	if na == 0 {
   298  		return ""
   299  	}
   300  
   301  	format, ok := msgAndArgs[0].(string)
   302  	if !ok {
   303  		return ""
   304  	}
   305  
   306  	if na == 1 {
   307  		return format
   308  	}
   309  
   310  	return fmt.Sprintf(format, msgAndArgs[1:]...)
   311  }
   312  
   313  // nonExistingFile returns the name of a non-existing file.
   314  func (ts *Suite) nonExistingFile(tb testing.TB, testDir string) string {
   315  	vfs := ts.vfsSetup
   316  	fileName := vfs.Join(testDir, defaultNonExisting)
   317  
   318  	_, err := vfs.Stat(fileName)
   319  	if !errors.Is(err, fs.ErrNotExist) {
   320  		tb.Fatalf("Stat : want error to be %v, got %v", avfs.ErrNoSuchFileOrDir, err)
   321  	}
   322  
   323  	return fileName
   324  }
   325  
   326  // openedEmptyFile returns an opened empty avfs.File and its file name.
   327  func (ts *Suite) openedEmptyFile(tb testing.TB, testDir string) (fd avfs.File, fileName string) {
   328  	fileName = ts.emptyFile(tb, testDir)
   329  	vfs := ts.vfsTest
   330  
   331  	if vfs.HasFeature(avfs.FeatReadOnly) {
   332  		f, err := vfs.OpenFile(fileName, os.O_RDONLY, 0)
   333  		RequireNoError(tb, err, "OpenFile %s", fileName)
   334  
   335  		return f, fileName
   336  	}
   337  
   338  	f, err := vfs.OpenFile(fileName, os.O_CREATE|os.O_RDWR, avfs.DefaultFilePerm)
   339  	RequireNoError(tb, err, "OpenFile %s", fileName)
   340  
   341  	return f, fileName
   342  }
   343  
   344  // openedNonExistingFile returns a non-existing avfs.File and its file name.
   345  func (ts *Suite) openedNonExistingFile(tb testing.TB, testDir string) (f avfs.File) {
   346  	fileName := ts.nonExistingFile(tb, testDir)
   347  	vfs := ts.vfsTest
   348  
   349  	f, err := vfs.OpenFile(fileName, os.O_RDONLY, 0)
   350  	if !errors.Is(err, fs.ErrNotExist) {
   351  		tb.Fatalf("Open %s : want non existing file, got %v", fileName, err)
   352  	}
   353  
   354  	return f
   355  }
   356  
   357  // randomDir returns one directory with random empty subdirectories, files and symbolic links.
   358  func (ts *Suite) randomDir(tb testing.TB, testDir string) *avfs.RndTree {
   359  	vfs := ts.vfsSetup
   360  
   361  	opts := &avfs.RndTreeOpts{NbDirs: 3, NbFiles: 11, NbSymlinks: 4, MaxFileSize: 0, MaxDepth: 0}
   362  	rt := avfs.NewRndTree(vfs, opts)
   363  
   364  	err := rt.CreateTree(testDir)
   365  	RequireNoError(tb, err, "rt.Create %s", testDir)
   366  
   367  	return rt
   368  }
   369  
   370  // removeDir removes all files under testDir.
   371  func (ts *Suite) removeDir(tb testing.TB, testDir string) {
   372  	vfs := ts.vfsSetup
   373  
   374  	err := vfs.Chdir(ts.rootDir)
   375  	RequireNoError(tb, err, "Chdir %s", ts.rootDir)
   376  
   377  	// RemoveAll() should be executed as the user who started the tests, generally root,
   378  	// to clean up files with different permissions.
   379  	ts.setInitUser(tb)
   380  
   381  	err = vfs.RemoveAll(testDir)
   382  	if err != nil && avfs.CurrentOSType() != avfs.OsWindows {
   383  		tb.Fatalf("RemoveAll %s : want error to be nil, got %v", testDir, err)
   384  	}
   385  }
   386  
   387  // RequireNoError require that a function returned no error.
   388  func RequireNoError(tb testing.TB, err error, msgAndArgs ...any) {
   389  	tb.Helper()
   390  
   391  	if !AssertNoError(tb, err, msgAndArgs...) {
   392  		tb.FailNow()
   393  	}
   394  }
   395  
   396  // RunBenchmarks runs all benchmark functions specified as user userName.
   397  func (ts *Suite) RunBenchmarks(b *testing.B, userName string, BenchFuncs ...func(b *testing.B, testDir string)) {
   398  	vfs := ts.vfsSetup
   399  
   400  	ts.createRootDir(b)
   401  
   402  	for _, bf := range BenchFuncs {
   403  		ts.setUser(b, userName)
   404  
   405  		fn := funcName(bf)
   406  		testDir := vfs.Join(ts.rootDir, fn)
   407  
   408  		ts.createDir(b, testDir, avfs.DefaultDirPerm)
   409  		ts.changeDir(b, testDir)
   410  
   411  		bf(b, testDir)
   412  
   413  		ts.removeDir(b, testDir)
   414  	}
   415  
   416  	ts.removeDir(b, ts.rootDir)
   417  }
   418  
   419  // RunTests runs all test functions specified as user userName.
   420  func (ts *Suite) RunTests(t *testing.T, userName string, testFuncs ...func(t *testing.T, testDir string)) {
   421  	vfs := ts.vfsSetup
   422  
   423  	ts.createRootDir(t)
   424  
   425  	defer ts.setInitUser(t)
   426  
   427  	for _, tf := range testFuncs {
   428  		ts.setUser(t, userName)
   429  
   430  		fn := funcName(tf)
   431  		testDir := vfs.Join(ts.rootDir, fn)
   432  
   433  		ts.createDir(t, testDir, avfs.DefaultDirPerm)
   434  		ts.changeDir(t, testDir)
   435  
   436  		t.Run(fn, func(t *testing.T) {
   437  			tf(t, testDir)
   438  		})
   439  
   440  		ts.removeDir(t, testDir)
   441  	}
   442  
   443  	ts.removeDir(t, ts.rootDir)
   444  }
   445  
   446  // setUser sets the test user to userName.
   447  func (ts *Suite) setUser(tb testing.TB, userName string) {
   448  	vfs := ts.vfsTest
   449  
   450  	u := vfs.User()
   451  	if !ts.canTestPerm || u.Name() == userName {
   452  		return
   453  	}
   454  
   455  	err := vfs.SetUserByName(userName)
   456  	RequireNoError(tb, err, "SetUser %s", userName)
   457  }
   458  
   459  // setInitUser reset the user to the initial user.
   460  func (ts *Suite) setInitUser(tb testing.TB) {
   461  	ts.setUser(tb, ts.initUser.Name())
   462  }
   463  
   464  // testDataDir return the testdata directory of the test package.
   465  func testDataDir() string {
   466  	_, file, _, _ := runtime.Caller(0)
   467  	dir := filepath.Dir(file)
   468  
   469  	return filepath.Join(dir, "testdata")
   470  }
   471  
   472  // TestVFSAll runs all file system tests.
   473  func (ts *Suite) TestVFSAll(t *testing.T) {
   474  	ts.TestVFS(t)
   475  	ts.TestFile(t)
   476  	ts.TestUtils(t)
   477  }
   478  
   479  // VFSSetup returns the file system used to set up the tests.
   480  func (ts *Suite) VFSSetup() avfs.VFSBase {
   481  	return ts.vfsSetup
   482  }
   483  
   484  // VFSTest returns the file system used to run the tests.
   485  func (ts *Suite) VFSTest() avfs.VFSBase {
   486  	return ts.vfsTest
   487  }