activesupport 6.0.6.1 → 6.1.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +441 -455
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_support/array_inquirer.rb +4 -2
  5. data/lib/active_support/backtrace_cleaner.rb +3 -3
  6. data/lib/active_support/benchmarkable.rb +1 -1
  7. data/lib/active_support/cache/file_store.rb +3 -3
  8. data/lib/active_support/cache/mem_cache_store.rb +28 -18
  9. data/lib/active_support/cache/memory_store.rb +46 -26
  10. data/lib/active_support/cache/redis_cache_store.rb +25 -25
  11. data/lib/active_support/cache/strategy/local_cache.rb +20 -5
  12. data/lib/active_support/cache.rb +87 -40
  13. data/lib/active_support/callbacks.rb +65 -56
  14. data/lib/active_support/concern.rb +46 -2
  15. data/lib/active_support/configurable.rb +3 -3
  16. data/lib/active_support/configuration_file.rb +51 -0
  17. data/lib/active_support/core_ext/benchmark.rb +2 -2
  18. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  19. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  20. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  21. data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
  22. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  23. data/lib/active_support/core_ext/digest/uuid.rb +1 -0
  24. data/lib/active_support/core_ext/enumerable.rb +76 -4
  25. data/lib/active_support/core_ext/hash/conversions.rb +2 -2
  26. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  27. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  28. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  29. data/lib/active_support/core_ext/load_error.rb +1 -1
  30. data/lib/active_support/core_ext/marshal.rb +2 -0
  31. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  32. data/lib/active_support/core_ext/module/attribute_accessors.rb +23 -29
  33. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +8 -4
  34. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  35. data/lib/active_support/core_ext/module/delegation.rb +38 -28
  36. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  37. data/lib/active_support/core_ext/name_error.rb +29 -2
  38. data/lib/active_support/core_ext/numeric/conversions.rb +22 -18
  39. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  40. data/lib/active_support/core_ext/object/json.rb +12 -1
  41. data/lib/active_support/core_ext/object/try.rb +2 -2
  42. data/lib/active_support/core_ext/range/compare_range.rb +9 -3
  43. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  44. data/lib/active_support/core_ext/regexp.rb +8 -1
  45. data/lib/active_support/core_ext/string/access.rb +5 -24
  46. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  47. data/lib/active_support/core_ext/string/inflections.rb +38 -4
  48. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  49. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  50. data/lib/active_support/core_ext/string/output_safety.rb +7 -4
  51. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  52. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  53. data/lib/active_support/core_ext/symbol.rb +3 -0
  54. data/lib/active_support/core_ext/time/calculations.rb +19 -0
  55. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  56. data/lib/active_support/core_ext/uri.rb +5 -1
  57. data/lib/active_support/core_ext.rb +1 -1
  58. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  59. data/lib/active_support/current_attributes.rb +9 -2
  60. data/lib/active_support/dependencies/zeitwerk_integration.rb +4 -1
  61. data/lib/active_support/dependencies.rb +37 -18
  62. data/lib/active_support/deprecation/behaviors.rb +15 -2
  63. data/lib/active_support/deprecation/disallowed.rb +56 -0
  64. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  65. data/lib/active_support/deprecation/method_wrappers.rb +3 -2
  66. data/lib/active_support/deprecation/proxy_wrappers.rb +2 -2
  67. data/lib/active_support/deprecation/reporting.rb +50 -7
  68. data/lib/active_support/deprecation.rb +6 -1
  69. data/lib/active_support/descendants_tracker.rb +6 -2
  70. data/lib/active_support/digest.rb +2 -0
  71. data/lib/active_support/duration/iso8601_serializer.rb +15 -9
  72. data/lib/active_support/duration.rb +75 -25
  73. data/lib/active_support/encrypted_file.rb +27 -11
  74. data/lib/active_support/environment_inquirer.rb +20 -0
  75. data/lib/active_support/evented_file_update_checker.rb +69 -133
  76. data/lib/active_support/fork_tracker.rb +64 -0
  77. data/lib/active_support/gem_version.rb +3 -3
  78. data/lib/active_support/hash_with_indifferent_access.rb +48 -24
  79. data/lib/active_support/i18n_railtie.rb +14 -19
  80. data/lib/active_support/inflector/inflections.rb +1 -2
  81. data/lib/active_support/inflector/methods.rb +36 -33
  82. data/lib/active_support/inflector/transliterate.rb +4 -4
  83. data/lib/active_support/json/decoding.rb +4 -4
  84. data/lib/active_support/json/encoding.rb +5 -1
  85. data/lib/active_support/key_generator.rb +1 -1
  86. data/lib/active_support/locale/en.yml +7 -3
  87. data/lib/active_support/log_subscriber.rb +8 -0
  88. data/lib/active_support/logger.rb +1 -1
  89. data/lib/active_support/logger_silence.rb +2 -26
  90. data/lib/active_support/logger_thread_safe_level.rb +34 -12
  91. data/lib/active_support/message_encryptor.rb +4 -7
  92. data/lib/active_support/message_verifier.rb +5 -5
  93. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  94. data/lib/active_support/messages/rotator.rb +6 -5
  95. data/lib/active_support/multibyte/chars.rb +4 -42
  96. data/lib/active_support/multibyte/unicode.rb +9 -83
  97. data/lib/active_support/notifications/fanout.rb +23 -8
  98. data/lib/active_support/notifications/instrumenter.rb +6 -15
  99. data/lib/active_support/notifications.rb +32 -5
  100. data/lib/active_support/number_helper/number_converter.rb +1 -1
  101. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  102. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  103. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  104. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  105. data/lib/active_support/number_helper.rb +29 -14
  106. data/lib/active_support/option_merger.rb +2 -1
  107. data/lib/active_support/ordered_options.rb +8 -2
  108. data/lib/active_support/parameter_filter.rb +16 -11
  109. data/lib/active_support/per_thread_registry.rb +2 -1
  110. data/lib/active_support/rails.rb +1 -4
  111. data/lib/active_support/railtie.rb +23 -1
  112. data/lib/active_support/rescuable.rb +4 -4
  113. data/lib/active_support/secure_compare_rotator.rb +51 -0
  114. data/lib/active_support/security_utils.rb +19 -12
  115. data/lib/active_support/string_inquirer.rb +4 -2
  116. data/lib/active_support/subscriber.rb +12 -7
  117. data/lib/active_support/tagged_logging.rb +30 -5
  118. data/lib/active_support/testing/assertions.rb +18 -11
  119. data/lib/active_support/testing/parallelization/server.rb +78 -0
  120. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  121. data/lib/active_support/testing/parallelization.rb +12 -95
  122. data/lib/active_support/testing/time_helpers.rb +40 -3
  123. data/lib/active_support/time_with_zone.rb +67 -43
  124. data/lib/active_support/values/time_zone.rb +22 -10
  125. data/lib/active_support/xml_mini/rexml.rb +8 -1
  126. data/lib/active_support.rb +13 -1
  127. metadata +34 -36
  128. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  129. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  130. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  131. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  132. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  133. data/lib/active_support/core_ext/range/include_range.rb +0 -9
