test-unit 3.6.2 → 3.6.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f41e0d3b7f646e1dd57ac1feb94dd9d91e9a3a6b79e7391371319733e0cd627
4
- data.tar.gz: 6ea673fe38e82189ab39c3bd24d90b1e862b29cfecd71d260ae82b7995d80e3a
3
+ metadata.gz: dbfa8042a3b49c307c2dec7f7fb71d521a70d822c4a11b5f437cf502656f02e0
4
+ data.tar.gz: 84746c3a622ee7ef0fceabeb66b2b148fc07b52deffebe222260aadb8396fd77
5
5
  SHA512:
6
- metadata.gz: 53689a9a862aae643553b81f7475b9efb19cf4cabb4f23f020833849d211d1ee36a5c5f8c8b0a1c4ea3ad53cb086b3aab73b031f19b329353a83840d9853793b
7
- data.tar.gz: 239c343b398180bd337ef19b47815abe347e93ef0972af56534edd6763763603e1e7975135876e61ba042195b2247fa153f4fe2bc61383fabbac11f256c44a6b
6
+ metadata.gz: bf443c2babdd0c7fa20cb9f747a507c960335cd9cb2376f3f20f5e7ae6056f4e6d6d05d18cd83571c9e5c6b10c1c5483a715ff03302a87d9eda26d8df174dfe7
7
+ data.tar.gz: e79090a030aa272b772820ad61f095524d6930b01a8eaa6372d58b3ebfbb55e74e9e05fe4251426d74199667be85fd8a1f2df0fb7331685ba7fd5f8d3ce1c5b2
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  # -*- ruby -*-
2
2
  #
3
- # Copyright (C) 2008-2017 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2008-2024 Sutou Kouhei <kou@clear-code.com>
4
4
 
5
5
  Encoding.default_internal = "UTF-8" if defined?(Encoding.default_internal)
6
6
 
@@ -48,10 +48,6 @@ Packnga::ReleaseTask.new(spec) do |task|
48
48
  task.index_html_dir = test_unit_github_io_dir
49
49
  end
50
50
 
51
- def rake(*arguments)
52
- ruby($0, *arguments)
53
- end
54
-
55
51
  task :test do
56
52
  ruby("test/run-test.rb")
57
53
  end
@@ -66,3 +62,7 @@ namespace :doc do
66
62
  File.write("doc/text/news.md", applied_permalink)
67
63
  end
68
64
  end
65
+
66
+ release_task = Rake.application["release"]
67
+ # We use Trusted Publishing.
68
+ release_task.prerequisites.delete("release:rubygem_push")
data/doc/text/news.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # News
2
2
 
3
+ ## 3.6.3 - 2024-11-24 {#version-3-6-3}
4
+
5
+ ### Improvements
6
+
7
+ * Added support for thread based parallel test running. You can use
8
+ it by the `--parallel=thread` option. You can disable parallel
9
+ test running per test case by defining `parallel_safe?` class
10
+ method that returns `false`.
11
+ * GH-235
12
+ * Patch by Tsutomu Katsube
13
+
14
+ * Added the `--n-workers` option.
15
+
16
+ * Added the `--[no-]report-slow-tests` option. You can show the top
17
+ 5 slow tests with this option.
18
+ * GH-253
19
+ * Patch by Tsutomu Katsube
20
+
21
+ * UI: console: Add support for outputting `Exception#cause`.
22
+
23
+ * Added support for inspecting `BasicObject`.
24
+ * GH-262.
25
+ * Patch by Yuta Saito
26
+
27
+ ### Thanks
28
+
29
+ * Tsutomu Katsube
30
+
31
+ * Yuta Saito
32
+
3
33
  ## 3.6.2 - 2024-02-16 {#version-3-6-2}
4
34
 
5
35
  ### Improvements
@@ -2093,7 +2093,7 @@ EOT
2093
2093
 
2094
2094
  class << self
2095
2095
  def cached_new(object, inspected_objects)
2096
- inspected_objects[object.object_id] ||=
2096
+ inspected_objects[object.__id__] ||=
2097
2097
  new(object, inspected_objects)
2098
2098
  end
2099
2099
 
@@ -2115,7 +2115,7 @@ EOT
2115
2115
  def initialize(object, inspected_objects={})
2116
2116
  @inspected_objects = inspected_objects
2117
2117
  @object = object
2118
- @inspected_objects[@object.object_id] = self
2118
+ @inspected_objects[@object.__id__] = self
2119
2119
  @inspect_target = inspect_target
2120
2120
  end
2121
2121
 
@@ -2317,7 +2317,7 @@ EOT
2317
2317
 
2318
2318
  def initialize(parts)
