activesupport 5.2.5 → 6.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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +452 -398
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/backtrace_cleaner.rb +27 -1
  7. data/lib/active_support/cache/file_store.rb +32 -32
  8. data/lib/active_support/cache/mem_cache_store.rb +12 -7
  9. data/lib/active_support/cache/memory_store.rb +15 -9
  10. data/lib/active_support/cache/null_store.rb +8 -3
  11. data/lib/active_support/cache/redis_cache_store.rb +47 -20
  12. data/lib/active_support/cache/strategy/local_cache.rb +22 -22
  13. data/lib/active_support/cache.rb +71 -48
  14. data/lib/active_support/callbacks.rb +16 -8
  15. data/lib/active_support/concern.rb +24 -1
  16. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
  17. data/lib/active_support/concurrency/share_lock.rb +0 -1
  18. data/lib/active_support/configurable.rb +7 -11
  19. data/lib/active_support/core_ext/array/access.rb +18 -6
  20. data/lib/active_support/core_ext/array/conversions.rb +5 -5
  21. data/lib/active_support/core_ext/array/extract.rb +21 -0
  22. data/lib/active_support/core_ext/array/prepend_and_append.rb +2 -6
  23. data/lib/active_support/core_ext/array.rb +1 -1
  24. data/lib/active_support/core_ext/class/attribute.rb +11 -16
  25. data/lib/active_support/core_ext/class/subclasses.rb +1 -1
  26. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  27. data/lib/active_support/core_ext/date_and_time/calculations.rb +24 -47
  28. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  29. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  30. data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
  31. data/lib/active_support/core_ext/enumerable.rb +97 -73
  32. data/lib/active_support/core_ext/hash/compact.rb +2 -26
  33. data/lib/active_support/core_ext/hash/conversions.rb +1 -1
  34. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  35. data/lib/active_support/core_ext/hash/except.rb +2 -2
  36. data/lib/active_support/core_ext/hash/keys.rb +0 -29
  37. data/lib/active_support/core_ext/hash/slice.rb +3 -25
  38. data/lib/active_support/core_ext/hash/transform_values.rb +2 -29
  39. data/lib/active_support/core_ext/hash.rb +1 -2
  40. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  41. data/lib/active_support/core_ext/kernel.rb +0 -1
  42. data/lib/active_support/core_ext/load_error.rb +1 -1
  43. data/lib/active_support/core_ext/module/attribute_accessors.rb +7 -10
  44. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +13 -19
  45. data/lib/active_support/core_ext/module/delegation.rb +41 -8
  46. data/lib/active_support/core_ext/module/introspection.rb +38 -13
  47. data/lib/active_support/core_ext/module/reachable.rb +1 -6
  48. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  49. data/lib/active_support/core_ext/module.rb +0 -1
  50. data/lib/active_support/core_ext/numeric/conversions.rb +124 -128
  51. data/lib/active_support/core_ext/numeric/inquiry.rb +2 -25
  52. data/lib/active_support/core_ext/numeric.rb +0 -1
  53. data/lib/active_support/core_ext/object/blank.rb +1 -2
  54. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  55. data/lib/active_support/core_ext/object/json.rb +2 -1
  56. data/lib/active_support/core_ext/object/try.rb +17 -7
  57. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  58. data/lib/active_support/core_ext/range/compare_range.rb +28 -13
  59. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  60. data/lib/active_support/core_ext/range/each.rb +0 -1
  61. data/lib/active_support/core_ext/range/include_range.rb +6 -0
  62. data/lib/active_support/core_ext/range/include_time_with_zone.rb +2 -2
  63. data/lib/active_support/core_ext/regexp.rb +0 -4
  64. data/lib/active_support/core_ext/securerandom.rb +23 -3
  65. data/lib/active_support/core_ext/string/access.rb +8 -0
  66. data/lib/active_support/core_ext/string/filters.rb +42 -1
  67. data/lib/active_support/core_ext/string/inflections.rb +7 -2
  68. data/lib/active_support/core_ext/string/multibyte.rb +4 -3
  69. data/lib/active_support/core_ext/string/output_safety.rb +68 -10
  70. data/lib/active_support/core_ext/string/strip.rb +3 -1
  71. data/lib/active_support/core_ext/time/calculations.rb +34 -3
  72. data/lib/active_support/core_ext/uri.rb +1 -0
  73. data/lib/active_support/current_attributes.rb +8 -0
  74. data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
  75. data/lib/active_support/dependencies.rb +74 -18
  76. data/lib/active_support/deprecation/behaviors.rb +1 -1
  77. data/lib/active_support/deprecation/method_wrappers.rb +17 -23
  78. data/lib/active_support/deprecation/proxy_wrappers.rb +28 -5
  79. data/lib/active_support/deprecation.rb +1 -1
  80. data/lib/active_support/descendants_tracker.rb +55 -9
  81. data/lib/active_support/duration/iso8601_parser.rb +2 -4
  82. data/lib/active_support/duration/iso8601_serializer.rb +3 -5
  83. data/lib/active_support/duration.rb +7 -8
  84. data/lib/active_support/encrypted_configuration.rb +0 -4
  85. data/lib/active_support/encrypted_file.rb +3 -2
  86. data/lib/active_support/evented_file_update_checker.rb +39 -10
  87. data/lib/active_support/execution_wrapper.rb +1 -0
  88. data/lib/active_support/file_update_checker.rb +0 -1
  89. data/lib/active_support/gem_version.rb +4 -4
  90. data/lib/active_support/hash_with_indifferent_access.rb +22 -18
  91. data/lib/active_support/i18n.rb +1 -0
  92. data/lib/active_support/i18n_railtie.rb +13 -1
  93. data/lib/active_support/inflector/inflections.rb +1 -5
  94. data/lib/active_support/inflector/methods.rb +16 -29
  95. data/lib/active_support/inflector/transliterate.rb +47 -18
  96. data/lib/active_support/json/decoding.rb +23 -24
  97. data/lib/active_support/json/encoding.rb +6 -2
  98. data/lib/active_support/key_generator.rb +0 -32
  99. data/lib/active_support/lazy_load_hooks.rb +5 -2
  100. data/lib/active_support/locale/en.rb +33 -0
  101. data/lib/active_support/log_subscriber.rb +31 -9
  102. data/lib/active_support/logger.rb +1 -16
  103. data/lib/active_support/logger_silence.rb +28 -12
  104. data/lib/active_support/logger_thread_safe_level.rb +26 -4
  105. data/lib/active_support/message_encryptor.rb +4 -6
  106. data/lib/active_support/message_verifier.rb +5 -5
  107. data/lib/active_support/messages/metadata.rb +11 -2
  108. data/lib/active_support/messages/rotator.rb +4 -4
  109. data/lib/active_support/multibyte/chars.rb +29 -49
  110. data/lib/active_support/multibyte/unicode.rb +44 -282
  111. data/lib/active_support/notifications/fanout.rb +98 -13
  112. data/lib/active_support/notifications/instrumenter.rb +80 -9
  113. data/lib/active_support/notifications.rb +41 -4
  114. data/lib/active_support/number_helper/number_converter.rb +4 -5
  115. data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
  116. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
  117. data/lib/active_support/number_helper/number_to_human_converter.rb +3 -2
  118. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -2
  119. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  120. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  121. data/lib/active_support/number_helper/number_to_rounded_converter.rb +5 -4
  122. data/lib/active_support/number_helper/rounding_helper.rb +1 -1
  123. data/lib/active_support/number_helper.rb +11 -0
  124. data/lib/active_support/option_merger.rb +21 -3
  125. data/lib/active_support/ordered_hash.rb +1 -1
  126. data/lib/active_support/ordered_options.rb +5 -1
  127. data/lib/active_support/parameter_filter.rb +128 -0
  128. data/lib/active_support/rails.rb +0 -6
  129. data/lib/active_support/reloader.rb +4 -5
  130. data/lib/active_support/security_utils.rb +1 -1
  131. data/lib/active_support/string_inquirer.rb +0 -1
  132. data/lib/active_support/subscriber.rb +65 -26
  133. data/lib/active_support/tagged_logging.rb +13 -4
  134. data/lib/active_support/test_case.rb +91 -0
  135. data/lib/active_support/testing/assertions.rb +15 -1
  136. data/lib/active_support/testing/deprecation.rb +0 -1
  137. data/lib/active_support/testing/file_fixtures.rb +2 -0
  138. data/lib/active_support/testing/isolation.rb +2 -2
  139. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  140. data/lib/active_support/testing/parallelization.rb +134 -0
  141. data/lib/active_support/testing/stream.rb +1 -2
  142. data/lib/active_support/testing/time_helpers.rb +7 -9
  143. data/lib/active_support/time_with_zone.rb +15 -5
  144. data/lib/active_support/values/time_zone.rb +12 -7
  145. data/lib/active_support/xml_mini/jdom.rb +2 -3
  146. data/lib/active_support/xml_mini/libxml.rb +2 -2
  147. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  148. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  149. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  150. data/lib/active_support/xml_mini/rexml.rb +2 -2
  151. data/lib/active_support/xml_mini.rb +2 -10
  152. data/lib/active_support.rb +2 -1
  153. metadata +40 -12
  154. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  155. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -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
