minitest-reporters 1.0.18 → 1.0.19
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/README.md +1 -1
- data/Rakefile +2 -1
- data/lib/minitest/reporters.rb +1 -0
- data/lib/minitest/reporters/html_reporter.rb +212 -0
- data/lib/minitest/reporters/version.rb +1 -1
- data/lib/minitest/templates/index.html.erb +83 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c79f88a67d61dda24ebe17a89046fe86046e101a
|
4
|
+
data.tar.gz: 26cec6b5fab44023ba5e4ceb71e9554ebc9a53ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d089e802175a8ba1d677f7e3710ca6f08771a94a4bbeb5d2bb83b6349b782ebde0c8ffe3546826f313a677e86aa87177e0e119ad7caeb5101c543a81c345296
|
7
|
+
data.tar.gz: ccf2cd2b9dc9e473c60d4b4be0ddb2b78b9118354eac249ba855e4045f222853a1ec6082952055926932b8cdf2afaee30effb468a9c9edee2ceaf7b170e69e0c
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
[gem]: https://rubygems.org/gems/minitest-reporters
|
2
|
-
[travis]: https://travis-ci.org/
|
2
|
+
[travis]: https://travis-ci.org/kern/minitest-reporters
|
3
3
|
|
4
4
|
# minitest-reporters - create customizable Minitest output formats
|
5
5
|
[][gem]
|
data/Rakefile
CHANGED
data/lib/minitest/reporters.rb
CHANGED
@@ -15,6 +15,7 @@ module Minitest
|
|
15
15
|
autoload :RubyMateReporter, "minitest/reporters/ruby_mate_reporter"
|
16
16
|
autoload :RubyMineReporter, "minitest/reporters/rubymine_reporter"
|
17
17
|
autoload :JUnitReporter, "minitest/reporters/junit_reporter"
|
18
|
+
autoload :HtmlReporter, "minitest/reporters/html_reporter"
|
18
19
|
|
19
20
|
class << self
|
20
21
|
attr_accessor :reporters
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'builder'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module Minitest
|
6
|
+
module Reporters
|
7
|
+
# A reporter for generating HTML test reports
|
8
|
+
# This is recommended to be used with a CI server, where the report is kept as an artifact and is accessible via a shared link
|
9
|
+
#
|
10
|
+
# The reporter sorts the results alphabetically and then by results so that failing and skipped tests are at the top.
|
11
|
+
#
|
12
|
+
# When using Minitest Specs, the number prefix is dropped from the name of the test so that it reads well
|
13
|
+
#
|
14
|
+
# On each test run all files in the reports directory are deleted, this prevents a build up of old reports
|
15
|
+
#
|
16
|
+
# The report is generated using ERB. A custom ERB template can be provided but it is not required
|
17
|
+
# The default ERB template uses JQuery and Bootstrap, both of these are included by referencing the CDN sites
|
18
|
+
class HtmlReporter < BaseReporter
|
19
|
+
|
20
|
+
# The title of the report
|
21
|
+
attr_reader :title
|
22
|
+
|
23
|
+
# The number of tests that passed
|
24
|
+
def passes
|
25
|
+
count - failures - errors - skips
|
26
|
+
end
|
27
|
+
|
28
|
+
# The percentage of tests that passed, calculated in a way that avoids rounding errors
|
29
|
+
def percent_passes
|
30
|
+
100 - percent_skipps - percent_errors_failures
|
31
|
+
end
|
32
|
+
|
33
|
+
# The percentage of tests that were skipped
|
34
|
+
def percent_skipps
|
35
|
+
(skips/count.to_f * 100).to_i
|
36
|
+
end
|
37
|
+
|
38
|
+
# The percentage of tests that failed
|
39
|
+
def percent_errors_failures
|
40
|
+
((errors+failures)/count.to_f * 100).to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
# Trims off the number prefix on test names when using Minitest Specs
|
44
|
+
def friendly_name(test)
|
45
|
+
groups = test.name.scan(/(test_\d+_)(.*)/i)
|
46
|
+
return test.name if groups.empty?
|
47
|
+
"it #{groups[0][1]}"
|
48
|
+
end
|
49
|
+
|
50
|
+
# The constructor takes a hash, and uses the following keys:
|
51
|
+
# :title - the title that will be used in the report, defaults to 'Test Results'
|
52
|
+
# :reports_dir - the directory the reports should be written to, defaults to 'test/html_reports'
|
53
|
+
# :erb_template - the path to a custom ERB template, defaults to the supplied ERB template
|
54
|
+
# :mode - Useful for debugging, :terse suppresses errors and is the default, :verbose lets errors bubble up
|
55
|
+
def initialize(args = {})
|
56
|
+
super({})
|
57
|
+
|
58
|
+
defaults = {
|
59
|
+
:title => 'Test Results',
|
60
|
+
:erb_template => "#{File.dirname(__FILE__)}/../templates/index.html.erb",
|
61
|
+
:reports_dir => 'test/html_reports',
|
62
|
+
:mode => :safe
|
63
|
+
}
|
64
|
+
|
65
|
+
settings = defaults.merge(args)
|
66
|
+
|
67
|
+
@mode = settings[:mode]
|
68
|
+
@title = settings[:title]
|
69
|
+
@erb_template = settings[:erb_template]
|
70
|
+
reports_dir = settings[:reports_dir]
|
71
|
+
|
72
|
+
@reports_path = File.absolute_path(reports_dir)
|
73
|
+
|
74
|
+
puts "Emptying #{@reports_path}"
|
75
|
+
FileUtils.remove_dir(@reports_path) if File.exists?(@reports_path)
|
76
|
+
FileUtils.mkdir_p(@reports_path)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Called by the framework to generate the report
|
80
|
+
def report
|
81
|
+
super
|
82
|
+
|
83
|
+
begin
|
84
|
+
puts "Writing HTML reports to #{@reports_path}"
|
85
|
+
html_file = @reports_path + "/index.html"
|
86
|
+
erb_str = File.read(@erb_template)
|
87
|
+
renderer = ERB.new(erb_str)
|
88
|
+
|
89
|
+
tests_by_suites = tests.group_by(&:class) # taken from the JUnit reporter
|
90
|
+
|
91
|
+
suites = tests_by_suites.map do |suite, tests|
|
92
|
+
suite_summary = summarize_suite(suite, tests)
|
93
|
+
suite_summary[:tests] = tests.sort { |a, b| compare_tests(a, b) }
|
94
|
+
suite_summary
|
95
|
+
end
|
96
|
+
|
97
|
+
suites.sort! { |a, b| compare_suites(a, b) }
|
98
|
+
|
99
|
+
result = renderer.result(binding)
|
100
|
+
File.open(html_file, 'w') do |f|
|
101
|
+
f.write(result)
|
102
|
+
end
|
103
|
+
|
104
|
+
rescue Exception => e
|
105
|
+
puts 'There was an error writing the HTML report'
|
106
|
+
puts 'This may have been caused by cancelling the test run'
|
107
|
+
puts 'Use mode => :verbose in the HTML reporters constructor to see more detail' if @mode == :terse
|
108
|
+
puts 'Use mode => :terse in the HTML reporters constructor to see less detail' if @mode != :terse
|
109
|
+
raise e if @mode != :terse
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def compare_suites_by_name(suite_a, suite_b)
|
117
|
+
suite_a[:name] <=> suite_b[:name]
|
118
|
+
end
|
119
|
+
|
120
|
+
def compare_tests_by_name(test_a, test_b)
|
121
|
+
friendly_name(test_a) <=> friendly_name(test_b)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Test suites are first ordered by evaluating the results of the tests, then by test suite name
|
125
|
+
# Test suites which have failing tests are given highest order
|
126
|
+
# Tests suites which have skipped tests are given second highest priority
|
127
|
+
def compare_suites(suite_a, suite_b)
|
128
|
+
return compare_suites_by_name(suite_a, suite_b) if suite_a[:has_errors_or_failures] && suite_b[:has_errors_or_failures]
|
129
|
+
return -1 if suite_a[:has_errors_or_failures] && !suite_b[:has_errors_or_failures]
|
130
|
+
return 1 if !suite_a[:has_errors_or_failures] && suite_b[:has_errors_or_failures]
|
131
|
+
|
132
|
+
return compare_suites_by_name(suite_a, suite_b) if suite_a[:has_skipps] && suite_b[:has_skipps]
|
133
|
+
return -1 if suite_a[:has_skipps] && !suite_b[:has_skipps]
|
134
|
+
return 1 if !suite_a[:has_skipps] && suite_b[:has_skipps]
|
135
|
+
|
136
|
+
compare_suites_by_name(suite_a, suite_b)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Tests are first ordered by evaluating the results of the tests, then by tests names
|
140
|
+
# Tess which fail are given highest order
|
141
|
+
# Tests which are skipped are given second highest priority
|
142
|
+
def compare_tests(test_a, test_b)
|
143
|
+
return compare_tests_by_name(test_a, test_b) if test_fail_or_error?(test_a) && test_fail_or_error?(test_b)
|
144
|
+
|
145
|
+
return -1 if test_fail_or_error?(test_a) && !test_fail_or_error?(test_b)
|
146
|
+
return 1 if !test_fail_or_error?(test_a) && test_fail_or_error?(test_b)
|
147
|
+
|
148
|
+
return compare_tests_by_name(test_a, test_b) if test_a.skipped? && test_b.skipped?
|
149
|
+
return -1 if test_a.skipped? && !test_b.skipped?
|
150
|
+
return 1 if !test_a.skipped? && test_b.skipped?
|
151
|
+
|
152
|
+
compare_tests_by_name(test_a, test_b)
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_fail_or_error?(test)
|
156
|
+
test.error? || test.failure
|
157
|
+
end
|
158
|
+
|
159
|
+
# based on analyze_suite from the JUnit reporter
|
160
|
+
def summarize_suite(suite, tests)
|
161
|
+
summary = Hash.new(0)
|
162
|
+
summary[:name] = suite.to_s
|
163
|
+
tests.each do |test|
|
164
|
+
summary[:"#{result(test)}_count"] += 1
|
165
|
+
summary[:assertion_count] += test.assertions
|
166
|
+
summary[:test_count] += 1
|
167
|
+
summary[:time] += test.time
|
168
|
+
end
|
169
|
+
summary[:has_errors_or_failures] = (summary[:fail_count] + summary[:error_count]) > 0
|
170
|
+
summary[:has_skipps] = summary[:skip_count] > 0
|
171
|
+
summary
|
172
|
+
end
|
173
|
+
|
174
|
+
# based on message_for(test) from the JUnit reporter
|
175
|
+
def message_for(test)
|
176
|
+
suite = test.class
|
177
|
+
name = test.name
|
178
|
+
e = test.failure
|
179
|
+
|
180
|
+
if test.passed?
|
181
|
+
nil
|
182
|
+
elsif test.skipped?
|
183
|
+
"Skipped:\n#{name}(#{suite}) [#{location(e)}]:\n#{e.message}\n"
|
184
|
+
elsif test.failure
|
185
|
+
"Failure:\n#{name}(#{suite}) [#{location(e)}]:\n#{e.message}\n"
|
186
|
+
elsif test.error?
|
187
|
+
"Error:\n#{name}(#{suite}):\n#{e.message}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# taken from the JUnit reporter
|
192
|
+
def location(exception)
|
193
|
+
last_before_assertion = ''
|
194
|
+
exception.backtrace.reverse_each do |s|
|
195
|
+
break if s =~ /in .(assert|refute|flunk|pass|fail|raise|must|wont)/
|
196
|
+
last_before_assertion = s
|
197
|
+
end
|
198
|
+
last_before_assertion.sub(/:in .*$/, '')
|
199
|
+
end
|
200
|
+
|
201
|
+
def total_time_to_hms
|
202
|
+
return ('%.2fs' % total_time) if total_time < 1
|
203
|
+
|
204
|
+
hours = total_time / (60 * 60)
|
205
|
+
minutes = ((total_time / 60) % 60).to_s.rjust(2,'0')
|
206
|
+
seconds = (total_time % 60).to_s.rjust(2,'0')
|
207
|
+
|
208
|
+
"#{ hours }h#{ minutes }m#{ seconds }s"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<title><%= title %></title>
|
5
|
+
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
|
6
|
+
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
7
|
+
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
|
8
|
+
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
|
9
|
+
</head>
|
10
|
+
|
11
|
+
<body>
|
12
|
+
<div class="container">
|
13
|
+
<div class="jumbotron">
|
14
|
+
<h1><%= title %></h1>
|
15
|
+
<p>
|
16
|
+
Finished in <%= total_time_to_hms %>, <%= '%.2f tests/s' % (count / total_time) %>, <%= '%.2f assertions/s' % (assertions / total_time) %>
|
17
|
+
</p>
|
18
|
+
<p>
|
19
|
+
<strong>
|
20
|
+
<span class="<%= 'text-info' if (failures == 0 && errors == 0) %>"><%= '%d' % count %> tests</span>,
|
21
|
+
<span class="<%= 'text-info' if (failures == 0 && errors == 0) %>"> <%= '%d' % assertions %> assertions</span>,
|
22
|
+
<span class="<%= 'text-danger' if failures > 0 %>"> <%= '%d' % failures %> failures</span>,
|
23
|
+
<span class="<%= 'text-danger' if errors > 0 %>"> <%= '%d' % errors %> errors</span>,
|
24
|
+
<span class="<%= 'text-warning' if skips > 0 %>"> <%= '%d' % skips %> skips</span>
|
25
|
+
</strong>
|
26
|
+
</p>
|
27
|
+
|
28
|
+
<div class="progress">
|
29
|
+
<div class="progress-bar progress-bar-info" style="width: <%= percent_passes %>%">
|
30
|
+
<%= '%d' % percent_passes %>% passed
|
31
|
+
</div>
|
32
|
+
<div class="progress-bar progress-bar-danger" style="width: <%= percent_errors_failures %>%">
|
33
|
+
<%= '%d' % percent_errors_failures %>% failed
|
34
|
+
</div>
|
35
|
+
<div class="progress-bar progress-bar-warning" style="width: <%= percent_skipps %>%">
|
36
|
+
<%= '%d' % percent_skipps %>% skipped
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
|
40
|
+
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<% suites.each do |suite| %>
|
44
|
+
<div class="panel panel-default">
|
45
|
+
<div class="panel-heading"><strong><%= suite[:name] %></strong>
|
46
|
+
<span class="pull-right">
|
47
|
+
<span class="<%= 'text-info' if (suite[:fail_count] == 0 && suite[:error_count] == 0) %>"><%= '%d' % suite[:test_count] %> tests</span>,
|
48
|
+
<span class="<%= 'text-info' if (failures == 0 && errors == 0) %>"> <%= '%d' % suite[:assertion_count] %> assertions</span>,
|
49
|
+
<span class="<%= 'text-danger' if suite[:fail_count] > 0 %>"> <%= '%d' % suite[:fail_count] %> failures</span>,
|
50
|
+
<span class="<%= 'text-danger' if suite[:error_count] > 0 %>"> <%= '%d' % suite[:error_count] %> errors</span>,
|
51
|
+
<span class="<%= 'text-warning' if suite[:skip_count] > 0 %>"> <%= '%d' % suite[:skip_count] %> skips</span>,
|
52
|
+
<span> finished in <%= '%.4fs' % suite[:time] %></span>
|
53
|
+
</span>
|
54
|
+
</div>
|
55
|
+
<div class="panel-body">
|
56
|
+
<div class="list-group">
|
57
|
+
<% suite[:tests].each do |test| %>
|
58
|
+
<div class="list-group-item">
|
59
|
+
<h5 class="list-group-item-heading">
|
60
|
+
<% if result(test) == :pass %>
|
61
|
+
<span class="glyphicon glyphicon-ok text-info" aria-hidden="true"></span>
|
62
|
+
<% elsif result(test) == :skip %>
|
63
|
+
<span class="glyphicon glyphicon-ban-circle text-warning" aria-hidden="true"></span>
|
64
|
+
<% else %>
|
65
|
+
<span class="glyphicon glyphicon-remove text-danger" aria-hidden="true"></span>
|
66
|
+
<% end %>
|
67
|
+
<%= friendly_name(test) %>
|
68
|
+
<span class="pull-right">
|
69
|
+
Assertions <%= test.assertions %>, time <%= ('%.6fs' % test.time) %>
|
70
|
+
</span>
|
71
|
+
</h5>
|
72
|
+
<% if !test.passed? %>
|
73
|
+
<pre class="list-group-item-text"><%= "#{location(test.failure)}\n\n#{test.failure.message}" %></pre>
|
74
|
+
<% end %>
|
75
|
+
</div>
|
76
|
+
<% end %>
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
</div>
|
80
|
+
<% end %>
|
81
|
+
</div>
|
82
|
+
</body>
|
83
|
+
</html>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minitest-reporters
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.19
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Kern
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-07-
|
11
|
+
date: 2015-07-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -120,12 +120,14 @@ files:
|
|
120
120
|
- lib/minitest/reporters/ansi.rb
|
121
121
|
- lib/minitest/reporters/base_reporter.rb
|
122
122
|
- lib/minitest/reporters/default_reporter.rb
|
123
|
+
- lib/minitest/reporters/html_reporter.rb
|
123
124
|
- lib/minitest/reporters/junit_reporter.rb
|
124
125
|
- lib/minitest/reporters/progress_reporter.rb
|
125
126
|
- lib/minitest/reporters/ruby_mate_reporter.rb
|
126
127
|
- lib/minitest/reporters/rubymine_reporter.rb
|
127
128
|
- lib/minitest/reporters/spec_reporter.rb
|
128
129
|
- lib/minitest/reporters/version.rb
|
130
|
+
- lib/minitest/templates/index.html.erb
|
129
131
|
- minitest-reporters.gemspec
|
130
132
|
- test/fixtures/junit_filename_bug_example_test.rb
|
131
133
|
- test/fixtures/progress_detailed_skip_test.rb
|