2319
2319
  @parts = parts
2320
- @count = parts.find_all{|e| e == '?'}.size
2320
+ @count = parts.count('?')
2321
2321
  end
2322
2322
 
2323
2323
  def result(parameters)
@@ -5,6 +5,7 @@ require "test/unit/color-scheme"
5
5
  require "test/unit/priority"
6
6
  require "test/unit/attribute-matcher"
7
7
  require "test/unit/testcase"
8
+ require "test/unit/test-suite-thread-runner"
8
9
 
9
10
  module Test
10
11
  module Unit
@@ -163,6 +164,7 @@ module Test
163
164
  @stop_on_failure = false
164
165
  @debug_on_failure = false
165
166
  @gc_stress = false
167
+ @test_suite_runner_class = TestSuiteRunner
166
168
  config_file = "test-unit.yml"
167
169
  if File.exist?(config_file)
168
170
  load_config(config_file)
@@ -401,6 +403,26 @@ module Test
401
403
  @gc_stress = boolean
402
404
  end
403
405
 
406
+ parallel_options = [
407
+ :thread,
408
+ ]
409
+ o.on("--[no-]parallel=[thread]", parallel_options,
410
+ "Runs tests in parallel",
411
+ "(#{parallel_options.first})") do |parallel|
412
+ case parallel
413
+ when nil, :thread
414
+ @test_suite_runner_class = TestSuiteThreadRunner
415
+ else
416
+ @test_suite_runner_class = TestSuiteRunner
417
+ end
418
+ end
419
+
420
+ o.on("--n-workers=N", Integer,
421
+ "The number of parallelism",
422
+ "(#{TestSuiteRunner.n_workers})") do |n|
423
+ TestSuiteRunner.n_workers = n
424
+ end
425
+
404
426
  ADDITIONAL_OPTIONS.each do |option_builder|
405
427
  option_builder.call(self, o)
406
428
  end
@@ -465,6 +487,7 @@ module Test
465
487
  if @gc_stress
466
488
  @runner_options[:listeners] << GCStressListener.new
467
489
  end
490
+ @runner_options[:test_suite_runner_class] = @test_suite_runner_class
468
491
  change_work_directory do
469
492
  runner.run(suite, @runner_options).passed?
470
493
  end
@@ -32,7 +32,7 @@ module Test
32
32
  @inspected_actual = options[:inspected_actual]
33
33
  @user_message = options[:user_message]
34
34
  end
35
-
35
+
36
36
  # Returns a single character representation of a failure.
37
37
  def single_character_display
38
38
  SINGLE_CHARACTER
@@ -71,7 +71,11 @@ module Test
71
71
  end
72
72
 
73
73
  def target_method?(method_name)
74
- @fault_method_name == method_name
74
+ return false if method_name.nil?
75
+ # method_name may be test_XXX or MyTest#test_XXX (Ruby >= 3.4).
76
+ return true if @fault_method_name == method_name
77
+ return true if method_name.end_with?("\##{@fault_method_name}")
78
+ false
75
79
  end
76
80
 
77
81
  def guess_indent_level(line)
@@ -180,7 +180,7 @@ module Test
180
180
  end
181
181
  end
182
182
 
183
- def priority_setup
183
+ def priority_setup(&_)
184
184
  return unless Priority.enabled?
185
185
  Checker.new(self).setup
186
186
  end
@@ -9,6 +9,7 @@ module Test
9
9
 
10
10
  AutoRunner.setup_option do |auto_runner, opts|
11
11
  require 'test/unit/ui/console/outputlevel'
12
+ require 'test/unit/ui/console/testrunner'
12
13
 
13
14
  output_levels = [
14
15
  ["silent", UI::Console::OutputLevel::SILENT],
@@ -58,7 +59,7 @@ module Test
58
59
  ]
59
60
  opts.on("--progress-style=STYLE", progress_styles,
60
61
  "Uses STYLE as progress style",
61
- "(#{auto_runner.keyword_display(progress_styles)}") do |style|
62
+ "(#{auto_runner.keyword_display(progress_styles)})") do |style|
62
63
  auto_runner.runner_options[:progress_style] = style
63
64
  end
64
65
 
@@ -73,6 +74,13 @@ module Test
73
74
  "(default is yes for tty output, no otherwise)") do |boolean|
74
75
  auto_runner.runner_options[:reverse_output] = boolean
75
76
  end
77
+
78
+ n_report_slow_tests = UI::Console::TestRunner::N_REPORT_SLOW_TESTS
79
+ opts.on("--[no-]report-slow-tests",
80
+ "Shows the top #{n_report_slow_tests} slow tests in the summary output",
81
+ "(false)") do |boolean|
82
+ auto_runner.runner_options[:report_slow_tests] = boolean
83
+ end
76
84
  end
