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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -1
  3. data/Gemfile +1 -1
  4. data/README.md +37 -6
  5. data/lib/minitest-bender/colorizer.rb +61 -0
  6. data/lib/minitest-bender/configuration.rb +174 -0
  7. data/lib/minitest-bender/printers/plain.rb +29 -0
  8. data/lib/minitest-bender/printers/with_progress_bar.rb +115 -0
  9. data/lib/minitest-bender/recorders/grouped_icons.rb +36 -0
  10. data/lib/minitest-bender/recorders/icons.rb +26 -0
  11. data/lib/minitest-bender/recorders/none.rb +17 -0
  12. data/lib/minitest-bender/recorders/progress.rb +25 -0
  13. data/lib/minitest-bender/recorders/progress_groups.rb +48 -0
  14. data/lib/minitest-bender/recorders/progress_groups_and_issues.rb +55 -0
  15. data/lib/minitest-bender/recorders/progress_issues.rb +32 -0
  16. data/lib/minitest-bender/recorders/progress_verbose.rb +34 -0
  17. data/lib/minitest-bender/result_context.rb +39 -0
  18. data/lib/minitest-bender/result_factory.rb +5 -0
  19. data/lib/minitest-bender/results/base.rb +94 -38
  20. data/lib/minitest-bender/results/expectation.rb +10 -8
  21. data/lib/minitest-bender/results/test.rb +21 -8
  22. data/lib/minitest-bender/sections/activity.rb +95 -0
  23. data/lib/minitest-bender/sections/issues.rb +22 -0
  24. data/lib/minitest-bender/sections/sorted_overview.rb +115 -0
  25. data/lib/minitest-bender/sections/suite_status.rb +72 -0
  26. data/lib/minitest-bender/sections/time_ranking.rb +49 -0
  27. data/lib/minitest-bender/states/base.rb +76 -19
  28. data/lib/minitest-bender/states/failing.rb +11 -8
  29. data/lib/minitest-bender/states/passing.rb +19 -8
  30. data/lib/minitest-bender/states/raising.rb +42 -20
  31. data/lib/minitest-bender/states/skipped.rb +12 -9
  32. data/lib/minitest-bender/utils.rb +26 -0
  33. data/lib/minitest-bender/version.rb +1 -1
  34. data/lib/minitest/bender.rb +166 -77
  35. data/lib/minitest/bender_plugin.rb +49 -3
  36. data/lib/minitest_bender.rb +23 -5
  37. data/minitest-bender.gemspec +6 -6
  38. 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 line_to_report
11
- "#{formatted_label}#{formatted_time} #{formatted_number} #{name} #{formatted_message}"
12
+ def formatted_number(_sorted_siblings = nil)
13
+ " #{Colorizer.colorize(number, :number)} "
12
14
  end
13
15
 
14
- private
16
+ def number_sort_key
17
+ @number_sort_key ||= number.to_i
18
+ end
15
19
 
16
- attr_reader :number, :name
20
+ private
17
21
 
18
- def formatted_number
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 context
10
- super.gsub(/^Test|Test$/, '')
11
- end
9
+ def formatted_number(sorted_siblings = nil)
10
+ return '' if sorted_siblings.nil?
12
11
 
13
- def line_to_report
14
- "#{formatted_label}#{formatted_time} #{name} #{formatted_message}"
15
- end
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
- private
17
+ padded_number = number.to_s.rjust(4, '0')
18
18
 
19
- attr_reader :raw_name
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).bold.underline}"
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 print_details(io, results)
13
- filtered_results = only_with_this_state(results)
14
- return :no_details if filtered_results.empty?
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
- filtered_results.each_with_index do |result, i|
19
- number = "#{i + 1})".ljust(4)
20
- padding = ' ' * (number.size + 4)
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 colored(string)
50
- Colorin.public_send(color, string)
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 only_with_this_state(results)
54
- results.select { |result| result.state?(self) }
105
+ def with_colored_icon(message, padding_right)
106
+ colored("#{icon}#{message}".ljust(padding_right, ' '))
55
107
  end
56
108
 
57
- def do_print_details(io, result, padding)
109
+ def inner_detail_lines(result, padding)
110
+ lines = []
58
111
  result.failures[0].message.split("\n").each do |line|
59
- io.puts "#{padding}#{colored(line)}"
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
- io.puts "#{padding}#{Colorin.brown_400(location(result))}"
117
+ lines << "#{padding}#{Colorizer.colorize(location(result), :backtrace)}:"
118
+ lines
62
119
  end
63
120
 
64
121
  def location(result)