activesupport 6.0.4.4 → 7.0.4.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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +257 -532
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_support/actionable_error.rb +1 -1
  5. data/lib/active_support/array_inquirer.rb +2 -2
  6. data/lib/active_support/backtrace_cleaner.rb +5 -5
  7. data/lib/active_support/benchmarkable.rb +3 -3
  8. data/lib/active_support/cache/file_store.rb +16 -10
  9. data/lib/active_support/cache/mem_cache_store.rb +163 -42
  10. data/lib/active_support/cache/memory_store.rb +57 -29
  11. data/lib/active_support/cache/null_store.rb +10 -2
  12. data/lib/active_support/cache/redis_cache_store.rb +79 -98
  13. data/lib/active_support/cache/strategy/local_cache.rb +49 -57
  14. data/lib/active_support/cache.rb +378 -179
  15. data/lib/active_support/callbacks.rb +230 -122
  16. data/lib/active_support/code_generator.rb +65 -0
  17. data/lib/active_support/concern.rb +49 -5
  18. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  19. data/lib/active_support/concurrency/share_lock.rb +2 -2
  20. data/lib/active_support/configurable.rb +9 -6
  21. data/lib/active_support/configuration_file.rb +51 -0
  22. data/lib/active_support/core_ext/array/access.rb +1 -5
  23. data/lib/active_support/core_ext/array/conversions.rb +13 -12
  24. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  25. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  26. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  27. data/lib/active_support/core_ext/array.rb +1 -0
  28. data/lib/active_support/core_ext/benchmark.rb +2 -2
  29. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  30. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  31. data/lib/active_support/core_ext/class/subclasses.rb +9 -22
  32. data/lib/active_support/core_ext/date/blank.rb +1 -1
  33. data/lib/active_support/core_ext/date/calculations.rb +9 -9
  34. data/lib/active_support/core_ext/date/conversions.rb +16 -15
  35. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  36. data/lib/active_support/core_ext/date.rb +1 -0
  37. data/lib/active_support/core_ext/date_and_time/calculations.rb +17 -4
  38. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  39. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  40. data/lib/active_support/core_ext/date_time/conversions.rb +13 -13
  41. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  42. data/lib/active_support/core_ext/date_time.rb +1 -0
  43. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  44. data/lib/active_support/core_ext/enumerable.rb +164 -23
  45. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  46. data/lib/active_support/core_ext/hash/conversions.rb +2 -3
  47. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  48. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  49. data/lib/active_support/core_ext/hash/keys.rb +2 -2
  50. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  51. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  52. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  53. data/lib/active_support/core_ext/load_error.rb +1 -1
  54. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  55. data/lib/active_support/core_ext/module/attribute_accessors.rb +25 -29
  56. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +26 -13
  57. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  58. data/lib/active_support/core_ext/module/delegation.rb +40 -36
  59. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  60. data/lib/active_support/core_ext/name_error.rb +23 -2
  61. data/lib/active_support/core_ext/numeric/conversions.rb +80 -73
  62. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  63. data/lib/active_support/core_ext/numeric.rb +1 -0
  64. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  65. data/lib/active_support/core_ext/object/blank.rb +2 -2
  66. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  67. data/lib/active_support/core_ext/object/duplicable.rb +11 -0
  68. data/lib/active_support/core_ext/object/json.rb +42 -26
  69. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  70. data/lib/active_support/core_ext/object/try.rb +20 -20
  71. data/lib/active_support/core_ext/object/with_options.rb +20 -1
  72. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  73. data/lib/active_support/core_ext/pathname.rb +3 -0
  74. data/lib/active_support/core_ext/range/compare_range.rb +6 -25
  75. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  76. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  77. data/lib/active_support/core_ext/range/each.rb +1 -1
  78. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  79. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  80. data/lib/active_support/core_ext/range.rb +1 -1
  81. data/lib/active_support/core_ext/regexp.rb +8 -1
  82. data/lib/active_support/core_ext/securerandom.rb +1 -1
  83. data/lib/active_support/core_ext/string/access.rb +5 -24
  84. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  85. data/lib/active_support/core_ext/string/filters.rb +1 -1
  86. data/lib/active_support/core_ext/string/inflections.rb +39 -5
  87. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  88. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  89. data/lib/active_support/core_ext/string/output_safety.rb +92 -41
  90. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  91. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  92. data/lib/active_support/core_ext/symbol.rb +3 -0
  93. data/lib/active_support/core_ext/time/calculations.rb +25 -7
  94. data/lib/active_support/core_ext/time/conversions.rb +15 -12
  95. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  96. data/lib/active_support/core_ext/time/zones.rb +7 -22
  97. data/lib/active_support/core_ext/time.rb +1 -0
  98. data/lib/active_support/core_ext/uri.rb +3 -23
  99. data/lib/active_support/core_ext.rb +2 -1
  100. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  101. data/lib/active_support/current_attributes.rb +39 -16
  102. data/lib/active_support/dependencies/interlock.rb +10 -18
  103. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  104. data/lib/active_support/dependencies.rb +58 -769
  105. data/lib/active_support/deprecation/behaviors.rb +23 -7
  106. data/lib/active_support/deprecation/disallowed.rb +56 -0
  107. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  108. data/lib/active_support/deprecation/method_wrappers.rb +6 -5
  109. data/lib/active_support/deprecation/proxy_wrappers.rb +4 -4
  110. data/lib/active_support/deprecation/reporting.rb +50 -7
  111. data/lib/active_support/deprecation.rb +7 -2
  112. data/lib/active_support/descendants_tracker.rb +174 -64
  113. data/lib/active_support/digest.rb +5 -3
  114. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  115. data/lib/active_support/duration/iso8601_serializer.rb +24 -10
  116. data/lib/active_support/duration.rb +134 -55
  117. data/lib/active_support/encrypted_configuration.rb +13 -2
  118. data/lib/active_support/encrypted_file.rb +32 -3
  119. data/lib/active_support/environment_inquirer.rb +20 -0
  120. data/lib/active_support/error_reporter.rb +117 -0
  121. data/lib/active_support/evented_file_update_checker.rb +72 -138
  122. data/lib/active_support/execution_context/test_helper.rb +13 -0
  123. data/lib/active_support/execution_context.rb +53 -0
  124. data/lib/active_support/execution_wrapper.rb +43 -21
  125. data/lib/active_support/executor/test_helper.rb +7 -0
  126. data/lib/active_support/fork_tracker.rb +71 -0
  127. data/lib/active_support/gem_version.rb +3 -3
  128. data/lib/active_support/hash_with_indifferent_access.rb +51 -25
  129. data/lib/active_support/html_safe_translation.rb +43 -0
  130. data/lib/active_support/i18n.rb +1 -0
  131. data/lib/active_support/i18n_railtie.rb +14 -19
  132. data/lib/active_support/inflector/inflections.rb +24 -9
  133. data/lib/active_support/inflector/methods.rb +29 -49
  134. data/lib/active_support/inflector/transliterate.rb +5 -5
  135. data/lib/active_support/isolated_execution_state.rb +72 -0
  136. data/lib/active_support/json/decoding.rb +4 -4
  137. data/lib/active_support/json/encoding.rb +8 -4
  138. data/lib/active_support/key_generator.rb +23 -6
  139. data/lib/active_support/lazy_load_hooks.rb +28 -4
  140. data/lib/active_support/locale/en.yml +8 -4
  141. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  142. data/lib/active_support/log_subscriber.rb +23 -5
  143. data/lib/active_support/logger.rb +1 -1
  144. data/lib/active_support/logger_silence.rb +2 -26
  145. data/lib/active_support/logger_thread_safe_level.rb +34 -21
  146. data/lib/active_support/message_encryptor.rb +16 -13
  147. data/lib/active_support/message_verifier.rb +50 -18
  148. data/lib/active_support/messages/metadata.rb +2 -2
  149. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  150. data/lib/active_support/messages/rotator.rb +6 -5
  151. data/lib/active_support/multibyte/chars.rb +13 -52
  152. data/lib/active_support/multibyte/unicode.rb +1 -87
  153. data/lib/active_support/multibyte.rb +1 -1
  154. data/lib/active_support/notifications/fanout.rb +110 -69
  155. data/lib/active_support/notifications/instrumenter.rb +37 -29
  156. data/lib/active_support/notifications.rb +55 -28
  157. data/lib/active_support/number_helper/number_converter.rb +2 -4
  158. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  159. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  160. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  161. data/lib/active_support/number_helper/number_to_human_size_converter.rb +2 -2
  162. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  163. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  164. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  165. data/lib/active_support/number_helper.rb +29 -16
  166. data/lib/active_support/option_merger.rb +11 -18
  167. data/lib/active_support/ordered_hash.rb +1 -1
  168. data/lib/active_support/ordered_options.rb +9 -3
  169. data/lib/active_support/parameter_filter.rb +21 -11
  170. data/lib/active_support/per_thread_registry.rb +6 -1
  171. data/lib/active_support/rails.rb +1 -4
  172. data/lib/active_support/railtie.rb +77 -5
  173. data/lib/active_support/reloader.rb +1 -1
  174. data/lib/active_support/rescuable.rb +16 -16
  175. data/lib/active_support/ruby_features.rb +7 -0
  176. data/lib/active_support/secure_compare_rotator.rb +51 -0
  177. data/lib/active_support/security_utils.rb +19 -12
  178. data/lib/active_support/string_inquirer.rb +2 -2
  179. data/lib/active_support/subscriber.rb +19 -25
  180. data/lib/active_support/tagged_logging.rb +31 -6
  181. data/lib/active_support/test_case.rb +13 -21
  182. data/lib/active_support/testing/assertions.rb +50 -13
  183. data/lib/active_support/testing/deprecation.rb +52 -1
  184. data/lib/active_support/testing/isolation.rb +2 -2
  185. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  186. data/lib/active_support/testing/parallelization/server.rb +82 -0
  187. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  188. data/lib/active_support/testing/parallelization.rb +16 -95
  189. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  190. data/lib/active_support/testing/stream.rb +3 -5
  191. data/lib/active_support/testing/tagged_logging.rb +1 -1
  192. data/lib/active_support/testing/time_helpers.rb +53 -5
  193. data/lib/active_support/time_with_zone.rb +126 -62
  194. data/lib/active_support/values/time_zone.rb +54 -23
  195. data/lib/active_support/version.rb +1 -1
  196. data/lib/active_support/xml_mini/jdom.rb +1 -1
  197. data/lib/active_support/xml_mini/libxml.rb +5 -5
  198. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  199. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  200. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  201. data/lib/active_support/xml_mini/rexml.rb +9 -2
  202. data/lib/active_support/xml_mini.rb +5 -4
  203. data/lib/active_support.rb +29 -1
  204. metadata +46 -45
  205. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  206. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  207. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  208. data/lib/active_support/core_ext/marshal.rb +0 -24
  209. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  210. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  211. data/lib/active_support/core_ext/range/include_range.rb +0 -9
  212. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
