aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/testing/time_helpers.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support/testing/time_helpers.rb')
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb104
1 files changed, 85 insertions, 19 deletions
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index fca0947c5b..998a51a34c 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -1,39 +1,50 @@
+# frozen_string_literal: true
+
+require "active_support/core_ext/module/redefine_method"
+require "active_support/core_ext/string/strip" # for strip_heredoc
+require "active_support/core_ext/time/calculations"
+require "concurrent/map"
+
module ActiveSupport
module Testing
class SimpleStubs # :nodoc:
Stub = Struct.new(:object, :method_name, :original_method)
def initialize
- @stubs = {}
+ @stubs = Concurrent::Map.new { |h, k| h[k] = {} }
end
- def stub_object(object, method_name, return_value)
- key = [object.object_id, method_name]
-
- if stub = @stubs[key]
+ def stub_object(object, method_name, &block)
+ if stub = stubbing(object, method_name)
unstub_object(stub)
end
new_name = "__simple_stub__#{method_name}"
- @stubs[key] = Stub.new(object, method_name, new_name)
+ @stubs[object.object_id][method_name] = Stub.new(object, method_name, new_name)
object.singleton_class.send :alias_method, new_name, method_name
- object.define_singleton_method(method_name) { return_value }
+ object.define_singleton_method(method_name, &block)
end
def unstub_all!
- @stubs.each_value do |stub|
- unstub_object(stub)
+ @stubs.each_value do |object_stubs|
+ object_stubs.each_value do |stub|
+ unstub_object(stub)
+ end
end
- @stubs = {}
+ @stubs.clear
+ end
+
+ def stubbing(object, method_name)
+ @stubs[object.object_id][method_name]
end
private
def unstub_object(stub)
singleton_class = stub.object.singleton_class
- singleton_class.send :undef_method, stub.method_name
+ singleton_class.send :silence_redefinition_of_method, stub.method_name
singleton_class.send :alias_method, stub.method_name, stub.original_method
singleton_class.send :undef_method, stub.original_method
end
@@ -41,8 +52,14 @@ module ActiveSupport
# Contains helpers that help you test passage of time.
module TimeHelpers
+ def after_teardown
+ travel_back
+ super
+ end
+
# Changes current time to the time in the future or in the past by a given time difference by
- # stubbing +Time.now+, +Date.today+, and +DateTime.now+.
+ # stubbing +Time.now+, +Date.today+, and +DateTime.now+. The stubs are automatically removed
+ # at the end of the test.
#
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
# travel 1.day
@@ -64,9 +81,10 @@ module ActiveSupport
# Changes current time to the given time by stubbing +Time.now+,
# +Date.today+, and +DateTime.now+ to return the time or date passed into this method.
+ # The stubs are automatically removed at the end of the test.
#
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
- # travel_to Time.new(2004, 11, 24, 01, 04, 44)
+ # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44)
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
# Date.current # => Wed, 24 Nov 2004
# DateTime.current # => Wed, 24 Nov 2004 01:04:44 -0500
@@ -88,20 +106,48 @@ module ActiveSupport
# state at the end of the block:
#
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
- # travel_to Time.new(2004, 11, 24, 01, 04, 44) do
+ # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44) do
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
# end
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
def travel_to(date_or_time)
+ if block_given? && simple_stubs.stubbing(Time, :now)
+ travel_to_nested_block_call = <<-MSG.strip_heredoc
+
+ Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing.
+
+ Instead of:
+
+ travel_to 2.days.from_now do
+ # 2 days from today
+ travel_to 3.days.from_now do
+ # 5 days from today
+ end
+ end
+
+ preferred way to achieve above is:
+
+ travel 2.days do
+ # 2 days from today
+ end
+
+ travel 5.days do
+ # 5 days from today
+ end
+
+ MSG
+ raise travel_to_nested_block_call
+ end
+
if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime)
now = date_or_time.midnight.to_time
else
now = date_or_time.to_time.change(usec: 0)
end
- simple_stubs.stub_object(Time, :now, now)
- simple_stubs.stub_object(Date, :today, now.to_date)
- simple_stubs.stub_object(DateTime, :now, now.to_datetime)
+ simple_stubs.stub_object(Time, :now) { at(now.to_i) }
+ simple_stubs.stub_object(Date, :today) { jd(now.to_date.jd) }
+ simple_stubs.stub_object(DateTime, :now) { jd(now.to_date.jd, now.hour, now.min, now.sec, Rational(now.utc_offset, 86400)) }
if block_given?
begin
@@ -113,10 +159,10 @@ module ActiveSupport
end
# Returns the current time back to its original state, by removing the stubs added by
- # `travel` and `travel_to`.
+ # +travel+ and +travel_to+.
#
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
- # travel_to Time.new(2004, 11, 24, 01, 04, 44)
+ # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44)
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
# travel_back
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
@@ -124,6 +170,26 @@ module ActiveSupport
simple_stubs.unstub_all!
end
+ # Calls +travel_to+ with +Time.now+.
+ #
+ # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
+ # freeze_time
+ # sleep(1)
+ # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
+ #
+ # This method also accepts a block, which will return the current time back to its original
+ # state at the end of the block:
+ #
+ # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
+ # freeze_time do
+ # sleep(1)
+ # User.create.created_at # => Sun, 09 Jul 2017 15:34:49 EST -05:00
+ # end
+ # Time.current # => Sun, 09 Jul 2017 15:34:50 EST -05:00
+ def freeze_time(&block)
+ travel_to Time.now, &block
+ end
+
private
def simple_stubs