@@ -8,11 +8,20 @@ require "active_support/logger"
8
8
  module ActiveSupport
9
9
  # Wraps any standard Logger object to provide tagging capabilities.
10
10
  #
11
+ # May be called with a block:
12
+ #
11
13
  # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
12
14
  # logger.tagged('BCX') { logger.info 'Stuff' } # Logs "[BCX] Stuff"
13
15
  # logger.tagged('BCX', "Jason") { logger.info 'Stuff' } # Logs "[BCX] [Jason] Stuff"
14
16
  # logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff"
15
17
  #
18
+ # If called without a block, a new logger will be returned with applied tags:
19
+ #
20
+ # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
21
+ # logger.tagged("BCX").info "Stuff" # Logs "[BCX] Stuff"
22
+ # logger.tagged("BCX", "Jason").info "Stuff" # Logs "[BCX] [Jason] Stuff"
23
+ # logger.tagged("BCX").tagged("Jason").info "Stuff" # Logs "[BCX] [Jason] Stuff"
24
+ #
16
25
  # This is used by the default Rails.logger as configured by Railties to make
17
26
  # it easy to stamp log lines with subdomains, request ids, and anything else
18
27
  # to aid debugging of multi-user production applications.
@@ -31,9 +40,10 @@ module ActiveSupport
31
40
  end
