activesupport 5.2.1.1 → 6.0.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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +416 -351
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -2
  5. data/lib/active_support.rb +2 -1
  6. data/lib/active_support/actionable_error.rb +48 -0
  7. data/lib/active_support/backtrace_cleaner.rb +28 -1
  8. data/lib/active_support/cache.rb +45 -23
  9. data/lib/active_support/cache/file_store.rb +22 -22
  10. data/lib/active_support/cache/mem_cache_store.rb +5 -0
  11. data/lib/active_support/cache/memory_store.rb +9 -2
  12. data/lib/active_support/cache/null_store.rb +5 -0
  13. data/lib/active_support/cache/redis_cache_store.rb +37 -10
  14. data/lib/active_support/callbacks.rb +16 -5
  15. data/lib/active_support/concern.rb +31 -4
  16. data/lib/active_support/configurable.rb +7 -11
  17. data/lib/active_support/core_ext/array.rb +1 -1
  18. data/lib/active_support/core_ext/array/access.rb +18 -6
  19. data/lib/active_support/core_ext/array/extract.rb +21 -0
  20. data/lib/active_support/core_ext/array/prepend_and_append.rb +2 -6
  21. data/lib/active_support/core_ext/class/attribute.rb +11 -16
  22. data/lib/active_support/core_ext/class/subclasses.rb +1 -1
  23. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  24. data/lib/active_support/core_ext/date_and_time/calculations.rb +24 -47
  25. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  26. data/lib/active_support/core_ext/digest.rb +3 -0
  27. data/lib/active_support/core_ext/enumerable.rb +97 -73
  28. data/lib/active_support/core_ext/file/atomic.rb +1 -1
  29. data/lib/active_support/core_ext/hash.rb +1 -2
  30. data/lib/active_support/core_ext/hash/compact.rb +2 -26
  31. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  32. data/lib/active_support/core_ext/hash/except.rb +1 -1
  33. data/lib/active_support/core_ext/hash/keys.rb +0 -29
  34. data/lib/active_support/core_ext/hash/slice.rb +3 -25
  35. data/lib/active_support/core_ext/hash/transform_values.rb +2 -29
  36. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  37. data/lib/active_support/core_ext/kernel.rb +0 -1
  38. data/lib/active_support/core_ext/load_error.rb +1 -1
  39. data/lib/active_support/core_ext/module.rb +0 -1
  40. data/lib/active_support/core_ext/module/attribute_accessors.rb +7 -10
  41. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +13 -19
  42. data/lib/active_support/core_ext/module/delegation.rb +33 -7
  43. data/lib/active_support/core_ext/module/introspection.rb +37 -13
  44. data/lib/active_support/core_ext/module/reachable.rb +1 -6
  45. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  46. data/lib/active_support/core_ext/numeric.rb +0 -1
  47. data/lib/active_support/core_ext/numeric/conversions.rb +124 -128
  48. data/lib/active_support/core_ext/numeric/inquiry.rb +2 -25
  49. data/lib/active_support/core_ext/object/blank.rb +1 -2
  50. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  51. data/lib/active_support/core_ext/object/json.rb +1 -0
  52. data/lib/active_support/core_ext/object/try.rb +15 -7
  53. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  54. data/lib/active_support/core_ext/range.rb +1 -1
  55. data/lib/active_support/core_ext/range/compare_range.rb +76 -0
  56. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  57. data/lib/active_support/core_ext/range/include_range.rb +6 -22
  58. data/lib/active_support/core_ext/range/include_time_with_zone.rb +2 -2
  59. data/lib/active_support/core_ext/regexp.rb +0 -4
  60. data/lib/active_support/core_ext/securerandom.rb +23 -3
  61. data/lib/active_support/core_ext/string/access.rb +8 -0
  62. data/lib/active_support/core_ext/string/filters.rb +42 -1
  63. data/lib/active_support/core_ext/string/inflections.rb +7 -2
  64. data/lib/active_support/core_ext/string/multibyte.rb +4 -3
  65. data/lib/active_support/core_ext/string/output_safety.rb +63 -5
  66. data/lib/active_support/core_ext/string/strip.rb +3 -1
  67. data/lib/active_support/core_ext/time/calculations.rb +31 -2
  68. data/lib/active_support/core_ext/uri.rb +2 -1
  69. data/lib/active_support/current_attributes.rb +8 -0
  70. data/lib/active_support/dependencies.rb +74 -17
  71. data/lib/active_support/dependencies/zeitwerk_integration.rb +118 -0
  72. data/lib/active_support/deprecation.rb +1 -1
  73. data/lib/active_support/deprecation/behaviors.rb +1 -1
  74. data/lib/active_support/deprecation/method_wrappers.rb +13 -12
  75. data/lib/active_support/deprecation/proxy_wrappers.rb +24 -5
  76. data/lib/active_support/descendants_tracker.rb +56 -9
  77. data/lib/active_support/duration.rb +6 -5
  78. data/lib/active_support/duration/iso8601_parser.rb +2 -3
  79. data/lib/active_support/duration/iso8601_serializer.rb +3 -4
  80. data/lib/active_support/encrypted_configuration.rb +1 -5
  81. data/lib/active_support/encrypted_file.rb +2 -1
  82. data/lib/active_support/evented_file_update_checker.rb +39 -9
  83. data/lib/active_support/execution_wrapper.rb +1 -0
  84. data/lib/active_support/gem_version.rb +3 -3
  85. data/lib/active_support/hash_with_indifferent_access.rb +36 -18
  86. data/lib/active_support/i18n.rb +1 -0
  87. data/lib/active_support/i18n_railtie.rb +14 -2
  88. data/lib/active_support/inflector/inflections.rb +1 -4
  89. data/lib/active_support/inflector/methods.rb +17 -27
  90. data/lib/active_support/inflector/transliterate.rb +47 -18
  91. data/lib/active_support/json/decoding.rb +23 -23
  92. data/lib/active_support/json/encoding.rb +6 -2
  93. data/lib/active_support/key_generator.rb +0 -32
  94. data/lib/active_support/lazy_load_hooks.rb +5 -1
  95. data/lib/active_support/locale/en.rb +31 -0
  96. data/lib/active_support/log_subscriber.rb +31 -8
  97. data/lib/active_support/logger.rb +0 -15
  98. data/lib/active_support/logger_silence.rb +28 -12
  99. data/lib/active_support/logger_thread_safe_level.rb +28 -5
  100. data/lib/active_support/message_encryptor.rb +3 -5
  101. data/lib/active_support/message_verifier.rb +3 -3
  102. data/lib/active_support/multibyte/chars.rb +29 -48
  103. data/lib/active_support/multibyte/unicode.rb +44 -281
  104. data/lib/active_support/notifications.rb +41 -4
  105. data/lib/active_support/notifications/fanout.rb +100 -15
  106. data/lib/active_support/notifications/instrumenter.rb +80 -8
  107. data/lib/active_support/number_helper.rb +7 -0
  108. data/lib/active_support/number_helper/number_to_currency_converter.rb +2 -2
  109. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -1
  110. data/lib/active_support/number_helper/number_to_human_converter.rb +3 -1
  111. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -1
  112. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  113. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -0
  114. data/lib/active_support/number_helper/number_to_rounded_converter.rb +5 -3
  115. data/lib/active_support/ordered_hash.rb +1 -1
  116. data/lib/active_support/ordered_options.rb +1 -1
  117. data/lib/active_support/parameter_filter.rb +129 -0
  118. data/lib/active_support/rails.rb +0 -6
  119. data/lib/active_support/reloader.rb +4 -5
  120. data/lib/active_support/security_utils.rb +1 -1
  121. data/lib/active_support/subscriber.rb +65 -22
  122. data/lib/active_support/tagged_logging.rb +13 -4
  123. data/lib/active_support/test_case.rb +91 -0
  124. data/lib/active_support/testing/assertions.rb +15 -1
  125. data/lib/active_support/testing/deprecation.rb +0 -1
  126. data/lib/active_support/testing/file_fixtures.rb +2 -0
  127. data/lib/active_support/testing/isolation.rb +2 -2
  128. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  129. data/lib/active_support/testing/parallelization.rb +128 -0
  130. data/lib/active_support/testing/stream.rb +1 -1
  131. data/lib/active_support/testing/time_helpers.rb +7 -7
  132. data/lib/active_support/time_with_zone.rb +15 -5
  133. data/lib/active_support/values/time_zone.rb +12 -7
  134. data/lib/active_support/xml_mini.rb +2 -9
  135. data/lib/active_support/xml_mini/jdom.rb +2 -2
  136. data/lib/active_support/xml_mini/libxml.rb +2 -2
  137. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  138. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  139. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  140. data/lib/active_support/xml_mini/rexml.rb +2 -2
  141. metadata +33 -10
  142. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  143. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -16,7 +16,7 @@ module ActiveSupport
