activesupport 8.0.2.1 → 8.1.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +247 -136
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +71 -0
  5. data/lib/active_support/broadcast_logger.rb +46 -59
  6. data/lib/active_support/cache/mem_cache_store.rb +25 -27
  7. data/lib/active_support/cache/redis_cache_store.rb +36 -30
  8. data/lib/active_support/cache/strategy/local_cache.rb +16 -7
  9. data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
  10. data/lib/active_support/cache.rb +70 -6
  11. data/lib/active_support/configurable.rb +28 -0
  12. data/lib/active_support/continuous_integration.rb +145 -0
  13. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
  14. data/lib/active_support/core_ext/date_time/conversions.rb +4 -2
  15. data/lib/active_support/core_ext/enumerable.rb +16 -4
  16. data/lib/active_support/core_ext/erb/util.rb +3 -3
  17. data/lib/active_support/core_ext/object/json.rb +8 -1
  18. data/lib/active_support/core_ext/object/to_query.rb +7 -1
  19. data/lib/active_support/core_ext/object/try.rb +2 -2
  20. data/lib/active_support/core_ext/range/overlap.rb +3 -3
  21. data/lib/active_support/core_ext/range/sole.rb +17 -0
  22. data/lib/active_support/core_ext/range.rb +1 -1
  23. data/lib/active_support/core_ext/string/filters.rb +3 -3
  24. data/lib/active_support/core_ext/string/multibyte.rb +12 -3
  25. data/lib/active_support/core_ext/string/output_safety.rb +19 -12
  26. data/lib/active_support/current_attributes/test_helper.rb +2 -2
  27. data/lib/active_support/current_attributes.rb +26 -16
  28. data/lib/active_support/deprecation/reporting.rb +4 -2
  29. data/lib/active_support/deprecation.rb +1 -1
  30. data/lib/active_support/editor.rb +70 -0
  31. data/lib/active_support/error_reporter.rb +50 -6
  32. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  33. data/lib/active_support/event_reporter.rb +570 -0
  34. data/lib/active_support/evented_file_update_checker.rb +5 -1
  35. data/lib/active_support/execution_context.rb +64 -7
  36. data/lib/active_support/file_update_checker.rb +7 -5
  37. data/lib/active_support/gem_version.rb +3 -3
  38. data/lib/active_support/gzip.rb +1 -0
  39. data/lib/active_support/hash_with_indifferent_access.rb +47 -24
  40. data/lib/active_support/i18n_railtie.rb +1 -2
  41. data/lib/active_support/inflector/inflections.rb +31 -15
  42. data/lib/active_support/inflector/transliterate.rb +6 -8
  43. data/lib/active_support/isolated_execution_state.rb +7 -13
  44. data/lib/active_support/json/decoding.rb +6 -4
  45. data/lib/active_support/json/encoding.rb +103 -14
  46. data/lib/active_support/lazy_load_hooks.rb +1 -1
  47. data/lib/active_support/log_subscriber.rb +2 -0
  48. data/lib/active_support/logger_thread_safe_level.rb +6 -3
  49. data/lib/active_support/message_encryptors.rb +52 -0
  50. data/lib/active_support/message_pack/extensions.rb +5 -0
  51. data/lib/active_support/message_verifiers.rb +52 -0
  52. data/lib/active_support/messages/rotation_coordinator.rb +9 -0
  53. data/lib/active_support/messages/rotator.rb +5 -0
  54. data/lib/active_support/multibyte/chars.rb +8 -1
  55. data/lib/active_support/multibyte.rb +4 -0
  56. data/lib/active_support/railtie.rb +26 -12
  57. data/lib/active_support/syntax_error_proxy.rb +3 -0
  58. data/lib/active_support/test_case.rb +61 -6
  59. data/lib/active_support/testing/assertions.rb +34 -6
  60. data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
  61. data/lib/active_support/testing/event_reporter_assertions.rb +217 -0
  62. data/lib/active_support/testing/notification_assertions.rb +92 -0
  63. data/lib/active_support/testing/parallelization/worker.rb +2 -0
  64. data/lib/active_support/testing/parallelization.rb +13 -0
  65. data/lib/active_support/testing/tests_without_assertions.rb +1 -1
  66. data/lib/active_support/testing/time_helpers.rb +7 -3
  67. data/lib/active_support/time_with_zone.rb +19 -5
  68. data/lib/active_support/values/time_zone.rb +8 -1
  69. data/lib/active_support/xml_mini.rb +1 -2
  70. data/lib/active_support.rb +11 -0
  71. metadata +11 -5
  72. data/lib/active_support/core_ext/range/each.rb +0 -24
