activesupport 5.1.1 → 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 (262) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +360 -442
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -4
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/all.rb +2 -0
  7. data/lib/active_support/array_inquirer.rb +6 -2
  8. data/lib/active_support/backtrace_cleaner.rb +31 -3
  9. data/lib/active_support/benchmarkable.rb +3 -1
  10. data/lib/active_support/builder.rb +2 -0
  11. data/lib/active_support/cache/file_store.rb +37 -36
  12. data/lib/active_support/cache/mem_cache_store.rb +65 -53
  13. data/lib/active_support/cache/memory_store.rb +61 -33
  14. data/lib/active_support/cache/null_store.rb +10 -3
  15. data/lib/active_support/cache/redis_cache_store.rb +493 -0
  16. data/lib/active_support/cache/strategy/local_cache.rb +68 -22
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +2 -0
  18. data/lib/active_support/cache.rb +305 -127
  19. data/lib/active_support/callbacks.rb +106 -98
  20. data/lib/active_support/concern.rb +79 -6
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +35 -0
  22. data/lib/active_support/concurrency/share_lock.rb +2 -1
  23. data/lib/active_support/configurable.rb +12 -14
  24. data/lib/active_support/configuration_file.rb +46 -0
  25. data/lib/active_support/core_ext/array/access.rb +21 -7
  26. data/lib/active_support/core_ext/array/conversions.rb +7 -5
  27. data/lib/active_support/core_ext/array/extract.rb +21 -0
  28. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  29. data/lib/active_support/core_ext/array/grouping.rb +2 -0
  30. data/lib/active_support/core_ext/array/inquiry.rb +2 -0
  31. data/lib/active_support/core_ext/array/wrap.rb +2 -0
  32. data/lib/active_support/core_ext/array.rb +3 -1
  33. data/lib/active_support/core_ext/benchmark.rb +4 -2
  34. data/lib/active_support/core_ext/big_decimal/conversions.rb +2 -0
  35. data/lib/active_support/core_ext/big_decimal.rb +2 -0
  36. data/lib/active_support/core_ext/class/attribute.rb +50 -47
  37. data/lib/active_support/core_ext/class/attribute_accessors.rb +2 -0
  38. data/lib/active_support/core_ext/class/subclasses.rb +18 -40
  39. data/lib/active_support/core_ext/class.rb +2 -0
  40. data/lib/active_support/core_ext/date/acts_like.rb +2 -0
  41. data/lib/active_support/core_ext/date/blank.rb +2 -0
  42. data/lib/active_support/core_ext/date/calculations.rb +8 -5
  43. data/lib/active_support/core_ext/date/conversions.rb +12 -10
  44. data/lib/active_support/core_ext/date/zones.rb +2 -0
  45. data/lib/active_support/core_ext/date.rb +2 -0
  46. data/lib/active_support/core_ext/date_and_time/calculations.rb +61 -37
  47. data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -1
  48. data/lib/active_support/core_ext/date_and_time/zones.rb +2 -1
  49. data/lib/active_support/core_ext/date_time/acts_like.rb +2 -0
  50. data/lib/active_support/core_ext/date_time/blank.rb +2 -0
  51. data/lib/active_support/core_ext/date_time/calculations.rb +3 -1
  52. data/lib/active_support/core_ext/date_time/compatibility.rb +7 -5
  53. data/lib/active_support/core_ext/date_time/conversions.rb +2 -1
  54. data/lib/active_support/core_ext/date_time.rb +2 -0
  55. data/lib/active_support/core_ext/digest/uuid.rb +3 -1
  56. data/lib/active_support/core_ext/digest.rb +3 -0
  57. data/lib/active_support/core_ext/enumerable.rb +174 -71
  58. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  59. data/lib/active_support/core_ext/file.rb +2 -0
  60. data/lib/active_support/core_ext/hash/conversions.rb +7 -5
  61. data/lib/active_support/core_ext/hash/deep_merge.rb +8 -12
  62. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  63. data/lib/active_support/core_ext/hash/except.rb +4 -2
  64. data/lib/active_support/core_ext/hash/indifferent_access.rb +2 -0
  65. data/lib/active_support/core_ext/hash/keys.rb +3 -30
  66. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  67. data/lib/active_support/core_ext/hash/slice.rb +8 -29
  68. data/lib/active_support/core_ext/hash.rb +3 -2
  69. data/lib/active_support/core_ext/integer/inflections.rb +2 -0
  70. data/lib/active_support/core_ext/integer/multiple.rb +3 -1
  71. data/lib/active_support/core_ext/integer/time.rb +7 -14
  72. data/lib/active_support/core_ext/integer.rb +2 -0
  73. data/lib/active_support/core_ext/kernel/concern.rb +2 -0
  74. data/lib/active_support/core_ext/kernel/reporting.rb +2 -0
  75. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  76. data/lib/active_support/core_ext/kernel.rb +2 -1
  77. data/lib/active_support/core_ext/load_error.rb +3 -8
  78. data/lib/active_support/core_ext/marshal.rb +4 -0
  79. data/lib/active_support/core_ext/module/aliasing.rb +2 -0
  80. data/lib/active_support/core_ext/module/anonymous.rb +2 -0
  81. data/lib/active_support/core_ext/module/attr_internal.rb +4 -2
  82. data/lib/active_support/core_ext/module/attribute_accessors.rb +44 -56
  83. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +18 -18
  84. data/lib/active_support/core_ext/module/concerning.rb +15 -10
  85. data/lib/active_support/core_ext/module/delegation.rb +103 -58
  86. data/lib/active_support/core_ext/module/deprecation.rb +2 -0
  87. data/lib/active_support/core_ext/module/introspection.rb +18 -15
  88. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  89. data/lib/active_support/core_ext/module/remove_method.rb +5 -23
  90. data/lib/active_support/core_ext/module.rb +3 -1
  91. data/lib/active_support/core_ext/name_error.rb +36 -2
  92. data/lib/active_support/core_ext/numeric/bytes.rb +2 -0
  93. data/lib/active_support/core_ext/numeric/conversions.rb +131 -129
  94. data/lib/active_support/core_ext/numeric/time.rb +7 -15
  95. data/lib/active_support/core_ext/numeric.rb +2 -1
  96. data/lib/active_support/core_ext/object/acts_like.rb +12 -1
  97. data/lib/active_support/core_ext/object/blank.rb +13 -3
  98. data/lib/active_support/core_ext/object/conversions.rb +2 -0
  99. data/lib/active_support/core_ext/object/deep_dup.rb +3 -1
  100. data/lib/active_support/core_ext/object/duplicable.rb +6 -101
  101. data/lib/active_support/core_ext/object/inclusion.rb +2 -0
  102. data/lib/active_support/core_ext/object/instance_variables.rb +2 -0
  103. data/lib/active_support/core_ext/object/json.rb +22 -2
  104. data/lib/active_support/core_ext/object/to_param.rb +2 -0
  105. data/lib/active_support/core_ext/object/to_query.rb +7 -2
  106. data/lib/active_support/core_ext/object/try.rb +19 -7
  107. data/lib/active_support/core_ext/object/with_options.rb +4 -2
  108. data/lib/active_support/core_ext/object.rb +2 -0
  109. data/lib/active_support/core_ext/range/compare_range.rb +82 -0
  110. data/lib/active_support/core_ext/range/conversions.rb +35 -25
  111. data/lib/active_support/core_ext/range/each.rb +5 -2
  112. data/lib/active_support/core_ext/range/include_time_with_zone.rb +28 -0
  113. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  114. data/lib/active_support/core_ext/range.rb +4 -1
  115. data/lib/active_support/core_ext/regexp.rb +10 -5
  116. data/lib/active_support/core_ext/securerandom.rb +25 -3
  117. data/lib/active_support/core_ext/string/access.rb +7 -16
  118. data/lib/active_support/core_ext/string/behavior.rb +2 -0
  119. data/lib/active_support/core_ext/string/conversions.rb +3 -0
  120. data/lib/active_support/core_ext/string/exclude.rb +2 -0
  121. data/lib/active_support/core_ext/string/filters.rb +44 -1
  122. data/lib/active_support/core_ext/string/indent.rb +2 -0
  123. data/lib/active_support/core_ext/string/inflections.rb +69 -16
  124. data/lib/active_support/core_ext/string/inquiry.rb +3 -0
  125. data/lib/active_support/core_ext/string/multibyte.rb +9 -4
  126. data/lib/active_support/core_ext/string/output_safety.rb +76 -20
  127. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -2
  128. data/lib/active_support/core_ext/string/strip.rb +5 -1
  129. data/lib/active_support/core_ext/string/zones.rb +2 -0
  130. data/lib/active_support/core_ext/string.rb +2 -0
  131. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  132. data/lib/active_support/core_ext/symbol.rb +3 -0
  133. data/lib/active_support/core_ext/time/acts_like.rb +2 -0
  134. data/lib/active_support/core_ext/time/calculations.rb +73 -18
  135. data/lib/active_support/core_ext/time/compatibility.rb +4 -2
  136. data/lib/active_support/core_ext/time/conversions.rb +4 -0
  137. data/lib/active_support/core_ext/time/zones.rb +6 -4
  138. data/lib/active_support/core_ext/time.rb +2 -0
  139. data/lib/active_support/core_ext/uri.rb +11 -6
  140. data/lib/active_support/core_ext.rb +3 -1
  141. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  142. data/lib/active_support/current_attributes.rb +208 -0
  143. data/lib/active_support/dependencies/autoload.rb +2 -0
  144. data/lib/active_support/dependencies/interlock.rb +2 -0
  145. data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
  146. data/lib/active_support/dependencies.rb +135 -60
  147. data/lib/active_support/deprecation/behaviors.rb +43 -11
  148. data/lib/active_support/deprecation/constant_accessor.rb +4 -2
  149. data/lib/active_support/deprecation/disallowed.rb +56 -0
  150. data/lib/active_support/deprecation/instance_delegator.rb +2 -1
  151. data/lib/active_support/deprecation/method_wrappers.rb +30 -15
  152. data/lib/active_support/deprecation/proxy_wrappers.rb +32 -6
  153. data/lib/active_support/deprecation/reporting.rb +54 -9
  154. data/lib/active_support/deprecation.rb +9 -2
  155. data/lib/active_support/descendants_tracker.rb +61 -9
  156. data/lib/active_support/digest.rb +20 -0
  157. data/lib/active_support/duration/iso8601_parser.rb +6 -6
  158. data/lib/active_support/duration/iso8601_serializer.rb +20 -14
  159. data/lib/active_support/duration.rb +179 -41
  160. data/lib/active_support/encrypted_configuration.rb +45 -0
  161. data/lib/active_support/encrypted_file.rb +117 -0
  162. data/lib/active_support/environment_inquirer.rb +20 -0
  163. data/lib/active_support/evented_file_update_checker.rb +84 -117
  164. data/lib/active_support/execution_wrapper.rb +3 -0
  165. data/lib/active_support/executor.rb +2 -0
  166. data/lib/active_support/file_update_checker.rb +2 -1
  167. data/lib/active_support/fork_tracker.rb +62 -0
  168. data/lib/active_support/gem_version.rb +3 -1
  169. data/lib/active_support/gzip.rb +2 -0
  170. data/lib/active_support/hash_with_indifferent_access.rb +134 -37
  171. data/lib/active_support/i18n.rb +4 -1
  172. data/lib/active_support/i18n_railtie.rb +20 -11
  173. data/lib/active_support/inflections.rb +2 -0
  174. data/lib/active_support/inflector/inflections.rb +19 -8
  175. data/lib/active_support/inflector/methods.rb +87 -77
  176. data/lib/active_support/inflector/transliterate.rb +56 -18
  177. data/lib/active_support/inflector.rb +2 -0
  178. data/lib/active_support/json/decoding.rb +27 -26
  179. data/lib/active_support/json/encoding.rb +13 -3
  180. data/lib/active_support/json.rb +2 -0
  181. data/lib/active_support/key_generator.rb +3 -33
  182. data/lib/active_support/lazy_load_hooks.rb +33 -10
  183. data/lib/active_support/locale/en.rb +33 -0
  184. data/lib/active_support/locale/en.yml +7 -3
  185. data/lib/active_support/log_subscriber/test_helper.rb +2 -0
  186. data/lib/active_support/log_subscriber.rb +46 -13
  187. data/lib/active_support/logger.rb +4 -17
  188. data/lib/active_support/logger_silence.rb +13 -20
  189. data/lib/active_support/logger_thread_safe_level.rb +54 -7
  190. data/lib/active_support/message_encryptor.rb +101 -33
  191. data/lib/active_support/message_verifier.rb +85 -14
  192. data/lib/active_support/messages/metadata.rb +80 -0
  193. data/lib/active_support/messages/rotation_configuration.rb +23 -0
  194. data/lib/active_support/messages/rotator.rb +57 -0
  195. data/lib/active_support/multibyte/chars.rb +12 -68
  196. data/lib/active_support/multibyte/unicode.rb +17 -327
  197. data/lib/active_support/multibyte.rb +2 -0
  198. data/lib/active_support/notifications/fanout.rb +118 -16
  199. data/lib/active_support/notifications/instrumenter.rb +73 -9
  200. data/lib/active_support/notifications.rb +74 -8
  201. data/lib/active_support/number_helper/number_converter.rb +7 -6
  202. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -9
  203. data/lib/active_support/number_helper/number_to_delimited_converter.rb +5 -2
  204. data/lib/active_support/number_helper/number_to_human_converter.rb +8 -7
  205. data/lib/active_support/number_helper/number_to_human_size_converter.rb +6 -3
  206. data/lib/active_support/number_helper/number_to_percentage_converter.rb +5 -1
  207. data/lib/active_support/number_helper/number_to_phone_converter.rb +5 -2
  208. data/lib/active_support/number_helper/number_to_rounded_converter.rb +16 -53
  209. data/lib/active_support/number_helper/rounding_helper.rb +50 -0
  210. data/lib/active_support/number_helper.rb +41 -12
  211. data/lib/active_support/option_merger.rb +24 -3
  212. data/lib/active_support/ordered_hash.rb +3 -1
  213. data/lib/active_support/ordered_options.rb +17 -5
  214. data/lib/active_support/parameter_filter.rb +133 -0
  215. data/lib/active_support/per_thread_registry.rb +3 -1
  216. data/lib/active_support/proxy_object.rb +2 -0
  217. data/lib/active_support/rails.rb +3 -10
  218. data/lib/active_support/railtie.rb +60 -9
  219. data/lib/active_support/reloader.rb +11 -10
  220. data/lib/active_support/rescuable.rb +7 -6
  221. data/lib/active_support/secure_compare_rotator.rb +51 -0
  222. data/lib/active_support/security_utils.rb +26 -15
  223. data/lib/active_support/string_inquirer.rb +6 -3
  224. data/lib/active_support/subscriber.rb +74 -24
  225. data/lib/active_support/tagged_logging.rb +44 -8
  226. data/lib/active_support/test_case.rb +94 -2
  227. data/lib/active_support/testing/assertions.rb +58 -20
  228. data/lib/active_support/testing/autorun.rb +2 -4
  229. data/lib/active_support/testing/constant_lookup.rb +2 -0
  230. data/lib/active_support/testing/declarative.rb +2 -0
  231. data/lib/active_support/testing/deprecation.rb +2 -1
  232. data/lib/active_support/testing/file_fixtures.rb +4 -0
  233. data/lib/active_support/testing/isolation.rb +8 -4
  234. data/lib/active_support/testing/method_call_assertions.rb +30 -1
  235. data/lib/active_support/testing/parallelization/server.rb +78 -0
  236. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  237. data/lib/active_support/testing/parallelization.rb +51 -0
  238. data/lib/active_support/testing/setup_and_teardown.rb +12 -7
  239. data/lib/active_support/testing/stream.rb +3 -2
  240. data/lib/active_support/testing/tagged_logging.rb +2 -0
  241. data/lib/active_support/testing/time_helpers.rb +78 -13
  242. data/lib/active_support/time.rb +2 -0
  243. data/lib/active_support/time_with_zone.rb +113 -41
  244. data/lib/active_support/values/time_zone.rb +55 -25
  245. data/lib/active_support/version.rb +2 -0
  246. data/lib/active_support/xml_mini/jdom.rb +5 -4
  247. data/lib/active_support/xml_mini/libxml.rb +4 -2
  248. data/lib/active_support/xml_mini/libxmlsax.rb +6 -4
  249. data/lib/active_support/xml_mini/nokogiri.rb +4 -2
  250. data/lib/active_support/xml_mini/nokogirisax.rb +5 -3
  251. data/lib/active_support/xml_mini/rexml.rb +12 -3
  252. data/lib/active_support/xml_mini.rb +5 -11
  253. data/lib/active_support.rb +18 -13
  254. metadata +81 -35
  255. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -7
  256. data/lib/active_support/core_ext/hash/compact.rb +0 -27
  257. data/lib/active_support/core_ext/hash/transform_values.rb +0 -30
  258. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -11
  259. data/lib/active_support/core_ext/module/reachable.rb +0 -8
  260. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -26
  261. data/lib/active_support/core_ext/range/include_range.rb +0 -23
  262. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/security_utils"