16
16
  # oh.keys # => [:a, :b], this order is guaranteed
17
17
  #
18
18
  # Also, maps the +omap+ feature for YAML files
19
- # (See http://yaml.org/type/omap.html) to support ordered items
19
+ # (See https://yaml.org/type/omap.html) to support ordered items
20
20
  # when loading from yaml.
21
21
  #
22
22
  # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts
@@ -39,7 +39,7 @@ module ActiveSupport
39
39
  end
40
40
 
41
41
  def method_missing(name, *args)
42
- name_string = name.to_s
42
+ name_string = +name.to_s
43
43
  if name_string.chomp!("=")
44
44
  self[name_string] = args.first
45
45
  else
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/duplicable"
4
+ require "active_support/core_ext/array/extract"
5
+
6
+ module ActiveSupport
7
+ # +ParameterFilter+ allows you to specify keys for sensitive data from
8
+ # hash-like object and replace corresponding value. Filtering only certain
9
+ # sub-keys from a hash is possible by using the dot notation:
10
+ # 'credit_card.number'. If a proc is given, each key and value of a hash and
11
+ # all sub-hashes are passed to it, where the value or the key can be replaced
12
+ # using String#replace or similar methods.
13
+ #
14
+ # ActiveSupport::ParameterFilter.new([:password])
15
+ # => replaces the value to all keys matching /password/i with "[FILTERED]"
16
+ #
17
+ # ActiveSupport::ParameterFilter.new([:foo, "bar"])
18
+ # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
19
+ #
20
+ # ActiveSupport::ParameterFilter.new(["credit_card.code"])
21
+ # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
22
+ # change { file: { code: "xxxx"} }
23
+ #
24
+ # ActiveSupport::ParameterFilter.new([-> (k, v) do
25
+ # v.reverse! if k =~ /secret/i
26
+ # end])
27
+ # => reverses the value to all keys matching /secret/i
28
+ class ParameterFilter
29
+ FILTERED = "[FILTERED]" # :nodoc:
30
+
31
+ # Create instance with given filters. Supported type of filters are +String+, +Regexp+, and +Proc+.
32
+ # Other types of filters are treated as +String+ using +to_s+.
33
+ # For +Proc+ filters, key, value, and optional original hash is passed to block arguments.
34
+ #
35
+ # ==== Options
36
+ #
37
+ # * <tt>:mask</tt> - A replaced object when filtered. Defaults to +"[FILTERED]"+
38
+ def initialize(filters = [], mask: FILTERED)
39
+ @filters = filters
40
+ @mask = mask
41
+ end
42
+
43
+ # Mask value of +params+ if key matches one of filters.
44
+ def filter(params)
45
+ compiled_filter.call(params)
46
+ end
47
+
48
+ # Returns filtered value for given key. For +Proc+ filters, third block argument is not populated.
49
+ def filter_param(key, value)
50
+ @filters.empty? ? value : compiled_filter.value_for_key(key, value)
51
+ end
52
+
53
+ private
54
+
55
+ def compiled_filter
56
+ @compiled_filter ||= CompiledFilter.compile(@filters, mask: @mask)
57
+ end
58
+
59
+ class CompiledFilter # :nodoc:
60
+ def self.compile(filters, mask:)
61
+ return lambda { |params| params.dup } if filters.empty?
62
+
63
+ strings, regexps, blocks = [], [], []
64
+
65
+ filters.each do |item|
66
+ case item
67
+ when Proc
68
+ blocks << item
69
+ when Regexp
70
+ regexps << item
71
+ else
72
+ strings << Regexp.escape(item.to_s)
73
+ end
74
+ end
75
+
76
+ deep_regexps = regexps.extract! { |r| r.to_s.include?("\\.") }
77
+ deep_strings = strings.extract! { |s| s.include?("\\.") }
78
+
79
+ regexps << Regexp.new(strings.join("|"), true) unless strings.empty?
80
+ deep_regexps << Regexp.new(deep_strings.join("|"), true) unless deep_strings.empty?
81
+
82
+ new regexps, deep_regexps, blocks, mask: mask
83
+ end
84
+
85
+ attr_reader :regexps, :deep_regexps, :blocks
86
+
87
+ def initialize(regexps, deep_regexps, blocks, mask:)
88
+ @regexps = regexps
89
+ @deep_regexps = deep_regexps.any? ? deep_regexps : nil
90
+ @blocks = blocks
91
+ @mask = mask
92
+ end
93
+
94
+ def call(params, parents = [], original_params = params)
95
+ filtered_params = params.class.new
96
+
97
+ params.each do |key, value|
98
+ filtered_params[key] = value_for_key(key, value, parents, original_params)
99
+ end
100
+
101
+ filtered_params
102
+ end
103
+
104
+ def value_for_key(key, value, parents = [], original_params = nil)
105
+ parents.push(key) if deep_regexps
106
+ if regexps.any? { |r| r.match?(key) }
107
+ value = @mask
108
+ elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| r.match?(joined) }
109
+ value = @mask
110
+ elsif value.is_a?(Hash)
111
+ value = call(value, parents, original_params)
112
+ elsif value.is_a?(Array)
113
+ # If we don't pop the current parent it will be duplicated as we
114
+ # process each array value.
115
+ parents.pop if deep_regexps
116
+ value = value.map { |v| value_for_key(key, v, parents, original_params) }
117
+ # Restore the parent stack after processing the array.
118
+ parents.push(key) if deep_regexps
119
+ elsif blocks.any?
120
+ key = key.dup if key.duplicable?
121
+ value = value.dup if value.duplicable?
122
+ blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) }
123
+ end
124
+ parents.pop if deep_regexps
125
+ value
126
+ end
127
+ end
128
+ end
129
+ end
@@ -27,9 +27,3 @@ require "active_support/core_ext/module/delegation"
27
27
 
