activesupport 5.2.4.4 → 6.1.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +353 -435
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/active_support.rb +14 -1
  6. data/lib/active_support/actionable_error.rb +48 -0
  7. data/lib/active_support/array_inquirer.rb +4 -2
  8. data/lib/active_support/backtrace_cleaner.rb +29 -3
  9. data/lib/active_support/benchmarkable.rb +1 -1
  10. data/lib/active_support/cache.rb +142 -78
  11. data/lib/active_support/cache/file_store.rb +33 -33
  12. data/lib/active_support/cache/mem_cache_store.rb +32 -20
  13. data/lib/active_support/cache/memory_store.rb +59 -33
  14. data/lib/active_support/cache/null_store.rb +8 -3
  15. data/lib/active_support/cache/redis_cache_store.rb +70 -43
  16. data/lib/active_support/cache/strategy/local_cache.rb +41 -26
  17. data/lib/active_support/callbacks.rb +81 -64
  18. data/lib/active_support/concern.rb +70 -3
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
  20. data/lib/active_support/concurrency/share_lock.rb +0 -1
  21. data/lib/active_support/configurable.rb +10 -14
  22. data/lib/active_support/configuration_file.rb +46 -0
  23. data/lib/active_support/core_ext.rb +1 -1
  24. data/lib/active_support/core_ext/array.rb +1 -1
  25. data/lib/active_support/core_ext/array/access.rb +18 -6
  26. data/lib/active_support/core_ext/array/conversions.rb +5 -5
  27. data/lib/active_support/core_ext/array/extract.rb +21 -0
  28. data/lib/active_support/core_ext/benchmark.rb +2 -2
  29. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  30. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  31. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  32. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  33. data/lib/active_support/core_ext/date_and_time/calculations.rb +37 -47
  34. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  35. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  36. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  37. data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
  38. data/lib/active_support/core_ext/enumerable.rb +171 -75
  39. data/lib/active_support/core_ext/hash.rb +1 -2
  40. data/lib/active_support/core_ext/hash/conversions.rb +3 -3
  41. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  42. data/lib/active_support/core_ext/hash/except.rb +2 -2
  43. data/lib/active_support/core_ext/hash/keys.rb +1 -30
  44. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  45. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  46. data/lib/active_support/core_ext/kernel.rb +0 -1
  47. data/lib/active_support/core_ext/load_error.rb +1 -1
  48. data/lib/active_support/core_ext/marshal.rb +2 -0
  49. data/lib/active_support/core_ext/module.rb +0 -1
  50. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  51. data/lib/active_support/core_ext/module/attribute_accessors.rb +30 -39
  52. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +17 -19
  53. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  54. data/lib/active_support/core_ext/module/delegation.rb +76 -33
  55. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  56. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  57. data/lib/active_support/core_ext/name_error.rb +29 -2
  58. data/lib/active_support/core_ext/numeric.rb +0 -1
  59. data/lib/active_support/core_ext/numeric/conversions.rb +129 -129
  60. data/lib/active_support/core_ext/object/blank.rb +1 -2
  61. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  62. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  63. data/lib/active_support/core_ext/object/json.rb +14 -2
  64. data/lib/active_support/core_ext/object/try.rb +17 -7
  65. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  66. data/lib/active_support/core_ext/range/compare_range.rb +34 -13
  67. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  68. data/lib/active_support/core_ext/range/each.rb +0 -1
  69. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  70. data/lib/active_support/core_ext/regexp.rb +8 -5
  71. data/lib/active_support/core_ext/securerandom.rb +23 -3
  72. data/lib/active_support/core_ext/string/access.rb +5 -16
  73. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  74. data/lib/active_support/core_ext/string/filters.rb +42 -1
  75. data/lib/active_support/core_ext/string/inflections.rb +45 -6
  76. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  77. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  78. data/lib/active_support/core_ext/string/output_safety.rb +70 -13
  79. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  80. data/lib/active_support/core_ext/string/strip.rb +3 -1
  81. data/lib/active_support/core_ext/symbol.rb +3 -0
  82. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  83. data/lib/active_support/core_ext/time/calculations.rb +50 -3
  84. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  85. data/lib/active_support/core_ext/uri.rb +6 -1
  86. data/lib/active_support/current_attributes.rb +15 -2
  87. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  88. data/lib/active_support/dependencies.rb +109 -34
  89. data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
  90. data/lib/active_support/deprecation.rb +6 -1
  91. data/lib/active_support/deprecation/behaviors.rb +16 -3
  92. data/lib/active_support/deprecation/disallowed.rb +56 -0
  93. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  94. data/lib/active_support/deprecation/method_wrappers.rb +18 -23
  95. data/lib/active_support/deprecation/proxy_wrappers.rb +29 -6
  96. data/lib/active_support/deprecation/reporting.rb +50 -7
  97. data/lib/active_support/descendants_tracker.rb +59 -9
  98. data/lib/active_support/duration.rb +90 -38
  99. data/lib/active_support/duration/iso8601_parser.rb +2 -4
  100. data/lib/active_support/duration/iso8601_serializer.rb +18 -14
  101. data/lib/active_support/encrypted_configuration.rb +0 -4
  102. data/lib/active_support/encrypted_file.rb +22 -4
  103. data/lib/active_support/environment_inquirer.rb +20 -0
  104. data/lib/active_support/evented_file_update_checker.rb +82 -117
  105. data/lib/active_support/execution_wrapper.rb +1 -0
  106. data/lib/active_support/file_update_checker.rb +0 -1
  107. data/lib/active_support/fork_tracker.rb +62 -0
  108. data/lib/active_support/gem_version.rb +4 -4
  109. data/lib/active_support/hash_with_indifferent_access.rb +64 -41
  110. data/lib/active_support/i18n.rb +1 -0
  111. data/lib/active_support/i18n_railtie.rb +15 -8
  112. data/lib/active_support/inflector/inflections.rb +2 -7
  113. data/lib/active_support/inflector/methods.rb +49 -58
  114. data/lib/active_support/inflector/transliterate.rb +47 -18
  115. data/lib/active_support/json/decoding.rb +25 -26
  116. data/lib/active_support/json/encoding.rb +11 -3
  117. data/lib/active_support/key_generator.rb +1 -33
  118. data/lib/active_support/lazy_load_hooks.rb +5 -2
  119. data/lib/active_support/locale/en.rb +33 -0
  120. data/lib/active_support/locale/en.yml +7 -3
  121. data/lib/active_support/log_subscriber.rb +39 -9
  122. data/lib/active_support/logger.rb +2 -17
  123. data/lib/active_support/logger_silence.rb +11 -19
  124. data/lib/active_support/logger_thread_safe_level.rb +50 -6
  125. data/lib/active_support/message_encryptor.rb +8 -13
  126. data/lib/active_support/message_verifier.rb +10 -10
  127. data/lib/active_support/messages/metadata.rb +11 -2
  128. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  129. data/lib/active_support/messages/rotator.rb +10 -9
  130. data/lib/active_support/multibyte/chars.rb +10 -68
  131. data/lib/active_support/multibyte/unicode.rb +15 -327
  132. data/lib/active_support/notifications.rb +72 -8
  133. data/lib/active_support/notifications/fanout.rb +116 -16
  134. data/lib/active_support/notifications/instrumenter.rb +71 -9
  135. data/lib/active_support/number_helper.rb +38 -12
  136. data/lib/active_support/number_helper/number_converter.rb +5 -6
  137. data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
  138. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
  139. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  140. data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -3
  141. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  142. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  143. data/lib/active_support/number_helper/number_to_rounded_converter.rb +8 -7
  144. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  145. data/lib/active_support/option_merger.rb +22 -3
  146. data/lib/active_support/ordered_hash.rb +1 -1
  147. data/lib/active_support/ordered_options.rb +13 -3
  148. data/lib/active_support/parameter_filter.rb +133 -0
  149. data/lib/active_support/per_thread_registry.rb +1 -1
  150. data/lib/active_support/rails.rb +1 -10
  151. data/lib/active_support/railtie.rb +23 -1
  152. data/lib/active_support/reloader.rb +4 -5
  153. data/lib/active_support/rescuable.rb +4 -4
  154. data/lib/active_support/secure_compare_rotator.rb +51 -0
  155. data/lib/active_support/security_utils.rb +19 -12
  156. data/lib/active_support/string_inquirer.rb +4 -3
  157. data/lib/active_support/subscriber.rb +72 -28
  158. data/lib/active_support/tagged_logging.rb +42 -8
  159. data/lib/active_support/test_case.rb +91 -0
  160. data/lib/active_support/testing/assertions.rb +30 -9
  161. data/lib/active_support/testing/deprecation.rb +0 -1
  162. data/lib/active_support/testing/file_fixtures.rb +2 -0
  163. data/lib/active_support/testing/isolation.rb +2 -2
  164. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  165. data/lib/active_support/testing/parallelization.rb +51 -0
  166. data/lib/active_support/testing/parallelization/server.rb +78 -0
  167. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  168. data/lib/active_support/testing/stream.rb +1 -2
  169. data/lib/active_support/testing/time_helpers.rb +47 -12
  170. data/lib/active_support/time_with_zone.rb +81 -47
  171. data/lib/active_support/values/time_zone.rb +32 -17
  172. data/lib/active_support/xml_mini.rb +2 -10
  173. data/lib/active_support/xml_mini/jdom.rb +2 -3
  174. data/lib/active_support/xml_mini/libxml.rb +2 -2
  175. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  176. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  177. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  178. data/lib/active_support/xml_mini/rexml.rb +10 -3
  179. metadata +58 -32
  180. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  181. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  182. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  183. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  184. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  185. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  186. data/lib/active_support/core_ext/range/include_range.rb +0 -3
  187. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -11,6 +11,8 @@ require "active_support/testing/isolation"
