|
4 | 4 | "fmt"
|
5 | 5 | os "os"
|
6 | 6 | "reflect"
|
| 7 | + "slices" |
7 | 8 | "testing"
|
8 | 9 |
|
9 | 10 | "github.com/spf13/afero"
|
@@ -296,36 +297,212 @@ func TestNewWalk(t *testing.T) {
|
296 | 297 | }
|
297 | 298 | }
|
298 | 299 |
|
299 |
| -func TestWalkPreOrderDFS(t *testing.T) { |
300 |
| - root := NewPath(t.TempDir()) |
301 |
| - children := []string{ |
302 |
| - "1.txt", |
303 |
| - "2.txt", |
304 |
| - "3.txt", |
305 |
| - "subdir/4.txt", |
306 |
| - "subdir/5.txt", |
| 300 | +type FSObject struct { |
| 301 | + path *Path |
| 302 | + contents string |
| 303 | + dir bool |
| 304 | +} |
| 305 | + |
| 306 | +func TestWalkerOrder(t *testing.T) { |
| 307 | + type test struct { |
| 308 | + name string |
| 309 | + algorithm Algorithm |
| 310 | + walkOpts []WalkOptsFunc |
| 311 | + objects []FSObject |
| 312 | + expectedOrder []*Path |
| 313 | + } |
| 314 | + for _, tt := range []test{ |
| 315 | + { |
| 316 | + name: "Pre-Order DFS simple", |
| 317 | + algorithm: AlgorithmPreOrderDepthFirst, |
| 318 | + objects: []FSObject{ |
| 319 | + {path: NewPath("1.txt")}, |
| 320 | + {path: NewPath("2.txt")}, |
| 321 | + {path: NewPath("3.txt")}, |
| 322 | + {path: NewPath("subdir"), dir: true}, |
| 323 | + {path: NewPath("subdir").Join("4.txt")}, |
| 324 | + }, |
| 325 | + walkOpts: []WalkOptsFunc{WalkVisitDirs(true)}, |
| 326 | + expectedOrder: []*Path{ |
| 327 | + NewPath("1.txt"), |
| 328 | + NewPath("2.txt"), |
| 329 | + NewPath("3.txt"), |
| 330 | + NewPath("subdir"), |
| 331 | + NewPath("subdir").Join("4.txt"), |
| 332 | + }, |
| 333 | + }, |
| 334 | + { |
| 335 | + name: "Post-Order DFS simple", |
| 336 | + algorithm: AlgorithmDepthFirst, |
| 337 | + objects: []FSObject{ |
| 338 | + {path: NewPath("1.txt")}, |
| 339 | + {path: NewPath("2.txt")}, |
| 340 | + {path: NewPath("3.txt")}, |
| 341 | + {path: NewPath("subdir"), dir: true}, |
| 342 | + {path: NewPath("subdir").Join("4.txt")}, |
| 343 | + }, |
| 344 | + walkOpts: []WalkOptsFunc{WalkVisitDirs(true)}, |
| 345 | + expectedOrder: []*Path{ |
| 346 | + NewPath("subdir").Join("4.txt"), |
| 347 | + NewPath("1.txt"), |
| 348 | + NewPath("2.txt"), |
| 349 | + NewPath("3.txt"), |
| 350 | + NewPath("subdir"), |
| 351 | + }, |
| 352 | + }, |
| 353 | + { |
| 354 | + name: "Basic simple", |
| 355 | + algorithm: AlgorithmBasic, |
| 356 | + objects: []FSObject{ |
| 357 | + {path: NewPath("1")}, |
| 358 | + {path: NewPath("2"), dir: true}, |
| 359 | + {path: NewPath("2").Join("3")}, |
| 360 | + {path: NewPath("4")}, |
| 361 | + }, |
| 362 | + walkOpts: []WalkOptsFunc{WalkVisitDirs(true)}, |
| 363 | + expectedOrder: []*Path{ |
| 364 | + NewPath("1"), |
| 365 | + NewPath("2").Join("3"), |
| 366 | + NewPath("2"), |
| 367 | + NewPath("4"), |
| 368 | + }, |
| 369 | + }, |
| 370 | + } { |
| 371 | + t.Run(tt.name, func(t *testing.T) { |
| 372 | + root := NewPath(t.TempDir()) |
| 373 | + for _, child := range tt.objects { |
| 374 | + c := root.JoinPath(child.path) |
| 375 | + if child.dir { |
| 376 | + require.NoError(t, c.Mkdir()) |
| 377 | + continue |
| 378 | + } |
| 379 | + require.NoError(t, c.WriteFile([]byte(child.contents))) |
| 380 | + } |
| 381 | + opts := []WalkOptsFunc{WalkAlgorithm(tt.algorithm), WalkSortChildren(true)} |
| 382 | + opts = append(opts, tt.walkOpts...) |
| 383 | + walker, err := NewWalk(root, opts...) |
| 384 | + require.NoError(t, err) |
| 385 | + |
| 386 | + actualOrder := []*Path{} |
| 387 | + require.NoError( |
| 388 | + t, |
| 389 | + walker.Walk(func(path *Path, info os.FileInfo, err error) error { |
| 390 | + require.NoError(t, err) |
| 391 | + relative, err := path.RelativeTo(root) |
| 392 | + require.NoError(t, err) |
| 393 | + actualOrder = append(actualOrder, relative) |
| 394 | + return nil |
| 395 | + }), |
| 396 | + ) |
| 397 | + require.Equal(t, len(tt.expectedOrder), len(actualOrder)) |
| 398 | + for i, path := range tt.expectedOrder { |
| 399 | + assert.True(t, path.Equals(actualOrder[i]), "incorrect ordering at %d: %s != %s", i, path, actualOrder[i]) |
| 400 | + } |
| 401 | + }) |
| 402 | + } |
| 403 | +} |
| 404 | + |
| 405 | +// TestErrWalkSkipSubtree tests the behavior of each algorithm when we tell it to skip a subtree. |
| 406 | +func TestErrWalkSkipSubtree(t *testing.T) { |
| 407 | + type test struct { |
| 408 | + name string |
| 409 | + algorithm Algorithm |
| 410 | + tree []*Path |
| 411 | + skipAt *Path |
| 412 | + expected []*Path |
307 | 413 | }
|
308 |
| - for _, child := range children { |
309 |
| - c := root.Join(child) |
310 |
| - require.NoError(t, c.Parent().MkdirAll()) |
311 |
| - require.NoError(t, c.WriteFile([]byte("hello"))) |
312 | 414 |
|
| 415 | + for _, tt := range []test{ |
| 416 | + { |
| 417 | + // In AlgorithmBasic, the ordering in which children/nodes are visited |
| 418 | + // is filesystem and OS dependent. Some filesystems return paths in a lexically-ordered |
| 419 | + // manner, some return them in the order in which they were created. For this test, |
| 420 | + // we tell the walker to order the children before iterating over them. That way, |
| 421 | + // the test will visit "subdir1/subdir2/foo.txt" before "subdir1/subdir2/subdir3/foo.txt", |
| 422 | + // in which case we would tell the walker to skip the subdir3 subtree before it recursed. |
| 423 | + "Basic", |
| 424 | + AlgorithmBasic, |
| 425 | + nil, |
| 426 | + NewPath("subdir1").Join("subdir2", "foo.txt"), |
| 427 | + []*Path{ |
| 428 | + NewPath("foo1.txt"), |
| 429 | + NewPath("subdir1").Join("foo.txt"), |
| 430 | + NewPath("subdir1").Join("subdir2", "foo.txt"), |
| 431 | + }, |
| 432 | + }, |
| 433 | + { |
| 434 | + "PreOrderDFS", |
| 435 | + AlgorithmPreOrderDepthFirst, |
| 436 | + nil, |
| 437 | + NewPath("subdir1").Join("subdir2", "foo.txt"), |
| 438 | + []*Path{ |
| 439 | + NewPath("foo1.txt"), |
| 440 | + NewPath("subdir1").Join("foo.txt"), |
| 441 | + NewPath("subdir1").Join("subdir2", "foo.txt"), |
| 442 | + }, |
| 443 | + }, |
| 444 | + // Note about the PostOrderDFS case. ErrWalkSkipSubtree effectively |
| 445 | + // has no meaning to this algorithm because in this case, the algorithm |
| 446 | + // visits all children before visiting each node. Thus, our WalkFunc has |
| 447 | + // no opportunity to tell it to skip a particular subtree. This test |
| 448 | + // serves to ensure this behavior doesn't change. |
| 449 | + { |
| 450 | + "PostOrderDFS", |
| 451 | + AlgorithmPostOrderDepthFirst, |
| 452 | + nil, |
| 453 | + NewPath("subdir1").Join("subdir2", "foo.txt"), |
| 454 | + []*Path{ |
| 455 | + NewPath("foo1.txt"), |
| 456 | + NewPath("subdir1").Join("foo.txt"), |
| 457 | + NewPath("subdir1").Join("subdir2", "foo.txt"), |
| 458 | + NewPath("subdir1").Join("subdir2", "subdir3", "foo.txt"), |
| 459 | + }, |
| 460 | + }, |
| 461 | + } { |
| 462 | + t.Run(tt.name, func(t *testing.T) { |
| 463 | + root := NewPath(t.TempDir()) |
| 464 | + walker, err := NewWalk(root, WalkAlgorithm(tt.algorithm), WalkVisitDirs(false), WalkSortChildren(true)) |
| 465 | + require.NoError(t, err) |
| 466 | + |
| 467 | + var tree []*Path |
| 468 | + if tt.tree == nil { |
| 469 | + tree = []*Path{ |
| 470 | + NewPath("foo1.txt"), |
| 471 | + NewPath("subdir1").Join("foo.txt"), |
| 472 | + NewPath("subdir1").Join("subdir2", "foo.txt"), |
| 473 | + NewPath("subdir1").Join("subdir2", "subdir3", "foo.txt"), |
| 474 | + } |
| 475 | + } |
| 476 | + for _, path := range tree { |
| 477 | + p := root.JoinPath(path) |
| 478 | + require.NoError(t, p.Parent().MkdirAll()) |
| 479 | + require.NoError(t, p.WriteFile([]byte(""))) |
| 480 | + } |
| 481 | + |
| 482 | + visited := map[string]struct{}{} |
| 483 | + require.NoError(t, walker.Walk(func(path *Path, info os.FileInfo, err error) error { |
| 484 | + t.Logf("visited: %v", path.String()) |
| 485 | + require.NoError(t, err) |
| 486 | + rel, err := path.RelativeTo(root) |
| 487 | + require.NoError(t, err) |
| 488 | + visited[rel.String()] = struct{}{} |
| 489 | + if rel.Equals(tt.skipAt) { |
| 490 | + return ErrWalkSkipSubtree |
| 491 | + } |
| 492 | + return nil |
| 493 | + })) |
| 494 | + visitedSorted := []string{} |
| 495 | + for key := range visited { |
| 496 | + visitedSorted = append(visitedSorted, key) |
| 497 | + } |
| 498 | + slices.Sort(visitedSorted) |
| 499 | + |
| 500 | + expected := []string{} |
| 501 | + for _, path := range tt.expected { |
| 502 | + expected = append(expected, path.String()) |
| 503 | + } |
| 504 | + assert.Equal(t, expected, visitedSorted) |
| 505 | + |
| 506 | + }) |
313 | 507 | }
|
314 |
| - walker, err := NewWalk( |
315 |
| - root, |
316 |
| - WalkAlgorithm(AlgorithmPreOrderDepthFirst), |
317 |
| - WalkSortChildren(true), |
318 |
| - WalkVisitDirs(false), |
319 |
| - ) |
320 |
| - require.NoError(t, err) |
321 |
| - seenChildren := []string{} |
322 |
| - err = walker.Walk(func(path *Path, info os.FileInfo, err error) error { |
323 |
| - require.NoError(t, err) |
324 |
| - relative, err := path.RelativeTo(root) |
325 |
| - require.NoError(t, err) |
326 |
| - seenChildren = append(seenChildren, relative.String()) |
327 |
| - return nil |
328 |
| - }) |
329 |
| - require.NoError(t, err) |
330 |
| - assert.Equal(t, children, seenChildren) |
331 | 508 | }
|
0 commit comments