@@ -18,7 +18,6 @@ module ActiveSupport
18
18
  # vehicle.bike? # => false
19
19
  class StringInquirer < String
20
20
  private
21
-
22
21
  def respond_to_missing?(method_name, include_private = false)
23
22
  (method_name[-1] == "?") || super
24
23
  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,62 +77,78 @@ 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)
87
+
88
+ # Don't add multiple subscribers (eg. if methods are redefined).
89
+ return if pattern_subscribed?(pattern)
64
90
 
65
- def add_event_subscriber(event) # :doc:
66
- return if %w{ start finish }.include?(event.to_s)
91
+ subscriber.patterns[pattern] = notifier.subscribe(pattern, subscriber)
92
+ end
67
93
 
68
- pattern = "#{event}.#{namespace}"
94
+ def remove_event_subscriber(event) # :doc:
95
+ return if invalid_event?(event.to_s)
69
96
 
70
- # Don't add multiple subscribers (eg. if methods are redefined).
71
- return if subscriber.patterns.include?(pattern)
97
+ pattern = prepare_pattern(event)
72
98
 
73
- subscriber.patterns << pattern
74
- notifier.subscribe(pattern, subscriber)
75
- end
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, 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 = 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
109
-
110
- def now
111
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
112
- end
113
152
  end
