minitest-bender 0.0.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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)