11
11
  require "active_support/testing/constant_lookup"
12
12
  require "active_support/testing/time_helpers"
13
13
  require "active_support/testing/file_fixtures"
14
+ require "active_support/testing/parallelization"
15
+ require "concurrent/utility/processor_counter"
14
16
 
15
17
  module ActiveSupport
16
18
  class TestCase < ::Minitest::Test
@@ -39,6 +41,95 @@ module ActiveSupport
39
41
  def test_order
40
42
  ActiveSupport.test_order ||= :random
41
43
  end
44
+
45
+ # Parallelizes the test suite.
46
+ #
47
+ # Takes a +workers+ argument that controls how many times the process
48
+ # is forked. For each process a new database will be created suffixed
49
+ # with the worker number.
50
+ #
51
+ # test-database-0
52
+ # test-database-1
53
+ #
54
+ # If <tt>ENV["PARALLEL_WORKERS"]</tt> is set the workers argument will be ignored
55
+ # and the environment variable will be used instead. This is useful for CI
56
+ # environments, or other environments where you may need more workers than
57
+ # you do for local testing.
58
+ #
59
+ # If the number of workers is set to +1+ or fewer, the tests will not be
60
+ # parallelized.
61
+ #
62
+ # If +workers+ is set to +:number_of_processors+, the number of workers will be
63
+ # set to the actual core count on the machine you are on.
64
+ #
65
+ # The default parallelization method is to fork processes. If you'd like to
66
+ # use threads instead you can pass <tt>with: :threads</tt> to the +parallelize+
67
+ # method. Note the threaded parallelization does not create multiple
68
+ # database and will not work with system tests at this time.
69
+ #
70
+ # parallelize(workers: :number_of_processors, with: :threads)
71
+ #
72
+ # The threaded parallelization uses minitest's parallel executor directly.
73
+ # The processes parallelization uses a Ruby DRb server.
74
+ def parallelize(workers: :number_of_processors, with: :processes)
75
+ workers = Concurrent.physical_processor_count if workers == :number_of_processors
76
+ workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"]
77
+
78
+ return if workers <= 1
79
+
80
+ executor = case with
81
+ when :processes
82
+ Testing::Parallelization.new(workers)
83
+ when :threads
84
+ Minitest::Parallel::Executor.new(workers)
85
+ else
86
+ raise ArgumentError, "#{with} is not a supported parallelization executor."
87
+ end
88
+
89
+ self.lock_threads = false if defined?(self.lock_threads) && with == :threads
90
+
91
+ Minitest.parallel_executor = executor
92
+
93
+ parallelize_me!
94
+ end
95
+
96
+ # Set up hook for parallel testing. This can be used if you have multiple
97
+ # databases or any behavior that needs to be run after the process is forked
98
+ # but before the tests run.
99
+ #
100
+ # Note: this feature is not available with the threaded parallelization.
101
+ #
102
+ # In your +test_helper.rb+ add the following:
103
+ #
104
+ # class ActiveSupport::TestCase
105
+ # parallelize_setup do
106
+ # # create databases
107
+ # end
108
+ # end
109
+ def parallelize_setup(&block)
110
+ ActiveSupport::Testing::Parallelization.after_fork_hook do |worker|
111
+ yield worker
112
+ end
113
+ end
114
+
115
+ # Clean up hook for parallel testing. This can be used to drop databases
116
+ # if your app uses multiple write/read databases or other clean up before
117
+ # the tests finish. This runs before the forked process is closed.
118
+ #
119
+ # Note: this feature is not available with the threaded parallelization.
120
+ #
121
+ # In your +test_helper.rb+ add the following:
122
+ #
123
+ # class ActiveSupport::TestCase
124
+ # parallelize_teardown do
125
+ # # drop databases
126
+ # end
127
+ # end
128
+ def parallelize_teardown(&block)
129
+ ActiveSupport::Testing::Parallelization.run_cleanup_hook do |worker|
130
+ yield worker
131
+ end
132
+ end
42
133
  end
