test-unit 3.5.7 → 3.7.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/Rakefile +12 -6
  4. data/bin/test-unit +5 -0
  5. data/doc/text/getting-started.md +24 -150
  6. data/doc/text/how-to.md +4 -4
  7. data/doc/text/news.md +218 -0
  8. data/lib/test/unit/assertions.rb +8 -8
  9. data/lib/test/unit/auto-runner-loader.rb +2 -2
  10. data/lib/test/unit/autorunner.rb +77 -13
  11. data/lib/test/unit/collector/descendant.rb +1 -1
  12. data/lib/test/unit/collector/dir.rb +2 -2
  13. data/lib/test/unit/collector/load.rb +8 -6
  14. data/lib/test/unit/collector/objectspace.rb +1 -1
  15. data/lib/test/unit/collector/xml.rb +1 -1
  16. data/lib/test/unit/color-scheme.rb +1 -1
  17. data/lib/test/unit/data.rb +1 -1
  18. data/lib/test/unit/error.rb +1 -1
  19. data/lib/test/unit/failure.rb +1 -1
  20. data/lib/test/unit/fault-location-detector.rb +6 -2
  21. data/lib/test/unit/notification.rb +1 -1
  22. data/lib/test/unit/omission.rb +1 -1
  23. data/lib/test/unit/pending.rb +1 -1
  24. data/lib/test/unit/priority.rb +1 -1
  25. data/lib/test/unit/runner/console.rb +23 -4
  26. data/lib/test/unit/runner/emacs.rb +1 -1
  27. data/lib/test/unit/runner/xml.rb +1 -1
  28. data/lib/test/unit/sub-test-result.rb +59 -0
  29. data/lib/test/unit/test-run-context.rb +16 -0
  30. data/lib/test/unit/test-suite-creator.rb +1 -1
  31. data/lib/test/unit/test-suite-runner.rb +130 -0
  32. data/lib/test/unit/test-suite-thread-runner.rb +84 -0
  33. data/lib/test/unit/test-thread-run-context.rb +20 -0
  34. data/lib/test/unit/testcase.rb +65 -20
  35. data/lib/test/unit/testresult.rb +9 -9
  36. data/lib/test/unit/testsuite.rb +22 -85
  37. data/lib/test/unit/ui/console/testrunner.rb +165 -36
  38. data/lib/test/unit/ui/emacs/testrunner.rb +1 -1
  39. data/lib/test/unit/ui/testrunner.rb +2 -2
  40. data/lib/test/unit/ui/testrunnermediator.rb +16 -11
  41. data/lib/test/unit/ui/xml/testrunner.rb +3 -2
  42. data/lib/test/unit/util/observable.rb +4 -8
  43. data/lib/test/unit/version.rb +1 -1
  44. data/lib/test/unit.rb +6 -6
  45. data/lib/test-unit.rb +2 -2
  46. metadata +12 -78