28
28
  # Defines ActiveSupport::Deprecation.
29
29
  require "active_support/deprecation"
30
-
31
- # Defines Regexp#match?.
32
- #
33
- # This should be removed when Rails needs Ruby 2.4 or later, and the require
34
- # added where other Regexp extensions are being used (easy to grep).
35
- require "active_support/core_ext/regexp"
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/execution_wrapper"
4
+ require "active_support/executor"
4
5
 
5
6
  module ActiveSupport
6
7
  #--
@@ -49,11 +50,9 @@ module ActiveSupport
49
50
  def self.reload!
50
51
  executor.wrap do
51
52
  new.tap do |instance|
52
- begin
53
- instance.run!
54
- ensure
55
- instance.complete!
56
- end
53
+ instance.run!
54
+ ensure
55
+ instance.complete!
57
56
  end
58
57
  end
59
58
  prepare!
@@ -24,7 +24,7 @@ module ActiveSupport
24
24
  # The values are first processed by SHA256, so that we don't leak length info
25
25
  # via timing attacks.
26
26
  def secure_compare(a, b)
27
- fixed_length_secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b)) && a == b
27
+ fixed_length_secure_compare(::Digest::SHA256.digest(a), ::Digest::SHA256.digest(b)) && a == b
28
28
  end