32
41
 
33
42
  def push_tags(*tags)
34
- tags.flatten.reject(&:blank?).tap do |new_tags|
35
- current_tags.concat new_tags
36
- end
43
+ tags.flatten!
44
+ tags.reject!(&:blank?)
45
+ current_tags.concat tags
46
+ tags
37
47
  end
38
48
 
39
49
  def pop_tags(size = 1)
@@ -60,8 +70,16 @@ module ActiveSupport
60
70
  end
61
71
  end
62
72
 
73
+ module LocalTagStorage # :nodoc:
74
+ attr_accessor :current_tags
75
+
76
+ def self.extended(base)
77
+ base.current_tags = []
78
+ end
79
+ end
80
+
63
81
  def self.new(logger)
64
- logger = logger.dup
82
+ logger = logger.clone
65
83
 
66
84
  if logger.formatter
67
85
  logger.formatter = logger.formatter.dup
@@ -77,7 +95,14 @@ module ActiveSupport
77
95
  delegate :push_tags, :pop_tags, :clear_tags!, to: :formatter
78
96
 
79
97
  def tagged(*tags)
80
- formatter.tagged(*tags) { yield self }
98
+ if block_given?
99
+ formatter.tagged(*tags) { yield self }
100
+ else
101
+ logger = ActiveSupport::TaggedLogging.new(self)
102
+ logger.formatter.extend LocalTagStorage
103
+ logger.push_tags(*formatter.current_tags, *tags)
104
+ logger
105
+ end
81
106
  end
82
107
 
83
108
  def flush
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/enumerable"
4
+
3
5
  module ActiveSupport
4
6
  module Testing
5
7
  module Assertions
@@ -30,6 +32,8 @@ module ActiveSupport
30
32
  # end
31
33
  def assert_nothing_raised
32
34
  yield
35
+ rescue => error
36
+ raise Minitest::UnexpectedError.new(error)
33
37
  end
34
38
 
35
39
  # Test numeric difference between the return value of an expression as a
@@ -87,7 +91,7 @@ module ActiveSupport
87
91
  else
88
92
  difference = args[0] || 1
89
93
  message = args[1]
90
- Hash[Array(expression).map { |e| [e, difference] }]
94
+ Array(expression).index_with(difference)
91
95
  end
92
96
 
93
97
  exps = expressions.keys.map { |e|
@@ -95,7 +99,7 @@ module ActiveSupport
95
99
  }
96
100
  before = exps.map(&:call)
97
101
 
98
- retval = yield
102
+ retval = assert_nothing_raised(&block)
99
103
 
100
104
  expressions.zip(exps, before) do |(code, diff), exp, before_value|
101
105
  error = "#{code.inspect} didn't change by #{diff}"
@@ -172,10 +176,10 @@ module ActiveSupport
172
176
  exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) }
173
177
 
174
178
  before = exp.call
175
- retval = yield
179
+ retval = assert_nothing_raised(&block)
176
180
 
177
181
  unless from == UNTRACKED
178
- error = "#{expression.inspect} isn't #{from.inspect}"
182
+ error = "Expected change from #{from.inspect}"
179
183
  error = "#{message}.\n#{error}" if message
180
184
  assert from === before, error