43
134
 
44
135
  alias_method :method_name, :name
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/enumerable"
4
+
3
5
  module ActiveSupport
4
6
  module Testing
5
7
  module Assertions
@@ -30,6 +32,8 @@ module ActiveSupport
30
32
  # end
31
33
  def assert_nothing_raised
32
34
  yield
35
+ rescue => error
36
+ raise Minitest::UnexpectedError.new(error)
33
37
  end
34
38
 
35
39
  # Test numeric difference between the return value of an expression as a
@@ -87,7 +91,7 @@ module ActiveSupport
87
91
  else
88
92
  difference = args[0] || 1
89
93
  message = args[1]
90
- Hash[Array(expression).map { |e| [e, difference] }]
94
+ Array(expression).index_with(difference)
91
95
  end
92
96
 
93
97
  exps = expressions.keys.map { |e|
@@ -95,7 +99,7 @@ module ActiveSupport
95
99
  }
96
100
  before = exps.map(&:call)
97
101
 
98
- retval = yield
102
+ retval = assert_nothing_raised(&block)
99
103
 
100
104
  expressions.zip(exps, before) do |(code, diff), exp, before_value|
101
105
  error = "#{code.inspect} didn't change by #{diff}"
@@ -113,11 +117,23 @@ module ActiveSupport
113
117
  # post :create, params: { article: invalid_attributes }