@@ -8,11 +8,20 @@ require "active_support/logger"
8
8
  module ActiveSupport
9
9
  # Wraps any standard Logger object to provide tagging capabilities.
10
10
  #
11
+ # May be called with a block:
12
+ #
11
13
  # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
12
14
  # logger.tagged('BCX') { logger.info 'Stuff' } # Logs "[BCX] Stuff"
13
15
  # logger.tagged('BCX', "Jason") { logger.info 'Stuff' } # Logs "[BCX] [Jason] Stuff"
14
16
  # logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff"
15
17
  #
18
+ # If called without a block, a new logger will be returned with applied tags:
19
+ #
20
+ # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
21
+ # logger.tagged("BCX").info "Stuff" # Logs "[BCX] Stuff"
22
+ # logger.tagged("BCX", "Jason").info "Stuff" # Logs "[BCX] [Jason] Stuff"
23
+ # logger.tagged("BCX").tagged("Jason").info "Stuff" # Logs "[BCX] [Jason] Stuff"
24
+ #
16
25
  # This is used by the default Rails.logger as configured by Railties to make
17
26
  # it easy to stamp log lines with subdomains, request ids, and anything else
18
27
  # to aid debugging of multi-user production applications.
@@ -31,9 +40,10 @@ module ActiveSupport
31
40
  end