4
+ require "active_support/messages/rotator"
5
+
6
+ module ActiveSupport
7
+ # The ActiveSupport::SecureCompareRotator is a wrapper around +ActiveSupport::SecurityUtils.secure_compare+
8
+ # and allows you to rotate a previously defined value to a new one.
9
+ #
10
+ # It can be used as follow:
11
+ #
12
+ # rotator = ActiveSupport::SecureCompareRotator.new('new_production_value')
13
+ # rotator.rotate('previous_production_value')
14
+ # rotator.secure_compare!('previous_production_value')
15
+ #
16
+ # One real use case example would be to rotate a basic auth credentials:
17
+ #
18
+ # class MyController < ApplicationController
19
+ # def authenticate_request
20
+ # rotator = ActiveSupport::SecureComparerotator.new('new_password')
21
+ # rotator.rotate('old_password')
22
+ #
23
+ # authenticate_or_request_with_http_basic do |username, password|
24
+ # rotator.secure_compare!(password)
25
+ # rescue ActiveSupport::SecureCompareRotator::InvalidMatch
26
+ # false
27
+ # end
28
+ # end
29
+ # end
30
+ class SecureCompareRotator
31
+ include SecurityUtils
32
+ prepend Messages::Rotator
33
+
34
+ InvalidMatch = Class.new(StandardError)
35
+
36
+ def initialize(value, **_options)
37
+ @value = value
38
+ end
39
+
40
+ def secure_compare!(other_value, on_rotation: @on_rotation)
41
+ secure_compare(@value, other_value) ||
42
+ run_rotations(on_rotation) { |wrapper| wrapper.secure_compare!(other_value) } ||
43
+ raise(InvalidMatch)
44
+ end
45
+
46
+ private
47
+ def build_rotation(previous_value, _options)
48
+ self.class.new(previous_value)
49
+ end
50
+ end
51
+ end
@@ -1,27 +1,38 @@
1
- require "digest"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
4
  module SecurityUtils
