cover_rage 0.0.1 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb5a49ec54d2f39963fa0704561b1d49f27fd04b4070b760ad1a0dd361eaa2e4
4
- data.tar.gz: 926ef7443cf1efecfe908d9d6e2a74799b1cf5ddf260d16f03ddc9cd6c821ada
3
+ metadata.gz: 56c953122e72d8aaa3a4a50129595cfc36fb20f1c2feea245b255295a5ba8a90
4
+ data.tar.gz: 7b5b4972c31670c4c9ab6b5298682ce7b8d258f4a0ff31faf8790856f1691428
5
5
  SHA512:
6
- metadata.gz: 9ada124a26bd41c144f54391c5d424f396a1f260e2c2210d4d971c0905ff04578d3bd346d33115b5de1168e8476a869fb8710ab3f3b0b1201c01fcfaf5629bb9
7
- data.tar.gz: 0264676faf4d418cc51b27560fa3645b6a7d806aae8ac22aed6b835463d3318bc42083b6ca00766378bcbc8948276ac9d5a49c9412c1a855d1881aeb572a9bda
6
+ metadata.gz: 26cd15a4e159851270b8c9b351e1047049eb509e1ebc322634d94ab8c53f2e8e42d22ca4521c07af7d784e3d8fa5993fc59e57bbeba0d2c49fa83b86ba5b5a88
7
+ data.tar.gz: 38e670bed372e6117c64967fc32a124e2dcac34734e14f05e954b282de01cd557af0847a69c16474e3e13c4352ec57a878d96b95925fc08c88612a5774b6c89c
data/bin/cover_rage CHANGED
@@ -1,5 +1,37 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'rackup'
5
- Dir.chdir(__dir__) { Rackup::Server.start }
4
+ if ARGV.first == 'clear'
5
+ require 'cover_rage/config'
6
+ CoverRage::Config.store.clear
7
+ exit
8
+ end
9
+
10
+ require 'optparse'
11
+ options = { format: 'html' }
12
+ OptionParser.new do |parser|
13
+ parser.banner = <<~USAGE
14
+ Clear store:
15
+ #{$PROGRAM_NAME} clear
16
+
17
+ Export report:
18
+ #{$PROGRAM_NAME} [options]
19
+ USAGE
20
+ parser.on('-fFORMAT', '--format=FORMAT', 'output format (json|html)') { options[:format] = _1 }
21
+ end.parse!
22
+
23
+ reporter =
24
+ case options[:format]
25
+ when 'json'
26
+ require 'cover_rage/reporters/json_reporter'
27
+ CoverRage::Reporters::JsonReporter.new
28
+ when 'html'
29
+ require 'cover_rage/reporters/html_reporter'
30
+ CoverRage::Reporters::HtmlReporter.new
31
+ else
32
+ warn "Unknown format: #{options[:format]}"
33
+ exit 1
34
+ end
35
+
36
+ require 'cover_rage/config'
37
+ puts reporter.report(CoverRage::Config.store.list)
@@ -29,5 +29,9 @@ module CoverRage
29
29
  Range.new(*args, true)
30
30
  end
31
31
  end
32
+
33
+ def self.disable?
34
+ @disable ||= ENV.key?('COVER_RAGE_DISABLE')
35
+ end
32
36
  end
33
37
  end