114
118
  # end
115
119
  #
120
+ # A lambda can be passed in and evaluated.
121
+ #
122
+ # assert_no_difference -> { Article.count } do
123
+ # post :create, params: { article: invalid_attributes }
124
+ # end
125
+ #
116
126
  # An error message can be specified.
117
127
  #
118
128
  # assert_no_difference 'Article.count', 'An Article should not be created' do
119
129
  # post :create, params: { article: invalid_attributes }
120
130
  # end
131
+ #
132
+ # An array of expressions can also be passed in and evaluated.
133
+ #
134
+ # assert_no_difference [ 'Article.count', -> { Post.count } ] do
135
+ # post :create, params: { article: invalid_attributes }
136
+ # end
121
137
  def assert_no_difference(expression, message = nil, &block)
122
138
  assert_difference expression, 0, message, &block
123
139
  end
@@ -160,10 +176,10 @@ module ActiveSupport
160
176
  exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) }
161
177
 
162
178
  before = exp.call
163
- retval = yield
179
+ retval = assert_nothing_raised(&block)
164
180
 
165
181
  unless from == UNTRACKED
166
- error = "#{expression.inspect} isn't #{from.inspect}"
182
+ error = "Expected change from #{from.inspect}"
167
183
  error = "#{message}.\n#{error}" if message
168
184
  assert from === before, error
169
185
  end
@@ -173,10 +189,10 @@ module ActiveSupport
173
189
  error = "#{expression.inspect} didn't change"
174
190
  error = "#{error}. It was already #{to}" if before == to
175
191
  error = "#{message}.\n#{error}" if message
176
- assert before != after, error
192
+ assert_not_equal before, after, error
177
193
 
178
194
  unless to == UNTRACKED
179
- error = "#{expression.inspect} didn't change to #{to}"
195
+ error = "Expected change to #{to}\n"
180
196
  error = "#{message}.\n#{error}" if message
181
197
  assert to === after, error
182
198
  end
@@ -200,12 +216,17 @@ module ActiveSupport
200
216
  exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) }
201
217
 
202
218
  before = exp.call
203
- retval = yield
219
+ retval = assert_nothing_raised(&block)
204
220
  after = exp.call
205
221
 
206
- error = "#{expression.inspect} did change to #{after}"
222
+ error = "#{expression.inspect} changed"
207
223
  error = "#{message}.\n#{error}" if message
208
- assert before == after, error
224
+
225
+ if before.nil?
226
+ assert_nil after, error
227
+ else
228
+ assert_equal before, after, error
229
+ end
209
230
 
