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 }