review

review patchsets using your default editor
Log | Files | Refs

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 }