@@ -26,6 +26,9 @@ module ActiveSupport
26
26
  # as the first rotation and <tt>transitional = true</tt>. Then, after all
27
27
  # servers have been updated, perform a second rolling deploy with
28
28
  # <tt>transitional = false</tt>.
29
+ #
30
+ #--
31
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#transitional
29
32
 
30
33
  ##
31
34
  # :singleton-method: new
@@ -43,6 +46,9 @@ module ActiveSupport
43
46
  # end
44
47
  #
45
48
  # encryptors.rotate(base: "...")
49
+ #
50
+ #--
51
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#initialize
46
52
 
47
53
  ##
48
54
  # :method: []
@@ -51,12 +57,18 @@ module ActiveSupport
51
57
  # Returns a MessageEncryptor configured with a secret derived from the
52
58
  # given +salt+, and options from #rotate. MessageEncryptor instances will
53
59
  # be memoized, so the same +salt+ will return the same instance.
60
+ #
61
+ #--
62
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#[]
54
63
 
55
64
  ##
56
65
  # :method: []=
57
66
  # :call-seq: []=(salt, encryptor)
58
67
  #
59
68
  # Overrides a MessageEncryptor instance associated with a given +salt+.
69
+ #
70
+ #--
71
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#[]=
60
72
 
61
73
  ##
62
74
  # :method: rotate
@@ -106,18 +118,55 @@ module ActiveSupport
106
118
  #
107
119
  # # Uses `serializer: Marshal, url_safe: false`.
108
120
  # encryptors[:baz]
121
+ #
122
+ #--
123
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#rotate
124
+
125
+ ##
126
+ # :method: prepend
127
+ # :call-seq:
128
+ # prepend(**options)
129
+ # prepend(&block)
130
+ #
131
+ # Just like #rotate, but prepends the given options or block to the list of
132
+ # option sets.
133
+ #
134
+ # This can be useful when you have an already-configured +MessageEncryptors+
135
+ # instance, but you want to override the way messages are encrypted.
136
+ #
137
+ # module ThirdParty
138
+ # ENCRYPTORS = ActiveSupport::MessageEncryptors.new { ... }.
139
+ # rotate(serializer: Marshal, url_safe: true).
140
+ # rotate(serializer: Marshal, url_safe: false)
141
+ # end
142
+ #
143
+ # ThirdParty.ENCRYPTORS.prepend(serializer: JSON, url_safe: true)
144
+ #
145
+ # # Uses `serializer: JSON, url_safe: true`.
146
+ # # Falls back to `serializer: Marshal, url_safe: true` or
147
+ # # `serializer: Marshal, url_safe: false`.
148
+ # ThirdParty.ENCRYPTORS[:foo]
149
+ #
150
+ #--
151
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#prepend
109
152
 
110
153
  ##
111
154
  # :method: rotate_defaults
112
155
  # :call-seq: rotate_defaults
113
156
  #
114
157
  # Invokes #rotate with the default options.
158
+ #
159
+ #--
160
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#rotate_defaults
115
161
 
116
162
  ##
117
163
  # :method: clear_rotations
118
164
  # :call-seq: clear_rotations
119
165
  #
120
166
  # Clears the list of option sets.
167
+ #
168
+ #--
169
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#clear_rotations
121
170
 
122
171
  ##
123
172
  # :method: on_rotation
@@ -129,6 +178,9 @@ module ActiveSupport
129
178
  # For example, this callback could log each time it is called, and thus
130
179
  # indicate whether old option sets are still in use or can be removed from
131
180
  # rotation.
181
+ #
182
+ #--
183
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#on_rotation
132
184
 