@@ -0,0 +1,215 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>CoverRage</title>
8
+ <link
9
+ rel="stylesheet"
10
+ href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css"
11
+ />
12
+ <style>
13
+ .line {
14
+ width: 100%;
15
+ display: inline-block;
16
+ counter-increment: line_number;
17
+ }
18
+
19
+ .green {
20
+ background-color: rgb(221, 255, 221);
21
+ }
22
+
23
+ .red {
24
+ background-color: rgb(255, 212, 212);
25
+ }
26
+
27
+ .number {
28
+ display: inline-block;
29
+ text-align: right;
30
+ margin-right: 0.5em;
31
+ background-color: lightgray;
32
+ padding: 0 0.5em 0 1.5em;
33
+ }
34
+
35
+ .nav {
36
+ display: flex;
37
+ list-style: none;
38
+ padding-left: 0;
39
+ }
40
+ .nav > * {
41
+ margin-left: 8px;
42
+ }
43
+ </style>
44
+ </head>
45
+ <body>
46
+ <main id="main"></main>
47
+
48
+ <div style="display: none">
49
+ <div id="page-index">
50
+ <nav>
51
+ <ul class="nav">
52
+ <li><a href="#/">Index</a></li>
53
+ </ul>
54
+ </nav>
55
+ <table>
56
+ <thead>
57
+ <tr>
58
+ <th>path</th>
59
+ <th>lines</th>
60
+ <th>relevancy</th>
61
+ <th>hit</th>
62
+ <th>miss</th>
63
+ <th>coverage (%)</th>
64
+ </tr>
65
+ </thead>
66
+ <tbody></tbody>
67
+ </table>
68
+ </div>
69
+ <div id="page-file">
70
+ <nav>
71
+ <ul class="nav">
72
+ <li><a href="#/">Index</a></li>
73
+ <li>></li>
74
+ <li id="title"></li>
75
+ </ul>
76
+ </nav>
77
+ <pre><code id="code"></code></pre>
78
+ </div>
79
+ </div>
80
+
81
+ <script id="records" type="application/json"><%= records %></script>
82
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
83
+ <script>
84
+ const main = document.getElementById("main");
85
+ const page_index = document.getElementById("page-index");
86
+ const title = document.createTextNode("");
87
+ document.getElementById("title").appendChild(title);
88
+ const code = document.getElementById("code");
89
+ const page_file = document.getElementById("page-file");
90
+ const records = JSON.parse(
91
+ document.getElementById("records").childNodes[0].nodeValue
92
+ );
93
+
94
+ const route = (path) => {
95
+ if (path.startsWith("/file/")) {
96
+ const filepath = path.slice(6);
97
+ if (records.find((record) => record.path === filepath))
98
+ return { page: "file", path: filepath };
99
+ }
100
+
101
+ return { page: "index" };
102
+ };
103
+
104
+ const render = (routing) => {
105
+ let page;
106
+ if (routing.page === "file") {
107
+ const record = records.find(({ path }) => path === routing.path);
108
+ page = page_file;
109
+ title.nodeValue = routing.path;
110
+ const digit_width =
111
+ Math.floor(Math.log10(Math.max(...record.execution_count))) + 1;
112
+ code.innerHTML = hljs
113
+ .highlight(
114
+ records.find(({ path }) => path === routing.path).source,
115
+ {
116
+ language: "ruby",
117
+ }
118
+ )
119
+ .value.split("\n")
120
+ .map((line, index) => {
121
+ const value = record.execution_count[index];
122
+ let color = "";
123
+ if (typeof value === "number")
124
+ color = value > 0 ? "green" : "red";
125
+ const number = typeof value === "number" ? value : "-";
126
+ return `<span class="line ${color}"><span class="number">${number
127
+ .toString()
128
+ .padStart(digit_width, " ")}</span>${line}</span>`;
129
+ })
130
+ .join("\n");
131
+ }
132
+ if (routing.page === "index") {
133
+ page = page_index;
134
+ }
135
+ if (main.childNodes.length === 0) main.appendChild(page);
136
+ else main.replaceChild(page, main.childNodes[0]);
137
+ };
138
+ const createTdTextNode = (text) => {
139
+ const td = document.createElement("td");
140
+ td.appendChild(document.createTextNode(text));
141
+ return td;
142
+ };
143
+
144
+ page_index.querySelector("tbody").appendChild(
145
+ records
146
+ .map(({ path, revision, source, execution_count }) => {
147
+ const hit = execution_count.reduce(
148
+ (accumulator, current) =>
149
+ current > 0 ? accumulator + 1 : accumulator,
150
+ 0
151
+ );
152
+ const miss = execution_count.reduce(
153
+ (accumulator, current) =>
154
+ current === 0 ? accumulator + 1 : accumulator,
155
+ 0
156
+ );
157
+ const relevancy = execution_count.reduce(
158
+ (accumulator, current) =>
159
+ current !== null ? accumulator + 1 : accumulator,
160
+ 0
161
+ );
162
+ const coverage = hit / relevancy;
163
+ return {
164
+ path,
165
+ lines: execution_count.length,
166
+ hit,
167
+ miss,
168
+ relevancy,
169
+ coverage,
170
+ };
171
+ })
172
+ .sort(({ coverage: a }, { coverage: b }) => {
173
+ if (isNaN(b)) return -1;
174
+ if (isNaN(a)) return 1;
175
+ return a - b;
176
+ })
177
+ .reduce(
178
+ (fragment, { path, lines, hit, miss, relevancy, coverage }) => {
179
+ const tr = document.createElement("tr");
180
+ tr.appendChild(
181
+ (() => {
182
+ const anchor = document.createElement("a");
183
+ anchor.href = `#/file/${path}`;
184
+ anchor.appendChild(document.createTextNode(path));
185
+ const td = document.createElement("td");
186
+ td.appendChild(anchor);
187
+ return td;
188
+ })()
189
+ );
190
+ tr.appendChild(createTdTextNode(lines));
191
+ tr.appendChild(createTdTextNode(relevancy));
192
+ tr.appendChild(createTdTextNode(hit));
193
+ tr.appendChild(createTdTextNode(miss));
194
+ tr.appendChild(
195
+ createTdTextNode(Math.round(coverage * 10000) / 100)
196
+ );
197
+ fragment.appendChild(tr);
198
+ return fragment;
199
+ },
200
+ document.createDocumentFragment()
201
+ )
202
+ );
203
+
204
+ document.addEventListener("DOMContentLoaded", () => {
205
+ const routing = route(window.location.hash.slice(1));
206
+ render(routing);
207
+ });
208
+
209
+ window.addEventListener("popstate", (event) => {
210
+ const routing = route(window.location.hash.slice(1));
211
+ render(routing);
212
+ });
213
+ </script>
214
+ </body>
215
+ </html>
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+ require 'json'
5
+
6
+ module CoverRage
7
+ module Reporters
8
+ class HtmlReporter
9
+ def report(records)
10
+ records = JSON.dump(records.map(&:to_h))
11
+ ERB.new(File.read("#{__dir__}/html_reporter/index.html.erb")).result(binding)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module CoverRage
6
+ module Reporters
7
+ class JsonReporter
8
+ def report(records)
9
+ JSON.dump(records.map(&:to_h))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cover_rage/record'
3
4
  require 'redis'
