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,26 +1,28 @@
|
|
1
1
|
module MinitestBender
|
2
2
|
module Results
|
3
3
|
class Expectation < Base
|
4
|
+
attr_reader :name
|
5
|
+
|
4
6
|
def initialize(minitest_result, number, name)
|
5
7
|
super(minitest_result)
|
6
8
|
@number = number
|
7
9
|
@name = name
|
8
10
|
end
|
9
11
|
|
10
|
-
def
|
11
|
-
"
|
12
|
+
def formatted_number(_sorted_siblings = nil)
|
13
|
+
" #{Colorizer.colorize(number, :number)} "
|
12
14
|
end
|
13
15
|
|
14
|
-
|
16
|
+
def number_sort_key
|
17
|
+
@number_sort_key ||= number.to_i
|
18
|
+
end
|
15
19
|
|
16
|
-
|
20
|
+
private
|
17
21
|
|
18
|
-
|
19
|
-
"#{Colorin.brown_400(number)} "
|
20
|
-
end
|
22
|
+
attr_reader :number
|
21
23
|
|
22
24
|
def name_for_rerun_command
|
23
|
-
"/#{name.gsub(
|
25
|
+
"/#{name.gsub(/[^a-zA-Z0-9_\-{}#@]/, '.')}$/"
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
@@ -6,17 +6,22 @@ module MinitestBender
|
|
6
6
|
@raw_name = raw_name
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
10
|
-
|
11
|
-
end
|
9
|
+
def formatted_number(sorted_siblings = nil)
|
10
|
+
return '' if sorted_siblings.nil?
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
number = sorted_siblings.find_index do |result|
|
13
|
+
result.source_line_number > source_line_number
|
14
|
+
end || sorted_siblings.size
|
15
|
+
# this is never 0 because sorted_siblings includes self
|
16
16
|
|
17
|
-
|
17
|
+
padded_number = number.to_s.rjust(4, '0')
|
18
18
|
|
19
|
-
|
19
|
+
" #{Colorizer.colorize(padded_number, :number)} "
|
20
|
+
end
|
21
|
+
|
22
|
+
def number_sort_key
|
23
|
+
source_line_number
|
24
|
+
end
|
20
25
|
|
21
26
|
def name
|
22
27
|
@name ||= begin
|
@@ -28,6 +33,14 @@ module MinitestBender
|
|
28
33
|
end
|
29
34
|
end
|
30
35
|
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :raw_name
|
39
|
+
|
40
|
+
def adjusted_class_name
|
41
|
+
class_name.gsub(/^Test|Test$/, '')
|
42
|
+
end
|
43
|
+
|
31
44
|
def name_for_rerun_command
|
32
45
|
minitest_result.name
|
33
46
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module MinitestBender
|
2
|
+
module Sections
|
3
|
+
class Activity
|
4
|
+
TIME_UNITS = %w[day hour minute second].freeze
|
5
|
+
|
6
|
+
def initialize(io, started_at, results)
|
7
|
+
@io = io
|
8
|
+
@started_at = started_at
|
9
|
+
@results = results
|
10
|
+
end
|
11
|
+
|
12
|
+
def print
|
13
|
+
io.puts " #{formatted_total_tests} with #{formatted_total_assertions} #{auxiliary_verb} run in #{formatted_total_time} (#{formatted_tests_rate}, #{formatted_assertions_rate})"
|
14
|
+
io.puts
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :io, :started_at, :results
|
20
|
+
|
21
|
+
def formatted_total_tests
|
22
|
+
Colorizer.colorize(total_tests, :tests)
|
23
|
+
end
|
24
|
+
|
25
|
+
def formatted_total_assertions
|
26
|
+
Colorizer.colorize(total_assertions, :assertions)
|
27
|
+
end
|
28
|
+
|
29
|
+
def auxiliary_verb
|
30
|
+
test_count == 1 ? 'was' : 'were'
|
31
|
+
end
|
32
|
+
|
33
|
+
def formatted_total_time
|
34
|
+
Colorizer.colorize(total_time_string, :time)
|
35
|
+
end
|
36
|
+
|
37
|
+
def formatted_tests_rate
|
38
|
+
Colorizer.colorize("#{tests_rate} tests/s", :time)
|
39
|
+
end
|
40
|
+
|
41
|
+
def formatted_assertions_rate
|
42
|
+
Colorizer.colorize("#{assertions_rate} assertions/s", :time)
|
43
|
+
end
|
44
|
+
|
45
|
+
def tests_rate
|
46
|
+
(test_count / total_time).round(4)
|
47
|
+
end
|
48
|
+
|
49
|
+
def assertions_rate
|
50
|
+
(assertion_count / total_time).round(4)
|
51
|
+
end
|
52
|
+
|
53
|
+
def total_tests
|
54
|
+
"#{test_count} test#{test_count == 1 ? '' : 's'}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def total_assertions
|
58
|
+
"#{assertion_count} assertion#{assertion_count == 1 ? '' : 's'}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_count
|
62
|
+
results.size
|
63
|
+
end
|
64
|
+
|
65
|
+
def assertion_count
|
66
|
+
@assertion_count ||= results.reduce(0) { |acum, result| acum + result.assertions }
|
67
|
+
end
|
68
|
+
|
69
|
+
def total_time_string
|
70
|
+
minutes, seconds = total_time.divmod(60)
|
71
|
+
hours, minutes = minutes.divmod(60)
|
72
|
+
days, hours = hours.divmod(24)
|
73
|
+
|
74
|
+
values = [days, hours, minutes]
|
75
|
+
seconds_decimals = values.all?(&:zero?) ? 3 : 0
|
76
|
+
values.push(seconds.round(seconds_decimals))
|
77
|
+
|
78
|
+
values_with_units = values.map.with_index do |value, index|
|
79
|
+
unit = time_units[index]
|
80
|
+
value > 0 ? "#{value} #{unit}#{value.to_s == '1' ? '' : 's'}" : ''
|
81
|
+
end
|
82
|
+
|
83
|
+
Utils.english_join(values_with_units)
|
84
|
+
end
|
85
|
+
|
86
|
+
def total_time
|
87
|
+
@total_time ||= Time.now - started_at
|
88
|
+
end
|
89
|
+
|
90
|
+
def time_units
|
91
|
+
TIME_UNITS
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module MinitestBender
|
2
|
+
module Sections
|
3
|
+
class Issues
|
4
|
+
def initialize(io)
|
5
|
+
@io = io
|
6
|
+
end
|
7
|
+
|
8
|
+
def print
|
9
|
+
symbols = states.map { |state| state.print_details(io) }
|
10
|
+
io.puts unless symbols.all? { |symbol| symbol == :no_details }
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader :io
|
16
|
+
|
17
|
+
def states
|
18
|
+
MinitestBender.states.values
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module MinitestBender
|
2
|
+
module Sections
|
3
|
+
class SortedOverview
|
4
|
+
def initialize(io, results_by_context)
|
5
|
+
@io = io
|
6
|
+
@contexts_with_results = sorted_pairs(results_by_context)
|
7
|
+
end
|
8
|
+
|
9
|
+
def print
|
10
|
+
return if trivial?
|
11
|
+
|
12
|
+
io.puts(formatted_label)
|
13
|
+
io.puts
|
14
|
+
previous_context_path = []
|
15
|
+
contexts_with_results.each do |context, results|
|
16
|
+
io.puts
|
17
|
+
print_context(context, previous_context_path)
|
18
|
+
previous_context_path = context.path
|
19
|
+
words = []
|
20
|
+
results.sort_by(&sort_key).each do |result|
|
21
|
+
words = print_result(result, words, results)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
io.puts
|
25
|
+
print_divider
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :io, :contexts_with_results
|
31
|
+
|
32
|
+
def sorted_pairs(results_by_context)
|
33
|
+
results_by_context.map do |context, results|
|
34
|
+
[context, results.sort_by(&:source_location)]
|
35
|
+
end.sort
|
36
|
+
end
|
37
|
+
|
38
|
+
def trivial?
|
39
|
+
results.size < 2
|
40
|
+
end
|
41
|
+
|
42
|
+
def results
|
43
|
+
contexts_with_results.map(&:last).flatten
|
44
|
+
end
|
45
|
+
|
46
|
+
def formatted_label
|
47
|
+
" #{Colorizer.colorize('SORTED OVERVIEW', :normal, :bold, :underline)}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def print_context(result_context, previous_context_path)
|
51
|
+
formatted_context = formatted_old_and_new(
|
52
|
+
previous_context_path,
|
53
|
+
result_context.path,
|
54
|
+
result_context.separator
|
55
|
+
)
|
56
|
+
|
57
|
+
io.puts(result_context.prefix + formatted_context)
|
58
|
+
end
|
59
|
+
|
60
|
+
def sort_key
|
61
|
+
@sort_key ||= "#{Minitest::Bender.configuration.overview_sort_key}_sort_key".to_sym
|
62
|
+
end
|
63
|
+
|
64
|
+
def print_result(result, previous_words, sorted_siblings)
|
65
|
+
formatted_number = result.formatted_number(sorted_siblings)
|
66
|
+
|
67
|
+
prefix = " #{result.formatted_label_and_time}#{formatted_number}"
|
68
|
+
words = result.name.split(' ')
|
69
|
+
|
70
|
+
formatted_words = formatted_old_and_new(previous_words, words, ' ')
|
71
|
+
|
72
|
+
formatted_message = result.formatted_message
|
73
|
+
if formatted_message.empty?
|
74
|
+
details = ''
|
75
|
+
else
|
76
|
+
details = " #{formatted_message.split("\n").first}"
|
77
|
+
end
|
78
|
+
|
79
|
+
io.puts("#{prefix} #{formatted_words}#{details}")
|
80
|
+
words
|
81
|
+
end
|
82
|
+
|
83
|
+
def formatted_old_and_new(previous, current, separator)
|
84
|
+
old_part, new_part = old_and_new(previous, current)
|
85
|
+
|
86
|
+
old_part_string = old_part.join(separator)
|
87
|
+
old_part_string << separator unless old_part_string.empty?
|
88
|
+
new_part_string = new_part.join(separator)
|
89
|
+
|
90
|
+
formatted_old = Colorizer.colorize(old_part_string, :normal)
|
91
|
+
formatted_new = Colorizer.colorize(new_part_string, :normal, :bold)
|
92
|
+
|
93
|
+
"#{formatted_old}#{formatted_new}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def old_and_new(previous, current)
|
97
|
+
cut_index = first_difference_index(previous, current) || previous.size
|
98
|
+
cut_at(current, cut_index)
|
99
|
+
end
|
100
|
+
|
101
|
+
def first_difference_index(xs, ys)
|
102
|
+
xs.find_index.with_index { |x, i| x != ys[i] }
|
103
|
+
end
|
104
|
+
|
105
|
+
def cut_at(xs, i)
|
106
|
+
[xs.take(i), xs.drop(i)]
|
107
|
+
end
|
108
|
+
|
109
|
+
def print_divider
|
110
|
+
io.puts(Colorizer.colorize(" #{'_' * 23}", :normal, :bold))
|
111
|
+
io.puts
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module MinitestBender
|
2
|
+
module Sections
|
3
|
+
class SuiteStatus
|
4
|
+
def initialize(io, options, results, total_tests_count)
|
5
|
+
@io = io
|
6
|
+
@options = options
|
7
|
+
@results = results
|
8
|
+
@total_tests_count = total_tests_count
|
9
|
+
end
|
10
|
+
|
11
|
+
def print
|
12
|
+
final_divider_color = all_passed_color
|
13
|
+
|
14
|
+
if all_tests_passed?
|
15
|
+
message = Colorizer.colorize(' ALL TESTS PASS! (^_^)/', all_passed_color)
|
16
|
+
else
|
17
|
+
messages = MinitestBender.states.values.map do |state|
|
18
|
+
summary_message = state.summary_message
|
19
|
+
final_divider_color = state.color unless summary_message.empty?
|
20
|
+
summary_message
|
21
|
+
end
|
22
|
+
|
23
|
+
message = " #{Utils.english_join(messages)}"
|
24
|
+
end
|
25
|
+
io.puts(message)
|
26
|
+
|
27
|
+
print_divider(final_divider_color)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :io, :options, :results, :total_tests_count
|
33
|
+
|
34
|
+
def all_passed_color
|
35
|
+
:pass
|
36
|
+
end
|
37
|
+
|
38
|
+
def all_tests_passed?
|
39
|
+
all_tests_were_run? && all_run_tests_passed?
|
40
|
+
end
|
41
|
+
|
42
|
+
def all_tests_were_run?
|
43
|
+
!restricted_run? && !interrupted?
|
44
|
+
end
|
45
|
+
|
46
|
+
def restricted_run?
|
47
|
+
(options.key?(:filter) && options[:filter] != '/./') || options.key?(:exclude)
|
48
|
+
end
|
49
|
+
|
50
|
+
def interrupted?
|
51
|
+
test_count < total_tests_count
|
52
|
+
end
|
53
|
+
|
54
|
+
def all_run_tests_passed?
|
55
|
+
test_count == passed_count
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_count
|
59
|
+
results.size
|
60
|
+
end
|
61
|
+
|
62
|
+
def passed_count
|
63
|
+
@passed_count ||= results.count(&:passed?)
|
64
|
+
end
|
65
|
+
|
66
|
+
def print_divider(color)
|
67
|
+
io.puts(Colorizer.colorize(" #{'_' * 23}", color, :bold))
|
68
|
+
io.puts
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module MinitestBender
|
2
|
+
module Sections
|
3
|
+
class TimeRanking
|
4
|
+
def initialize(io, size, results)
|
5
|
+
@io = io
|
6
|
+
@size = size
|
7
|
+
@results = results
|
8
|
+
end
|
9
|
+
|
10
|
+
def print
|
11
|
+
return if trivial?
|
12
|
+
|
13
|
+
io.puts(formatted_label)
|
14
|
+
io.puts
|
15
|
+
sorted_results_to_show.each_with_index do |result, i|
|
16
|
+
number = "#{i + 1})".ljust(4)
|
17
|
+
io.puts " #{number}#{result.formatted_time} #{result.formatted_name_with_context}"
|
18
|
+
end
|
19
|
+
print_divider
|
20
|
+
io.puts
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :io, :size, :results
|
26
|
+
|
27
|
+
def trivial?
|
28
|
+
size < 1 || results.size < 2
|
29
|
+
end
|
30
|
+
|
31
|
+
def formatted_label
|
32
|
+
" #{Colorizer.colorize('TIME RANKING', :time, :bold, :underline)}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def sorted_results_to_show
|
36
|
+
sorted_results.take(size)
|
37
|
+
end
|
38
|
+
|
39
|
+
def sorted_results
|
40
|
+
results.sort_by { |r| -r.time }
|
41
|
+
end
|
42
|
+
|
43
|
+
def print_divider
|
44
|
+
io.puts(Colorizer.colorize(" #{'_' * 23}", :normal, :bold))
|
45
|
+
io.puts
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,33 +1,81 @@
|
|
1
1
|
module MinitestBender
|
2
2
|
module States
|
3
3
|
class Base
|
4
|
+
attr_reader :results
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@results = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_result(result)
|
11
|
+
results.push(result)
|
12
|
+
end
|
13
|
+
|
4
14
|
def formatted_label
|
5
15
|
@formatted_label ||= colored(label.ljust(7))
|
6
16
|
end
|
7
17
|
|
8
18
|
def formatted_group_label
|
9
|
-
@formatted_group_label ||= " #{colored(group_label
|
19
|
+
@formatted_group_label ||= " #{colored(group_label, :bold, :underline)}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def colored_icon
|
23
|
+
colored(icon)
|
24
|
+
end
|
25
|
+
|
26
|
+
def colored_icon_with_count(padding_right = 0)
|
27
|
+
with_colored_icon(results.size, padding_right)
|
10
28
|
end
|
11
29
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
30
|
+
def colored_icon_with_context_count(result_context, padding_right = 0)
|
31
|
+
context_count = results.count { |r| r.context == result_context }
|
32
|
+
with_colored_icon(context_count, padding_right)
|
33
|
+
end
|
34
|
+
|
35
|
+
def print_details(io)
|
36
|
+
return :no_details if results.empty?
|
37
|
+
|
38
|
+
sorted_results = results.sort_by(&:source_location)
|
15
39
|
|
16
40
|
io.puts formatted_group_label
|
17
41
|
io.puts
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
io.puts(result.details_header(number))
|
22
|
-
do_print_details(io, result, padding)
|
23
|
-
io.puts
|
24
|
-
io.puts(result.rerun_line(padding))
|
25
|
-
io.puts if i < filtered_results.size - 1
|
42
|
+
sorted_results.each_with_index do |result, i|
|
43
|
+
detail_lines(result).each { |line| io.puts line }
|
44
|
+
io.puts if i < results.size - 1
|
26
45
|
end
|
27
46
|
io.puts
|
28
47
|
:printed_details
|
29
48
|
end
|
30
49
|
|
50
|
+
def detail_lines(result)
|
51
|
+
number = "#{result.execution_order})".ljust(4)
|
52
|
+
padding = ' ' * (number.size + 4)
|
53
|
+
time = "(#{result.time_with_unit_and_padding_right.strip})"
|
54
|
+
lines = []
|
55
|
+
lines << " #{number}#{result.formatted_name_with_context} #{Colorizer.colorize(time, :time)}"
|
56
|
+
|
57
|
+
lines += inner_detail_lines(result, padding)
|
58
|
+
|
59
|
+
lines << ''
|
60
|
+
lines << result.rerun_line(padding)
|
61
|
+
lines
|
62
|
+
end
|
63
|
+
|
64
|
+
def detail_lines_without_header(result)
|
65
|
+
number = "#{result.execution_order})".ljust(4)
|
66
|
+
padding = ' ' * (number.size + 2)
|
67
|
+
lines = []
|
68
|
+
|
69
|
+
lines += inner_detail_lines(result, padding).tap do |ls|
|
70
|
+
ls[0] = " #{number}#{ls[0].strip}" unless ls.empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
lines << ''
|
74
|
+
lines << result.rerun_line(padding)
|
75
|
+
lines << ''
|
76
|
+
lines
|
77
|
+
end
|
78
|
+
|
31
79
|
def color
|
32
80
|
self.class::COLOR
|
33
81
|
end
|
@@ -46,19 +94,28 @@ module MinitestBender
|
|
46
94
|
self.class::GROUP_LABEL
|
47
95
|
end
|
48
96
|
|
49
|
-
def
|
50
|
-
|
97
|
+
def icon
|
98
|
+
self.class::ICON
|
99
|
+
end
|
100
|
+
|
101
|
+
def colored(string, *args)
|
102
|
+
Colorizer.colorize(string, color, *args)
|
51
103
|
end
|
52
104
|
|
53
|
-
def
|
54
|
-
|
105
|
+
def with_colored_icon(message, padding_right)
|
106
|
+
colored("#{icon}#{message}".ljust(padding_right, ' '))
|
55
107
|
end
|
56
108
|
|
57
|
-
def
|
109
|
+
def inner_detail_lines(result, padding)
|
110
|
+
lines = []
|
58
111
|
result.failures[0].message.split("\n").each do |line|
|
59
|
-
|
112
|
+
line.split("\\n").each do |actual_line|
|
113
|
+
adjusted_line = Utils.with_home_shorthand(actual_line)
|
114
|
+
lines << "#{padding}#{colored(adjusted_line)}"
|
115
|
+
end
|
60
116
|
end
|
61
|
-
|
117
|
+
lines << "#{padding}#{Colorizer.colorize(location(result), :backtrace)}:"
|
118
|
+
lines
|
62
119
|
end
|
63
120
|
|
64
121
|
def location(result)
|