133
185
  ##
134
186
  private
@@ -7,6 +7,7 @@ require "pathname"
7
7
  require "uri/generic"
8
8
  require "msgpack/bigint"
9
9
  require "active_support/hash_with_indifferent_access"
10
+ require "active_support/core_ext/string/output_safety"
10
11
  require "active_support/time"
11
12
 
12
13
  module ActiveSupport
@@ -102,6 +103,10 @@ module ActiveSupport
102
103
  packer: method(:write_hash_with_indifferent_access),
103
104
  unpacker: method(:read_hash_with_indifferent_access),
104
105
  recursive: true
106
+
107
+ registry.register_type 18, ActiveSupport::SafeBuffer,
108
+ packer: :to_s,
109
+ unpacker: :new
105
110
  end
106
111
 
107
112
  def install_unregistered_type_error(registry)
@@ -26,6 +26,9 @@ module ActiveSupport
26
26
  # as the first rotation and <tt>transitional = true</tt>. Then, after all
27
27
  # servers have been updated, perform a second rolling deploy with
28
28
  # <tt>transitional = false</tt>.
29
+ #
30
+ #--
31
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#transitional
29
32
 
30
33
  ##
31
34
  # :singleton-method: new
@@ -42,6 +45,9 @@ module ActiveSupport
42
45
  # end
43
46
  #
44
47
  # verifiers.rotate(base: "...")
48
+ #
49
+ #--
50
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#initialize
45
51
 
46
52
  ##
47
53
  # :method: []
@@ -50,12 +56,18 @@ module ActiveSupport
50
56
  # Returns a MessageVerifier configured with a secret derived from the
51
57
  # given +salt+, and options from #rotate. MessageVerifier instances will
52
58
  # be memoized, so the same +salt+ will return the same instance.
59
+ #
60
+ #--
61
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#[]
53
62
 
54
63
  ##
55
64
  # :method: []=
56
65
  # :call-seq: []=(salt, verifier)
57
66
  #
58
67
  # Overrides a MessageVerifier instance associated with a given +salt+.
68
+ #
69
+ #--
70
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#[]=
59
71
 
60
72
  ##
61
73
  # :method: rotate
@@ -104,18 +116,55 @@ module ActiveSupport
104
116
  #
105
117
  # # Uses `serializer: Marshal, url_safe: false`.
106
118
  # verifiers[:baz]
119
+ #
120
+ #--
121
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#rotate
122
+
123
+ ##
124
+ # :method: prepend
125
+ # :call-seq:
126
+ # prepend(**options)
127
+ # prepend(&block)
128
+ #
129
+ # Just like #rotate, but prepends the given options or block to the list of
130
+ # option sets.
131
+ #
132
+ # This can be useful when you have an already-configured +MessageVerifiers+
133
+ # instance, but you want to override the way messages are signed.
134
+ #
135
+ # module ThirdParty
136
+ # VERIFIERS = ActiveSupport::MessageVerifiers.new { ... }.
137
+ # rotate(serializer: Marshal, url_safe: true).
138
+ # rotate(serializer: Marshal, url_safe: false)
139
+ # end
140
+ #
141
+ # ThirdParty.VERIFIERS.prepend(serializer: JSON, url_safe: true)
142
+ #
143
+ # # Uses `serializer: JSON, url_safe: true`.
144
+ # # Falls back to `serializer: Marshal, url_safe: true` or
145
+ # # `serializer: Marshal, url_safe: false`.
146
+ # ThirdParty.VERIFIERS[:foo]
147
+ #
148
+ #--
149
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#prepend
107
150
 
108
151
  ##
109
152
  # :method: rotate_defaults
110
153
  # :call-seq: rotate_defaults
111
154
  #
112
155
  # Invokes #rotate with the default options.
156
+ #
157
+ #--
158
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#rotate_defaults
113
159
 
114
160
  ##
115
161
  # :method: clear_rotations
116
162
  # :call-seq: clear_rotations
117
163
  #
118
164
  # Clears the list of option sets.
165
+ #
166
+ #--
167
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#clear_rotations
119
168
 
120
169
  ##