114
153
 
115
154
  # This is a registry for all the event stacks kept for subscribers.
@@ -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
@@ -113,11 +113,23 @@ module ActiveSupport
113
113
  # post :create, params: { article: invalid_attributes }
114
114
  # end
115
115
  #
116
+ # A lambda can be passed in and evaluated.
117
+ #
118
+ # assert_no_difference -> { Article.count } do
119
+ # post :create, params: { article: invalid_attributes }
120
+ # end
121
+ #
116
122
  # An error message can be specified.
117
123
  #
118
124
  # assert_no_difference 'Article.count', 'An Article should not be created' do
119
125
  # post :create, params: { article: invalid_attributes }
120
126
  # end
127
+ #
128
+ # An array of expressions can also be passed in and evaluated.
129
+ #
130
+ # assert_no_difference [ 'Article.count', -> { Post.count } ] do
131
+ # post :create, params: { article: invalid_attributes }
132
+ # end
121
133
  def assert_no_difference(expression, message = nil, &block)
122
134
  assert_difference expression, 0, message, &block
123
135
  end
@@ -176,7 +188,9 @@ module ActiveSupport
176
188
  assert before != after, error
177
189
 
178
190
  unless to == UNTRACKED
179
- error = "#{expression.inspect} didn't change to #{to}"
191
+ error = "#{expression.inspect} didn't change to as expected\n"
192
+ error = "#{error}Expected: #{to.inspect}\n"
193
+ error = "#{error} Actual: #{after.inspect}"
180
194
  error = "#{message}.\n#{error}" if message
181
195
  assert to === after, error
182
196
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/deprecation"
4
- require "active_support/core_ext/regexp"
5
4
 
6
5
  module ActiveSupport