77
85
  end
78
86
  end
@@ -0,0 +1,51 @@
1
+ #--
2
+ #
3
+ # Author:: Tsutomu Katsube.
4
+ # Copyright:: Copyright (c) 2024 Tsutomu Katsube. All rights reserved.
5
+ # License:: Ruby license.
6
+
7
+ module Test
8
+ module Unit
9
+ class SubTestResult
10
+ attr_accessor :stop_tag
11
+
12
+ def initialize(parent_test_result)
13
+ @parent_test_result = parent_test_result
14
+ @stop_tag = nil
15
+ end
16
+
17
+ def add_run(result=self)
18
+ @parent_test_result.add_run(result)
19
+ end
20
+
21
+ def add_pass
22
+ @parent_test_result.add_pass
23
+ end
24
+
25
+ # Records an individual assertion.
26
+ def add_assertion
27
+ @parent_test_result.add_assertion
28
+ end
29
+
30
+ def add_error(error)
31
+ @parent_test_result.add_error(error)
32
+ end
33
+
34
+ def add_failure(failure)
35
+ @parent_test_result.add_failure(failure)
36
+ end
37
+
38
+ def add_omission(omission)
39
+ @parent_test_result.add_omission(omission)
40
+ end
41
+
42
+ def passed?
43
+ @parent_test_result.passed?
44
+ end
45
+
46
+ def stop
47
+ throw @stop_tag
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,120 @@
1
+ #--
2
+ #
3
+ # Author:: Nathaniel Talbott.
4
+ # Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
5
+ # Copyright:: Copyright (c) 2008-2011 Kouhei Sutou. All rights reserved.
6
+ # Copyright:: Copyright (c) 2024 Tsutomu Katsube. All rights reserved.
7
+ # License:: Ruby license.
8
+
9
+ require "etc"
10
+
11
+ module Test
12
+ module Unit
13
+ class TestSuiteRunner
14
+ @n_workers = Etc.respond_to?(:nprocessors) ? Etc.nprocessors : 1
15
+ class << self
16
+ def run_all_tests
17
+ yield
18
+ end
19
+
20
+ def n_workers
21
+ @n_workers
22
+ end
23
+
24
+ def n_workers=(n)
25
+ @n_workers = n
26
+ end
27
+ end
28
+
29
+ def initialize(test_suite)
30
+ @test_suite = test_suite
31
+ end
32
+
33
+ def run(result, &progress_block)
34
+ yield(TestSuite::STARTED, @test_suite.name)
35
+ yield(TestSuite::STARTED_OBJECT, @test_suite)
36
+ run_startup(result)
37
+ run_tests(result, &progress_block)
38
+ ensure
39
+ begin
40
+ run_shutdown(result)
41
+ ensure
42
+ yield(TestSuite::FINISHED, @test_suite.name)
43
+ yield(TestSuite::FINISHED_OBJECT, @test_suite)
44
+ end
45
+ end
46
+
47
+ private
48
+ def run_startup(result)
49
+ test_case = @test_suite.test_case
50
+ return if test_case.nil? or !test_case.respond_to?(:startup)
51
+ begin
52
+ test_case.startup
53
+ rescue Exception
54
+ raise unless handle_exception($!, result)
55
+ end
56
+ end
57
+
58
+ def run_tests(result, &progress_block)
59
+ @test_suite.tests.each do |test|
60
+ run_test(test, result, &progress_block)
61
+ end
62
+ end
63
+
64
+ def run_test(test, result)
65
+ finished_is_yielded = false
66
+ finished_object_is_yielded = false
67
+ previous_event_name = nil
68
+ test.run(result, runner: self.class) do |event_name, *args|
69
+ case previous_event_name
70
+ when Test::Unit::TestCase::STARTED
71
+ if event_name != Test::Unit::TestCase::STARTED_OBJECT
72
+ yield(Test::Unit::TestCase::STARTED_OBJECT, test)
73
+ end
74
+ when Test::Unit::TestCase::FINISHED
75
+ if event_name != Test::Unit::TestCase::FINISHED_OBJECT
76
+ yield(Test::Unit::TestCase::FINISHED_OBJECT, test)
77
+ end
78
+ finished_object_is_yielded = true
79
+ end
80
+
81
+ case event_name
82
+ when Test::Unit::TestCase::STARTED
83
+ finished_is_yielded = false
84
+ finished_object_is_yielded = false
85
+ when Test::Unit::TestCase::FINISHED
86
+ finished_is_yielded = true
87
+ end
88
+
89
+ previous_event_name = event_name
90
+ yield(event_name, *args)
91
+ end
92
+
93
+ if finished_is_yielded and not finished_object_is_yielded
94
+ yield(Test::Unit::TestCase::FINISHED_OBJECT, test)
95
+ end
96
+ end
97
+
98
+ def run_shutdown(result)
99
+ test_case = @test_suite.test_case
100
+ return if test_case.nil? or !test_case.respond_to?(:shutdown)
101
+ begin
102
+ test_case.shutdown
103
+ rescue Exception
104
+ raise unless handle_exception($!, result)
105
+ end
106
+ end
107
+
108
+ def handle_exception(exception, result)
109
+ case exception
110
+ when *ErrorHandler::PASS_THROUGH_EXCEPTIONS
111
+ false
112
+ else
113
+ result.add_error(Error.new(@test_suite.test_case.name, exception))
114
+ true
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+
@@ -0,0 +1,69 @@
1
+ #--
2
+ #
3
+ # Author:: Tsutomu Katsube.
4
+ # Copyright:: Copyright (c) 2024 Tsutomu Katsube. All rights reserved.
5
+ # License:: Ruby license.
6
+
7
+ require_relative "sub-test-result"
8
+ require_relative "test-suite-runner"
9
+
10
+ module Test
11
+ module Unit
12
+ class TestSuiteThreadRunner < TestSuiteRunner
13
+ @task_queue = Thread::Queue.new
14
+ class << self
15
+ def task_queue
16
+ @task_queue
17
+ end
18
+
19
+ def run_all_tests
20
+ n_consumers = TestSuiteRunner.n_workers
21
+
22
+ consumers = []
23
+ sub_exceptions = []
24
+ n_consumers.times do |i|
25
+ consumers << Thread.new(i) do |worker_id|
26
+ begin
27
+ loop do
28
+ task = @task_queue.pop
29
+ break if task.nil?
30
+ catch do |stop_tag|
31
+ task.call(stop_tag)
32
+ end
33
+ end
34
+ rescue Exception => exception
35
+ sub_exceptions << exception
36
+ end
37
+ end
38
+ end
39
+
40
+ yield
41
+
42
+ n_consumers.times do
43
+ @task_queue << nil
44
+ end
45
+ consumers.each(&:join)
46
+ sub_exceptions.each do |exception|
47
+ raise exception
48
+ end
49
+ end
50
+ end
51
+
52
+ private
53
+ def run_tests(result, &progress_block)
54
+ @test_suite.tests.each do |test|
55
+ if test.is_a?(TestSuite) or not @test_suite.parallel_safe?
56
+ run_test(test, result, &progress_block)
57
+ else
58
+ task = lambda do |stop_tag|
59
+ sub_result = SubTestResult.new(result)
60
+ sub_result.stop_tag = stop_tag
61
+ run_test(test, sub_result, &progress_block)
62
+ end
63
+ self.class.task_queue << task
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -125,6 +125,31 @@ module Test
125
125
  AVAILABLE_ORDERS = [:alphabetic, :random, :defined] # :nodoc:
126
126
 
127
127
  class << self
128
+ # Indicates whether the test is parallel safe.
129
+ #
130
+ # Tests that this method returns `false` are executed sequentially
131
+ # before parallel safe tests run. This only works when the `--parallel`
132
+ # option is specified.
133
+ #
134
+ # @example Indicates that test_parallel_unsafe is parallel unsafe
135
+ #
136
+ # class TestMyClass < Test::Unit::TestCase
137
+ # class << self
138
+ # def parallel_safe?
139
+ # false
140
+ # end
141
+ # end
142
+ #
143
+ # def test_parallel_unsafe
144
+ # # ...
145
+ # end
146
+ # end
147
+ #
148
+ # @since 3.6.3
149
+ def parallel_safe?
150
+ true
151
+ end
152
+
128
153
  def inherited(sub_class) # :nodoc:
129
154
  DESCENDANTS << sub_class
130
155
  super
@@ -552,7 +577,7 @@ module Test
552
577
  # Runs the individual test method represented by this
553
578
  # instance of the fixture, collecting statistics, failures
554
579
  # and errors in result.
555
- def run(result)
580
+ def run(result, runner: nil)
556
581
  begin
557
582
  @_result = result
558
583
  @internal_data.test_started
@@ -51,9 +51,9 @@ module Test
51
51
  end
52
52
 
53
53
  # Records a test run.
54
- def add_run
54
+ def add_run(result=self)
55
55
  @run_count += 1
56
- notify_listeners(FINISHED, self)
56
+ notify_listeners(FINISHED, result)
57
57
  notify_changed