210
231
  retval
211
232
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/deprecation"
4
- require "active_support/core_ext/regexp"
5
4
 
6
5
  module ActiveSupport
7
6
  module Testing
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/concern"
4
+
3
5
  module ActiveSupport
4
6
  module Testing
5
7
  # Adds simple access to sample files called file fixtures.
@@ -56,7 +56,7 @@ module ActiveSupport
56
56
  write.close
57
57
  result = read.read
58
58
  Process.wait2(pid)
59
- result.unpack("m")[0]
59
+ result.unpack1("m")
60
60
  end
61
61
  end
62
62
 
@@ -98,7 +98,7 @@ module ActiveSupport
98
98
  nil
99
99
  end
100
100
 
101
- return tmpfile.read.unpack("m")[0]
101
+ return tmpfile.read.unpack1("m")
102
102
  end
103
103
  end
104
104
  end
@@ -17,7 +17,7 @@ module ActiveSupport
17
17
  assert_equal times, times_called, error
18
18
  end
19
19
 
20
- def assert_called_with(object, method_name, args = [], returns: nil)
20
+ def assert_called_with(object, method_name, args, returns: nil)
21
21
  mock = Minitest::Mock.new
22
22
 
23
23
  if args.all? { |arg| arg.is_a?(Array) }
@@ -35,6 +35,33 @@ module ActiveSupport
35
35
  assert_called(object, method_name, message, times: 0, &block)
36
36
  end
37
37
 
38
+ def assert_called_on_instance_of(klass, method_name, message = nil, times: 1, returns: nil)
39
+ times_called = 0
40
+ klass.define_method("stubbed_#{method_name}") do |*|
41
+ times_called += 1
42
+
43
+ returns
44
+ end
45
+
46
+ klass.alias_method "original_#{method_name}", method_name
47
+ klass.alias_method method_name, "stubbed_#{method_name}"
48
+
49
+ yield
50
+
51
+ error = "Expected #{method_name} to be called #{times} times, but was called #{times_called} times"
52
+ error = "#{message}.\n#{error}" if message
53
+
54
+ assert_equal times, times_called, error
55
+ ensure
56
+ klass.alias_method method_name, "original_#{method_name}"
57
+ klass.undef_method "original_#{method_name}"
58
+ klass.undef_method "stubbed_#{method_name}"
59
+ end
60
+
61
+ def assert_not_called_on_instance_of(klass, method_name, message = nil, &block)
62
+ assert_called_on_instance_of(klass, method_name, message, times: 0, &block)
63
+ end
64
+
38
65
  def stub_any_instance(klass, instance: klass.new)
39
66
  klass.stub(:new, instance) { yield instance }