7
6
  module Testing
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/concern"
4
+
3
5
  module ActiveSupport
4
6
  module Testing
5
7
  # Adds simple access to sample files called file fixtures.
@@ -56,7 +56,7 @@ module ActiveSupport
56
56
  write.close
57
57
  result = read.read
58
58
  Process.wait2(pid)
59
- result.unpack("m")[0]
59
+ result.unpack1("m")
60
60
  end
61
61
  end
62
62
 
@@ -98,7 +98,7 @@ module ActiveSupport
98
98
  nil
99
99
  end
100
100
 
101
- return tmpfile.read.unpack("m")[0]
101
+ return tmpfile.read.unpack1("m")
102
102
  end
103
103
  end
104
104
  end
@@ -17,7 +17,7 @@ module ActiveSupport
17
17
  assert_equal times, times_called, error
18
18
  end
19
19
 
20
- def assert_called_with(object, method_name, args = [], returns: nil)
20
+ def assert_called_with(object, method_name, args, returns: nil)
21
21
  mock = Minitest::Mock.new
22
22
 
23
23
  if args.all? { |arg| arg.is_a?(Array) }
@@ -35,6 +35,33 @@ module ActiveSupport
35
35
  assert_called(object, method_name, message, times: 0, &block)
36
36
  end
37
37
 
38
+ def assert_called_on_instance_of(klass, method_name, message = nil, times: 1, returns: nil)
39
+ times_called = 0
40
+ klass.define_method("stubbed_#{method_name}") do |*|
41
+ times_called += 1
42
+
43
+ returns
44
+ end
45
+
46
+ klass.alias_method "original_#{method_name}", method_name
47
+ klass.alias_method method_name, "stubbed_#{method_name}"
48
+
49
+ yield
50
+
51
+ error = "Expected #{method_name} to be called #{times} times, but was called #{times_called} times"
52
+ error = "#{message}.\n#{error}" if message
53
+
54
+ assert_equal times, times_called, error
55
+ ensure
56
+ klass.alias_method method_name, "original_#{method_name}"
57
+ klass.undef_method "original_#{method_name}"
58
+ klass.undef_method "stubbed_#{method_name}"
59
+ end
60
+
61
+ def assert_not_called_on_instance_of(klass, method_name, message = nil, &block)
62
+ assert_called_on_instance_of(klass, method_name, message, times: 0, &block)
63
+ end
64
+
38
65
  def stub_any_instance(klass, instance: klass.new)
39
66
  klass.stub(:new, instance) { yield instance }
40
67
  end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "drb"