@@ -0,0 +1,84 @@
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
+ require_relative "test-thread-run-context"
10
+
11
+ module Test
12
+ module Unit
13
+ class TestSuiteThreadRunner < TestSuiteRunner
14
+ class << self
15
+ def run_all_tests
16
+ n_workers = TestSuiteRunner.n_workers
17
+
18
+ queue = Thread::Queue.new
19
+ shutdowns = []
20
+ yield(TestThreadRunContext.new(self, queue, shutdowns))
21
+ n_workers.times do
22
+ queue << nil
23
+ end
24
+
25
+ workers = []
26
+ sub_exceptions = []
27
+ n_workers.times do |i|
28
+ workers << Thread.new(i) do |worker_id|
29
+ begin
30
+ loop do
31
+ task = queue.pop
32
+ break if task.nil?
33
+ catch do |stop_tag|
34
+ task.call(stop_tag)
35
+ end
36
+ end
37
+ rescue Exception => exception
38
+ sub_exceptions << exception
39
+ end
40
+ end
41
+ end
42
+ workers.each(&:join)
43
+
44
+ shutdowns.each(&:call)
45
+ sub_exceptions.each do |exception|
46
+ raise exception
47
+ end
48
+ end
49
+ end
50
+
51
+ def run(result, run_context: nil, &progress_block)
52
+ yield(TestSuite::STARTED, @test_suite.name)
53
+ yield(TestSuite::STARTED_OBJECT, @test_suite)
54
+ run_startup(result)
55
+ run_tests(result, run_context: run_context, &progress_block)
56
+ ensure
57
+ run_context.shutdowns << lambda do
58
+ begin
59
+ run_shutdown(result)
60
+ ensure
61
+ yield(TestSuite::FINISHED, @test_suite.name)
62
+ yield(TestSuite::FINISHED_OBJECT, @test_suite)
63
+ end
64
+ end
65
+ end
66
+
67
+ private
68
+ def run_tests(result, run_context: nil, &progress_block)
69
+ @test_suite.tests.each do |test|
70
+ if test.is_a?(TestSuite) or not @test_suite.parallel_safe?
71
+ run_test(test, result, run_context: run_context, &progress_block)
72
+ else
73
+ task = lambda do |stop_tag|
74
+ sub_result = SubTestResult.new(result)
75
+ sub_result.stop_tag = stop_tag
76
+ run_test(test, sub_result, run_context: run_context, &progress_block)
77
+ end
78
+ run_context.queue << task
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,20 @@
1
+ #--
2
+ #
3
+ # Author:: Tsutomu Katsube.
4
+ # Copyright:: Copyright (c) 2025 Tsutomu Katsube. All rights reserved.
5
+ # License:: Ruby license.
6
+
7
+ require_relative "test-run-context"
8
+
9
+ module Test
10
+ module Unit
11
+ class TestThreadRunContext < TestRunContext
12
+ attr_reader :queue, :shutdowns
13
+ def initialize(runner_class, queue, shutdowns)
14
+ super(runner_class)
15
+ @queue = queue
16
+ @shutdowns = shutdowns
17
+ end
18
+ end
19
+ end
20
+ end
@@ -6,24 +6,24 @@
6
6
  # * Copyright (c) 2008-2012 Kouhei Sutou <tt><kou@clear-code.com></tt>
7
7
  # License:: Ruby license.
8
8
 
9
- require 'test/unit/attribute'
10
- require 'test/unit/fixture'
11
- require 'test/unit/exception-handler'
12
- require 'test/unit/assertions'
13
- require 'test/unit/failure'
14
- require 'test/unit/error'
15
- require 'test/unit/pending'
16
- require 'test/unit/omission'
17
- require 'test/unit/notification'
18
- require 'test/unit/priority'
19
- require 'test/unit/data'
20
- require 'test/unit/testsuite'
21
- require 'test/unit/test-suite-creator'
22
- require 'test/unit/assertion-failed-error'
23
- require 'test/unit/auto-runner-loader'
24
- require 'test/unit/util/backtracefilter'
25
- require 'test/unit/util/output'
26
- require 'test/unit/util/method-owner-finder'
9
+ require_relative 'attribute'
10
+ require_relative 'fixture'
11
+ require_relative 'exception-handler'
12
+ require_relative 'assertions'
13
+ require_relative 'failure'
14
+ require_relative 'error'
15
+ require_relative 'pending'
16
+ require_relative 'omission'
17
+ require_relative 'notification'
18
+ require_relative 'priority'
19
+ require_relative 'data'
20
+ require_relative 'testsuite'
21
+ require_relative 'test-suite-creator'
22
+ require_relative 'assertion-failed-error'
23
+ require_relative 'auto-runner-loader'
24
+ require_relative 'util/backtracefilter'
25
+ require_relative 'util/output'
26
+ require_relative 'util/method-owner-finder'
27
27
 
28
28
  module Test
29
29
  module Unit
@@ -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
@@ -357,7 +382,7 @@ module Test
357
382
  # Ractor mode is enabled in the current process and it's not
358
383
  # disabled even when only one Ractor is running after running
359
384
  # a test that uses Ractor on Ruby 3.0. It will be solved in
360
- # Ruby 3.1.
385
+ # the future.
361
386
  #
362
387
  # This is implemented by setting the `:ractor` attribute of
363
388
  # the test to `true`.
@@ -376,6 +401,14 @@ module Test
376
401
  # end
377
402
  # end
378
403
  #
404
+ # @example Declares that test_do_something_with_ractor uses Ractor in one line
405
+ #
406
+ # ractor def test_do_something_with_ractor
407
+ # Ractor.new do
408
+ # # ...
409
+ # end
410
+ # end
411
+ #
379
412
  # @since 3.4.6
380
413
  def ractor(options={})
381
414
  attribute(:ractor, true, options)