4
5
  require 'json'
5
6
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cover_rage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Weihang Jian
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-07 00:00:00.000000000 Z
11
+ date: 2023-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -24,14 +24,32 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '13.0'
27
- description: coverage recorder
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.18'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.18'
41
+ description: |
42
+ A Ruby production code coverage tool designed to assist you in identifying unused code, offering the following features:
43
+
44
+ 1. easy setup
45
+ 2. minimal performance overhead
46
+ 3. minimal external dependencies
28
47
  email: tonytonyjan@gmail.com
29
48
  executables:
30
49
  - cover_rage
31
50
  extensions: []
32
51
  extra_rdoc_files: []
33
52
  files:
34
- - bin/config.ru
35
53
  - bin/cover_rage
36
54
  - lib/cover_rage.rb
37
55
  - lib/cover_rage/config.rb
@@ -39,12 +57,11 @@ files:
39
57
  - lib/cover_rage/launcher.rb
40
58
  - lib/cover_rage/record.rb
41
59
  - lib/cover_rage/recorder.rb
60
+ - lib/cover_rage/reporters/html_reporter.rb
61
+ - lib/cover_rage/reporters/html_reporter/index.html.erb
62
+ - lib/cover_rage/reporters/json_reporter.rb
42
63
  - lib/cover_rage/stores/redis.rb
43
64
  - lib/cover_rage/stores/sqlite.rb
44
- - lib/cover_rage/viewer.rb
45
- - lib/cover_rage/viewer/file.erb
46
- - lib/cover_rage/viewer/index.erb
47
- - lib/cover_rage/viewer/layout.erb
48
65
  homepage: https://github.com/tonytonyjan/cover_rage
49
66
  licenses:
50
67
  - MIT
@@ -64,8 +81,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
81
  - !ruby/object:Gem::Version
65
82
  version: '0'
66
83
  requirements: []
67
- rubygems_version: 3.4.10
84
+ rubygems_version: 3.4.12
68
85
  signing_key:
69
86
  specification_version: 4
70
- summary: coverage recorder
87
+ summary: A Ruby production code coverage tool
71
88
  test_files: []