58
58
  end
59
59
 
@@ -6,6 +6,7 @@
6
6
  # License:: Ruby license.
7
7
 
8
8
  require 'test/unit/error'
9
+ require_relative 'test-suite-runner'
9
10
 
10
11
  module Test
11
12
  module Unit
@@ -34,32 +35,28 @@ module Test
34
35
  @name = name
35
36
  @tests = []
36
37
  @test_case = test_case
37
- @n_tests = 0
38
38
  @priority = 0
39
39
  @start_time = nil
40
40
  @elapsed_time = nil
41
- @passed = true
41
+ end
42
+
43
+ def parallel_safe?
44
+ return true if @test_case.nil?
45
+ @test_case.parallel_safe?
42
46
  end
43
47
 
44
48
  # Runs the tests and/or suites contained in this
45
49
  # TestSuite.
46
- def run(result, &progress_block)
47
- @start_time = Time.now
48
- yield(STARTED, name)
49
- yield(STARTED_OBJECT, self)
50
- run_startup(result)
51
- while test = @tests.shift
52
- @n_tests += test.size
53
- run_test(test, result, &progress_block)
54
- @passed = false unless test.passed?
55
- end
56
- ensure
57
- begin
58
- run_shutdown(result)
59
- ensure
60
- @elapsed_time = Time.now - @start_time
61
- yield(FINISHED, name)
62
- yield(FINISHED_OBJECT, self)
50
+ def run(result, runner: nil, &progress_block)
51
+ runner ||= TestSuiteRunner
52
+ runner.new(self).run(result) do |event, *args|
53
+ case event
54
+ when STARTED
55
+ @start_time = Time.now
56
+ when FINISHED
57
+ @elapsed_time = Time.now - @start_time
58
+ end
59
+ yield(event, *args)
63
60
  end
64
61
  end
65
62
 
@@ -81,7 +78,7 @@ module Test
81
78
  # i.e. if the suite contains other suites, it counts the
82
79
  # tests within those suites, not the suites themselves.
83
80
  def size
84
- total_size = @n_tests
81
+ total_size = 0
85
82
  @tests.each { |test| total_size += test.size }
86
83
  total_size
87
84
  end
@@ -104,71 +101,7 @@ module Test
104
101
  end
105
102
 
106
103
  def passed?
107
- @passed
108
- end
109
-
110
- private
111
- def run_startup(result)
112
- return if @test_case.nil? or !@test_case.respond_to?(:startup)
113
- begin
114
- @test_case.startup
115
- rescue Exception
116
- raise unless handle_exception($!, result)
117
- end
118
- end
119
-
120
- def run_test(test, result)
121
- finished_is_yielded = false
122
- finished_object_is_yielded = false
123
- previous_event_name = nil
124
- test.run(result) do |event_name, *args|
125
- case previous_event_name
126
- when Test::Unit::TestCase::STARTED
127
- if event_name != Test::Unit::TestCase::STARTED_OBJECT
128
- yield(Test::Unit::TestCase::STARTED_OBJECT, test)
129
- end
130
- when Test::Unit::TestCase::FINISHED
131
- if event_name != Test::Unit::TestCase::FINISHED_OBJECT
132
- yield(Test::Unit::TestCase::FINISHED_OBJECT, test)
133
- end
134
- finished_object_is_yielded = true
135
- end
136
-
137
- case event_name
138
- when Test::Unit::TestCase::STARTED
139
- finished_is_yielded = false
140
- finished_object_is_yielded = false
141
- when Test::Unit::TestCase::FINISHED
142
- finished_is_yielded = true
143
- end
144
-
145
- previous_event_name = event_name
146
- yield(event_name, *args)
147
- end
148
-
149
- if finished_is_yielded and not finished_object_is_yielded
150
- yield(Test::Unit::TestCase::FINISHED_OBJECT, test)
151
- end
152
- end
153
-
154
- def run_shutdown(result)
155
- return if @test_case.nil? or !@test_case.respond_to?(:shutdown)
156
- begin
157
- @test_case.shutdown
158
- rescue Exception
159
- raise unless handle_exception($!, result)
160
- end
161
- end
162
-
163
- def handle_exception(exception, result)
164
- case exception
165
- when *ErrorHandler::PASS_THROUGH_EXCEPTIONS
166
- false
167
- else
168
- result.add_error(Error.new(@test_case.name, exception))
169
- @passed = false
170
- true
171
- end
104
+ @tests.all?(&:passed?)
172
105
  end
173
106
  end
174
107
  end
@@ -10,6 +10,7 @@ begin
10
10
  require 'io/console'
11
11
  rescue LoadError
12
12
  end
