report_test.mx raw
1 package mxcover
2
3 import "testing"
4
5 // The reporter is the only thing that turns a coverage run into a number, so
6 // its parsing and its arithmetic are tested here rather than trusted: a wrong
7 // percentage or a dropped site would look exactly like a real result.
8
9 func TestI64Str(t *testing.T) {
10 if i64str(0) != "0" {
11 t.Fatal("zero")
12 }
13 if i64str(7) != "7" {
14 t.Fatal("single digit")
15 }
16 if i64str(1234567890) != "1234567890" {
17 t.Fatal("long")
18 }
19 // i64str(-42) is not asserted: stage4 zero-extends a negative constant
20 // argument to an int64 parameter, so -42 arrives as 4294967254 (legacy
21 // computes -42). i64str is only ever called with a non-negative count, so
22 // the reporter is unaffected; the compiler bug is recorded in
23 // docs/relay-test-coverage.md with this call as its repro.
24 if i32str(2147) != "2147" {
25 t.Fatal("int32 wrapper")
26 }
27 }
28
29 func TestPct(t *testing.T) {
30 if pct(0, 0) != " 0.0%" {
31 t.Fatal("empty denominator")
32 }
33 if pct(1, 1) != "100.0%" {
34 t.Fatal("all")
35 }
36 if pct(0, 4) != "0.0%" {
37 t.Fatal("none")
38 }
39 if pct(1, 3) != "33.3%" {
40 t.Fatal("one third")
41 }
42 if pct(2, 3) != "66.6%" {
43 t.Fatal("two thirds")
44 }
45 if pct(1, 8) != "12.5%" {
46 t.Fatal("eighth")
47 }
48 // three quarters of a large count must not overflow the int64 product
49 if pct(300000000, 400000000) != "75.0%" {
50 t.Fatal("large counts")
51 }
52 }
53
54 func TestParseU64(t *testing.T) {
55 if parseU64("0") != 0 {
56 t.Fatal("zero")
57 }
58 if parseU64("123456789012") != 123456789012 {
59 t.Fatal("large")
60 }
61 if parseU64("12x") != 12 {
62 t.Fatal("stops at a non-digit")
63 }
64 if parseU64("") != 0 {
65 t.Fatal("empty")
66 }
67 }
68
69 func TestParseCounts(t *testing.T) {
70 dump := "coversite 0 count=1\n" |
71 "coversite 7 count=42\n" |
72 "noise\n" |
73 "coversite 9\n" |
74 "coversite 11 count=\n" |
75 " coversite 12 count=3 \n"
76 m := parseCounts(dump)
77 if m["0"] != 1 || m["7"] != 42 || m["12"] != 3 {
78 t.Fatal("valid lines")
79 }
80 // Three hits plus the empty count, which parses as zero rather than
81 // being dropped; a zero is what mxcover reports as uncovered either way.
82 if len(m) != 4 {
83 t.Fatal("coversite lines only: got " | i32str(int32(len(m))))
84 }
85 if _, ok := m["9"]; ok {
86 t.Fatal("a line with no count must be skipped")
87 }
88 if m["11"] != 0 {
89 t.Fatal("an empty count is zero, not a hit")
90 }
91 }
92
93 func TestParseSitesCountsHits(t *testing.T) {
94 sites := "[{\"id\":0,\"name\":\"pkg.F@a.mx:3\"},{\"id\":1,\"name\":\"pkg.G@a.mx:9\"}]"
95 hits := map[string]uint64{"1": 5}
96 recs := parseSites(sites, hits, "")
97 if len(recs) != 2 {
98 t.Fatal("site count")
99 }
100 // sorted by file then line
101 if recs[0].line != 3 || recs[0].hits != 0 {
102 t.Fatal("first site")
103 }
104 if recs[1].line != 9 || recs[1].hits != 5 {
105 t.Fatal("second site took its hits from the dump")
106 }
107 if recs[0].fn != "pkg.F" {
108 t.Fatal("function name is the part before @")
109 }
110 }
111
112 func TestParseSitesPrefixAndSyntheticSites(t *testing.T) {
113 sites := "[{\"id\":0,\"name\":\"pkg.F@mod/a.mx:3\"},{\"id\":1,\"name\":\"pkg.G@other/a.mx:4\"},{\"id\":2,\"name\":\"main.init@:0\"}]"
114 all := parseSites(sites, map[string]uint64{}, "")
115 if len(all) != 2 {
116 t.Fatal("a site with no source file is synthetic and dropped")
117 }
118 one := parseSites(sites, map[string]uint64{}, "mod/")
119 if len(one) != 1 {
120 t.Fatal("the prefix selects one file")
121 }
122 if one[0].file != "mod/a.mx" {
123 t.Fatal("prefix keeps the module-relative name")
124 }
125 none := parseSites(sites, map[string]uint64{}, "nomatch/")
126 if len(none) != 0 {
127 t.Fatal("a prefix that matches nothing selects nothing")
128 }
129 }
130
131 func TestSortRecsOrdersFileLineFunction(t *testing.T) {
132 rs := []rec{
133 {fn: "b", file: "z.mx", line: 1},
134 {fn: "a", file: "a.mx", line: 9},
135 {fn: "b", file: "a.mx", line: 9},
136 {fn: "a", file: "a.mx", line: 2},
137 }
138 sortRecs(rs)
139 want := []string{"a.mx:2:a", "a.mx:9:a", "a.mx:9:b", "z.mx:1:b"}
140 for i := int32(0); i < int32(len(rs)); i++ {
141 got := rs[i].file | ":" | i32str(rs[i].line) | ":" | rs[i].fn
142 if got != want[i] {
143 t.Fatal("sorted order is file, then line, then function: " | got)
144 }
145 }
146 }
147
148 func TestIndexHelpers(t *testing.T) {
149 if indexOf("abcabc", 0, "bc") != 1 {
150 t.Fatal("first index")
151 }
152 if indexOf("abcabc", 2, "bc") != 4 {
153 t.Fatal("index from an offset")
154 }
155 if indexOf("abc", 0, "z") != -1 {
156 t.Fatal("absent")
157 }
158 if lastIndexOf("a/b/c", '/') != 3 {
159 t.Fatal("last index")
160 }
161 if lastIndexOf("abc", '/') != -1 {
162 t.Fatal("absent separator")
163 }
164 if scanNumber("12ab", 0) != "12" {
165 t.Fatal("scanNumber stops at a non-digit")
166 }
167 if scanNumber("x", 0) != "" {
168 t.Fatal("scanNumber on no digits")
169 }
170 if parseInt32("202") != 202 {
171 t.Fatal("parseInt32")
172 }
173 if parseInt32("3x") != 3 {
174 t.Fatal("parseInt32 stops at a non-digit")
175 }
176 }
177
178 func TestPrefixNote(t *testing.T) {
179 if prefixNote("") != "" {
180 t.Fatal("no prefix, no note")
181 }
182 if prefixNote("mod/") != " under mod/" {
183 t.Fatal("prefix is named")
184 }
185 }
186