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 }