cover_rage 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/cover_rage/record.rb +1 -1
- data/lib/cover_rage/reporters/html_reporter/index.html.erb +139 -144
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 00ad4585a873585456f2c9948a88bcab8d54438b5c952a7fdb03a0e411763e3e
|
|
4
|
+
data.tar.gz: 231662f71294e8e983e7632c8ad530b3c18752f2e6d4d0a5d2ea3ea2de0edc90
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '048eb40f6ed0df7c75e4f353ce17fa516345a2832d6b25497453d4bb94aa302197e8e57c5fdbe50444d3a35da91bf641d1a0bbbd2b8d911cf457988016588932'
|
|
7
|
+
data.tar.gz: 587134f993d40afb1c4b9242e030aac02ea886436a1e3912413630f1e7f4e97a403b1b2693a4af8907df23e9a4a8f91e7fa2f782089fa7609fe997ed6e45fa88
|
data/lib/cover_rage/record.rb
CHANGED
|
@@ -25,7 +25,7 @@ module CoverRage
|
|
|
25
25
|
if item.nil? && other.last_executed_at[index].nil? then nil
|
|
26
26
|
elsif item.nil? then other.last_executed_at[index]
|
|
27
27
|
elsif other.last_executed_at[index].nil? then item
|
|
28
|
-
else [item
|
|
28
|
+
else [item, other.last_executed_at[index]].max
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
)
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
-
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
6
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
6
|
<title>CoverRage</title>
|
|
8
7
|
<link
|
|
@@ -15,50 +14,41 @@
|
|
|
15
14
|
display: inline-block;
|
|
16
15
|
counter-increment: line_number;
|
|
17
16
|
}
|
|
18
|
-
|
|
19
|
-
.
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.red {
|
|
24
|
-
background-color: rgb(255, 212, 212);
|
|
25
|
-
}
|
|
26
|
-
|
|
17
|
+
.green { background-color: rgb(221, 255, 221); }
|
|
18
|
+
.blue { background-color: rgb(221, 221, 255); }
|
|
19
|
+
.red { background-color: rgb(255, 212, 212); }
|
|
27
20
|
.number {
|
|
28
21
|
display: inline-block;
|
|
29
22
|
text-align: right;
|
|
30
23
|
background-color: lightgray;
|
|
31
24
|
padding: 0 0.5em 0 1.5em;
|
|
32
25
|
}
|
|
33
|
-
|
|
34
26
|
.timestamp {
|
|
35
27
|
display: inline-block;
|
|
36
28
|
text-align: right;
|
|
37
29
|
margin-right: 0.5em;
|
|
38
30
|
background-color: lightgray;
|
|
39
|
-
padding: 0 0.5em
|
|
31
|
+
padding: 0 0.5em;
|
|
40
32
|
}
|
|
41
|
-
|
|
42
33
|
.nav {
|
|
43
34
|
display: flex;
|
|
44
35
|
list-style: none;
|
|
45
36
|
padding-left: 0;
|
|
46
|
-
|
|
47
|
-
.nav > * {
|
|
48
|
-
margin-left: 8px;
|
|
37
|
+
gap: 8px;
|
|
49
38
|
}
|
|
50
39
|
</style>
|
|
51
40
|
</head>
|
|
52
41
|
<body>
|
|
53
42
|
<main id="main"></main>
|
|
54
43
|
|
|
55
|
-
<
|
|
56
|
-
<div
|
|
44
|
+
<template id="tmpl-index">
|
|
45
|
+
<div>
|
|
57
46
|
<nav>
|
|
58
47
|
<ul class="nav">
|
|
59
48
|
<li><a href="#/">Index</a></li>
|
|
60
49
|
</ul>
|
|
61
50
|
</nav>
|
|
51
|
+
<label>stale threshold <input id="stale-threshold" type="number" min="0" value="30"> days</label>
|
|
62
52
|
<table>
|
|
63
53
|
<thead>
|
|
64
54
|
<tr>
|
|
@@ -67,160 +57,165 @@
|
|
|
67
57
|
<th>relevancy</th>
|
|
68
58
|
<th>hit</th>
|
|
69
59
|
<th>miss</th>
|
|
70
|
-
<th>
|
|
60
|
+
<th>stale</th>
|
|
61
|
+
<th class="sortable" data-sort="coverage" style="cursor:pointer">coverage (%) ▼</th>
|
|
62
|
+
<th class="sortable" data-sort="staleness" style="cursor:pointer">staleness (%)</th>
|
|
71
63
|
</tr>
|
|
72
64
|
</thead>
|
|
73
65
|
<tbody></tbody>
|
|
74
66
|
</table>
|
|
75
67
|
</div>
|
|
76
|
-
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<template id="tmpl-file">
|
|
71
|
+
<div>
|
|
77
72
|
<nav>
|
|
78
73
|
<ul class="nav">
|
|
79
74
|
<li><a href="#/">Index</a></li>
|
|
80
|
-
<li
|
|
75
|
+
<li>></li>
|
|
81
76
|
<li id="title"></li>
|
|
82
77
|
</ul>
|
|
83
78
|
</nav>
|
|
79
|
+
<label>stale threshold <input id="stale-threshold" type="number" min="0" value="30"> days</label>
|
|
84
80
|
<pre><code id="code"></code></pre>
|
|
85
81
|
</div>
|
|
86
|
-
</
|
|
82
|
+
</template>
|
|
87
83
|
|
|
88
84
|
<script id="records" type="application/json"><%= records %></script>
|
|
89
85
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
|
|
90
86
|
<script>
|
|
87
|
+
const records = JSON.parse(document.getElementById("records").textContent);
|
|
91
88
|
const main = document.getElementById("main");
|
|
92
|
-
const page_index = document.getElementById("page-index");
|
|
93
|
-
const title = document.createTextNode("");
|
|
94
|
-
document.getElementById("title").appendChild(title);
|
|
95
|
-
const code = document.getElementById("code");
|
|
96
|
-
const page_file = document.getElementById("page-file");
|
|
97
|
-
const records = JSON.parse(
|
|
98
|
-
document.getElementById("records").childNodes[0].nodeValue
|
|
99
|
-
);
|
|
100
89
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
90
|
+
function route() {
|
|
91
|
+
const hash = window.location.hash.slice(1);
|
|
92
|
+
if (hash.startsWith("/file/")) {
|
|
93
|
+
const path = hash.slice(6);
|
|
94
|
+
const record = records.find((r) => r.path === path);
|
|
95
|
+
if (record) return { page: "file", path, record };
|
|
106
96
|
}
|
|
107
|
-
|
|
108
97
|
return { page: "index" };
|
|
109
|
-
}
|
|
98
|
+
}
|
|
110
99
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
.value.split("\n")
|
|
127
|
-
.map((line, index) => {
|
|
128
|
-
const value = record.execution_count[index];
|
|
129
|
-
let color = "";
|
|
130
|
-
if (typeof value === "number")
|
|
131
|
-
color = value > 0 ? "green" : "red";
|
|
132
|
-
const number = typeof value === "number" ? value : "-";
|
|
133
|
-
const posix_time = record.last_executed_at[index];
|
|
134
|
-
const iso8601_time = typeof posix_time === "number" ? new Date(posix_time * 1000).toISOString().slice(0, 10) : "-";
|
|
135
|
-
return `<span class="line ${color}"><span class="number">${
|
|
136
|
-
number.toString().padStart(digit_width, " ")
|
|
137
|
-
}</span><time class="timestamp">${
|
|
138
|
-
iso8601_time.padStart(10, " ")
|
|
139
|
-
}</time>${line}</span>`;
|
|
140
|
-
})
|
|
141
|
-
.join("\n");
|
|
100
|
+
function cloneTemplate(id) {
|
|
101
|
+
return document.getElementById(id).content.cloneNode(true).firstElementChild;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function formatDate(posixTime) {
|
|
105
|
+
if (typeof posixTime !== "number") return "-";
|
|
106
|
+
return new Date(posixTime * 1000).toISOString().slice(0, 10);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function colorClass(count, lastExecutedAt, staleThresholdDays) {
|
|
110
|
+
if (typeof count !== "number") return "";
|
|
111
|
+
if (count === 0) return "red";
|
|
112
|
+
if (typeof lastExecutedAt === "number") {
|
|
113
|
+
const ageDays = (Date.now() / 1000 - lastExecutedAt) / 86400;
|
|
114
|
+
if (ageDays > staleThresholdDays) return "blue";
|
|
142
115
|
}
|
|
143
|
-
|
|
144
|
-
|
|
116
|
+
return "green";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function summarize({ path, execution_count, last_executed_at }, staleThresholdDays) {
|
|
120
|
+
let hit = 0, miss = 0, relevancy = 0, stale = 0;
|
|
121
|
+
const staleCutoff = Date.now() / 1000 - staleThresholdDays * 86400;
|
|
122
|
+
for (let i = 0; i < execution_count.length; i++) {
|
|
123
|
+
const count = execution_count[i];
|
|
124
|
+
if (count !== null) {
|
|
125
|
+
relevancy++;
|
|
126
|
+
if (count > 0) {
|
|
127
|
+
hit++;
|
|
128
|
+
const t = last_executed_at[i];
|
|
129
|
+
if (typeof t === "number" && t < staleCutoff) stale++;
|
|
130
|
+
} else {
|
|
131
|
+
miss++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
145
134
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
135
|
+
return {
|
|
136
|
+
path,
|
|
137
|
+
lines: execution_count.length,
|
|
138
|
+
relevancy,
|
|
139
|
+
hit,
|
|
140
|
+
miss,
|
|
141
|
+
stale,
|
|
142
|
+
coverage: hit / relevancy,
|
|
143
|
+
staleness: stale / relevancy,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
154
146
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
147
|
+
let staleThresholdDays = 30;
|
|
148
|
+
let sortKey = "coverage";
|
|
149
|
+
let sortAsc = true;
|
|
150
|
+
|
|
151
|
+
function getStaleThreshold(el) {
|
|
152
|
+
const input = el.querySelector("#stale-threshold");
|
|
153
|
+
input.value = staleThresholdDays;
|
|
154
|
+
input.addEventListener("change", () => {
|
|
155
|
+
staleThresholdDays = Number(input.value) || 30;
|
|
156
|
+
render();
|
|
157
|
+
});
|
|
158
|
+
return staleThresholdDays;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function renderIndex() {
|
|
162
|
+
const el = cloneTemplate("tmpl-index");
|
|
163
|
+
const staleThresholdDays = getStaleThreshold(el);
|
|
164
|
+
const tbody = el.querySelector("tbody");
|
|
165
|
+
el.querySelectorAll(".sortable").forEach((th) => {
|
|
166
|
+
const key = th.dataset.sort;
|
|
167
|
+
th.textContent = th.textContent.replace(/ [▲▼]$/, "") + (sortKey === key ? (sortAsc ? " ▲" : " ▼") : "");
|
|
168
|
+
th.addEventListener("click", () => {
|
|
169
|
+
if (sortKey === key) { sortAsc = !sortAsc; } else { sortKey = key; sortAsc = true; }
|
|
170
|
+
render();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
const rows = records.map((r) => summarize(r, staleThresholdDays)).sort((a, b) => {
|
|
174
|
+
const av = a[sortKey], bv = b[sortKey];
|
|
175
|
+
if (isNaN(bv)) return -1;
|
|
176
|
+
if (isNaN(av)) return 1;
|
|
177
|
+
return sortAsc ? av - bv : bv - av;
|
|
178
|
+
});
|
|
179
|
+
for (const { path, lines, relevancy, hit, miss, stale, coverage, staleness } of rows) {
|
|
180
|
+
const tr = tbody.insertRow();
|
|
181
|
+
tr.insertCell().innerHTML = `<a href="#/file/${path}">${path}</a>`;
|
|
182
|
+
tr.insertCell().textContent = lines;
|
|
183
|
+
tr.insertCell().textContent = relevancy;
|
|
184
|
+
tr.insertCell().textContent = hit;
|
|
185
|
+
tr.insertCell().textContent = miss;
|
|
186
|
+
tr.insertCell().textContent = stale;
|
|
187
|
+
tr.insertCell().textContent = Math.round(coverage * 10000) / 100;
|
|
188
|
+
tr.insertCell().textContent = Math.round(staleness * 10000) / 100;
|
|
189
|
+
}
|
|
190
|
+
return el;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function renderFile(record) {
|
|
194
|
+
const el = cloneTemplate("tmpl-file");
|
|
195
|
+
const staleThresholdDays = getStaleThreshold(el);
|
|
196
|
+
el.querySelector("#title").textContent = record.path;
|
|
197
|
+
const digitWidth = Math.floor(Math.log10(Math.max(...record.execution_count))) + 1;
|
|
198
|
+
const highlighted = hljs.highlight(record.source, { language: "ruby" }).value;
|
|
199
|
+
el.querySelector("#code").innerHTML = highlighted
|
|
200
|
+
.split("\n")
|
|
201
|
+
.map((line, i) => {
|
|
202
|
+
const count = record.execution_count[i];
|
|
203
|
+
const displayCount = typeof count === "number" ? String(count).padStart(digitWidth) : "-".padStart(digitWidth);
|
|
204
|
+
const date = formatDate(record.last_executed_at[i]).padStart(10);
|
|
205
|
+
return `<span class="line ${colorClass(count, record.last_executed_at[i], staleThresholdDays)}"><span class="number">${displayCount}</span><time class="timestamp">${date}</time>${line}</span>`;
|
|
187
206
|
})
|
|
188
|
-
.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
tr.appendChild(
|
|
192
|
-
(() => {
|
|
193
|
-
const anchor = document.createElement("a");
|
|
194
|
-
anchor.href = `#/file/${path}`;
|
|
195
|
-
anchor.appendChild(document.createTextNode(path));
|
|
196
|
-
const td = document.createElement("td");
|
|
197
|
-
td.appendChild(anchor);
|
|
198
|
-
return td;
|
|
199
|
-
})()
|
|
200
|
-
);
|
|
201
|
-
tr.appendChild(createTdTextNode(lines));
|
|
202
|
-
tr.appendChild(createTdTextNode(relevancy));
|
|
203
|
-
tr.appendChild(createTdTextNode(hit));
|
|
204
|
-
tr.appendChild(createTdTextNode(miss));
|
|
205
|
-
tr.appendChild(
|
|
206
|
-
createTdTextNode(Math.round(coverage * 10000) / 100)
|
|
207
|
-
);
|
|
208
|
-
fragment.appendChild(tr);
|
|
209
|
-
return fragment;
|
|
210
|
-
},
|
|
211
|
-
document.createDocumentFragment()
|
|
212
|
-
)
|
|
213
|
-
);
|
|
207
|
+
.join("\n");
|
|
208
|
+
return el;
|
|
209
|
+
}
|
|
214
210
|
|
|
215
|
-
|
|
216
|
-
const routing = route(
|
|
217
|
-
|
|
218
|
-
|
|
211
|
+
function render() {
|
|
212
|
+
const routing = route();
|
|
213
|
+
const page = routing.page === "file" ? renderFile(routing.record) : renderIndex();
|
|
214
|
+
main.replaceChildren(page);
|
|
215
|
+
}
|
|
219
216
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
render(routing);
|
|
223
|
-
});
|
|
217
|
+
render();
|
|
218
|
+
window.addEventListener("hashchange", render);
|
|
224
219
|
</script>
|
|
225
220
|
</body>
|
|
226
221
|
</html>
|