4
+ require "drb/unix" unless Gem.win_platform?
5
+ require "active_support/core_ext/module/attribute_accessors"
6
+
7
+ module ActiveSupport
8
+ module Testing
9
+ class Parallelization # :nodoc:
10
+ class Server
11
+ include DRb::DRbUndumped
12
+
13
+ def initialize
14
+ @queue = Queue.new
15
+ end
16
+
17
+ def record(reporter, result)
18
+ raise DRb::DRbConnError if result.is_a?(DRb::DRbUnknown)
19
+
20
+ reporter.synchronize do
21
+ reporter.record(result)
22
+ end
23
+ end
24
+
25
+ def <<(o)
26
+ o[2] = DRbObject.new(o[2]) if o
27
+ @queue << o
28
+ end
29
+
30
+ def length
31
+ @queue.length
32
+ end
33
+
34
+ def pop; @queue.pop; end
35
+ end
36
+
37
+ @@after_fork_hooks = []
38
+
39
+ def self.after_fork_hook(&blk)
40
+ @@after_fork_hooks << blk
41
+ end
42
+
43
+ cattr_reader :after_fork_hooks
44
+
45
+ @@run_cleanup_hooks = []
46
+
47
+ def self.run_cleanup_hook(&blk)
48
+ @@run_cleanup_hooks << blk
49
+ end
50
+
51
+ cattr_reader :run_cleanup_hooks
52
+
53
+ def initialize(queue_size)
54
+ @queue_size = queue_size
55
+ @queue = Server.new
56
+ @pool = []
57
+
58
+ @url = DRb.start_service("drbunix:", @queue).uri
59
+ end
60
+
61
+ def after_fork(worker)
62
+ self.class.after_fork_hooks.each do |cb|
63
+ cb.call(worker)
64
+ end
65
+ end
66
+
67
+ def run_cleanup(worker)
68
+ self.class.run_cleanup_hooks.each do |cb|
69
+ cb.call(worker)
70
+ end
71
+ end
72
+
73
+ def start
74
+ @pool = @queue_size.times.map do |worker|
75
+ fork do
76
+ DRb.stop_service
77
+
78
+ begin
79
+ after_fork(worker)
80
+ rescue => setup_exception; end
81
+
82
+ queue = DRbObject.new_with_uri(@url)
83
+
84
+ while job = queue.pop
85
+ klass = job[0]
86
+ method = job[1]
87
+ reporter = job[2]
88
+ result = klass.with_info_handler reporter do
89
+ Minitest.run_one_method(klass, method)
90
+ end
91
+
92
+ add_setup_exception(result, setup_exception) if setup_exception
93
+
94
+ begin
95
+ queue.record(reporter, result)
96
+ rescue DRb::DRbConnError
97
+ result.failures.map! do |failure|
98
+ if failure.respond_to?(:error)
99
+ # minitest >5.14.0
100
+ error = DRb::DRbRemoteError.new(failure.error)
101
+ else
102
+ error = DRb::DRbRemoteError.new(failure.exception)
103
+ end
104
+ Minitest::UnexpectedError.new(error)
105
+ end
106
+ queue.record(reporter, result)
107
+ end
108
+ end
109
+ ensure
110
+ run_cleanup(worker)
111
+ end
112
+ end
113
+ end
114
+
115
+ def <<(work)
116
+ @queue << work
117
+ end
118
+
119
+ def shutdown
120
+ @queue_size.times { @queue << nil }
121
+ @pool.each { |pid| Process.waitpid pid }
122
+
123
+ if @queue.length > 0
124
+ raise "Queue not empty, but all workers have finished. This probably means that a worker crashed and #{@queue.length} tests were missed."
125
+ end
126
+ end
127
+
128
+ private
129
+ def add_setup_exception(result, setup_exception)
130
+ result.failures.prepend Minitest::UnexpectedError.new(setup_exception)
131
+ end
132
+ end
133
+ end
134
+ end
@@ -4,7 +4,6 @@ module ActiveSupport
4
4
  module Testing
5
5
  module Stream #:nodoc:
6
6
  private
7
-
8
7
  def silence_stream(stream)
9
8
  old_stream = stream.dup
10
9
  stream.reopen(IO::NULL)
@@ -33,7 +32,7 @@ module ActiveSupport
33
32
  yield
34
33
 
35
34
  stream_io.rewind
36
- return captured_stream.read
35
+ captured_stream.read
37
36
  ensure
38
37
  captured_stream.close
39
38
  captured_stream.unlink
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/module/redefine_method"
4
- require "active_support/core_ext/string/strip" # for strip_heredoc
5
4
  require "active_support/core_ext/time/calculations"
6
5
  require "concurrent/map"
7
6
 
@@ -23,7 +22,7 @@ module ActiveSupport
23
22
 
24
23
  @stubs[object.object_id][method_name] = Stub.new(object, method_name, new_name)
25
24
 
26
- object.singleton_class.send :alias_method, new_name, method_name
25
+ object.singleton_class.alias_method new_name, method_name
27
26
  object.define_singleton_method(method_name, &block)
28
27
  end
29
28
 
@@ -41,12 +40,11 @@ module ActiveSupport
41
40
  end
42
41
 
43
42
  private
44
-
45
43
  def unstub_object(stub)
46
44
  singleton_class = stub.object.singleton_class
47
- singleton_class.send :silence_redefinition_of_method, stub.method_name
48
- singleton_class.send :alias_method, stub.method_name, stub.original_method
49
- singleton_class.send :undef_method, stub.original_method
45
+ singleton_class.silence_redefinition_of_method stub.method_name
46
+ singleton_class.alias_method stub.method_name, stub.original_method
47
+ singleton_class.undef_method stub.original_method
50
48
  end