40
67
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "drb"
4
+ require "drb/unix" unless Gem.win_platform?
5
+ require "active_support/core_ext/module/attribute_accessors"
6
+ require "active_support/testing/parallelization/server"
7
+ require "active_support/testing/parallelization/worker"
8
+
9
+ module ActiveSupport
10
+ module Testing
11
+ class Parallelization # :nodoc:
12
+ @@after_fork_hooks = []
13
+
14
+ def self.after_fork_hook(&blk)
15
+ @@after_fork_hooks << blk
16
+ end
17
+
18
+ cattr_reader :after_fork_hooks
19
+
20
+ @@run_cleanup_hooks = []
21
+
22
+ def self.run_cleanup_hook(&blk)
23
+ @@run_cleanup_hooks << blk
24
+ end
25
+
26
+ cattr_reader :run_cleanup_hooks
27
+
28
+ def initialize(worker_count)
29
+ @worker_count = worker_count
30
+ @queue_server = Server.new
31
+ @worker_pool = []
32
+ @url = DRb.start_service("drbunix:", @queue_server).uri
33
+ end
34
+
35
+ def start
36
+ @worker_pool = @worker_count.times.map do |worker|
37
+ Worker.new(worker, @url).start
38
+ end
39
+ end
40
+
41
+ def <<(work)
42
+ @queue_server << work
43
+ end
44
+
45
+ def shutdown
46
+ @queue_server.shutdown
47
+ @worker_pool.each { |pid| Process.waitpid pid }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "drb"
4
+ require "drb/unix" unless Gem.win_platform?
5
+
6
+ module ActiveSupport
7
+ module Testing
8
+ class Parallelization # :nodoc:
9
+ class Server
10
+ include DRb::DRbUndumped
11
+
12
+ def initialize
13
+ @queue = Queue.new
14
+ @active_workers = Concurrent::Map.new
15
+ @in_flight = Concurrent::Map.new
16
+ end
17
+
18
+ def record(reporter, result)
19
+ raise DRb::DRbConnError if result.is_a?(DRb::DRbUnknown)
20
+
21
+ @in_flight.delete([result.klass, result.name])
22
+
23
+ reporter.synchronize do
24
+ reporter.record(result)
25
+ end
26
+ end
27
+
28
+ def <<(o)
29
+ o[2] = DRbObject.new(o[2]) if o
30
+ @queue << o
31
+ end
32
+
33
+ def pop
34
+ if test = @queue.pop
35
+ @in_flight[[test[0].to_s, test[1]]] = test
36
+ test
37
+ end
38
+ end
39
+
40
+ def start_worker(worker_id)
41
+ @active_workers[worker_id] = true
42
+ end
43
+
44
+ def stop_worker(worker_id)
45
+ @active_workers.delete(worker_id)
46
+ end
47
+
48
+ def active_workers?
49
+ @active_workers.size > 0
50
+ end
51
+
52
+ def shutdown
53
+ # Wait for initial queue to drain
54
+ while @queue.length != 0
55
+ sleep 0.1
56
+ end
57
+
58
+ @queue.close
59
+
60
+ # Wait until all workers have finished
61
+ while active_workers?
62
+ sleep 0.1
63
+ end
64
+
65
+ @in_flight.values.each do |(klass, name, reporter)|
66
+ result = Minitest::Result.from(klass.new(name))
67
+ error = RuntimeError.new("result not reported")
68
+ error.set_backtrace([""])
69
+ result.failures << Minitest::UnexpectedError.new(error)
70
+ reporter.synchronize do
71
+ reporter.record(result)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module Testing
5
+ class Parallelization # :nodoc:
6
+ class Worker
7
+ def initialize(number, url)
8
+ @id = SecureRandom.uuid
9
+ @number = number
10
+ @url = url
11
+ @setup_exception = nil
12
+ end
13
+
14
+ def start
15
+ fork do
16
+ set_process_title("(starting)")
17
+
18
+ DRb.stop_service
19
+
20
+ @queue = DRbObject.new_with_uri(@url)
21
+ @queue.start_worker(@id)
22
+
23
+ begin
24
+ after_fork
25
+ rescue => @setup_exception; end
26
+
27
+ work_from_queue
28
+ ensure
29
+ set_process_title("(stopping)")
30
+
31
+ run_cleanup
32
+ @queue.stop_worker(@id)
33
+ end
34
+ end
35
+
36
+ def work_from_queue
37
+ while job = @queue.pop
38
+ perform_job(job)
39
+ end
40
+ end
41
+
42
+ def perform_job(job)
43
+ klass = job[0]
44
+ method = job[1]
45
+ reporter = job[2]
46
+
47
+ set_process_title("#{klass}##{method}")
48
+
49
+ result = klass.with_info_handler reporter do
50
+ Minitest.run_one_method(klass, method)
51
+ end
52
+
53
+ safe_record(reporter, result)
54
+ end
55
+
56
+ def safe_record(reporter, result)
57
+ add_setup_exception(result) if @setup_exception
58
+
59
+ begin
60
+ @queue.record(reporter, result)
61
+ rescue DRb::DRbConnError
62
+ result.failures.map! do |failure|
63
+ if failure.respond_to?(:error)
64
+ # minitest >5.14.0
65
+ error = DRb::DRbRemoteError.new(failure.error)
66
+ else
67
+ error = DRb::DRbRemoteError.new(failure.exception)
68
+ end
69
+ Minitest::UnexpectedError.new(error)
70
+ end
71
+ @queue.record(reporter, result)
72
+ end
73
+
74
+ set_process_title("(idle)")
75
+ end
76
+
77
+ def after_fork
78
+ Parallelization.after_fork_hooks.each do |cb|
79
+ cb.call(@number)
80
+ end
81
+ end
82
+
83
+ def run_cleanup
84
+ Parallelization.run_cleanup_hooks.each do |cb|
85
+ cb.call(@number)
86
+ end
87
+ end
88
+
89
+ private
90
+ def add_setup_exception(result)
91
+ result.failures.prepend Minitest::UnexpectedError.new(@setup_exception)
92
+ end
93
+
94
+ def set_process_title(status)
95
+ Process.setproctitle("Rails test worker #{@number} - #{status}")
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end