change_test.odin (10789B)
1 package change 2 3 import "core:fmt" 4 import "core:os" 5 import "core:path/filepath" 6 import "core:strings" 7 import "core:testing" 8 import "jm:sh" 9 10 import "../frontend" 11 import "../tree" 12 import "../txt" 13 14 canned :: `diff --git a/a.go b/a.go 15 index 1..2 100644 16 --- a/a.go 17 +++ b/a.go 18 @@ -1,3 +1,5 @@ 19 package a 20 + 21 +// added is new. 22 +func added() {} 23 func old() {} 24 -func gone() {} 25 diff --git a/b.txt b/b.txt 26 deleted file mode 100644 27 --- a/b.txt 28 +++ /dev/null 29 @@ -1,2 +0,0 @@ 30 -first 31 -+++ not a header 32 ` 33 34 @(test) 35 diff_sides_reads_both_sides :: proc(t: ^testing.T) { 36 added, removed := diff_sides(canned, context.temp_allocator) 37 testing.expect_value(t, len(added), 1) 38 a := added["a.go"] 39 testing.expect_value(t, len(a), 3) 40 testing.expect_value(t, a[0].line, 2) 41 testing.expect_value(t, a[1].line, 3) 42 testing.expect_value(t, a[1].text, "// added is new.") 43 testing.expect_value(t, a[2].line, 4) 44 testing.expect_value(t, len(removed["a.go"]), 1) 45 testing.expect_value(t, removed["a.go"][0], "func gone() {}") 46 testing.expect_value(t, len(removed["b.txt"]), 2) 47 testing.expect_value(t, removed["b.txt"][1], "+++ not a header") 48 testing.expect_value(t, hunk_start("@@ -10,7 +12 @@ func x") or_else -1, 12) 49 } 50 51 // new_repo makes a repository with two commits: a.go first, then a 52 // function, a test and a comment added to it. 53 new_repo :: proc(t: ^testing.T) -> (root: string, ok: bool) { 54 temp := os.temp_directory(context.temp_allocator) or_else "" 55 scratch, err := os.make_directory_temp(temp, "review-change-*", context.temp_allocator) 56 if err != nil { 57 testing.fail_now(t, "no scratch directory") 58 } 59 root = scratch 60 git := proc(root: string, args: ..string) -> bool { 61 argv := make([dynamic]string, context.temp_allocator) 62 append( 63 &argv, 64 "git", 65 "-c", 66 "user.email=t@t", 67 "-c", 68 "user.name=t", 69 "-c", 70 "commit.gpgsign=false", 71 ) 72 append(&argv, ..args) 73 return sh.exec(argv[:], {dir = root}, context.temp_allocator).ok 74 } 75 write := proc(root, name, src: string) -> bool { 76 path := filepath.join({root, name}, context.temp_allocator) or_else name 77 return os.write_entire_file(path, transmute([]byte)src) == nil 78 } 79 ok = git(root, "init", "-q") 80 ok &&= write(root, "a.go", "package a\n\nfunc old() {}\n") 81 ok &&= git(root, "add", "a.go") 82 ok &&= git(root, "commit", "-q", "-m", "a: begin") 83 ok &&= write( 84 root, 85 "a.go", 86 "package a\n\nimport \"fmt\"\n\nfunc old() {}\n\n// added is new.\nfunc added() {}\n", 87 ) 88 ok &&= write( 89 root, 90 "a_test.go", 91 "package a\n\nimport \"testing\"\n\nfunc TestAdded(t *testing.T) {\n\tadded()\n}\n", 92 ) 93 ok &&= git(root, "add", "a.go", "a_test.go") 94 ok &&= git(root, "commit", "-q", "-m", "a: add added") 95 return root, ok 96 } 97 98 @(test) 99 gather_reads_a_range_through_the_sidecar :: proc(t: ^testing.T) { 100 if !frontend.installed(.Go) { 101 testing.fail_now(t, "review-go is not on the path") 102 } 103 root, made := new_repo(t) 104 testing.expect(t, made, "the fixture repository") 105 defer os.remove_all(root) 106 107 c, ok := gather("HEAD^..HEAD", root, context.temp_allocator) 108 testing.expect(t, ok, "gather") 109 testing.expect_value(t, len(c.files), 2) 110 testing.expect_value(t, strings.trim_space(c.message), "a: add added") 111 testing.expect_value(t, c.truncated, false) 112 113 tr, at_ok := tree.at(root, "HEAD^..HEAD", context.temp_allocator) 114 testing.expect(t, at_ok, "materialise") 115 defer tree.close(tr) 116 testing.expect(t, tr.dir != root, "a range is read from scratch") 117 testing.expect(t, tree.exists(tr, "a_test.go")) 118 119 testing.expect(t, read(&c, tr, context.temp_allocator), "read") 120 testing.expect_value(t, len(c.uncovered), 0) 121 testing.expect_value(t, len(c.symbols), 1) 122 testing.expect_value(t, c.symbols[0].name, "added") 123 testing.expect_value(t, c.symbols[0].kind, "func") 124 testing.expect_value(t, c.symbols[0].doc, "added is new.") 125 testing.expect_value(t, c.symbols[0].body, "func added() {}") 126 testing.expect_value(t, c.symbols[0].pkg, "a") 127 testing.expect_value(t, len(c.tests), 1) 128 testing.expect_value(t, c.tests[0].name, "TestAdded") 129 testing.expect_value(t, c.tests[0].body, "func TestAdded(t *testing.T) {\n\tadded()\n}") 130 testing.expect_value(t, len(c.comments), 1) 131 testing.expect_value(t, c.comments[0].text, "added is new.") 132 testing.expect_value(t, len(c.imports["a.go"]), 1) 133 testing.expect_value(t, c.imports["a.go"][0], "fmt") 134 135 declared, indexed := index(tr, context.temp_allocator) 136 testing.expect(t, indexed, "index") 137 names := make([dynamic]string, context.temp_allocator) 138 for d in declared { 139 append(&names, d.name) 140 } 141 testing.expect_value(t, fmt.tprint(names[:]), `["old", "added"]`) 142 } 143 144 @(test) 145 comments_are_read_beside_their_code :: proc(t: ^testing.T) { 146 lines := []string { 147 "package x", 148 "", 149 "// above", 150 "// and more", 151 "", 152 "func f() {", 153 "\treturn", 154 "}", 155 } 156 testing.expect_value(t, code_below(lines, 3, context.temp_allocator), "func f() {\nreturn") 157 testing.expect_value(t, code_below(lines, 8, context.temp_allocator), "") 158 testing.expect_value( 159 t, 160 skip_line( 161 Function{line = 10, body = "func TestX(t *testing.T) {\n\tt.Skip(\"later\")\n}"}, 162 ), 163 11, 164 ) 165 testing.expect_value( 166 t, 167 skip_line(Function{line = 10, body = "func TestX(t *testing.T) {}"}), 168 0, 169 ) 170 testing.expect_value(t, count_numstat("3\t1\ta.go\n-\t-\tb.png\n2\t0\tc.go\n"), 6) 171 } 172 173 @(test) 174 heuristic_reads_comments_by_shape :: proc(t: ^testing.T) { 175 src := "#!/usr/bin/env python\n# one\nx = 1 # not a comment line\n\"\"\"\n# two\n/* three\n * four */\n" 176 touched := make([dynamic]Diff_Line, context.temp_allocator) 177 for line in ([]int{1, 2, 3, 5, 6, 7}) { 178 append(&touched, Diff_Line{line, ""}) 179 } 180 got := comment_prose(transmute([]byte)src, "a.py", touched, context.temp_allocator) 181 texts := make([dynamic]string, context.temp_allocator) 182 for l in got { 183 append(&texts, l.text) 184 } 185 testing.expect_value(t, fmt.tprint(texts[:]), `["one", "two", "three", "four"]`) 186 testing.expect(t, heuristic_covers("a.zig")) 187 testing.expect(t, !heuristic_covers("a.md")) 188 } 189 190 @(test) 191 temporal_counts_the_pair :: proc(t: ^testing.T) { 192 root, made := new_repo(t) 193 testing.expect(t, made, "the fixture repository") 194 defer os.remove_all(root) 195 git := proc(root: string, args: ..string) -> bool { 196 argv := make([dynamic]string, context.temp_allocator) 197 append( 198 &argv, 199 "git", 200 "-c", 201 "user.email=t@t", 202 "-c", 203 "user.name=t", 204 "-c", 205 "commit.gpgsign=false", 206 ) 207 append(&argv, ..args) 208 return sh.exec(argv[:], {dir = root}, context.temp_allocator).ok 209 } 210 for i in 0 ..< 8 { 211 a := filepath.join({root, "pair_a.go"}, context.temp_allocator) or_else "" 212 b := filepath.join({root, "pair_b.go"}, context.temp_allocator) or_else "" 213 testing.expect( 214 t, 215 os.write_entire_file( 216 a, 217 transmute([]byte)fmt.tprintf("package a\n\nvar v%d = %d\n", i, i), 218 ) == 219 nil, 220 ) 221 testing.expect( 222 t, 223 os.write_entire_file( 224 b, 225 transmute([]byte)fmt.tprintf("package b\n\nvar w%d = %d\n", i, i), 226 ) == 227 nil, 228 ) 229 testing.expect(t, git(root, "add", "pair_a.go", "pair_b.go")) 230 testing.expect(t, git(root, "commit", "-q", "-m", fmt.tprintf("grow: round %d", i))) 231 } 232 a := filepath.join({root, "pair_a.go"}, context.temp_allocator) or_else "" 233 testing.expect( 234 t, 235 os.write_entire_file(a, transmute([]byte)string("package a\n\nvar v9 = 9\n")) == nil, 236 ) 237 testing.expect(t, git(root, "add", "pair_a.go")) 238 c, ok := gather("", root, context.temp_allocator) 239 testing.expect(t, ok) 240 temporal, counted := c.temporal.? 241 testing.expect(t, counted, "history counted") 242 testing.expect_value(t, temporal.commits["pair_a.go"], 8) 243 testing.expect_value(t, len(temporal.partners["pair_a.go"]), 1) 244 testing.expect_value(t, temporal.partners["pair_a.go"][0].name, "pair_b.go") 245 testing.expect_value(t, temporal.partners["pair_a.go"][0].shared, 8) 246 testing.expect(t, c.changed > 0) 247 testing.expect(t, len(c.history) >= 10) 248 } 249 250 @(test) 251 a_message_file_loses_its_template :: proc(t: ^testing.T) { 252 temp := os.temp_directory(context.temp_allocator) or_else "" 253 path := filepath.join({temp, "review_message_fixture.txt"}, context.temp_allocator) or_else "" 254 testing.expect( 255 t, 256 os.write_entire_file( 257 path, 258 transmute([]byte)string( 259 "x: do it\n\n# Please enter the commit message\n# Lines starting with '#' are ignored\nbecause it matters\n", 260 ), 261 ) == 262 nil, 263 ) 264 defer os.remove(path) 265 message, ok := read_message(path, context.temp_allocator) 266 testing.expect(t, ok) 267 testing.expect_value(t, message, "x: do it\n\nbecause it matters") 268 _, missing := read_message("/nowhere/at/all", context.temp_allocator) 269 testing.expect(t, !missing) 270 } 271 272 @(test) 273 code_files_are_the_ones_a_reader_could_exist_for :: proc(t: ^testing.T) { 274 testing.expect(t, is_code_file("a/b.zig")) 275 testing.expect(t, is_code_file("Justfile")) 276 testing.expect(t, !is_code_file("readme.md")) 277 testing.expect(t, !is_code_file("go.sum")) 278 testing.expect(t, !is_code_file("x/.gitignore")) 279 testing.expect(t, !is_code_file("Dockerfile")) 280 } 281 282 @(test) 283 gather_of_nothing_staged_is_empty :: proc(t: ^testing.T) { 284 root, made := new_repo(t) 285 testing.expect(t, made, "the fixture repository") 286 defer os.remove_all(root) 287 c, ok := gather("", root, context.temp_allocator) 288 testing.expect(t, ok, "gather") 289 testing.expect_value(t, len(c.files), 0) 290 testing.expect_value(t, c.diff, "") 291 testing.expect_value(t, c.message, "") 292 } 293 294 @(test) 295 candidates_rank_by_shared_words :: proc(t: ^testing.T) { 296 context.allocator = context.temp_allocator 297 c := Change{} 298 c.symbols = make([dynamic]Symbol, context.temp_allocator) 299 append( 300 &c.symbols, 301 Symbol { 302 name = "IconEntrySize", 303 kind = "value", 304 file = "b.go", 305 line = 9, 306 signature = "const IconEntrySize = 16", 307 }, 308 ) 309 c.index = []Declared { 310 { 311 name = "EntrySize", 312 kind = "value", 313 file = "a.go", 314 line = 3, 315 text = "const EntrySize = 16", 316 }, 317 { 318 name = "iconCount", 319 kind = "func", 320 file = "a.go", 321 line = 8, 322 text = "func iconCount() int {", 323 }, 324 { 325 name = "unrelated", 326 kind = "value", 327 file = "a.go", 328 line = 1, 329 text = "const unrelated = 16", 330 }, 331 { 332 name = "IconEntrySize", 333 kind = "value", 334 file = "b.go", 335 line = 9, 336 text = "const IconEntrySize = 16", 337 }, 338 } 339 find_candidates(&c, context.temp_allocator) 340 twins := c.twins["IconEntrySize"] 341 testing.expect_value(t, len(twins), 2) 342 if len(twins) == 2 { 343 testing.expect_value(t, twins[0], "a.go:3: const EntrySize = 16 <- same value") 344 } 345 near := c.candidates["IconEntrySize"] 346 testing.expect_value(t, len(near), 4) 347 if len(near) == 4 { 348 testing.expect_value(t, near[2], "a.go:3: const EntrySize = 16") 349 testing.expect_value(t, near[3], "a.go:8: func iconCount() int {") 350 } 351 testing.expect(t, same("x = 3", "y = 3 ")) 352 testing.expect(t, same("A :: 3", "B = 3")) 353 testing.expect(t, !same("f()", "g()")) 354 testing.expect_value( 355 t, 356 fmt.tprint(txt.split_words("icoEntry_size")), 357 `["ico", "Entry", "size"]`, 358 ) 359 }