32
41
 
33
42
  def push_tags(*tags)
34
- tags.flatten.reject(&:blank?).tap do |new_tags|
35
- current_tags.concat new_tags
36
- end
43
+ tags.flatten!
44
+ tags.reject!(&:blank?)
45
+ current_tags.concat tags
46
+ tags
37
47
  end
38
48
 
39
49
  def pop_tags(size = 1)
@@ -47,7 +57,7 @@ module ActiveSupport
47
57
  def current_tags
48
58
  # We use our object ID here to avoid conflicting with other instances
49
59
  thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}"
50
- Thread.current[thread_key] ||= []
60
+ IsolatedExecutionState[thread_key] ||= []
51
61
  end
52
62
 
53
63
  def tags_text
@@ -60,8 +70,16 @@ module ActiveSupport
60
70
  end
61
71
  end
62
72
 
73
+ module LocalTagStorage # :nodoc:
74
+ attr_accessor :current_tags
75
+
76
+ def self.extended(base)
77
+ base.current_tags = []
78
+ end
79
+ end
80
+
63
81
  def self.new(logger)
64
- logger = logger.dup
82
+ logger = logger.clone
65
83
 
66
84
  if logger.formatter
67
85
  logger.formatter = logger.formatter.dup
@@ -77,7 +95,14 @@ module ActiveSupport
77
95
  delegate :push_tags, :pop_tags, :clear_tags!, to: :formatter
