minitest-bender 0.0.3 → 1.0.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/CHANGELOG.md +15 -1
- data/Gemfile +1 -1
- data/README.md +37 -6
- data/lib/minitest-bender/colorizer.rb +61 -0
- data/lib/minitest-bender/configuration.rb +174 -0
- data/lib/minitest-bender/printers/plain.rb +29 -0
- data/lib/minitest-bender/printers/with_progress_bar.rb +115 -0
- data/lib/minitest-bender/recorders/grouped_icons.rb +36 -0
- data/lib/minitest-bender/recorders/icons.rb +26 -0
- data/lib/minitest-bender/recorders/none.rb +17 -0
- data/lib/minitest-bender/recorders/progress.rb +25 -0
- data/lib/minitest-bender/recorders/progress_groups.rb +48 -0
- data/lib/minitest-bender/recorders/progress_groups_and_issues.rb +55 -0
- data/lib/minitest-bender/recorders/progress_issues.rb +32 -0
- data/lib/minitest-bender/recorders/progress_verbose.rb +34 -0
- data/lib/minitest-bender/result_context.rb +39 -0
- data/lib/minitest-bender/result_factory.rb +5 -0
- data/lib/minitest-bender/results/base.rb +94 -38
- data/lib/minitest-bender/results/expectation.rb +10 -8
- data/lib/minitest-bender/results/test.rb +21 -8
- data/lib/minitest-bender/sections/activity.rb +95 -0
- data/lib/minitest-bender/sections/issues.rb +22 -0
- data/lib/minitest-bender/sections/sorted_overview.rb +115 -0
- data/lib/minitest-bender/sections/suite_status.rb +72 -0
- data/lib/minitest-bender/sections/time_ranking.rb +49 -0
- data/lib/minitest-bender/states/base.rb +76 -19
- data/lib/minitest-bender/states/failing.rb +11 -8
- data/lib/minitest-bender/states/passing.rb +19 -8
- data/lib/minitest-bender/states/raising.rb +42 -20
- data/lib/minitest-bender/states/skipped.rb +12 -9
- data/lib/minitest-bender/utils.rb +26 -0
- data/lib/minitest-bender/version.rb +1 -1
- data/lib/minitest/bender.rb +166 -77
- data/lib/minitest/bender_plugin.rb +49 -3
- data/lib/minitest_bender.rb +23 -5
- data/minitest-bender.gemspec +6 -6
- metadata +39 -22
@@ -1,18 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
1
4
|
module MinitestBender
|
2
5
|
module States
|
3
6
|
class Failing < Base
|
4
|
-
COLOR = :
|
5
|
-
LABEL = 'FAILED'
|
6
|
-
GROUP_LABEL = 'FAILURES'
|
7
|
+
COLOR = :fail
|
8
|
+
LABEL = 'FAILED'
|
9
|
+
GROUP_LABEL = 'FAILURES'
|
10
|
+
ICON = '✖'
|
7
11
|
|
8
12
|
def formatted_message(result)
|
9
|
-
|
13
|
+
colored(location(result))
|
10
14
|
end
|
11
15
|
|
12
|
-
def summary_message
|
13
|
-
|
14
|
-
|
15
|
-
colored("#{filtered_results.size} failed")
|
16
|
+
def summary_message
|
17
|
+
return '' if results.empty?
|
18
|
+
colored("#{results.size} failed")
|
16
19
|
end
|
17
20
|
end
|
18
21
|
end
|
@@ -1,22 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
1
4
|
module MinitestBender
|
2
5
|
module States
|
3
6
|
class Passing < Base
|
4
|
-
COLOR = :
|
5
|
-
LABEL = 'PASSED'
|
6
|
-
GROUP_LABEL = 'PASSING'
|
7
|
+
COLOR = :pass
|
8
|
+
LABEL = 'PASSED'
|
9
|
+
GROUP_LABEL = 'PASSING'
|
10
|
+
ICON = '✔'
|
7
11
|
|
8
12
|
def formatted_message(_result)
|
9
13
|
''
|
10
14
|
end
|
11
15
|
|
12
|
-
def print_details(_io
|
16
|
+
def print_details(_io)
|
13
17
|
:no_details
|
14
18
|
end
|
15
19
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
def detail_lines(_result)
|
21
|
+
[]
|
22
|
+
end
|
23
|
+
|
24
|
+
def detail_lines_without_header(_result)
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
|
28
|
+
def summary_message
|
29
|
+
return '' if results.empty?
|
30
|
+
colored("#{results.size} passed")
|
20
31
|
end
|
21
32
|
end
|
22
33
|
end
|
@@ -1,46 +1,68 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
1
4
|
module MinitestBender
|
2
5
|
module States
|
3
6
|
class Raising < Base
|
4
|
-
COLOR = :
|
5
|
-
LABEL = 'RAISED'
|
6
|
-
GROUP_LABEL = 'ERRORS'
|
7
|
+
COLOR = :error
|
8
|
+
LABEL = 'RAISED'
|
9
|
+
GROUP_LABEL = 'ERRORS'
|
10
|
+
ICON = '💥'
|
7
11
|
|
8
12
|
def formatted_message(result)
|
9
|
-
|
13
|
+
colored(error_message(result))
|
10
14
|
end
|
11
15
|
|
12
|
-
def summary_message
|
13
|
-
|
14
|
-
|
15
|
-
colored("#{filtered_results.size} raised an error")
|
16
|
+
def summary_message
|
17
|
+
return '' if results.empty?
|
18
|
+
colored("#{results.size} raised an error")
|
16
19
|
end
|
17
20
|
|
18
21
|
def test_location(result)
|
19
|
-
|
20
|
-
Utils.relative_path(backtrace_line).split(':').first
|
22
|
+
Utils.relative_path(result.file_path)
|
21
23
|
end
|
22
24
|
|
23
25
|
private
|
24
26
|
|
25
|
-
def
|
26
|
-
|
27
|
+
def inner_detail_lines(result, padding)
|
28
|
+
lines = []
|
29
|
+
message = colored(error_message(result))
|
30
|
+
lines << "#{padding}#{message.gsub("\n", "\n#{padding}")}"
|
27
31
|
backtrace(result).each do |line|
|
28
|
-
|
32
|
+
adjusted_line = Utils.with_home_shorthand(line)
|
33
|
+
lines << "#{padding}#{Colorizer.colorize(adjusted_line, :backtrace)}"
|
29
34
|
end
|
35
|
+
lines
|
30
36
|
end
|
31
37
|
|
32
38
|
def error_message(result)
|
33
|
-
|
34
|
-
"#{
|
39
|
+
error = result.failures[0].error
|
40
|
+
"#{error.class}: #{error.message}"
|
35
41
|
end
|
36
42
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
43
|
+
def backtrace(result)
|
44
|
+
case backtrace_view
|
45
|
+
when :user
|
46
|
+
user_backtrace(result)
|
47
|
+
when :full
|
48
|
+
full_backtrace(result)
|
49
|
+
else
|
50
|
+
raise "unknown backtrace view: #{backtrace_view}"
|
51
|
+
end
|
40
52
|
end
|
41
53
|
|
42
|
-
def
|
43
|
-
|
54
|
+
def backtrace_view
|
55
|
+
Minitest::Bender.configuration.backtrace_view
|
56
|
+
end
|
57
|
+
|
58
|
+
def user_backtrace(result)
|
59
|
+
full_backtrace(result).take_while do |line|
|
60
|
+
line !~ %r{minitest/test\.rb}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def full_backtrace(result)
|
65
|
+
result.failures[0].backtrace || []
|
44
66
|
end
|
45
67
|
end
|
46
68
|
end
|
@@ -1,20 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
1
4
|
module MinitestBender
|
2
5
|
module States
|
3
6
|
class Skipped < Base
|
4
|
-
COLOR = :
|
5
|
-
LABEL = 'SKIPPED'
|
6
|
-
GROUP_LABEL = 'SKIPS'
|
7
|
+
COLOR = :skip
|
8
|
+
LABEL = 'SKIPPED'
|
9
|
+
GROUP_LABEL = 'SKIPS'
|
10
|
+
ICON = '○'
|
7
11
|
|
8
12
|
def formatted_message(result)
|
9
|
-
|
13
|
+
colored(result.failures[0].message)
|
10
14
|
end
|
11
15
|
|
12
|
-
def summary_message
|
13
|
-
|
14
|
-
|
15
|
-
skipped_count = filtered_results.size
|
16
|
+
def summary_message
|
17
|
+
return '' if results.empty?
|
18
|
+
skipped_count = results.size
|
16
19
|
auxiliary_verb = skipped_count == 1 ? 'was' : 'were'
|
17
|
-
colored("#{
|
20
|
+
colored("#{skipped_count} #{auxiliary_verb} skipped")
|
18
21
|
end
|
19
22
|
end
|
20
23
|
end
|
@@ -1,7 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MinitestBender
|
2
4
|
module Utils
|
3
5
|
def self.relative_path(full_path)
|
4
6
|
full_path.gsub("#{Dir.pwd}/", '')
|
5
7
|
end
|
8
|
+
|
9
|
+
def self.with_home_shorthand(full_path)
|
10
|
+
if ENV['HOME'].to_s.start_with?('/home/')
|
11
|
+
full_path.sub(ENV['HOME'], '~')
|
12
|
+
else
|
13
|
+
full_path
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.english_join(strings)
|
18
|
+
strings.reject(&:empty?).join(', ').gsub(/(.*), /, '\1 and ')
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.with_symbolized_keys(hash)
|
22
|
+
hash.each_with_object({}) do |(k, v), h|
|
23
|
+
h[k.to_sym] = v
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.without_nil_values(hash)
|
28
|
+
hash.each_with_object({}) do |(k, v), h|
|
29
|
+
h[k] = v unless v.nil?
|
30
|
+
end
|
31
|
+
end
|
6
32
|
end
|
7
33
|
end
|
data/lib/minitest/bender.rb
CHANGED
@@ -3,78 +3,130 @@ require 'minitest_bender'
|
|
3
3
|
|
4
4
|
module Minitest
|
5
5
|
class Bender < AbstractReporter
|
6
|
-
|
6
|
+
Colorizer = MinitestBender::Colorizer
|
7
|
+
|
8
|
+
attr_accessor :io, :options
|
9
|
+
attr_reader :previous_context, :results, :results_by_context, :started_at
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def enable!(client_config = {})
|
13
|
+
@is_enabled = true
|
14
|
+
configuration.add_client_config(client_config)
|
15
|
+
Colorizer.custom_colors = configuration.custom_colors
|
16
|
+
end
|
17
|
+
|
18
|
+
def enabled?
|
19
|
+
@is_enabled ||= false
|
20
|
+
end
|
21
|
+
|
22
|
+
def configuration
|
23
|
+
@configuration ||= MinitestBender::Configuration.new
|
24
|
+
end
|
25
|
+
end
|
7
26
|
|
8
27
|
def initialize(io, options = {})
|
9
28
|
@io = io
|
10
29
|
@options = options
|
11
30
|
@previous_context = nil
|
12
31
|
@results = []
|
13
|
-
@
|
32
|
+
@results_by_context = {}
|
33
|
+
@time_ranking_is_relevant = false
|
14
34
|
end
|
15
35
|
|
16
36
|
def start
|
17
37
|
@started_at = Time.now
|
18
38
|
io.puts
|
19
|
-
io.puts
|
20
|
-
io.puts
|
39
|
+
io.puts Colorizer.colorize("Minitest started at #{started_at}", :normal)
|
40
|
+
io.puts Colorizer.colorize("Options: #{options_args}", :normal)
|
21
41
|
io.puts
|
42
|
+
io.flush
|
22
43
|
end
|
23
44
|
|
24
45
|
def record(minitest_result)
|
46
|
+
flush_stdio
|
47
|
+
|
25
48
|
result = MinitestBender.result_factory.create(minitest_result)
|
26
49
|
results << result
|
27
50
|
|
28
51
|
current_context = result.context
|
29
52
|
|
30
53
|
if current_context != previous_context
|
31
|
-
|
32
|
-
|
54
|
+
recorder.print_context_with_results(previous_context, results_by_context[previous_context]) unless previous_context.nil?
|
55
|
+
recorder.print_context(current_context)
|
33
56
|
@previous_context = current_context
|
34
57
|
end
|
35
58
|
|
36
|
-
|
59
|
+
(results_by_context[current_context] ||= []) << result
|
60
|
+
|
61
|
+
@time_ranking_is_relevant = true if result.time > 0.01
|
62
|
+
|
63
|
+
if run_count == total_tests_count
|
64
|
+
recorder.print_context_with_results(current_context, results_by_context[current_context])
|
65
|
+
end
|
37
66
|
|
38
|
-
|
67
|
+
recorder.print_result(result)
|
68
|
+
|
69
|
+
io.flush
|
39
70
|
end
|
40
71
|
|
41
72
|
def passed?
|
42
|
-
passed_count + skipped_count ==
|
73
|
+
passed_count + skipped_count == run_count
|
43
74
|
end
|
44
75
|
|
45
76
|
def report
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
print_details
|
50
|
-
|
51
|
-
if @slowness_podium_is_relevant && passed?
|
52
|
-
print_slowness_podium
|
53
|
-
io.puts
|
77
|
+
if results.empty?
|
78
|
+
print_no_tests_status
|
79
|
+
return
|
54
80
|
end
|
55
81
|
|
56
|
-
print_statistics
|
57
82
|
io.puts
|
83
|
+
io.puts
|
84
|
+
print_divider(:normal)
|
58
85
|
|
59
|
-
|
86
|
+
print_sections
|
60
87
|
end
|
61
88
|
|
62
89
|
private
|
63
90
|
|
64
|
-
def
|
65
|
-
|
91
|
+
def configuration
|
92
|
+
self.class.configuration
|
66
93
|
end
|
67
94
|
|
68
|
-
def
|
69
|
-
|
95
|
+
def flush_stdio
|
96
|
+
# as we might already have some output from the test itself,
|
97
|
+
# make sure we see *all* of it before we report anything
|
98
|
+
STDOUT.flush
|
99
|
+
STDERR.flush
|
70
100
|
end
|
71
101
|
|
72
|
-
def
|
73
|
-
|
102
|
+
def options_args
|
103
|
+
options.fetch(:args, '(none)')
|
74
104
|
end
|
75
105
|
|
76
|
-
def
|
77
|
-
|
106
|
+
def recorder
|
107
|
+
@recorder ||= begin
|
108
|
+
recorder_sym = configuration.recorder
|
109
|
+
case recorder_sym
|
110
|
+
when :progress
|
111
|
+
MinitestBender::Recorders::Progress.new(io, total_tests_count)
|
112
|
+
when :progress_groups
|
113
|
+
MinitestBender::Recorders::ProgressGroups.new(io, total_tests_count)
|
114
|
+
when :progress_issues
|
115
|
+
MinitestBender::Recorders::ProgressIssues.new(io, total_tests_count)
|
116
|
+
when :progress_groups_and_issues
|
117
|
+
MinitestBender::Recorders::ProgressGroupsAndIssues.new(io, total_tests_count)
|
118
|
+
when :progress_verbose
|
119
|
+
MinitestBender::Recorders::ProgressVerbose.new(io, total_tests_count)
|
120
|
+
when :icons
|
121
|
+
MinitestBender::Recorders::Icons.new(io)
|
122
|
+
when :grouped_icons
|
123
|
+
MinitestBender::Recorders::GroupedIcons.new(io)
|
124
|
+
when :none
|
125
|
+
MinitestBender::Recorders::None.new
|
126
|
+
else
|
127
|
+
raise "unknown recorder: #{recorder_sym}"
|
128
|
+
end
|
129
|
+
end
|
78
130
|
end
|
79
131
|
|
80
132
|
def passed_count
|
@@ -85,74 +137,111 @@ module Minitest
|
|
85
137
|
@skipped_count ||= results.count(&:skipped?)
|
86
138
|
end
|
87
139
|
|
88
|
-
def
|
89
|
-
|
90
|
-
end
|
91
|
-
|
92
|
-
def print_divider(color)
|
93
|
-
io.puts(Colorin.public_send(color, ' _______________________').bold)
|
94
|
-
io.puts
|
140
|
+
def run_count
|
141
|
+
results.size
|
95
142
|
end
|
96
143
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
144
|
+
# Minitest should share this with reporters...
|
145
|
+
def total_tests_count
|
146
|
+
@total_tests_count ||= begin
|
147
|
+
filter = options[:filter] || '/./'
|
148
|
+
filter = Regexp.new($1) if filter.is_a?(String) && filter =~ %r%/(.*)/%
|
102
149
|
|
103
|
-
|
104
|
-
|
105
|
-
total_tests = total_tests.chop if test_count == 1
|
106
|
-
formatted_total_tests = Colorin.blue_a700(total_tests)
|
150
|
+
exclude = options[:exclude]
|
151
|
+
exclude = Regexp.new($1) if exclude.is_a?(String) && exclude =~ %r%/(.*)/%
|
107
152
|
|
108
|
-
|
109
|
-
|
110
|
-
|
153
|
+
Minitest::Runnable.runnables.map do |runnable|
|
154
|
+
runnable.runnable_methods.count do |m|
|
155
|
+
(filter === m || filter === "#{runnable}##{m}") &&
|
156
|
+
!(exclude === m || exclude === "#{runnable}##{m}")
|
157
|
+
end
|
158
|
+
end.inject(:+)
|
159
|
+
end
|
160
|
+
end
|
111
161
|
|
112
|
-
|
162
|
+
def print_no_tests_status
|
163
|
+
message = no_tests_message
|
164
|
+
padded_message = " #{message}"
|
165
|
+
io.puts(Colorizer.colorize(padded_message, :tests))
|
166
|
+
print_divider(:tests, message.length)
|
167
|
+
end
|
113
168
|
|
114
|
-
|
115
|
-
|
169
|
+
def no_tests_message
|
170
|
+
'NO TESTS WERE RUN! (-_-)zzz'.freeze
|
171
|
+
end
|
116
172
|
|
117
|
-
|
118
|
-
|
173
|
+
def print_divider(color, line_length = 23)
|
174
|
+
io.puts(Colorizer.colorize(" #{'_' * line_length}", color, :bold))
|
175
|
+
io.puts
|
176
|
+
end
|
119
177
|
|
120
|
-
|
178
|
+
def print_sections
|
179
|
+
sections.each(&:print)
|
180
|
+
end
|
181
|
+
|
182
|
+
def sections
|
183
|
+
section_names.map do |section_name|
|
184
|
+
case section_name
|
185
|
+
when :overview
|
186
|
+
MinitestBender::Sections::SortedOverview.new(io, results_by_context)
|
187
|
+
when :time_ranking
|
188
|
+
MinitestBender::Sections::TimeRanking.new(io, time_ranking_size, results)
|
189
|
+
when :issues
|
190
|
+
MinitestBender::Sections::Issues.new(io)
|
191
|
+
when :activity
|
192
|
+
MinitestBender::Sections::Activity.new(io, started_at, results)
|
193
|
+
when :suite_status
|
194
|
+
MinitestBender::Sections::SuiteStatus.new(io, options, results, total_tests_count)
|
195
|
+
else
|
196
|
+
raise "unknown section: #{section_name}"
|
197
|
+
end
|
198
|
+
end
|
121
199
|
end
|
122
200
|
|
123
|
-
def
|
124
|
-
|
125
|
-
|
201
|
+
def section_names
|
202
|
+
configuration.sections
|
203
|
+
end
|
126
204
|
|
127
|
-
|
128
|
-
|
205
|
+
def time_ranking_size
|
206
|
+
if @time_ranking_is_relevant
|
207
|
+
configuration.time_ranking_size
|
129
208
|
else
|
130
|
-
|
131
|
-
summary_message = state.summary_message(results)
|
132
|
-
final_divider_color = state.color unless summary_message.empty?
|
133
|
-
summary_message
|
134
|
-
end
|
135
|
-
|
136
|
-
message = " #{messages.reject(&:empty?).join(', ').gsub(/(.*), /, '\1 and ')}"
|
209
|
+
0
|
137
210
|
end
|
138
|
-
io.puts(message)
|
139
|
-
|
140
|
-
print_divider(final_divider_color)
|
141
211
|
end
|
212
|
+
end
|
142
213
|
|
143
|
-
|
144
|
-
|
214
|
+
##
|
215
|
+
# Compatibility with
|
216
|
+
# [minitest-reporters](https://github.com/kern/minitest-reporters)
|
217
|
+
#
|
218
|
+
# Given:
|
219
|
+
#
|
220
|
+
# ```
|
221
|
+
# require 'minitest/reporters'
|
222
|
+
# Minitest::Reporters.use!
|
223
|
+
# ```
|
224
|
+
#
|
225
|
+
# Bender can be selected with:
|
226
|
+
#
|
227
|
+
# ```
|
228
|
+
# MINITEST_REPORTER=BenderReporter rake test
|
229
|
+
# ```
|
230
|
+
|
231
|
+
module Reporters
|
232
|
+
class BenderReporter < Minitest::Bender
|
233
|
+
def initialize(options = {})
|
234
|
+
super(options.fetch(:io, $stdout), options)
|
235
|
+
Minitest::Bender.enable!
|
236
|
+
end
|
145
237
|
|
146
|
-
|
147
|
-
|
148
|
-
results.take(3).each_with_index do |result, i|
|
149
|
-
number = "#{i + 1})".ljust(4)
|
150
|
-
io.puts " #{number}#{result.line_for_slowness_podium}"
|
238
|
+
def add_defaults(defaults)
|
239
|
+
@options = defaults.merge(options)
|
151
240
|
end
|
152
|
-
end
|
153
241
|
|
154
|
-
|
155
|
-
|
242
|
+
def before_test(_test_cls); end
|
243
|
+
|
244
|
+
def after_test(_test_cls); end
|
156
245
|
end
|
157
246
|
end
|
158
247
|
end
|