13
+ require "pathname"
13
14
 
14
15
  require 'test/unit/color-scheme'
15
16
  require 'test/unit/code-snippet-fetcher'
@@ -28,6 +29,8 @@ module Test
28
29
  class TestRunner < UI::TestRunner
29
30
  include OutputLevel
30
31
 
32
+ N_REPORT_SLOW_TESTS = 5
33
+
31
34
  # Creates a new TestRunner for running the passed
32
35
  # suite. If quiet_mode is true, the output while
33
36
  # running is limited to progress dots, errors and
@@ -36,6 +39,7 @@ module Test
36
39
  # STDOUT.
37
40
  def initialize(suite, options={})
38
41
  super
42
+ @on_github_actions = (ENV["GITHUB_ACTIONS"] == "true")
39
43
  @output_level = @options[:output_level] || guess_output_level
40
44
  @output = @options[:output] || STDOUT
41
45
  @use_color = @options[:use_color]
@@ -57,6 +61,7 @@ module Test
57
61
  @faults = []
58
62
  @code_snippet_fetcher = CodeSnippetFetcher.new
59
63
  @test_suites = []
64
+ @test_statistics = []
60
65
  end
61
66
 
62
67
  private
@@ -192,12 +197,71 @@ module Test
192
197
  output(fault.test_name, fault_color(fault))
193
198
  output_fault_backtrace(fault)
194
199
  output_failure_message(fault)
200
+ output_fault_on_github_actions(fault)
195
201
  else
196
202
  output_single("#{fault.label}: ")
197
203
  output_single(fault.test_name, fault_color(fault))
198
204
  output_fault_message(fault)
199
205
  output_fault_backtrace(fault)
206
+ output_fault_on_github_actions(fault) if fault.is_a?(Error)
207
+ if fault.is_a?(Error) and fault.exception.respond_to?(:cause)
208
+ cause = fault.exception.cause
209
+ i = 0
210
+ while cause
211
+ sub_fault = Error.new(fault.test_name,
212
+ cause,
213
+ method_name: fault.method_name)
214
+ output_single("Cause#{i}", fault_color(sub_fault))
215
+ output_fault_message(sub_fault)
216
+ output_fault_backtrace(sub_fault)
217
+ cause = cause.cause
218
+ i += 1
219
+ end
220
+ end
221
+ end
222
+ end
223
+
224
+ def detect_target_location_on_github_actions(fault)
225
+ return nil unless @on_github_actions
226
+
227
+ base_dir = ENV["GITHUB_WORKSPACE"]
228
+ return nil unless base_dir
229
+ base_dir = Pathname(base_dir).expand_path
230
+
231
+ detector = FaultLocationDetector.new(fault, @code_snippet_fetcher)
232
+ backtrace = fault.location || []
233
+ backtrace.each_with_index do |entry, i|
234
+ next unless detector.target?(entry)
235
+ file, line, = detector.split_backtrace_entry(entry)
236
+ file = Pathname(file).expand_path
237
+ relative_file = file.relative_path_from(base_dir)
238
+ first_component = relative_file.descend do |component|
239
+ break component
240
+ end
241
+ # file isn't under base_dir
242
+ next if first_component.to_s == "..."
243
+ return [relative_file, line]
244
+ end
245
+ nil
246
+ end
247
+
248
+ def output_fault_on_github_actions(fault)
249
+ location = detect_target_location_on_github_actions(fault)
250
+ return unless location
251
+
252
+ parameters = [
253
+ "file=#{location[0]}",
254
+ "line=#{location[1]}",
255
+ "title=#{fault.label}",
256
+ ].join(",")
257
+ message = fault.message
258
+ if fault.is_a?(Error)
259
+ message = ([message] + (fault.location || [])).join("\n")
200
260
  end
261
+ # We need to use URL encode for new line:
262
+ # https://github.com/actions/toolkit/issues/193
263
+ message = message.gsub("\n", "%0A")
264
+ output("::error #{parameters}::#{message}")
201
265
  end
202
266
 
203
267
  def output_fault_message(fault)
@@ -331,6 +395,22 @@ module Test
331
395
  change_output_level(IMPORTANT_FAULTS_ONLY) do
332
396
  output("Finished in #{elapsed_time} seconds.")
333
397
  end
398
+ if @options[:report_slow_tests]
399
+ output_summary_marker
400
+ output("Top #{N_REPORT_SLOW_TESTS} slow tests")
401
+ @test_statistics.sort_by {|statistic| -statistic[:elapsed_time]}
402
+ .first(N_REPORT_SLOW_TESTS)
403
+ .each do |slow_statistic|
404
+ left_side = "#{slow_statistic[:name]}: "
405
+ right_width = @progress_row_max - left_side.size
406
+ output("%s%*f" % [
407
+ left_side,
408
+ right_width,
409
+ slow_statistic[:elapsed_time],
410
+ ])
411
+ output("--location #{slow_statistic[:location]}")
412
+ end
413
+ end
334
414
  output_summary_marker