5
- # Constant time string comparison.
5
+ # Constant time string comparison, for fixed length strings.
6
6
  #
7
7
  # The values compared should be of fixed length, such as strings
8
- # that have already been processed by HMAC. This should not be used
9
- # on variable length plaintext strings because it could leak length info
10
- # via timing attacks.
11
- def secure_compare(a, b)
12
- return false unless a.bytesize == b.bytesize
8
+ # that have already been processed by HMAC. Raises in case of length mismatch.
9
+
10
+ if defined?(OpenSSL.fixed_length_secure_compare)
11
+ def fixed_length_secure_compare(a, b)
12
+ OpenSSL.fixed_length_secure_compare(a, b)
13
+ end
14
+ else
15
+ def fixed_length_secure_compare(a, b)
16
+ raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
13
17
 
14
- l = a.unpack "C#{a.bytesize}"
18
+ l = a.unpack "C#{a.bytesize}"
15
19
 
16
- res = 0
17
- b.each_byte { |byte| res |= byte ^ l.shift }
18
- res == 0
20
+ res = 0
21
+ b.each_byte { |byte| res |= byte ^ l.shift }
22
+ res == 0
23
+ end
19
24
  end
20
- module_function :secure_compare
25
+ module_function :fixed_length_secure_compare
21
26
 