29
29
  module_function :secure_compare
30
30
  end
@@ -24,6 +24,10 @@ module ActiveSupport
24
24
  # After configured, whenever a "sql.active_record" notification is published,
25
25
  # it will properly dispatch the event (ActiveSupport::Notifications::Event) to
26
26
  # the +sql+ method.
27
+ #
28
+ # We can detach a subscriber as well:
29
+ #
30
+ # ActiveRecord::StatsSubscriber.detach_from(:active_record)
27
31
  class Subscriber
28
32
  class << self
29
33
  # Attach the subscriber to a namespace.
@@ -40,6 +44,25 @@ module ActiveSupport
40
44
  end
41
45
  end
42
46
 
47
+ # Detach the subscriber from a namespace.
48
+ def detach_from(namespace, notifier = ActiveSupport::Notifications)
49
+ @namespace = namespace
50
+ @subscriber = find_attached_subscriber
51
+ @notifier = notifier
52
+
53
+ return unless subscriber
54
+
55
+ subscribers.delete(subscriber)
56
+
57
+ # Remove event subscribers of all existing methods on the class.
58
+ subscriber.public_methods(false).each do |event|
59
+ remove_event_subscriber(event)
60
+ end
61
+
62
+ # Reset notifier so that event subscribers will not add for new methods added to the class.
63
+ @notifier = nil
64
+ end
65
+
43
66
  # Adds event subscribers for all new methods added to the class.
44
67
  def method_added(event)
45
68
  # Only public methods are added as subscribers, and only if a notifier
@@ -54,55 +77,75 @@ module ActiveSupport
54
77
  @@subscribers ||= []
55
78
  end
56
79
 