@@ -552,9 +585,11 @@ module Test
552
585
  # Runs the individual test method represented by this
553
586
  # instance of the fixture, collecting statistics, failures
554
587
  # and errors in result.
555
- def run(result)
588
+ def run(result, run_context: nil)
556
589
  begin
557
590
  @_result = result
591
+ instance_variables_before = instance_variables
592
+ @internal_data.run_context = run_context
558
593
  @internal_data.test_started
559
594
  yield(STARTED, name)
560
595
  yield(STARTED_OBJECT, self)
@@ -596,6 +631,9 @@ module Test
596
631
  yield(FINISHED_OBJECT, self)
597
632
  ensure
598
633
  # @_result = nil # For test-spec's after_all :<
634
+ (instance_variables - instance_variables_before).each do |name|
635
+ remove_instance_variable(name)
636
+ end
599
637
  end
600
638
  end
601
639
 
@@ -831,6 +869,11 @@ module Test
831
869
  @internal_data.problem_occurred
832
870
  end
833
871
 
872
+ # Returns test suite runner class for easy to test.
873
+ def runner_class
874
+ @internal_data.run_context.runner_class
875
+ end
876
+
834
877
  # Notify that the test is passed. Normally, it is not needed
835
878
  # because #run calls it automatically. If you want to override
836
879
  # #run, it is not a good idea. Please contact test-unit
@@ -891,6 +934,7 @@ module Test
891
934
  class InternalData
892
935
  attr_reader :start_time, :elapsed_time
893
936
  attr_reader :test_data_label, :test_data
937
+ attr_accessor :run_context
894
938
  def initialize
895
939
  @start_time = nil
896
940
  @elapsed_time = nil
@@ -898,6 +942,7 @@ module Test
898
942
  @interrupted = false
899
943
  @test_data_label = nil
900
944
  @test_data = nil
945
+ @run_context = nil
901
946
  end
902
947
 
903
948
  def passed?
@@ -3,12 +3,12 @@
3
3
  # Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
4
4
  # License:: Ruby license.
5
5
 
6
- require 'test/unit/util/observable'
7
- require 'test/unit/failure'
8
- require 'test/unit/error'
9
- require 'test/unit/omission'
10
- require 'test/unit/pending'
11
- require 'test/unit/notification'
6
+ require_relative 'util/observable'
7
+ require_relative 'failure'
8
+ require_relative 'error'
9
+ require_relative 'omission'
10
+ require_relative 'pending'
11
+ require_relative 'notification'
12
12
 
13
13
  module Test
14
14
  module Unit
@@ -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
 
@@ -76,7 +76,7 @@ module Test
76
76
  *@summary_generators.collect {|generator| __send__(generator)}].join(", ")
77
77
  end
78
78
 
79
- # Returnes a string that shows result status.
79
+ # Returns a string that shows result status.
80
80
  def status
81
81
  if passed?
82
82
  if pending_count > 0
@@ -5,7 +5,8 @@
5
5
  # Copyright:: Copyright (c) 2008-2011 Kouhei Sutou. All rights reserved.
6
6
  # License:: Ruby license.
7
7
 
8
- require 'test/unit/error'
8
+ require_relative 'error'
9
+ require_relative 'test-suite-runner'
9
10
 
10
11
  module Test
11
12
  module Unit
@@ -34,32 +35,32 @@ 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?
50
+ def run(result, run_context: nil, &progress_block)
51
+ if run_context
52
+ runner_class = run_context.runner_class
53
+ else
54
+ runner_class = TestSuiteRunner
55
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)
56
+ runner_class.new(self).run(result, run_context: run_context) do |event, *args|
57
+ case event
58
+ when STARTED
59
+ @start_time = Time.now
60
+ when FINISHED
61
+ @elapsed_time = Time.now - @start_time
62
+ end
63
+ yield(event, *args)
63
64
  end
64
65
  end
65
66
 
@@ -81,7 +82,7 @@ module Test
81
82
  # i.e. if the suite contains other suites, it counts the
82
83
  # tests within those suites, not the suites themselves.
83
84
  def size
84
- total_size = @n_tests
85
+ total_size = 0
85
86
  @tests.each { |test| total_size += test.size }
86
87
  total_size
87
88
  end
@@ -104,71 +105,7 @@ module Test
104
105
  end
105
106
 
106
107
  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
108
+ @tests.all?(&:passed?)
172
109
  end
173
110
  end
174
111
  end