78
96
 
79
97
  def tagged(*tags)
80
- formatter.tagged(*tags) { yield self }
98
+ if block_given?
99
+ formatter.tagged(*tags) { yield self }
100
+ else
101
+ logger = ActiveSupport::TaggedLogging.new(self)
102
+ logger.formatter.extend LocalTagStorage
103
+ logger.push_tags(*formatter.current_tags, *tags)
104
+ logger
105
+ end
81
106
  end
82
107
 
83
108
  def flush
@@ -12,6 +12,7 @@ require "active_support/testing/constant_lookup"
12
12
  require "active_support/testing/time_helpers"
13
13
  require "active_support/testing/file_fixtures"
14
14
  require "active_support/testing/parallelization"
15
+ require "active_support/testing/parallelize_executor"
15
16
  require "concurrent/utility/processor_counter"
16
17
 
17
18
  module ActiveSupport
@@ -71,26 +72,17 @@ module ActiveSupport
71
72
  #
72
73
  # The threaded parallelization uses minitest's parallel executor directly.
73
74
  # The processes parallelization uses a Ruby DRb server.
74
- def parallelize(workers: :number_of_processors, with: :processes)
75
+ #
76
+ # Because parallelization presents an overhead, it is only enabled when the
77
+ # number of tests to run is above the +threshold+ param. The default value is
78
+ # 50, and it's configurable via +config.active_support.test_parallelization_threshold+.
79
+ def parallelize(workers: :number_of_processors, with: :processes, threshold: ActiveSupport.test_parallelization_threshold)
75
80
  workers = Concurrent.physical_processor_count if workers == :number_of_processors
76
81
  workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"]
77
82
 
78
83
  return if workers <= 1
79
84
 
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!
85
+ Minitest.parallel_executor = ActiveSupport::Testing::ParallelizeExecutor.new(size: workers, with: with, threshold: threshold)
94
86
  end
95
87
 
96
88
  # Set up hook for parallel testing. This can be used if you have multiple
@@ -107,9 +99,7 @@ module ActiveSupport
107
99
  # end
108
100
  # end
109
101
  def parallelize_setup(&block)
