sonar

Scan files at memory bandwidth speed.
Log | Files | Refs

walk_test.odin (5024B)


      1 package walk
      2 
      3 import "core:fmt"
      4 import "core:os"
      5 import "core:slice"
      6 import "core:testing"
      7 
      8 import "../scan"
      9 
     10 /*
     11 Build a known tree and return its root.
     12 
     13 	<root>/a.txt          100 bytes
     14 	<root>/sub/b.txt      5000 bytes
     15 	<root>/sub/deep/c.txt 1 byte
     16 	<root>/link           a symlink to sub
     17 
     18 Six entries below the root, one of which leads back into the tree, which is what
     19 makes following symlinks worth a decision rather than a default.
     20 */
     21 @(private = "file")
     22 fixture :: proc(t: ^testing.T, name: string) -> string {
     23 	dir, dir_err := os.temp_directory(context.temp_allocator)
     24 	testing.expect(t, dir_err == nil, "no temp directory")
     25 	root := fmt.tprintf("%s/sonar_walk_%s", dir, name)
     26 	os.remove_all(root)
     27 
     28 	write :: proc(t: ^testing.T, path: string, size: int) {
     29 		body := make([]byte, size, context.temp_allocator)
     30 		slice.fill(body, 'x')
     31 		testing.expectf(t, os.write_entire_file(path, body) == nil, "could not write %s", path)
     32 	}
     33 	testing.expect(t, os.make_directory(root) == nil, "could not make the root")
     34 	testing.expect(t, os.make_directory(fmt.tprintf("%s/sub", root)) == nil, "could not make sub")
     35 	testing.expect(
     36 		t,
     37 		os.make_directory(fmt.tprintf("%s/sub/deep", root)) == nil,
     38 		"could not make deep",
     39 	)
     40 	write(t, fmt.tprintf("%s/a.txt", root), 100)
     41 	write(t, fmt.tprintf("%s/sub/b.txt", root), 5000)
     42 	write(t, fmt.tprintf("%s/sub/deep/c.txt", root), 1)
     43 	testing.expect(
     44 		t,
     45 		os.symlink("sub", fmt.tprintf("%s/link", root)) == nil,
     46 		"could not make the symlink",
     47 	)
     48 	return root
     49 }
     50 
     51 // Every node the walk recorded, by name. Names repeat across directories in general;
     52 // none of them do in the fixture.
     53 @(private = "file")
     54 named :: proc(tree: ^scan.Tree, name: string) -> (^scan.Node, int) {
     55 	found: ^scan.Node
     56 	count := 0
     57 	for i in 0 ..< tree.count {
     58 		n := scan.node(tree, i)
     59 		if .Used in n.flags && n.name == name {
     60 			found = n
     61 			count += 1
     62 		}
     63 	}
     64 	return found, count
     65 }
     66 
     67 @(private = "file")
     68 walked :: proc(t: ^testing.T, root: string, cfg := Config{}) -> scan.Tree {
     69 	tree: scan.Tree
     70 	testing.expect_value(t, scan.tree_init(&tree, 1), scan.Error.None)
     71 	testing.expect_value(t, read(root, &tree, cfg), Error.None)
     72 	return tree
     73 }
     74 
     75 @(test)
     76 test_walk_records_every_entry :: proc(t: ^testing.T) {
     77 	root := fixture(t, "every")
     78 	defer os.remove_all(root)
     79 	tree := walked(t, root)
     80 	defer scan.tree_destroy(&tree)
     81 
     82 	// The root, plus the six entries below it.
     83 	testing.expect_value(t, tree.count, u32(7))
     84 	testing.expect_value(t, tree.nodes_done, u64(6))
     85 
     86 	for name in ([]string{"a.txt", "sub", "deep", "b.txt", "c.txt", "link"}) {
     87 		n, count := named(&tree, name)
     88 		testing.expectf(t, count == 1, "expected one %s, found %d", name, count)
     89 		if n != nil {
     90 			testing.expectf(t, .Settled in n.flags, "%s was left unsettled", name)
     91 		}
     92 	}
     93 }
     94 
     95 @(test)
     96 test_walk_marks_directories_and_sizes :: proc(t: ^testing.T) {
     97 	root := fixture(t, "shapes")
     98 	defer os.remove_all(root)
     99 	tree := walked(t, root)
    100 	defer scan.tree_destroy(&tree)
    101 
    102 	sub, _ := named(&tree, "sub")
    103 	testing.expect(t, .Directory in sub.flags, "sub is not marked a directory")
    104 	testing.expect_value(t, sub.disk, u64(0)) // a directory is charged through what it holds
    105 
    106 	a, _ := named(&tree, "a.txt")
    107 	testing.expect(t, .Directory not_in a.flags, "a.txt is marked a directory")
    108 	testing.expect_value(t, a.size, u64(100))
    109 	// 100 bytes occupy a block, whether the reader knows the block or estimates it.
    110 	testing.expectf(t, a.disk >= 512 && a.disk <= 8192, "a.txt charged %d bytes", a.disk)
    111 
    112 	b, _ := named(&tree, "b.txt")
    113 	testing.expect_value(t, b.size, u64(5000))
    114 	testing.expectf(t, b.disk >= 5000, "b.txt charged %d for 5000 bytes", b.disk)
    115 }
    116 
    117 @(test)
    118 test_walk_does_not_follow_a_symlink_by_default :: proc(t: ^testing.T) {
    119 	root := fixture(t, "nofollow")
    120 	defer os.remove_all(root)
    121 	tree := walked(t, root)
    122 	defer scan.tree_destroy(&tree)
    123 
    124 	link, _ := named(&tree, "link")
    125 	testing.expect(t, .Reparse in link.flags, "the symlink is not marked a reparse")
    126 	testing.expect(t, .Directory not_in link.flags, "the symlink is counted as a directory")
    127 	// Descending it would record sub's contents a second time.
    128 	_, subs := named(&tree, "b.txt")
    129 	testing.expect_value(t, subs, 1)
    130 }
    131 
    132 @(test)
    133 test_walk_follows_a_symlink_when_asked :: proc(t: ^testing.T) {
    134 	root := fixture(t, "follow")
    135 	defer os.remove_all(root)
    136 	tree := walked(t, root, Config{follow = true})
    137 	defer scan.tree_destroy(&tree)
    138 
    139 	// sub reached twice: once by name, once through the link. Its three entries
    140 	// therefore appear twice over.
    141 	_, b := named(&tree, "b.txt")
    142 	testing.expect_value(t, b, 2)
    143 	testing.expect_value(t, tree.count, u32(10))
    144 }
    145 
    146 @(test)
    147 test_walk_stops_when_cancelled :: proc(t: ^testing.T) {
    148 	root := fixture(t, "cancel")
    149 	defer os.remove_all(root)
    150 
    151 	tree: scan.Tree
    152 	testing.expect_value(t, scan.tree_init(&tree, 1), scan.Error.None)
    153 	defer scan.tree_destroy(&tree)
    154 
    155 	// Called off before it starts, so the seed directory is the only work offered
    156 	// and it declines it.
    157 	scan.cancel(&tree)
    158 	testing.expect_value(t, read(root, &tree, Config{}), Error.Cancelled)
    159 }