22
- def variable_size_secure_compare(a, b) # :nodoc:
23
- secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b))
27
+ # Secure string comparison for strings of variable length.
28
+ #
29
+ # While a timing attack would not be able to discern the content of
30
+ # a secret compared via secure_compare, it is possible to determine
31
+ # the secret length. This should be considered when using secure_compare
32
+ # to compare weak, short secrets to user input.
33
+ def secure_compare(a, b)
34
+ a.length == b.length && fixed_length_secure_compare(a, b)
24
35
  end
25
- module_function :variable_size_secure_compare
36
+ module_function :secure_compare
26
37
  end
27
38
  end
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/symbol/starts_ends_with"
4
+
1
5
  module ActiveSupport
2
6
  # Wrapping a string in this class gives you a prettier way to test
3
7
  # for equality. The value returned by <tt>Rails.env</tt> is wrapped
@@ -16,13 +20,12 @@ module ActiveSupport
16
20
  # vehicle.bike? # => false
17
21
  class StringInquirer < String
18
22
  private
19
-
20
23
  def respond_to_missing?(method_name, include_private = false)
21
- (method_name[-1] == "?") || super
24
+ method_name.end_with?("?") || super
22
25
  end
23
26
 
24
27
  def method_missing(method_name, *arguments)
25
- if method_name[-1] == "?"
28
+ if method_name.end_with?("?")
26
29
  self == method_name[0..-2]