121
170
  # :method: on_rotation
@@ -127,6 +176,9 @@ module ActiveSupport
127
176
  # For example, this callback could log each time it is called, and thus
128
177
  # indicate whether old option sets are still in use or can be removed from
129
178
  # rotation.
179
+ #
180
+ #--
181
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#on_rotation
130
182
 
131
183
  ##
132
184
  private
@@ -32,6 +32,15 @@ module ActiveSupport
32
32
  self
33
33
  end
34
34
 
35
+ def prepend(**options, &block)
36
+ raise ArgumentError, "Options cannot be specified when using a block" if block && !options.empty?
37
+ changing_configuration!
38
+
39
+ @rotate_options.unshift(block || options)
40
+
41
+ self
42
+ end
43
+
35
44
  def rotate_defaults
36
45
  rotate()
37
46
  end
@@ -45,6 +45,11 @@ module ActiveSupport
45
45
  end
46
46
  end
47
47
 
48
+ def initialize_dup(*)
49
+ super
50
+ @rotations = @rotations.dup
51
+ end
52
+
48
53
  private
49
54
  def build_rotation(*args, **options)
50
55
  self.class.new(*args, *@args.drop(args.length), **@options, **options)
@@ -53,7 +53,14 @@ module ActiveSupport # :nodoc:
53
53
  delegate :<=>, :=~, :match?, :acts_like_string?, to: :wrapped_string
54
54
 
55
55
  # Creates a new Chars instance by wrapping _string_.
56
- def initialize(string)
56
+ def initialize(string, deprecation: true)
57
+ if deprecation
58
+ ActiveSupport.deprecator.warn(
59
+ "ActiveSupport::Multibyte::Chars is deprecated and will be removed in Rails 8.2. " \
60
+ "Use normal string methods instead."
61
+ )
62
+ end
63
+
57
64
  @wrapped_string = string
58
65
  if string.encoding != Encoding::UTF_8
59
66
  @wrapped_string = @wrapped_string.dup
@@ -12,6 +12,10 @@ module ActiveSupport # :nodoc:
12
12
  #
13
13
  # ActiveSupport::Multibyte.proxy_class = CharsForUTF32
14
14
  def self.proxy_class=(klass)
15
+ ActiveSupport.deprecator.warn(
16
+ "ActiveSupport::Multibyte.proxy_class= is deprecated and will be removed in Rails 8.2. " \
17
+ "Use normal string methods instead."
18
+ )
15
19
  @proxy_class = klass
16
20
  end
17
21
 
@@ -15,7 +15,7 @@ module ActiveSupport
15
15
 
16
16
  initializer "active_support.isolation_level" do |app|
17
17
  config.after_initialize do
18
- if level = app.config.active_support.delete(:isolation_level)
18
+ if level = app.config.active_support.isolation_level
19
19
  ActiveSupport::IsolatedExecutionState.isolation_level = level
20
20
  end
21
21
  end
@@ -38,19 +38,35 @@ module ActiveSupport
38
38
  end
39
39
  end
40
40
 
41
- initializer "active_support.reset_execution_context" do |app|
42
- app.reloader.before_class_unload { ActiveSupport::ExecutionContext.clear }
43
- app.executor.to_run { ActiveSupport::ExecutionContext.clear }
44
- app.executor.to_complete { ActiveSupport::ExecutionContext.clear }
41
+ initializer "active_support.set_event_reporter_context_store" do |app|
42
+ config.after_initialize do
43
+ if klass = app.config.active_support.event_reporter_context_store
44
+ ActiveSupport::EventReporter.context_store = klass
45
+ end
46
+ end
45
47
  end
46
48
 