110
- ActiveSupport::Testing::Parallelization.after_fork_hook do |worker|
111
- yield worker
112
- end
102
+ ActiveSupport::Testing::Parallelization.after_fork_hook(&block)
113
103
  end
114
104
 
115
105
  # Clean up hook for parallel testing. This can be used to drop databases
@@ -126,9 +116,7 @@ module ActiveSupport
126
116
  # end
127
117
  # end
128
118
  def parallelize_teardown(&block)
129
- ActiveSupport::Testing::Parallelization.run_cleanup_hook do |worker|
130
- yield worker
131
- end
119
+ ActiveSupport::Testing::Parallelization.run_cleanup_hook(&block)
132
120
  end
133
121
  end
134
122
 
@@ -159,5 +147,9 @@ module ActiveSupport
159
147
  alias :assert_not_same :refute_same
160
148
 
161
149
  ActiveSupport.run_load_hooks(:active_support_test_case, self)
150
+
151
+ def inspect # :nodoc:
152
+ Object.instance_method(:to_s).bind_call(self)
153
+ end
162
154
  end
163
155
  end
@@ -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_or_warn("assert_difference", &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}"
@@ -155,7 +159,7 @@ module ActiveSupport
155
159
  # @object = 42
156
160
  # end
157
161
  #
158
- # The keyword arguments :from and :to can be given to specify the
162
+ # The keyword arguments +:from+ and +:to+ can be given to specify the
159
163
  # expected initial value and the expected value after the block was
160
164
  # executed.
161
165
  #
@@ -172,10 +176,10 @@ module ActiveSupport
172
176
  exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) }
173
177
 
174
178
  before = exp.call
175
- retval = yield
179
+ retval = _assert_nothing_raised_or_warn("assert_changes", &block)
176
180
 
177
181
  unless from == UNTRACKED
178
- error = "#{expression.inspect} isn't #{from.inspect}"
182
+ error = "Expected change from #{from.inspect}"
179
183
  error = "#{message}.\n#{error}" if message
180
184
  assert from === before, error
181
185
  end
@@ -185,12 +189,10 @@ module ActiveSupport
185
189
  error = "#{expression.inspect} didn't change"
186
190
  error = "#{error}. It was already #{to}" if before == to
187
191
  error = "#{message}.\n#{error}" if message
188
- assert before != after, error
192
+ refute_equal before, after, error
189
193
 
190
194
  unless to == UNTRACKED
191
- error = "#{expression.inspect} didn't change to as expected\n"
192
- error = "#{error}Expected: #{to.inspect}\n"
193
- error = "#{error} Actual: #{after.inspect}"
195
+ error = "Expected change to #{to}\n"
194
196
  error = "#{message}.\n#{error}" if message
195
197
  assert to === after, error
196
198
  end
@@ -205,24 +207,59 @@ module ActiveSupport
205
207
  # post :create, params: { status: { ok: true } }
206
208
  # end
207
209
  #
210
+ # Provide the optional keyword argument :from to specify the expected
211
+ # initial value.
212
+ #
213
+ # assert_no_changes -> { Status.all_good? }, from: true do
214
+ # post :create, params: { status: { ok: true } }
215
+ # end
216
+ #
208
217
  # An error message can be specified.
209
218
  #
210
219
  # assert_no_changes -> { Status.all_good? }, 'Expected the status to be good' do
211
220
  # post :create, params: { status: { ok: false } }
212
221
  # end
213
- def assert_no_changes(expression, message = nil, &block)
222
+ def assert_no_changes(expression, message = nil, from: UNTRACKED, &block)
214
223
  exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) }
215
224
 
216
225
  before = exp.call
217
- retval = yield
226
+ retval = _assert_nothing_raised_or_warn("assert_no_changes", &block)
227
+
228
+ unless from == UNTRACKED
229
+ error = "Expected initial value of #{from.inspect}"
230
+ error = "#{message}.\n#{error}" if message
231
+ assert from === before, error
232
+ end
233
+
218
234
  after = exp.call
