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 }