47
- initializer "active_support.reset_all_current_attributes_instances" do |app|
48
- app.reloader.before_class_unload { ActiveSupport::CurrentAttributes.clear_all }
49
- app.executor.to_run { ActiveSupport::CurrentAttributes.reset_all }
50
- app.executor.to_complete { ActiveSupport::CurrentAttributes.reset_all }
49
+ initializer "active_support.reset_execution_context" do |app|
50
+ app.reloader.before_class_unload do
51
+ ActiveSupport::CurrentAttributes.clear_all
52
+ ActiveSupport::ExecutionContext.clear
53
+ ActiveSupport.event_reporter.clear_context
54
+ end
55
+
56
+ app.executor.to_run do
57
+ ActiveSupport::ExecutionContext.push
58
+ end
59
+
60
+ app.executor.to_complete do
61
+ ActiveSupport::CurrentAttributes.clear_all
62
+ ActiveSupport::ExecutionContext.pop
63
+ ActiveSupport.event_reporter.clear_context
64
+ end
51
65
 
52
66
  ActiveSupport.on_load(:active_support_test_case) do
53
67
  if app.config.active_support.executor_around_test_case
68
+ ActiveSupport::ExecutionContext.nestable = true
69
+
54
70
  require "active_support/executor/test_helper"
55
71
  include ActiveSupport::Executor::TestHelper
56
72
  else
@@ -97,9 +113,7 @@ module ActiveSupport
97
113
  end
98
114
 
99
115
  initializer "active_support.to_time_preserves_timezone" do |app|
100
- config.after_initialize do
101
- ActiveSupport.to_time_preserves_timezone = app.config.active_support.to_time_preserves_timezone
102
- end
116
+ ActiveSupport.to_time_preserves_timezone = app.config.active_support.to_time_preserves_timezone
103
117
  end
104
118
 
105
119
  # Sets the default week start
@@ -18,6 +18,9 @@ module ActiveSupport
18
18
 
19
19
  def label
20
20
  end
21
+
22
+ def base_label
23
+ end
21
24
  end
22
25
 
23
26
  class BacktraceLocationProxy < DelegateClass(Thread::Backtrace::Location) # :nodoc:
@@ -6,6 +6,7 @@ require "active_support/testing/setup_and_teardown"
6
6
  require "active_support/testing/tests_without_assertions"
7
7
  require "active_support/testing/assertions"
8
8
  require "active_support/testing/error_reporter_assertions"
9
+ require "active_support/testing/event_reporter_assertions"
9
10
  require "active_support/testing/deprecation"
10
11
  require "active_support/testing/declarative"
11
12
  require "active_support/testing/isolation"
@@ -15,13 +16,29 @@ require "active_support/testing/constant_stubbing"
15
16
  require "active_support/testing/file_fixtures"
16
17
  require "active_support/testing/parallelization"
17
18
  require "active_support/testing/parallelize_executor"
19
+ require "active_support/testing/notification_assertions"
18
20
  require "concurrent/utility/processor_counter"
19
21
 
20
22
  module ActiveSupport
21
23
  class TestCase < ::Minitest::Test
22
24
  Assertion = Minitest::Assertion
23
25
 
26
+ # Class variable to store the parallel worker ID
27
+ @@parallel_worker_id = nil
28
+
24
29
  class << self
30
+ # Returns the current parallel worker ID if tests are running in parallel,
31
+ # nil otherwise.
32
+ #
33
+ # ActiveSupport::TestCase.parallel_worker_id # => 2
34
+ def parallel_worker_id
35
+ @@parallel_worker_id
36
+ end
37
+
38
+ def parallel_worker_id=(value) # :nodoc:
39
+ @@parallel_worker_id = value
40
+ end
41
+
25
42
  # Sets the order in which test cases are run.
26
43
  #
27
44
  # ActiveSupport::TestCase.test_order = :random # => :random
@@ -51,8 +68,8 @@ module ActiveSupport
51
68
  # is forked. For each process a new database will be created suffixed
52
69
  # with the worker number.
53
70
  #
54
- # test-database-0
55
- # test-database-1
71
+ # test-database_0
72
+ # test-database_1
56
73
  #
57
74
  # If <tt>ENV["PARALLEL_WORKERS"]</tt> is set the workers argument will be ignored
58
75
  # and the environment variable will be used instead. This is useful for CI
@@ -78,14 +95,45 @@ module ActiveSupport
78
95
  # Because parallelization presents an overhead, it is only enabled when the
79
96
  # number of tests to run is above the +threshold+ param. The default value is