data/bin/config.ru DELETED
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'cover_rage/viewer'
4
- require 'cover_rage/config'
5
- run CoverRage::Viewer.new(store: CoverRage::Config.store)
@@ -1,51 +0,0 @@
1
- <link
2
- rel="stylesheet"
3
- href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css"
4
- />
5
- <style>
6
- .line {
7
- width: 100%;
8
- display: inline-block;
9
- counter-increment: line_number;
10
- }
11
-
12
- .green {
13
- background-color: rgb(221, 255, 221);
14
- }
15
-
16
- .red {
17
- background-color: rgb(255, 212, 212);
18
- }
19
-
20
- .number {
21
- display: inline-block;
22
- text-align: right;
23
- margin-right: 0.5em;
24
- background-color: lightgray;
25
- padding: 0 0.5em 0 1.5em;
26
- }
27
- </style>
28
- <h1><%= path %></h1>
29
- <pre><code id="code"><%= source_code.encode(xml: :text) %></code></pre>
30
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
31
- <script id="execution_count" type="application/json"><%= execution_count.encode(xml: :text) %></script>
32
- <script>
33
- const execution_count = JSON.parse(
34
- document.querySelector("#execution_count").childNodes[0].nodeValue
35
- );
36
- const digit_width = Math.floor(Math.log10(Math.max(...execution_count))) + 1;
37
- const $code = document.querySelector("#code");
38
- $code.innerHTML = hljs
39
- .highlight($code.textContent, {
40
- language: "ruby",
41
- })
42
- .value.split("\n")
43
- .map((line, index) => {
44
- const value = execution_count[index];
45
- let color = "";
46
- if (typeof value === "number") color = value > 0 ? "green" : "red";
47
- const number = typeof value === "number" ? value : "-";
48
- return `<span class="line ${color}"><span class="number">${number.toString().padStart(digit_width, ' ')}</span>${line}</span>`;
49
- })
50
- .join("\n");
51
- </script>
@@ -1,28 +0,0 @@
1
- <table>
2
- <thead>
3
- <tr>
4
- <th>path</th>
5
- <th>lines</th>
6
- <th>relevancy</th>
7
- <th>hit</th>
8
- <th>miss</th>
9
- <th>coverage (%)</th>
10
- </tr>
11
- </thead>
12
- <tbody>
13
- <% items.each do |item| %>
14
- <tr>
15
- <td>
16
- <a href="/file?path=<%= URI.encode_uri_component(item.path) %>"
17
- ><%= item.path %></a
18
- >
19
- </td>
20
- <td style="text-align: right"><%= item.number_of_lines %></td>
21
- <td style="text-align: right"><%= item.number_of_relevancies %></td>
22
- <td style="text-align: right"><%= item.number_of_hits %></td>
23
- <td style="text-align: right"><%= item.number_of_misses %></td>
24
- <td style="text-align: right"><%= '%.2f' % (item.coverage * 100) %></td>
25
- </tr>
26
- <% end %>
27
- </tbody>
28
- </table>
@@ -1,9 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>CoverRage</title>
5
- </head>
6
- <body>
7
- <%= yield %>
8
- </body>
9
- </html>
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'cover_rage/record'
4
- require 'erb'
5
- require 'json'
6
-
7
- module CoverRage
8
- class Viewer
9
- LAYOUT_PAGE = ERB.new(File.read("#{__dir__}/viewer/layout.erb"))
10
- INDEX_PAGE = ERB.new(File.read("#{__dir__}/viewer/index.erb"))
11
- FILE_PAGE = ERB.new(File.read("#{__dir__}/viewer/file.erb"))
12
-
13
- class Item
14
- attr_reader(
15
- :path,
16
- :execution_count,
17
- :number_of_lines,
18
- :number_of_hits,
19
- :number_of_misses,
20
- :number_of_relevancies,
21
- :coverage
22
- )
23
-
24
- def initialize(record)
25
- @path = record.path
26
- @execution_count = record.execution_count
27
- @number_of_lines = record.execution_count.length
28
- @number_of_hits = record.execution_count.count { _1&.positive? }
29
- @number_of_misses = record.execution_count.count { _1&.zero? }
30
- @number_of_relevancies = record.execution_count.count { !_1.nil? }
31
- @coverage = @number_of_hits.to_f / @number_of_relevancies
32
- end
33
- end
34
-
35
- def initialize(store:)
36
- @store = store
37
- end
38
-
39
- def call(env)
40
- template =
41
- case env['PATH_INFO']
42
- when '/'
43
- items =
44
- @store
45
- .list
46
- .map! { Item.new(_1) }
47
- .sort! do |a, b|
48
- next -1 if b.coverage.nan?
49
- next 1 if a.coverage.nan?
50
-
51
- a.coverage <=> b.coverage
52
- end
53
- INDEX_PAGE
54
- when '/file'
55
- path = URI.decode_www_form(env['QUERY_STRING']).assoc('path').last
56
-
57
- record = @store.find(path)
58
- return [404, {}, []] if record.nil?
59
-
60
- source_code = record.source
61
- execution_count = JSON.dump(record.execution_count)
62
- FILE_PAGE
63
- when '/clear'
64
- return [405, {}, []] unless env['REQUEST_METHOD'] == 'DELETE'
65
-
66
- @store.clear
67
- return [302, { 'location' => '/' }, []]
68
- else return [404, {}, []]
69
- end
70
- body = layout { template.result(binding) }
71
- [200, {}, [body]]
72
- end
73
-
74
- private
75
-
76
- def layout
77
- LAYOUT_PAGE.result(binding)
78
- end
79
- end
80
- end