219
235
 
220
- error = "#{expression.inspect} did change to #{after}"
236
+ error = "#{expression.inspect} changed"
221
237
  error = "#{message}.\n#{error}" if message
222
- assert before == after, error
238
+
239
+ if before.nil?
240
+ assert_nil after, error
241
+ else
242
+ assert_equal before, after, error
243
+ end
223
244
 
224
245
  retval
225
246
  end
247
+
248
+ private
249
+ def _assert_nothing_raised_or_warn(assertion, &block)
250
+ assert_nothing_raised(&block)
251
+ rescue Minitest::UnexpectedError => e
252
+ if tagged_logger && tagged_logger.warn?
253
+ warning = <<~MSG
254
+ #{self.class} - #{name}: #{e.error.class} raised.
255
+ If you expected this exception, use `assert_raises` as near to the code that raises as possible.
256
+ Other block based assertions (e.g. `#{assertion}`) can be used, as long as `assert_raises` is inside their block.
257
+ MSG
258
+ tagged_logger.warn warning
259
+ end
260
+
261
+ raise
262
+ end
226
263
  end
227
264
  end
228
265
  end
@@ -4,7 +4,30 @@ require "active_support/deprecation"
4
4
 
5
5
  module ActiveSupport
6
6
  module Testing
7
- module Deprecation #:nodoc:
7
+ module Deprecation
8
+ # Asserts that a matching deprecation warning was emitted by the given deprecator during the execution of the yielded block.
9
+ #
10
+ # assert_deprecated(/foo/, CustomDeprecator) do
11
+ # CustomDeprecator.warn "foo should no longer be used"
12
+ # end
13
+ #
14
+ # The +match+ object may be a +Regexp+, or +String+ appearing in the message.
15
+ #
16
+ # assert_deprecated('foo', CustomDeprecator) do
17
+ # CustomDeprecator.warn "foo should no longer be used"
18
+ # end
19
+ #
20
+ # If the +match+ is omitted (or explicitly +nil+), any deprecation warning will match.
21
+ #
22
+ # assert_deprecated(nil, CustomDeprecator) do
23
+ # CustomDeprecator.warn "foo should no longer be used"
24
+ # end
25
+ #
26
+ # If no +deprecator+ is given, defaults to ActiveSupport::Deprecation.
27
+ #
28
+ # assert_deprecated do
29
+ # ActiveSupport::Deprecation.warn "foo should no longer be used"
30
+ # end
8
31
  def assert_deprecated(match = nil, deprecator = nil, &block)
9
32
  result, warnings = collect_deprecations(deprecator, &block)
10
33
  assert !warnings.empty?, "Expected a deprecation warning within the block but received none"
@@ -15,12 +38,40 @@ module ActiveSupport
15
38
  result
16
39
  end
17
40
 
41
+ # Asserts that no deprecation warnings are emitted by the given deprecator during the execution of the yielded block.
42
+ #
43
+ # assert_not_deprecated(CustomDeprecator) do
44
+ # CustomDeprecator.warn "message" # fails assertion
45
+ # end
46
+ #
47
+ # If no +deprecator+ is given, defaults to ActiveSupport::Deprecation.
48
+ #
49
+ # assert_not_deprecated do
50
+ # ActiveSupport::Deprecation.warn "message" # fails assertion
51
+ # end
52
+ #
53
+ # assert_not_deprecated do
54
+ # CustomDeprecator.warn "message" # passes assertion
55
+ # end
18
56
  def assert_not_deprecated(deprecator = nil, &block)
19
57
  result, deprecations = collect_deprecations(deprecator, &block)
20
58
  assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}"
21
59
  result
22
60
  end
23
61
 
62
+ # Returns an array of all the deprecation warnings emitted by the given
63
+ # +deprecator+ during the execution of the yielded block.
64
+ #
65
+ # collect_deprecations(CustomDeprecator) do
66
+ # CustomDeprecator.warn "message"
67
+ # end # => ["message"]
68
+ #
69
+ # If no +deprecator+ is given, defaults to ActiveSupport::Deprecation.
70
+ #
71
+ # collect_deprecations do
72
+ # CustomDeprecator.warn "custom message"
73
+ # ActiveSupport::Deprecation.warn "message"
74
+ # end # => ["message"]
24
75
  def collect_deprecations(deprecator = nil)