27
30
  else
28
31
  super
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/per_thread_registry"
2
4
  require "active_support/notifications"
3
5
 
@@ -22,22 +24,46 @@ module ActiveSupport
22
24
  # After configured, whenever a "sql.active_record" notification is published,
23
25
  # it will properly dispatch the event (ActiveSupport::Notifications::Event) to
24
26
  # the +sql+ method.
27
+ #
28
+ # We can detach a subscriber as well:
29
+ #
30
+ # ActiveRecord::StatsSubscriber.detach_from(:active_record)
25
31
  class Subscriber
26
32
  class << self
27
33
  # Attach the subscriber to a namespace.
28
- def attach_to(namespace, subscriber = new, notifier = ActiveSupport::Notifications)
34
+ def attach_to(namespace, subscriber = new, notifier = ActiveSupport::Notifications, inherit_all: false)
29
35
  @namespace = namespace
30
36
  @subscriber = subscriber
31
37
  @notifier = notifier
38
+ @inherit_all = inherit_all
32
39
 
33
40
  subscribers << subscriber
34
41
 
35
42
  # Add event subscribers for all existing methods on the class.
36
- subscriber.public_methods(false).each do |event|
43
+ fetch_public_methods(subscriber, inherit_all).each do |event|
37
44
  add_event_subscriber(event)
38
45
  end
39
46
  end
40
47
 