181
185
  end
@@ -185,12 +189,10 @@ module ActiveSupport
185
189
  error = "#{expression.inspect} didn't change"
186
190
  error = "#{error}. It was already #{to}" if before == to
187
191
  error = "#{message}.\n#{error}" if message
188
- assert before != after, error
192
+ refute_equal before, after, error
189
193
 
190
194
  unless to == UNTRACKED
191
- error = "#{expression.inspect} didn't change to as expected\n"
192
- error = "#{error}Expected: #{to.inspect}\n"
193
- error = "#{error} Actual: #{after.inspect}"
195
+ error = "Expected change to #{to}\n"
194
196
  error = "#{message}.\n#{error}" if message
195
197
  assert to === after, error
196
198
  end
@@ -214,12 +216,17 @@ module ActiveSupport
214
216
  exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) }
215
217
 
216
218
  before = exp.call
217
- retval = yield
219
+ retval = assert_nothing_raised(&block)
218
220
  after = exp.call
219
221
 
220
- error = "#{expression.inspect} did change to #{after}"
222
+ error = "#{expression.inspect} changed"
221
223
  error = "#{message}.\n#{error}" if message
222
- assert before == after, error
224
+
225
+ if before.nil?
226
+ assert_nil after, error
227
+ else
228
+ assert_equal before, after, error
229
+ end
223
230
 
224
231
  retval
225
232
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "drb"
4
+ require "drb/unix" unless Gem.win_platform?
5
+
6
+ module ActiveSupport
7
+ module Testing
8
+ class Parallelization # :nodoc:
9
+ class Server
10
+ include DRb::DRbUndumped
11
+
12
+ def initialize
13
+ @queue = Queue.new
14
+ @active_workers = Concurrent::Map.new
15
+ @in_flight = Concurrent::Map.new
16
+ end
17
+
18
+ def record(reporter, result)
19
+ raise DRb::DRbConnError if result.is_a?(DRb::DRbUnknown)
20
+
21
+ @in_flight.delete([result.klass, result.name])
22
+
23
+ reporter.synchronize do
24
+ reporter.record(result)
25
+ end
26
+ end
27
+
28
+ def <<(o)
29
+ o[2] = DRbObject.new(o[2]) if o
30
+ @queue << o
31
+ end
32
+
33
+ def pop
34
+ if test = @queue.pop
35
+ @in_flight[[test[0].to_s, test[1]]] = test
36
+ test
37
+ end
38
+ end
39
+
40
+ def start_worker(worker_id)
41
+ @active_workers[worker_id] = true
42
+ end
43
+
44
+ def stop_worker(worker_id)
45
+ @active_workers.delete(worker_id)
46
+ end
47
+
48
+ def active_workers?
49
+ @active_workers.size > 0
50
+ end
51
+
52
+ def shutdown
53
+ # Wait for initial queue to drain
54
+ while @queue.length != 0
55
+ sleep 0.1
56
+ end
57
+
58
+ @queue.close
59
+
60
+ # Wait until all workers have finished
61
+ while active_workers?
62
+ sleep 0.1
63
+ end
64
+
65
+ @in_flight.values.each do |(klass, name, reporter)|
66
+ result = Minitest::Result.from(klass.new(name))
67
+ error = RuntimeError.new("result not reported")
68
+ error.set_backtrace([""])
69
+ result.failures << Minitest::UnexpectedError.new(error)
70
+ reporter.synchronize do
71
+ reporter.record(result)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module Testing
5
+ class Parallelization # :nodoc:
6
+ class Worker
7
+ def initialize(number, url)
8
+ @id = SecureRandom.uuid
9
+ @number = number
10
+ @url = url
11
+ @setup_exception = nil
12
+ end
13
+
14
+ def start
15
+ fork do
16
+ set_process_title("(starting)")
17
+
18
+ DRb.stop_service
19
+
20
+ @queue = DRbObject.new_with_uri(@url)
21
+ @queue.start_worker(@id)
22
+
23
+ begin
24
+ after_fork
25
+ rescue => @setup_exception; end
26
+
27
+ work_from_queue
28
+ ensure
29
+ set_process_title("(stopping)")
30
+
31
+ run_cleanup
32
+ @queue.stop_worker(@id)
33
+ end
34
+ end
35
+
36
+ def work_from_queue
37
+ while job = @queue.pop
38
+ perform_job(job)
39
+ end
40
+ end
41
+
42
+ def perform_job(job)
43
+ klass = job[0]
44
+ method = job[1]
45
+ reporter = job[2]
46
+
47
+ set_process_title("#{klass}##{method}")
48
+
49
+ result = klass.with_info_handler reporter do
50
+ Minitest.run_one_method(klass, method)
51
+ end
52
+
53
+ safe_record(reporter, result)
54
+ end
55
+
56
+ def safe_record(reporter, result)
57
+ add_setup_exception(result) if @setup_exception
58
+
59
+ begin
60
+ @queue.record(reporter, result)
61
+ rescue DRb::DRbConnError
62
+ result.failures.map! do |failure|
63
+ if failure.respond_to?(:error)
64
+ # minitest >5.14.0
65
+ error = DRb::DRbRemoteError.new(failure.error)
66
+ else
67
+ error = DRb::DRbRemoteError.new(failure.exception)
68
+ end
69
+ Minitest::UnexpectedError.new(error)
70
+ end
71
+ @queue.record(reporter, result)
72
+ end
73
+
74
+ set_process_title("(idle)")
75
+ end
76
+
77
+ def after_fork
78
+ Parallelization.after_fork_hooks.each do |cb|
79
+ cb.call(@number)
80
+ end
81
+ end
82
+
83
+ def run_cleanup
84
+ Parallelization.run_cleanup_hooks.each do |cb|
85
+ cb.call(@number)
86
+ end
87
+ end
88
+
89
+ private
90
+ def add_setup_exception(result)
91
+ result.failures.prepend Minitest::UnexpectedError.new(@setup_exception)
92
+ end
93
+
94
+ def set_process_title(status)
95
+ Process.setproctitle("Rails test worker #{@number} - #{status}")
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -3,37 +3,12 @@
3
3
  require "drb"