80
97
  # 50, and it's configurable via +config.active_support.test_parallelization_threshold+.
81
- def parallelize(workers: :number_of_processors, with: :processes, threshold: ActiveSupport.test_parallelization_threshold)
82
- workers = Concurrent.processor_count if workers == :number_of_processors
83
- workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"]
98
+ #
99
+ # If you want to skip Rails default creation of one database per process in favor of
100
+ # writing your own implementation, you can set +parallelize_databases+, or configure it
101
+ # via +config.active_support.parallelize_test_databases+.
102
+ #
103
+ # parallelize(workers: :number_of_processors, parallelize_databases: false)
104
+ #
105
+ # Note that your test suite may deadlock if you attempt to use only one database
106
+ # with multiple processes.
107
+ def parallelize(workers: :number_of_processors, with: :processes, threshold: ActiveSupport.test_parallelization_threshold, parallelize_databases: ActiveSupport.parallelize_test_databases)
108
+ case
109
+ when ENV["PARALLEL_WORKERS"]
110
+ workers = ENV["PARALLEL_WORKERS"].to_i
111
+ when workers == :number_of_processors
112
+ workers = (Concurrent.available_processor_count || Concurrent.processor_count).floor
113
+ end
114
+
115
+ if with == :processes
116
+ ActiveSupport.parallelize_test_databases = parallelize_databases
117
+ end
84
118
 
85
119
  Minitest.parallel_executor = ActiveSupport::Testing::ParallelizeExecutor.new(size: workers, with: with, threshold: threshold)
86
120
  end
87
121
 
88
- # Set up hook for parallel testing. This can be used if you have multiple
122
+ # Before fork hook for parallel testing. This can be used to run anything
123
+ # before the processes are forked.
124
+ #
125
+ # In your +test_helper.rb+ add the following:
126
+ #
127
+ # class ActiveSupport::TestCase
128
+ # parallelize_before_fork do
129
+ # # run this before fork
130
+ # end
131
+ # end
132
+ def parallelize_before_fork(&block)
133
+ ActiveSupport::Testing::Parallelization.before_fork_hook(&block)
134
+ end
135
+
136
+ # Setup hook for parallel testing. This can be used if you have multiple
89
137
  # databases or any behavior that needs to be run after the process is forked
90
138
  # but before the tests run.
91
139
  #
@@ -141,11 +189,18 @@ module ActiveSupport
141
189
 
142
190
  alias_method :method_name, :name
143
191
 
192
+ # Returns the current parallel worker ID if tests are running in parallel
193
+ def parallel_worker_id
194
+ self.class.parallel_worker_id
195
+ end
196
+
144
197
  include ActiveSupport::Testing::TaggedLogging
145
198
  prepend ActiveSupport::Testing::SetupAndTeardown
146
199
  prepend ActiveSupport::Testing::TestsWithoutAssertions
147
200
  include ActiveSupport::Testing::Assertions
148
201
  include ActiveSupport::Testing::ErrorReporterAssertions
202
+ include ActiveSupport::Testing::EventReporterAssertions
203
+ include ActiveSupport::Testing::NotificationAssertions
149
204
  include ActiveSupport::Testing::Deprecation
150
205
  include ActiveSupport::Testing::ConstantStubbing
151
206
  include ActiveSupport::Testing::TimeHelpers
@@ -71,19 +71,19 @@ module ActiveSupport
71
71
  # post :delete, params: { id: ... }
72
72
  # end
73
73
  #
74
- # An array of expressions can also be passed in and evaluated.
74
+ # An array of expressions can be passed in and evaluated.
75
75
  #
76
76
  # assert_difference [ 'Article.count', 'Post.count' ], 2 do
77
77
  # post :create, params: { article: {...} }
78
78
  # end
79
79
  #
80
- # A hash of expressions/numeric differences can also be passed in and evaluated.
80
+ # A hash of expressions/numeric differences can be passed in and evaluated.
81
81
  #
82
- # assert_difference ->{ Article.count } => 1, ->{ Notification.count } => 2 do
82
+ # assert_difference({ 'Article.count' => 1, 'Notification.count' => 2 }) do
83
83
  # post :create, params: { article: {...} }