48
+ # Detach the subscriber from a namespace.
49
+ def detach_from(namespace, notifier = ActiveSupport::Notifications)
50
+ @namespace = namespace
51
+ @subscriber = find_attached_subscriber
52
+ @notifier = notifier
53
+
54
+ return unless subscriber
55
+
56
+ subscribers.delete(subscriber)
57
+
58
+ # Remove event subscribers of all existing methods on the class.
59
+ fetch_public_methods(subscriber, true).each do |event|
60
+ remove_event_subscriber(event)
61
+ end
62
+
63
+ # Reset notifier so that event subscribers will not add for new methods added to the class.
64
+ @notifier = nil
65
+ end
66
+
41
67
  # Adds event subscribers for all new methods added to the class.
42
68
  def method_added(event)
43
69
  # Only public methods are added as subscribers, and only if a notifier
@@ -52,55 +78,79 @@ module ActiveSupport
52
78
  @@subscribers ||= []
53
79
  end
54
80
 
55
- # TODO Change this to private once we've dropped Ruby 2.2 support.
56
- # Workaround for Ruby 2.2 "private attribute?" warning.
57
- protected
81
+ private
82
+ attr_reader :subscriber, :notifier, :namespace
58
83
 
59
- attr_reader :subscriber, :notifier, :namespace
84
+ def add_event_subscriber(event) # :doc:
85
+ return if invalid_event?(event)
60
86
 
61
- private
87
+ pattern = prepare_pattern(event)
88
+
89
+ # Don't add multiple subscribers (e.g. if methods are redefined).
90
+ return if pattern_subscribed?(pattern)
62
91
 
63
- def add_event_subscriber(event) # :doc:
64
- return if %w{ start finish }.include?(event.to_s)
92
+ subscriber.patterns[pattern] = notifier.subscribe(pattern, subscriber)
93
+ end
65
94
 
66
- pattern = "#{event}.#{namespace}"
95
+ def remove_event_subscriber(event) # :doc:
96
+ return if invalid_event?(event)
67
97
 
68
- # Don't add multiple subscribers (eg. if methods are redefined).
69
- return if subscriber.patterns.include?(pattern)
98
+ pattern = prepare_pattern(event)
70
99
 
71
- subscriber.patterns << pattern
72
- notifier.subscribe(pattern, subscriber)
73
- end
100
+ return unless pattern_subscribed?(pattern)
101
+
102
+ notifier.unsubscribe(subscriber.patterns[pattern])
103
+ subscriber.patterns.delete(pattern)
104
+ end
105
+
106
+ def find_attached_subscriber
107
+ subscribers.find { |attached_subscriber| attached_subscriber.instance_of?(self) }
108
+ end
109
+
110
+ def invalid_event?(event)
111
+ %i{ start finish }.include?(event.to_sym)
112
+ end
113
+
114
+ def prepare_pattern(event)
115
+ "#{event}.#{namespace}"
116
+ end
117
+
118
+ def pattern_subscribed?(pattern)
119
+ subscriber.patterns.key?(pattern)
120
+ end
121
+
122
+ def fetch_public_methods(subscriber, inherit_all)
123
+ subscriber.public_methods(inherit_all) - Subscriber.public_instance_methods(true)
124
+ end
74
125
  end
75
126
 
76
127
  attr_reader :patterns # :nodoc:
77
128
 
78
129
  def initialize
79
130
  @queue_key = [self.class.name, object_id].join "-"
80
- @patterns = []
131
+ @patterns = {}
81
132
  super
82
133
  end
83
134
 
84
135
  def start(name, id, payload)
85
- e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload)
136
+ event = ActiveSupport::Notifications::Event.new(name, nil, nil, id, payload)
137
+ event.start!
86
138
  parent = event_stack.last
87
- parent << e if parent
139
+ parent << event if parent
88
140
 
89
- event_stack.push e
141
+ event_stack.push event
90
142
  end
91
143
 
92
144
  def finish(name, id, payload)
93
- finished = Time.now
94
- event = event_stack.pop
95
- event.end = finished
145
+ event = event_stack.pop
146
+ event.finish!
96
147
  event.payload.merge!(payload)
97
148
 
98
- method = name.split(".".freeze).first
149
+ method = name.split(".").first
99
150
  send(method, event)
100
151
  end
101
152
 
102
153
  private
103
-
104
154
  def event_stack
105
155
  SubscriberQueueRegistry.instance.get_queue(@queue_key)
106
156
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/module/delegation"
2
4
  require "active_support/core_ext/object/blank"
3
5
  require "logger"
@@ -6,11 +8,20 @@ require "active_support/logger"
6
8
  module ActiveSupport
7
9
  # Wraps any standard Logger object to provide tagging capabilities.
8
10
  #
