test-unit 3.6.2 → 3.6.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f41e0d3b7f646e1dd57ac1feb94dd9d91e9a3a6b79e7391371319733e0cd627
4
- data.tar.gz: 6ea673fe38e82189ab39c3bd24d90b1e862b29cfecd71d260ae82b7995d80e3a
3
+ metadata.gz: db526a6169793f43216a4519521037141c9243f0d7b39d520d2ddc09d59576da
4
+ data.tar.gz: 5351d58e84881dc2faaaf6ad395a2c00b8964ce378a7dfff410d76b83e925b84
5
5
  SHA512:
6
- metadata.gz: 53689a9a862aae643553b81f7475b9efb19cf4cabb4f23f020833849d211d1ee36a5c5f8c8b0a1c4ea3ad53cb086b3aab73b031f19b329353a83840d9853793b
7
- data.tar.gz: 239c343b398180bd337ef19b47815abe347e93ef0972af56534edd6763763603e1e7975135876e61ba042195b2247fa153f4fe2bc61383fabbac11f256c44a6b
6
+ metadata.gz: 9f028832fae917599c17ab226de5376c103ad7d6059850fe8af81d1a0b3df77ce9a66d84367e2c61106cb92150883b4031c47119a34a97cd8e3def17f6b84d6b
7
+ data.tar.gz: c9a01ad68395703aa58d0b01ac53d0d01c3a25def3a6edc11af6267f37839039a5670f79af74b05182e2eef96bab59cdc9d6612d762e99aff6eacbafc9b64e5c
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,41 @@
1
1
  # News
2
2
 
3
+ ## 3.6.4 - 2024-11-28 {#version-3-6-4}
4
+
5
+ ### Improvements
6
+
7
+ * Added support for Ruby 3.4.0.
8
+
9
+ ## 3.6.3 - 2024-11-24 {#version-3-6-3}
10
+
11
+ ### Improvements
12
+
13
+ * Added support for thread based parallel test running. You can use
14
+ it by the `--parallel=thread` option. You can disable parallel
15
+ test running per test case by defining `parallel_safe?` class
16
+ method that returns `false`.
17
+ * GH-235
18
+ * Patch by Tsutomu Katsube
19
+
20
+ * Added the `--n-workers` option.
21
+
22
+ * Added the `--[no-]report-slow-tests` option. You can show the top
23
+ 5 slow tests with this option.
24
+ * GH-253
25
+ * Patch by Tsutomu Katsube
26
+
27
+ * UI: console: Add support for outputting `Exception#cause`.
28
+
29
+ * Added support for inspecting `BasicObject`.
30
+ * GH-262.
31
+ * Patch by Yuta Saito
32
+
33
+ ### Thanks
34
+
35
+ * Tsutomu Katsube
36
+
37
+ * Yuta Saito
38
+
3
39
  ## 3.6.2 - 2024-02-16 {#version-3-6-2}
4
40
 
5
41
  ### 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.4"
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.4
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-28 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: []