review

review patchsets using your default editor
Log | Files | Refs

report_test.odin (6904B)


      1 package report
      2 
      3 import "core:encoding/json"
      4 import "core:fmt"
      5 import "core:os"
      6 import "core:path/filepath"
      7 import "core:strings"
      8 import "core:testing"
      9 
     10 import "../change"
     11 import "../finding"
     12 
     13 @(test)
     14 render_groups_by_severity :: proc(t: ^testing.T) {
     15 	out := render(
     16 		Contract {
     17 			findings = {
     18 				{
     19 					job = "tests",
     20 					rule = "cannot-fail",
     21 					severity = .Must_Fix,
     22 					file = "a_test.go",
     23 					line = 4,
     24 					message = "one",
     25 				},
     26 				{
     27 					job = "namer",
     28 					rule = "abbreviation",
     29 					severity = .Note,
     30 					symbol = "cfg",
     31 					message = "two",
     32 				},
     33 			},
     34 			dismissed = {{}, {}},
     35 		},
     36 		allocator = context.temp_allocator,
     37 	)
     38 	for want in ([]string{"MUST-FIX", "NOTE", "one", "two", "2 findings", "2 dismissed in the source"}) {
     39 		testing.expectf(t, strings.contains(out, want), "%q missing from:\n%s", want, out)
     40 	}
     41 	testing.expect_value(t, strings.count(out, "MUST-FIX"), 1)
     42 }
     43 
     44 @(test)
     45 render_nothing :: proc(t: ^testing.T) {
     46 	testing.expect_value(
     47 		t,
     48 		strings.trim_space(render(Contract{}, allocator = context.temp_allocator)),
     49 		"no findings",
     50 	)
     51 	out := render(
     52 		Contract{dismissed = {{}, {}, {}}, retracted = {{}}},
     53 		allocator = context.temp_allocator,
     54 	)
     55 	testing.expect(t, strings.contains(out, "3 dismissed in the source"))
     56 	testing.expect(
     57 		t,
     58 		strings.contains(out, "1 of the findings reported did not survive verification"),
     59 	)
     60 	out = render(
     61 		Contract{baseline = Baseline{from = "old.json", persisting = {"abc"}}},
     62 		allocator = context.temp_allocator,
     63 	)
     64 	testing.expect(t, strings.contains(out, "against old.json: 0 resolved, 1 persisting, 0 new"))
     65 	testing.expect(t, strings.contains(out, "persisting: abc"))
     66 }
     67 
     68 @(test)
     69 encode_carries_the_contract :: proc(t: ^testing.T) {
     70 	out, ok := encode(
     71 		Contract {
     72 			status = "incomplete",
     73 			provider = "api/probe",
     74 			findings = {
     75 				{
     76 					job = "static",
     77 					rule = "test-deleted",
     78 					severity = .Must_Fix,
     79 					file = "x_test.go",
     80 					line = 3,
     81 					message = "m",
     82 					verified = true,
     83 				},
     84 			},
     85 			retracted = {
     86 				{
     87 					finding = {
     88 						job = "tests",
     89 						rule = "cannot-fail",
     90 						severity = .Must_Fix,
     91 						file = "x_test.go",
     92 						message = "m",
     93 					},
     94 					reason = "the test can fail",
     95 				},
     96 			},
     97 			failed = {{job = "duplication", error = "context canceled"}},
     98 			skipped = {"hygiene"},
     99 			uncovered = {{file = "a.odin", reason = "no tests parser"}},
    100 			dismissed = {{finding = {rule = "cannot-fail"}, why = "wrong"}},
    101 			truncated = true,
    102 			usage = {tokens_in = 10, tokens_out = 2, cached = 1, replayed = 3, cost = 0.5},
    103 		},
    104 		context.temp_allocator,
    105 	)
    106 	testing.expect(t, ok, "encode")
    107 	for want in ([]string{`"version": 1`, `"status": "incomplete"`, `"provider": "api/probe"`, `"severity": "must-fix"`, `"verified": true`, `"id": "`, `"reason": "the test can fail"`, `"error": "context canceled"`, `"skipped": [`, `"a.odin"`, `"why": "wrong"`, `"truncated": true`, `"usd": 0.5`}) {
    108 		testing.expectf(t, strings.contains(out, want), "%s missing from:\n%s", want, out)
    109 	}
    110 	testing.expect(t, !strings.contains(out, `"baseline"`), "an absent baseline is left out")
    111 	testing.expect(t, !strings.contains(out, `"part"`), "part is the reading's own")
    112 
    113 	// Every list is present even when empty, and the JSON parses.
    114 	empty, empty_ok := encode(Contract{status = "complete"}, context.temp_allocator)
    115 	testing.expect(t, empty_ok)
    116 	for key in ([]string{"findings", "retracted", "failed", "skipped", "uncovered", "dismissed"}) {
    117 		testing.expectf(t, strings.contains(empty, key), "%s missing from:\n%s", key, empty)
    118 	}
    119 	parsed, parse_err := json.parse_string(empty, allocator = context.temp_allocator)
    120 	testing.expect(t, parse_err == nil, "the JSON parses")
    121 	root := parsed.(json.Object)
    122 	testing.expect_value(t, len(root["findings"].(json.Array)), 0)
    123 	testing.expect(t, !strings.contains(empty, "null"), "no list is null")
    124 	testing.expect(t, strings.contains(empty, `"findings": []`), "an empty list is one line")
    125 	testing.expect(
    126 		t,
    127 		!strings.contains(empty, `"truncated"`),
    128 		"a truncation that did not happen is left out",
    129 	)
    130 	testing.expect(t, strings.contains(out, `"truncated": true`))
    131 }
    132 
    133 @(test)
    134 filter_drops_what_the_source_dismisses :: proc(t: ^testing.T) {
    135 	temp := os.temp_directory(context.temp_allocator) or_else ""
    136 	root, err := os.make_directory_temp(temp, "review-report-*", context.temp_allocator)
    137 	testing.expect(t, err == nil)
    138 	defer os.remove_all(root)
    139 	path := filepath.join({root, "x.go"}, context.temp_allocator) or_else ""
    140 	testing.expect(
    141 		t,
    142 		os.write_entire_file(
    143 			path,
    144 			transmute([]byte)string(
    145 				"package x\n\n//review:" +
    146 				"ignore restates-a-fact the ico package owns it\nconst b = 6\n",
    147 			),
    148 		) ==
    149 		nil,
    150 	)
    151 
    152 	kept, dismissed := filter(
    153 		root,
    154 		{
    155 			{file = "x.go", line = 4, rule = "restates-a-fact", message = "dismissed"},
    156 			{file = "x.go", line = 4, rule = "already-named", message = "kept"},
    157 		},
    158 		context.temp_allocator,
    159 	)
    160 	testing.expect_value(t, len(dismissed), 1)
    161 	testing.expect_value(t, dismissed[0].why, "the ico package owns it")
    162 	testing.expect_value(t, len(kept), 1)
    163 	testing.expect_value(t, kept[0].message, "kept")
    164 }
    165 
    166 @(test)
    167 compare_names_this_run_against_the_last :: proc(t: ^testing.T) {
    168 	context.allocator = context.temp_allocator
    169 	temp := os.temp_directory(context.temp_allocator) or_else ""
    170 	dir, err := os.make_directory_temp(temp, "review-baseline-*", context.temp_allocator)
    171 	testing.expect(t, err == nil)
    172 	defer os.remove_all(dir)
    173 	path := filepath.join({dir, "previous.json"}, context.temp_allocator) or_else ""
    174 	testing.expect(
    175 		t,
    176 		os.write_entire_file(
    177 			path,
    178 			transmute([]byte)string(`{"findings":[{"id":"aaa"},{"id":"bbb"},{"id":""}]}`),
    179 		) ==
    180 		nil,
    181 	)
    182 	b, cmp_err := compare(path, {{id = "bbb"}, {id = "ccc"}})
    183 	testing.expect_value(t, cmp_err, "")
    184 	testing.expect_value(t, b.from, path)
    185 	testing.expect_value(t, fmt.tprint(b.resolved), `["aaa"]`)
    186 	testing.expect_value(t, fmt.tprint(b.persisting), `["bbb"]`)
    187 	testing.expect_value(t, fmt.tprint(b.new), `["ccc"]`)
    188 	_, missing := compare(
    189 		filepath.join({dir, "nowhere.json"}, context.temp_allocator) or_else "",
    190 		{},
    191 	)
    192 	testing.expect(t, strings.has_prefix(missing, "reading the baseline"))
    193 	testing.expect(t, os.write_entire_file(path, transmute([]byte)string("prose")) == nil)
    194 	_, prose := compare(path, {})
    195 	testing.expect_value(t, prose, "the baseline is not a review report")
    196 }
    197 
    198 @(test)
    199 status_says_where_the_hole_is :: proc(t: ^testing.T) {
    200 	c: change.Change
    201 	c.uncovered = make([dynamic]change.Gap, context.temp_allocator)
    202 	testing.expect_value(t, status_of(c, 0), "complete")
    203 	testing.expect_value(t, status_of(c, 1), "incomplete")
    204 	c.truncated = true
    205 	testing.expect_value(t, status_of(c, 0), "incomplete")
    206 	c.truncated = false
    207 	append(&c.uncovered, change.Gap{"a.zig", "no reader"})
    208 	testing.expect_value(t, status_of(c, 0), "incomplete")
    209 	f := finding.Finding{}
    210 	testing.expect_value(t, f.severity, finding.Severity.Must_Fix)
    211 }