11
+ # May be called with a block:
12
+ #
9
13
  # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
10
14
  # logger.tagged('BCX') { logger.info 'Stuff' } # Logs "[BCX] Stuff"
11
15
  # logger.tagged('BCX', "Jason") { logger.info 'Stuff' } # Logs "[BCX] [Jason] Stuff"
12
16
  # logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff"
13
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
+ #
14
25
  # This is used by the default Rails.logger as configured by Railties to make
15
26
  # it easy to stamp log lines with subdomains, request ids, and anything else
16
27
  # to aid debugging of multi-user production applications.
@@ -29,9 +40,10 @@ module ActiveSupport
29
40
  end
30
41
 
31
42
  def push_tags(*tags)
32
- tags.flatten.reject(&:blank?).tap do |new_tags|
33
- current_tags.concat new_tags
34
- end
43
+ tags.flatten!
44
+ tags.reject!(&:blank?)
45
+ current_tags.concat tags
46
+ tags
35
47
  end
36
48
 
37
49
  def pop_tags(size = 1)
@@ -44,21 +56,38 @@ module ActiveSupport
44
56
 
45
57
  def current_tags
46
58
  # We use our object ID here to avoid conflicting with other instances
47
- thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}".freeze
59
+ thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}"
48
60
  Thread.current[thread_key] ||= []
49
61
  end
50
62
 
51
63
  def tags_text
52
64
  tags = current_tags
53
- if tags.any?
65
+ if tags.one?
66
+ "[#{tags[0]}] "
67
+ elsif tags.any?
54
68
  tags.collect { |tag| "[#{tag}] " }.join
55
69
  end
56
70
  end
57
71
  end
58
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
+
59
81
  def self.new(logger)
60
- # Ensure we set a default formatter so we aren't extending nil!
61
- logger.formatter ||= ActiveSupport::Logger::SimpleFormatter.new
82
+ logger = logger.dup
83
+
84
+ if logger.formatter
85
+ logger.formatter = logger.formatter.dup
86
+ else
87
+ # Ensure we set a default formatter so we aren't extending nil!
88
+ logger.formatter = ActiveSupport::Logger::SimpleFormatter.new
89
+ end
90
+
62
91
  logger.formatter.extend Formatter
63
92
  logger.extend(self)
64
93
  end
@@ -66,7 +95,14 @@ module ActiveSupport
66
95
  delegate :push_tags, :pop_tags, :clear_tags!, to: :formatter
67
96
 
68
97
  def tagged(*tags)
69
- 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
70
106
  end
71
107
 
72
108
  def flush
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  gem "minitest" # make sure we get the gem, not stdlib
2
4
  require "minitest"
3
5
  require "active_support/testing/tagged_logging"
@@ -9,7 +11,8 @@ require "active_support/testing/isolation"
9
11
  require "active_support/testing/constant_lookup"
10
12
  require "active_support/testing/time_helpers"
11
13
  require "active_support/testing/file_fixtures"
12
- require "active_support/core_ext/kernel/reporting"
14
+ require "active_support/testing/parallelization"
15
+ require "concurrent/utility/processor_counter"
13
16
 
14
17
  module ActiveSupport
15
18
  class TestCase < ::Minitest::Test
@@ -38,12 +41,101 @@ module ActiveSupport
38
41
  def test_order
39
42
  ActiveSupport.test_order ||= :random
40
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
41
133
  end
42
134
 
43
135
  alias_method :method_name, :name
44
136
 
45
137
  include ActiveSupport::Testing::TaggedLogging
46
- include ActiveSupport::Testing::SetupAndTeardown
138
+ prepend ActiveSupport::Testing::SetupAndTeardown
47
139
  include ActiveSupport::Testing::Assertions
48
140
  include ActiveSupport::Testing::Deprecation
49
141
  include ActiveSupport::Testing::TimeHelpers
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/enumerable"
4
+
1
5
  module ActiveSupport
2
6
  module Testing
3
7
  module Assertions
@@ -28,6 +32,8 @@ module ActiveSupport
28
32
  # end
29
33
  def assert_nothing_raised
30
34
  yield
35
+ rescue => error
36
+ raise Minitest::UnexpectedError.new(error)
31
37
  end
32
38
 
33
39
  # Test numeric difference between the return value of an expression as a
@@ -56,6 +62,12 @@ module ActiveSupport
56
62
  # post :create, params: { article: {...} }
57
63
  # end
58
64
  #
65
+ # A hash of expressions/numeric differences can also be passed in and evaluated.
66
+ #
67
+ # assert_difference ->{ Article.count } => 1, ->{ Notification.count } => 2 do
68
+ # post :create, params: { article: {...} }
69
+ # end
70
+ #
59
71
  # A lambda or a list of lambdas can be passed in and evaluated:
60
72
  #
61
73
  # assert_difference ->{ Article.count }, 2 do
@@ -71,20 +83,28 @@ module ActiveSupport
71
83
  # assert_difference 'Article.count', -1, 'An Article should be destroyed' do
72
84
  # post :delete, params: { id: ... }
73
85
  # end
74
- def assert_difference(expression, difference = 1, message = nil, &block)
75
- expressions = Array(expression)
76
-
77
- exps = expressions.map { |e|
86
+ def assert_difference(expression, *args, &block)
87
+ expressions =
88
+ if expression.is_a?(Hash)
89
+ message = args[0]
90
+ expression
91
+ else
92
+ difference = args[0] || 1
93
+ message = args[1]
94
+ Array(expression).index_with(difference)
95
+ end
96
+
97
+ exps = expressions.keys.map { |e|
78
98
  e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
79
99
  }
80
100
  before = exps.map(&:call)
81
101
 
82
- retval = yield
102
+ retval = assert_nothing_raised(&block)
83
103
 
84
- expressions.zip(exps).each_with_index do |(code, e), i|
85
- error = "#{code.inspect} didn't change by #{difference}"
104
+ expressions.zip(exps, before) do |(code, diff), exp, before_value|
105
+ error = "#{code.inspect} didn't change by #{diff}"
86
106
  error = "#{message}.\n#{error}" if message
87
- assert_equal(before[i] + difference, e.call, error)
107
+ assert_equal(before_value + diff, exp.call, error)
88
108
  end
89
109
 
90
110
  retval
@@ -97,11 +117,23 @@ module ActiveSupport
97
117
  # post :create, params: { article: invalid_attributes }
98
118
  # end
99
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
+ #
100
126
  # An error message can be specified.
101
127
  #
102
128
  # assert_no_difference 'Article.count', 'An Article should not be created' do
103
129
  # post :create, params: { article: invalid_attributes }
104
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
105
137
  def assert_no_difference(expression, message = nil, &block)
106
138
  assert_difference expression, 0, message, &block
107
139
  end
@@ -144,22 +176,23 @@ module ActiveSupport
144
176
  exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) }
145
177
 
146
178
  before = exp.call
147
- retval = yield
179
+ retval = assert_nothing_raised(&block)
148
180
 
149
181
  unless from == UNTRACKED
150
- error = "#{expression.inspect} isn't #{from.inspect}"
182
+ error = "Expected change from #{from.inspect}"
151
183
  error = "#{message}.\n#{error}" if message
152
184
  assert from === before, error
153
185
  end
154
186
 
155
187
  after = exp.call
156
188
 
157
- if to == UNTRACKED
158
- error = "#{expression.inspect} didn't changed"
159
- error = "#{message}.\n#{error}" if message
160
- assert_not_equal before, after, error
161
- else
162
- error = "#{expression.inspect} didn't change to #{to}"
189
+ error = "#{expression.inspect} didn't change"
190
+ error = "#{error}. It was already #{to}" if before == to
191
+ error = "#{message}.\n#{error}" if message
192
+ assert_not_equal before, after, error
193
+
194
+ unless to == UNTRACKED
195
+ error = "Expected change to #{to}\n"
163
196
  error = "#{message}.\n#{error}" if message
164
197
  assert to === after, error
165
198
  end
@@ -167,7 +200,7 @@ module ActiveSupport
167
200
  retval
168
201
  end
169
202
 
170
- # Assertion that the result of evaluating an expression is changed before
203
+ # Assertion that the result of evaluating an expression is not changed before
171
204
  # and after invoking the passed in block.
172
205
  #
173
206
  # assert_no_changes 'Status.all_good?' do
@@ -183,12 +216,17 @@ module ActiveSupport
183
216
  exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) }
184
217
 
185
218
  before = exp.call
186
- retval = yield
219
+ retval = assert_nothing_raised(&block)
187
220
  after = exp.call
188
221
 
189
- error = "#{expression.inspect} did change to #{after}"
222
+ error = "#{expression.inspect} changed"
190
223
  error = "#{message}.\n#{error}" if message
191
- assert_equal before, after, error
224
+
225
+ if before.nil?
226
+ assert_nil after, error
227
+ else
228
+ assert_equal before, after, error
229
+ end
192
230
 
193
231
  retval
194
232
  end
@@ -1,9 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  gem "minitest"
2
4
 
3
5
  require "minitest"
4
6
 
5
- if Minitest.respond_to?(:run_via) && !Minitest.run_via.set?
6
- Minitest.run_via = :ruby
7
- end
8
-
9
7
  Minitest.autorun
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/concern"
2
4
  require "active_support/inflector"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport
2
4
  module Testing
3
5
  module Declarative