51
49
  end
52
50
 
@@ -112,7 +110,7 @@ module ActiveSupport
112
110
  # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
113
111
  def travel_to(date_or_time)
114
112
  if block_given? && simple_stubs.stubbing(Time, :now)
115
- travel_to_nested_block_call = <<-MSG.strip_heredoc
113
+ travel_to_nested_block_call = <<~MSG
116
114
 
117
115
  Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing.
118
116
 
@@ -159,7 +157,7 @@ module ActiveSupport
159
157
  end
160
158
 
161
159
  # Returns the current time back to its original state, by removing the stubs added by
162
- # +travel+ and +travel_to+.
160
+ # +travel+, +travel_to+, and +freeze_time+.
163
161
  #
164
162
  # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
165
163
  # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44)
@@ -169,6 +167,7 @@ module ActiveSupport
169
167
  def travel_back
170
168
  simple_stubs.unstub_all!
171
169
  end
170
+ alias_method :unfreeze_time, :travel_back
172
171
 
173
172
  # Calls +travel_to+ with +Time.now+.
174
173
  #
@@ -191,7 +190,6 @@ module ActiveSupport
191
190
  end
192
191
 
193
192
  private
194
-
195
193
  def simple_stubs
196
194
  @simple_stubs ||= SimpleStubs.new
197
195
  end
@@ -43,8 +43,8 @@ module ActiveSupport
43
43
  "Time"
44
44
  end
45
45
 
46
- PRECISIONS = Hash.new { |h, n| h[n] = "%FT%T.%#{n}N".freeze }
47
- PRECISIONS[0] = "%FT%T".freeze
46
+ PRECISIONS = Hash.new { |h, n| h[n] = "%FT%T.%#{n}N" }
47
+ PRECISIONS[0] = "%FT%T"
48
48
 
49
49
  include Comparable, DateAndTime::Compatibility
50
50
  attr_reader :time_zone
@@ -147,7 +147,7 @@ module ActiveSupport
147
147
  #
148
148
  # Time.zone.now.xmlschema # => "2014-12-04T11:02:37-05:00"
149
149
  def xmlschema(fraction_digits = 0)
150
- "#{time.strftime(PRECISIONS[fraction_digits.to_i])}#{formatted_offset(true, 'Z'.freeze)}"
150
+ "#{time.strftime(PRECISIONS[fraction_digits.to_i])}#{formatted_offset(true, 'Z')}"
151
151
  end
152
152
  alias_method :iso8601, :xmlschema
153
153
  alias_method :rfc3339, :xmlschema
@@ -225,6 +225,8 @@ module ActiveSupport
225
225
  def <=>(other)
226
226
  utc <=> other
227
227
  end
228
+ alias_method :before?, :<
229
+ alias_method :after?, :>
228
230
 
229
231
  # Returns true if the current object's time is within the specified
230
232
  # +min+ and +max+ time.
@@ -284,8 +286,10 @@ module ActiveSupport
284
286
  alias_method :since, :+
285
287
  alias_method :in, :+
286
288
 
287
- # Returns a new TimeWithZone object that represents the difference between
288
- # the current object's time and the +other+ time.
289
+ # Subtracts an interval of time and returns a new TimeWithZone object unless
290
+ # the other value `acts_like?` time. Then it will return a Float of the difference
291
+ # between the two times that represents the difference between the current
292
+ # object's time and the +other+ time.
289
293
  #
290
294
  # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
291
295
  # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00
@@ -300,6 +304,12 @@ module ActiveSupport
300
304
  #
301
305
  # now - 24.hours # => Sun, 02 Nov 2014 01:26:28 EDT -04:00
302
306
  # now - 1.day # => Sun, 02 Nov 2014 00:26:28 EDT -04:00
307
+ #
308
+ # If both the TimeWithZone object and the other value act like Time, a Float
309
+ # will be returned.
310
+ #
311
+ # Time.zone.now - 1.day.ago # => 86399.999967
312
+ #
303
313
  def -(other)
304
314
  if other.acts_like?(:time)
305
315
  to_time - other.to_time