25
76
  deprecator ||= ActiveSupport::Deprecation
26
77
  old_behavior = deprecator.behavior
@@ -5,7 +5,7 @@ module ActiveSupport
5
5
  module Isolation
6
6
  require "thread"
7
7
 
8
- def self.included(klass) #:nodoc:
8
+ def self.included(klass) # :nodoc:
9
9
  klass.class_eval do
10
10
  parallelize_me!
11
11
  end
@@ -63,7 +63,7 @@ module ActiveSupport
63
63
  module Subprocess
64
64
  ORIG_ARGV = ARGV.dup unless defined?(ORIG_ARGV)
65
65
 
66
- # Crazy H4X to get this working in windows / jruby with
66
+ # Complicated H4X to get this working in windows / jruby with
67
67
  # no forking.
68
68
  def run_in_isolation(&blk)
69
69
  require "tempfile"
@@ -6,10 +6,10 @@ module ActiveSupport
6
6
  module Testing
7
7
  module MethodCallAssertions # :nodoc:
8
8
  private
9
- def assert_called(object, method_name, message = nil, times: 1, returns: nil)
9
+ def assert_called(object, method_name, message = nil, times: 1, returns: nil, &block)
10
10
  times_called = 0
11
11
 
12
- object.stub(method_name, proc { times_called += 1; returns }) { yield }
12
+ object.stub(method_name, proc { times_called += 1; returns }, &block)
13
13
 
14
14
  error = "Expected #{method_name} to be called #{times} times, " \
15
15
  "but was called #{times_called} times"
@@ -17,16 +17,16 @@ 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, &block)
21
21
  mock = Minitest::Mock.new
22
22
 
23
- if args.all? { |arg| arg.is_a?(Array) }
23
+ if args.all?(Array)
24
24
  args.each { |arg| mock.expect(:call, returns, arg) }
25
25
  else
26
26
  mock.expect(:call, returns, args)
27
27
  end
28
28
 
29
- object.stub(method_name, mock) { yield }
29
+ object.stub(method_name, mock, &block)
30
30
 
31
31
  mock.verify
32
32
  end
@@ -0,0 +1,82 @@
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 interrupt
53
+ @queue.clear
54
+ end
55
+
56
+ def shutdown
57
+ # Wait for initial queue to drain
58
+ while @queue.length != 0
59
+ sleep 0.1
60
+ end
61
+
62
+ @queue.close
63
+
64
+ # Wait until all workers have finished
65
+ while active_workers?
66
+ sleep 0.1
67
+ end
68
+
69
+ @in_flight.values.each do |(klass, name, reporter)|
70
+ result = Minitest::Result.from(klass.new(name))
71
+ error = RuntimeError.new("result not reported")
72
+ error.set_backtrace([""])
73
+ result.failures << Minitest::UnexpectedError.new(error)
74
+ reporter.synchronize do
75
+ reporter.record(result)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,103 @@
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
+ rescue Interrupt
73
+ @queue.interrupt
74
+ raise
75
+ end
76
+
77
+ set_process_title("(idle)")
78
+ end
79
+
80
+ def after_fork
81
+ Parallelization.after_fork_hooks.each do |cb|
82
+ cb.call(@number)
83
+ end
84
+ end
85
+
86
+ def run_cleanup
87
+ Parallelization.run_cleanup_hooks.each do |cb|
88
+ cb.call(@number)
89
+ end
90
+ end
91
+
92
+ private
93
+ def add_setup_exception(result)
94
+ result.failures.prepend Minitest::UnexpectedError.new(@setup_exception)
95
+ end
96
+
97
+ def set_process_title(status)
98
+ Process.setproctitle("Rails test worker #{@number} - #{status}")
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end