57
- # TODO Change this to private once we've dropped Ruby 2.2 support.
58
- # Workaround for Ruby 2.2 "private attribute?" warning.
59
- protected
80
+ private
81
+ attr_reader :subscriber, :notifier, :namespace
60
82
 
61
- attr_reader :subscriber, :notifier, :namespace
83
+ def add_event_subscriber(event) # :doc:
84
+ return if invalid_event?(event.to_s)
62
85
 
63
- private
86
+ pattern = prepare_pattern(event)
64
87
 
65
- def add_event_subscriber(event) # :doc:
66
- return if %w{ start finish }.include?(event.to_s)
88
+ # Don't add multiple subscribers (eg. if methods are redefined).
89
+ return if pattern_subscribed?(pattern)
67
90
 
68
- pattern = "#{event}.#{namespace}"
91
+ subscriber.patterns[pattern] = notifier.subscribe(pattern, subscriber)
92
+ end
69
93
 
70
- # Don't add multiple subscribers (eg. if methods are redefined).
71
- return if subscriber.patterns.include?(pattern)
94
+ def remove_event_subscriber(event) # :doc:
95
+ return if invalid_event?(event.to_s)
72
96
 
73
- subscriber.patterns << pattern
74
- notifier.subscribe(pattern, subscriber)
75
- end
97
+ pattern = prepare_pattern(event)
98
+
99
+ return unless pattern_subscribed?(pattern)
100
+
101
+ notifier.unsubscribe(subscriber.patterns[pattern])
102
+ subscriber.patterns.delete(pattern)
103
+ end
104
+
105
+ def find_attached_subscriber
106
+ subscribers.find { |attached_subscriber| attached_subscriber.instance_of?(self) }
107
+ end
108
+
109
+ def invalid_event?(event)
110
+ %w{ start finish }.include?(event.to_s)
111
+ end
112
+
113
+ def prepare_pattern(event)
114
+ "#{event}.#{namespace}"
115
+ end
116
+
117
+ def pattern_subscribed?(pattern)
118
+ subscriber.patterns.key?(pattern)
119
+ end
76
120
  end
77
121
 
78
122
  attr_reader :patterns # :nodoc:
79
123
 
80
124
  def initialize
81
125
  @queue_key = [self.class.name, object_id].join "-"
82
- @patterns = []
126
+ @patterns = {}
83
127
  super
84
128
  end
85
129
 
86
130
  def start(name, id, payload)
87
- e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload)
131
+ event = ActiveSupport::Notifications::Event.new(name, nil, nil, id, payload)
132
+ event.start!
88
133
  parent = event_stack.last
89
- parent << e if parent
134
+ parent << event if parent
90
135
 
91
- event_stack.push e
136
+ event_stack.push event
92
137
  end
93
138
 
94
139
  def finish(name, id, payload)
95
- finished = Time.now
96
- event = event_stack.pop
97
- event.end = finished
140
+ event = event_stack.pop
141
+ event.finish!
98
142
  event.payload.merge!(payload)
99
143
 
100
- method = name.split(".".freeze).first
144
+ method = name.split(".").first
101
145
  send(method, event)
102
146
  end
103
147
 
104
148
  private
105
-
106
149
  def event_stack
107
150
  SubscriberQueueRegistry.instance.get_queue(@queue_key)
108
151
  end
@@ -46,21 +46,30 @@ module ActiveSupport
46
46
 
47
47
  def current_tags
48
48
  # We use our object ID here to avoid conflicting with other instances
49
- thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}".freeze
49
+ thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}"
50
50
  Thread.current[thread_key] ||= []
51
51
  end
52
52
 
53
53
  def tags_text
54
54
  tags = current_tags
55
- if tags.any?
55
+ if tags.one?
56
+ "[#{tags[0]}] "
57
+ elsif tags.any?
56
58
  tags.collect { |tag| "[#{tag}] " }.join
57
59
  end
58
60
  end
59
61
  end
60
62
 
61
63
  def self.new(logger)
62
- # Ensure we set a default formatter so we aren't extending nil!
63
- logger.formatter ||= ActiveSupport::Logger::SimpleFormatter.new
64
+ logger = logger.dup
65
+
66
+ if logger.formatter
67
+ logger.formatter = logger.formatter.dup
68
+ else
69
+ # Ensure we set a default formatter so we aren't extending nil!
70
+ logger.formatter = ActiveSupport::Logger::SimpleFormatter.new
71
+ end
72
+
64
73
  logger.formatter.extend Formatter
65
74
  logger.extend(self)
66
75
  end
@@ -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