335
415
  change_output_level(IMPORTANT_FAULTS_ONLY) do
336
416
  output(@result)
@@ -374,7 +454,6 @@ module Test
374
454
  output_single("#{indent}#{name}#{separator}#{tab_stop}",
375
455
  nil,
376
456
  VERBOSE)
377
- @test_start = Time.now
378
457
  end
379
458
 
380
459
  def test_finished(test)
@@ -391,9 +470,17 @@ module Test
391
470
  end
392
471
  @already_outputted = false
393
472
 
473
+ if @options[:report_slow_tests]
474
+ @test_statistics << {
475
+ name: test.name,
476
+ elapsed_time: test.elapsed_time,
477
+ location: test.method(test.method_name).source_location.join(":"),
478
+ }
479
+ end
480
+
394
481
  return unless output?(VERBOSE)
395
482
 
396
- output(": (%f)" % (Time.now - @test_start), nil, VERBOSE)
483
+ output(": (%f)" % test.elapsed_time, nil, VERBOSE)
397
484
  end
398
485
 
399
486
  def suite_name(prefix, suite)
@@ -535,7 +622,7 @@ module Test
535
622
  end
536
623
 
537
624
  def guess_output_level
538
- if ENV["GITHUB_ACTIONS"] == "true"
625
+ if @on_github_actions
539
626
  IMPORTANT_FAULTS_ONLY
540
627
  else
541
628
  NORMAL
@@ -558,7 +645,7 @@ module Test
558
645
  /x
559
646
 
560
647
  def guess_color_availability
561
- return true if ENV["GITHUB_ACTIONS"] == "true"
648
+ return true if @on_github_actions
562
649
  return false unless @output.tty?
563
650
  return true if windows? and ruby_2_0_or_later?
564
651
  case ENV["TERM"]
@@ -576,7 +663,7 @@ module Test
576
663
  if @output_level >= VERBOSE
577
664
  :mark
578
665
  else
579
- return :fault_only if ENV["GITHUB_ACTIONS"] == "true"
666
+ return :fault_only if @on_github_actions
580
667
  return :fault_only unless @output.tty?
581
668
  :inplace
582
669
  end
@@ -27,7 +27,7 @@ module Test
27
27
 
28
28
  private
29
29
  def setup_mediator
30
- @mediator = TestRunnerMediator.new(@suite)
30
+ @mediator = TestRunnerMediator.new(@suite, @options)
31
31
  end
32
32
 
33
33
  def attach_listeners
@@ -22,8 +22,11 @@ module Test
22
22
 
23
23
  # Creates a new TestRunnerMediator initialized to run
24
24
  # the passed suite.
25
- def initialize(suite)
25
+ def initialize(suite, options={})
26
26
  @suite = suite
27
+ @options = options
28
+ @test_suite_runner_class = @options[:test_suite_runner_class]
29
+ @test_suite_runner_class ||= TestSuiteRunner
27
30
  end
28
31
 
29
32
  # Runs the suite the TestRunnerMediator was created
@@ -36,13 +39,15 @@ module Test
36
39
  Test::Unit.run_at_start_hooks
37
40
  start_time = Time.now
38
41
  begin
39
- catch do |stop_tag|
40
- result.stop_tag = stop_tag
41
- with_listener(result) do
42
- notify_listeners(RESET, @suite.size)
43
- notify_listeners(STARTED, result)
42
+ with_listener(result) do
43
+ @test_suite_runner_class.run_all_tests do
44
+ catch do |stop_tag|
45
+ result.stop_tag = stop_tag
46
+ notify_listeners(RESET, @suite.size)
47
+ notify_listeners(STARTED, result)
44
48
 
45
- run_suite(result)
49
+ run_suite(result)
50
+ end
46
51
  end
47
52
  end
48
53
  ensure
@@ -64,7 +69,7 @@ module Test
64
69
  if result.nil?
65
70
  run
66
71
  else
67
- @suite.run(result) do |channel, value|
72
+ @suite.run(result, runner: @test_suite_runner_class) do |channel, value|
68
73
  notify_listeners(channel, value)
69
74
  end
70
75
  end
@@ -55,10 +55,7 @@ module Test
55
55
  if (listener_key.instance_of?(Proc))
56
56
  key = ProcWrapper.new(listener_key)