4
4
  require "drb/unix" unless Gem.win_platform?
5
5
  require "active_support/core_ext/module/attribute_accessors"
6
+ require "active_support/testing/parallelization/server"
7
+ require "active_support/testing/parallelization/worker"
6
8
 
7
9
  module ActiveSupport
8
10
  module Testing
9
11
  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
12
  @@after_fork_hooks = []
38
13
 
39
14
  def self.after_fork_hook(&blk)
@@ -50,85 +25,27 @@ module ActiveSupport
50
25
 
51
26
  cattr_reader :run_cleanup_hooks
52
27
 
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
28
+ def initialize(worker_count)
29
+ @worker_count = worker_count
30
+ @queue_server = Server.new
31
+ @worker_pool = []
32
+ @url = DRb.start_service("drbunix:", @queue_server).uri
71
33
  end
72
34
 
73
35
  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
36
+ @worker_pool = @worker_count.times.map do |worker|
37
+ Worker.new(worker, @url).start
112
38
  end
113
39
  end
114
40
 
115
41
  def <<(work)
116
- @queue << work
42
+ @queue_server << work
117
43
  end
118
44
 
119
45
  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
46
+ @queue_server.shutdown
47
+ @worker_pool.each { |pid| Process.waitpid pid }
126
48
  end
127
-
128
- private
129
- def add_setup_exception(result, setup_exception)
130
- result.failures.prepend Minitest::UnexpectedError.new(setup_exception)
131
- end
132
49
  end
133
50
  end
134
51
  end
@@ -6,6 +6,7 @@ require "concurrent/map"
6
6
 