84
84
  # end
85
85
  #
86
- # A lambda or a list of lambdas can be passed in and evaluated:
86
+ # A lambda, a list of lambdas or a hash of lambdas/numeric differences can be passed in and evaluated:
87
87
  #
88
88
  # assert_difference ->{ Article.count }, 2 do
89
89
  # post :create, params: { article: {...} }
@@ -93,6 +93,10 @@ module ActiveSupport
93
93
  # post :create, params: { article: {...} }
94
94
  # end
95
95
  #
96
+ # assert_difference ->{ Article.count } => 1, ->{ Notification.count } => 2 do
97
+ # post :create, params: { article: {...} }
98
+ # end
99
+ #
96
100
  # An error message can be specified.
97
101
  #
98
102
  # assert_difference 'Article.count', -1, 'An Article should be destroyed' do
@@ -181,12 +185,24 @@ module ActiveSupport
181
185
  #
182
186
  # The keyword arguments +:from+ and +:to+ can be given to specify the
183
187
  # expected initial value and the expected value after the block was
184
- # executed.
188
+ # executed. The comparison is done using case equality (===), which means
189
+ # you can specify patterns or classes:
185
190
  #
191
+ # # Exact value match
186
192
  # assert_changes :@object, from: nil, to: :foo do
187
193
  # @object = :foo
188
194
  # end
189
195
  #
196
+ # # Case equality
197
+ # assert_changes -> { user.token }, to: /\w{32}/ do
198
+ # user.generate_token
199
+ # end
200
+ #
201
+ # # Type check
202
+ # assert_changes -> { current_error }, from: nil, to: RuntimeError do
203
+ # raise "Oops"
204
+ # end
205
+ #
190
206
  # An error message can be specified.
191
207
  #
192
208
  # assert_changes -> { Status.all_good? }, 'Expected the status to be bad' do
@@ -238,12 +254,24 @@ module ActiveSupport
238
254
  # end
239
255
  #
240
256
  # Provide the optional keyword argument +:from+ to specify the expected
241
- # initial value.
257
+ # initial value. The comparison is done using case equality (===), which means
258
+ # you can specify patterns or classes:
242
259
  #
260
+ # # Exact value match
243
261
  # assert_no_changes -> { Status.all_good? }, from: true do
244
262
  # post :create, params: { status: { ok: true } }
245
263
  # end
246
264
  #
265
+ # # Case equality
266
+ # assert_no_changes -> { user.token }, from: /\w{32}/ do
267
+ # user.touch
268
+ # end
269
+ #
270
+ # # Type check
271
+ # assert_no_changes -> { current_error }, from: RuntimeError do
272
+ # retry_operation
273
+ # end
274
+ #
247
275
  # An error message can be specified.
248
276
  #
249
277
  # assert_no_changes -> { Status.all_good? }, 'Expected the status to be good' do
@@ -44,7 +44,7 @@ module ActiveSupport
44
44
  ActiveSupport.error_reporter.subscribe(self)
45
45
  @subscribed = true
46
46
  else
47
- raise Minitest::Assertion, "No error reporter is configured"
47
+ flunk("No error reporter is configured")
48
48
  end
49
49
  end
50
50
  end
@@ -102,6 +102,23 @@ module ActiveSupport
102
102
  assert(false, message)
103
103
  end
104
104
  end
105
+
106
+ # Captures reported errors from within the block that match the given
107
+ # error class.
108
+ #
109
+ # reports = capture_error_reports(IOError) do
110
+ # Rails.error.report(IOError.new("Oops"))
111
+ # Rails.error.report(IOError.new("Oh no"))
112
+ # Rails.error.report(StandardError.new)
113
+ # end
114
+ #
115
+ # assert_equal 2, reports.size
116
+ # assert_equal "Oops", reports.first.error.message
117
+ # assert_equal "Oh no", reports.last.error.message
118
+ def capture_error_reports(error_class = StandardError, &block)
119
+ reports = ErrorCollector.record(&block)
120
+ reports.select { |r| error_class === r.error }
121
+ end
105
122
  end
106
123
  end
107
124
  end