57
57
  end
58
- if (channel.has_key?(key))
59
- return channel.delete(key)
60
- end
61
- return nil
58
+ return channel.delete(key)
62
59
  end
63
60
 
64
61
  # Calls all the procs registered on the channel
@@ -74,9 +71,8 @@ module Test
74
71
  def notify_listeners(channel_name, *arguments)
75
72
  channel = channels[channel_name]
76
73
  return 0 unless (channel)
77
- listeners = channel.values
78
- listeners.each { |listener| listener.call(*arguments) }
79
- return listeners.size
74
+ channel.each_value { |listener| listener.call(*arguments) }
75
+ return channel.size
80
76
  end
81
77
 
82
78
  private
@@ -1,5 +1,5 @@
1
1
  module Test
2
2
  module Unit
3
- VERSION = "3.6.2"
3
+ VERSION = "3.6.3"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test-unit
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.2
4
+ version: 3.6.3
5
5
  platform: ruby
6
+ original_platform: ''
6
7
  authors:
7
8
  - Kouhei Sutou
8
9
  - Haruka Yoshihara
9
- autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-02-15 00:00:00.000000000 Z
12
+ date: 2024-11-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: power_assert
@@ -25,76 +25,6 @@ dependencies:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: '0'
28
- - !ruby/object:Gem::Dependency
29
- name: bundler
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - ">="
33
- - !ruby/object:Gem::Version
34
- version: '0'
35
- type: :development
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - ">="
40
- - !ruby/object:Gem::Version
41
- version: '0'
42
- - !ruby/object:Gem::Dependency
43
- name: rake
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - ">="
47
- - !ruby/object:Gem::Version
48
- version: '0'
49
- type: :development
50
- prerelease: false
51
- version_requirements: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - ">="
54
- - !ruby/object:Gem::Version
55
- version: '0'
56
- - !ruby/object:Gem::Dependency
57
- name: yard
58
- requirement: !ruby/object:Gem::Requirement
59
- requirements:
60
- - - ">="
61
- - !ruby/object:Gem::Version
62
- version: '0'
63
- type: :development
64
- prerelease: false
65
- version_requirements: !ruby/object:Gem::Requirement
66
- requirements:
67
- - - ">="
68
- - !ruby/object:Gem::Version
69
- version: '0'
70
- - !ruby/object:Gem::Dependency
71
- name: kramdown
72
- requirement: !ruby/object:Gem::Requirement
73
- requirements:
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- version: '0'
77
- type: :development
78
- prerelease: false
79
- version_requirements: !ruby/object:Gem::Requirement
80
- requirements:
81
- - - ">="
82
- - !ruby/object:Gem::Version
83
- version: '0'
84
- - !ruby/object:Gem::Dependency
85
- name: packnga
86
- requirement: !ruby/object:Gem::Requirement
87
- requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- version: '0'
91
- type: :development
92
- prerelease: false
93
- version_requirements: !ruby/object:Gem::Requirement
94
- requirements:
95
- - - ">="
96
- - !ruby/object:Gem::Version
97
- version: '0'
98
28
  description: |-
99
29
  test-unit (Test::Unit) is unit testing framework for Ruby, based on xUnit
100
30
  principles. These were originally designed by Kent Beck, creator of extreme
@@ -147,7 +77,10 @@ files:
147
77
  - lib/test/unit/runner/console.rb
148
78
  - lib/test/unit/runner/emacs.rb
149
79
  - lib/test/unit/runner/xml.rb
80
+ - lib/test/unit/sub-test-result.rb
150
81
  - lib/test/unit/test-suite-creator.rb
82
+ - lib/test/unit/test-suite-runner.rb
83
+ - lib/test/unit/test-suite-thread-runner.rb
151
84
  - lib/test/unit/testcase.rb
152
85
  - lib/test/unit/testresult.rb
153
86
  - lib/test/unit/testsuite.rb
@@ -180,7 +113,6 @@ metadata:
180
113
  source_code_uri: https://github.com/test-unit/test-unit
181
114
  documentation_uri: https://test-unit.github.io/test-unit/en/
182
115
  bug_tracker_uri: https://github.com/test-unit/test-unit/issues
183
- post_install_message:
184
116
  rdoc_options: []
185
117
  require_paths:
186
118
  - lib
@@ -195,8 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
195
127
  - !ruby/object:Gem::Version
196
128
  version: '0'
197
129
  requirements: []
198
- rubygems_version: 3.5.1
199
- signing_key:
130
+ rubygems_version: 3.6.0.dev
200
131
  specification_version: 4
201
132
  summary: An xUnit family unit testing framework for Ruby.
202
133
  test_files: []