7
7
  module ActiveSupport
8
8
  module Testing
9
+ # Manages stubs for TimeHelpers
9
10
  class SimpleStubs # :nodoc:
10
11
  Stub = Struct.new(:object, :method_name, :original_method)
11
12
 
@@ -13,6 +14,13 @@ module ActiveSupport
13
14
  @stubs = Concurrent::Map.new { |h, k| h[k] = {} }
14
15
  end
15
16
 
17
+ # Stubs object.method_name with the given block
18
+ # If the method is already stubbed, remove that stub
19
+ # so that removing this stub will restore the original implementation.
20
+ # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
21
+ # target = Time.zone.local(2004, 11, 24, 1, 4, 44)
22
+ # simple_stubs.stub_object(Time, :now) { at(target.to_i) }
23
+ # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
16
24
  def stub_object(object, method_name, &block)
17
25
  if stub = stubbing(object, method_name)
18
26
  unstub_object(stub)
@@ -26,6 +34,7 @@ module ActiveSupport
26
34
  object.define_singleton_method(method_name, &block)
27
35
  end
28
36
 
37
+ # Remove all object-method stubs held by this instance
29
38
  def unstub_all!
30
39
  @stubs.each_value do |object_stubs|
31
40
  object_stubs.each_value do |stub|
@@ -35,11 +44,19 @@ module ActiveSupport
35
44
  @stubs.clear
36
45
  end
37
46
 
47
+ # Returns the Stub for object#method_name
48
+ # (nil if it is not stubbed)
38
49
  def stubbing(object, method_name)
39
50
  @stubs[object.object_id][method_name]
40
51
  end
41
52
 
53
+ # Returns true if any stubs are set, false if there are none
54
+ def stubbed?
55
+ !@stubs.empty?
56
+ end
57
+
42
58
  private
59
+ # Restores the original object.method described by the Stub
43
60
  def unstub_object(stub)
44
61
  singleton_class = stub.object.singleton_class
45
62
  singleton_class.silence_redefinition_of_method stub.method_name
@@ -82,7 +99,7 @@ module ActiveSupport
82
99
  # The stubs are automatically removed at the end of the test.
83
100
  #
84
101
  # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
85
- # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44)
102
+ # travel_to Time.zone.local(2004, 11, 24, 1, 4, 44)
86
103
  # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
87
104
  # Date.current # => Wed, 24 Nov 2004
88
105
  # DateTime.current # => Wed, 24 Nov 2004 01:04:44 -0500
@@ -104,7 +121,7 @@ module ActiveSupport
104
121
  # state at the end of the block:
105
122
  #
106
123
  # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
107
- # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44) do
124
+ # travel_to Time.zone.local(2004, 11, 24, 1, 4, 44) do
108
125
  # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
109
126
  # end
110
127
  # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
@@ -160,12 +177,32 @@ module ActiveSupport
160
177
  # +travel+, +travel_to+, and +freeze_time+.
161
178
  #
162
179
  # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
163
- # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44)
180
+ #
181
+ # travel_to Time.zone.local(2004, 11, 24, 1, 4, 44)
164
182
  # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
183
+ #
165
184
  # travel_back
166
185
  # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
186
+ #
187
+ # This method also accepts a block, which brings the stubs back at the end of the block:
188
+ #
189
+ # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
190
+ #
191
+ # travel_to Time.zone.local(2004, 11, 24, 1, 4, 44)
192
+ # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
193
+ #
194
+ # travel_back do
195
+ # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
196
+ # end
197
+ #
198
+ # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
167
199
  def travel_back
200
+ stubbed_time = Time.current if block_given? && simple_stubs.stubbed?
201
+
168
202
  simple_stubs.unstub_all!
203
+ yield if block_given?
204
+ ensure
205
+ travel_to stubbed_time if stubbed_time
169
206
  end
170
207
  alias_method :unfreeze_time, :travel_back
171
208