timecop 0.8.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.markdown +8 -2
- data/lib/timecop/time_extensions.rb +39 -26
- data/lib/timecop/time_stack_item.rb +10 -7
- data/lib/timecop/timecop.rb +73 -18
- data/lib/timecop/version.rb +1 -1
- data/test/test_helper.rb +4 -0
- data/test/time_stack_item_test.rb +10 -0
- data/test/timecop_test.rb +38 -1
- data/test/timecop_without_date_test.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 580525a47a6026f3d83689807d96949dd5269fa5
|
4
|
+
data.tar.gz: ffb9cc8cf58489f231f3348656f99597b3c79989
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc3c024856bf2bbaab09a0734aa1d87712e26c8b1e47774edf2d65153ceb22ae6bbe3b04499a4f12c68acf5f9352fb26e614c8c4996c80e14353f75049899598
|
7
|
+
data.tar.gz: c759c9cc704c61d2bd1cf13b4d44a6ad8a1b36a03fc398b4796f1dd1bc0d055a6ddda86aa0a31addc753eb6accc57049648d3fd5334477ccb0cc8cbc594076b7
|
data/README.markdown
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# timecop
|
2
2
|
|
3
|
-
[![Build Status](https://secure.travis-ci.org/travisjeffery/timecop.
|
3
|
+
[![Build Status](https://secure.travis-ci.org/travisjeffery/timecop.svg)](http://travis-ci.org/travisjeffery/timecop)
|
4
4
|
|
5
5
|
## DESCRIPTION
|
6
6
|
|
@@ -104,7 +104,7 @@ being able to simulate activity via subsequent calls to your application.
|
|
104
104
|
Timecop.scale(3600)
|
105
105
|
Time.now
|
106
106
|
# => 2012-09-20 21:23:25 -0500
|
107
|
-
# seconds later, hours have
|
107
|
+
# seconds later, hours have passed and it's gone from 9pm at night to 6am in the morning
|
108
108
|
Time.now
|
109
109
|
# => 2012-09-21 06:22:59 -0500
|
110
110
|
```
|
@@ -128,6 +128,12 @@ Timecop.freeze
|
|
128
128
|
# => Timecop::SafeModeException: Safe mode is enabled, only calls passing a block are allowed.
|
129
129
|
```
|
130
130
|
|
131
|
+
### Rails v Ruby Date/Time libraries
|
132
|
+
|
133
|
+
Sometimes [Rails Date/Time methods don't play nicely with Ruby Date/Time methods.](https://rails.lighthouseapp.com/projects/8994/tickets/6410-dateyesterday-datetoday)
|
134
|
+
|
135
|
+
Be careful mixing Ruby `Date.today` with Rails `Date.tomorrow` / `Date.yesterday` as things might break.
|
136
|
+
|
131
137
|
## Contribute
|
132
138
|
|
133
139
|
timecop is maintained by [travisjeffery](http://github.com/travisjeffery), and
|
@@ -27,19 +27,8 @@ class Time #:nodoc:
|
|
27
27
|
end
|
28
28
|
|
29
29
|
class Date #:nodoc:
|
30
|
-
WEEKDAYS = {
|
31
|
-
"sunday" => 0,
|
32
|
-
"monday" => 1,
|
33
|
-
"tuesday" => 2,
|
34
|
-
"wednesday" => 3,
|
35
|
-
"thursday" => 4,
|
36
|
-
"friday" => 5,
|
37
|
-
"saturday" => 6
|
38
|
-
}
|
39
|
-
|
40
30
|
class << self
|
41
31
|
def mock_date
|
42
|
-
mocked_time_stack_item = Timecop.top_stack_item
|
43
32
|
mocked_time_stack_item.nil? ? nil : mocked_time_stack_item.date(self)
|
44
33
|
end
|
45
34
|
|
@@ -65,26 +54,41 @@ class Date #:nodoc:
|
|
65
54
|
alias_method :strptime, :strptime_with_mock_date
|
66
55
|
|
67
56
|
def parse_with_mock_date(*args)
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
57
|
+
parsed_date = parse_without_mock_date(*args)
|
58
|
+
return parsed_date unless mocked_time_stack_item
|
59
|
+
date_hash = Date._parse(*args)
|
60
|
+
|
61
|
+
case
|
62
|
+
when date_hash[:year] && date_hash[:mon] && date_hash[:mday]
|
63
|
+
parsed_date
|
64
|
+
when date_hash[:mon] && date_hash[:mday]
|
65
|
+
Date.new(mocked_time_stack_item.year, date_hash[:mon], date_hash[:mday])
|
66
|
+
when date_hash[:wday]
|
67
|
+
closest_wday(date_hash[:wday])
|
73
68
|
else
|
74
|
-
|
69
|
+
parsed_date + mocked_time_stack_item.travel_offset_days
|
75
70
|
end
|
76
71
|
end
|
77
72
|
|
78
73
|
alias_method :parse_without_mock_date, :parse
|
79
74
|
alias_method :parse, :parse_with_mock_date
|
80
75
|
|
76
|
+
def mocked_time_stack_item
|
77
|
+
Timecop.top_stack_item
|
78
|
+
end
|
79
|
+
|
80
|
+
def closest_wday(wday)
|
81
|
+
today = Date.today
|
82
|
+
result = today - today.wday
|
83
|
+
result += 1 until wday == result.wday
|
84
|
+
result
|
85
|
+
end
|
81
86
|
end
|
82
87
|
end
|
83
88
|
|
84
89
|
class DateTime #:nodoc:
|
85
90
|
class << self
|
86
91
|
def mock_time
|
87
|
-
mocked_time_stack_item = Timecop.top_stack_item
|
88
92
|
mocked_time_stack_item.nil? ? nil : mocked_time_stack_item.datetime(self)
|
89
93
|
end
|
90
94
|
|
@@ -97,19 +101,28 @@ class DateTime #:nodoc:
|
|
97
101
|
alias_method :now, :now_with_mock_time
|
98
102
|
|
99
103
|
def parse_with_mock_date(*args)
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
104
|
+
date_hash = Date._parse(*args)
|
105
|
+
parsed_date = parse_without_mock_date(*args)
|
106
|
+
return parsed_date unless mocked_time_stack_item
|
107
|
+
date_hash = DateTime._parse(*args)
|
108
|
+
|
109
|
+
case
|
110
|
+
when date_hash[:year] && date_hash[:mon] && date_hash[:mday]
|
111
|
+
parsed_date
|
112
|
+
when date_hash[:mon] && date_hash[:mday]
|
113
|
+
DateTime.new(mocked_time_stack_item.year, date_hash[:mon], date_hash[:mday])
|
114
|
+
when date_hash[:wday]
|
115
|
+
Date.closest_wday(date_hash[:wday]).to_datetime
|
107
116
|
else
|
108
|
-
|
117
|
+
parsed_date + mocked_time_stack_item.travel_offset_days
|
109
118
|
end
|
110
119
|
end
|
111
120
|
|
112
121
|
alias_method :parse_without_mock_date, :parse
|
113
122
|
alias_method :parse, :parse_with_mock_date
|
123
|
+
|
124
|
+
def mocked_time_stack_item
|
125
|
+
Timecop.top_stack_item
|
126
|
+
end
|
114
127
|
end
|
115
128
|
end
|
@@ -43,7 +43,11 @@ class Timecop
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def travel_offset
|
46
|
-
@travel_offset
|
46
|
+
@travel_offset unless mock_type == :freeze
|
47
|
+
end
|
48
|
+
|
49
|
+
def travel_offset_days
|
50
|
+
(@travel_offset / 60 / 60 / 24).round
|
47
51
|
end
|
48
52
|
|
49
53
|
def scaling_factor
|
@@ -52,7 +56,7 @@ class Timecop
|
|
52
56
|
|
53
57
|
def time(time_klass = Time) #:nodoc:
|
54
58
|
if @time.respond_to?(:in_time_zone)
|
55
|
-
time = time_klass.at(@time.dup.
|
59
|
+
time = time_klass.at(@time.dup.localtime)
|
56
60
|
else
|
57
61
|
time = time_klass.at(@time)
|
58
62
|
end
|
@@ -60,7 +64,7 @@ class Timecop
|
|
60
64
|
if travel_offset.nil?
|
61
65
|
time
|
62
66
|
elsif scaling_factor.nil?
|
63
|
-
time_klass.at(
|
67
|
+
time_klass.at(Time.now_without_mock_time + travel_offset)
|
64
68
|
else
|
65
69
|
time_klass.at(scaled_time)
|
66
70
|
end
|
@@ -102,12 +106,12 @@ class Timecop
|
|
102
106
|
elsif Object.const_defined?(:Date) && arg.is_a?(Date)
|
103
107
|
time_klass.local(arg.year, arg.month, arg.day, 0, 0, 0)
|
104
108
|
elsif args.empty? && (arg.kind_of?(Integer) || arg.kind_of?(Float))
|
105
|
-
|
109
|
+
time_klass.now + arg
|
106
110
|
elsif arg.nil?
|
107
|
-
|
111
|
+
time_klass.now
|
108
112
|
else
|
109
113
|
if arg.is_a?(String) && Time.respond_to?(:parse)
|
110
|
-
|
114
|
+
time_klass.parse(arg)
|
111
115
|
else
|
112
116
|
# we'll just assume it's a list of y/m/d/h/m/s
|
113
117
|
year = arg || 2000
|
@@ -122,7 +126,6 @@ class Timecop
|
|
122
126
|
end
|
123
127
|
|
124
128
|
def compute_travel_offset
|
125
|
-
return nil if mock_type == :freeze
|
126
129
|
time - Time.now_without_mock_time
|
127
130
|
end
|
128
131
|
|
data/lib/timecop/timecop.rb
CHANGED
@@ -100,7 +100,7 @@ class Timecop
|
|
100
100
|
end
|
101
101
|
|
102
102
|
def top_stack_item #:nodoc:
|
103
|
-
instance.
|
103
|
+
instance.send(:stack).last
|
104
104
|
end
|
105
105
|
|
106
106
|
def safe_mode=(safe)
|
@@ -111,9 +111,17 @@ class Timecop
|
|
111
111
|
@safe_mode ||= false
|
112
112
|
end
|
113
113
|
|
114
|
+
def thread_safe=(t)
|
115
|
+
instance.send(:thread_safe=, t)
|
116
|
+
end
|
117
|
+
|
118
|
+
def thread_safe
|
119
|
+
instance.send(:thread_safe)
|
120
|
+
end
|
121
|
+
|
114
122
|
# Returns whether or not Timecop is currently frozen/travelled
|
115
123
|
def frozen?
|
116
|
-
!instance.
|
124
|
+
!instance.send(:stack).empty?
|
117
125
|
end
|
118
126
|
|
119
127
|
private
|
@@ -125,50 +133,97 @@ class Timecop
|
|
125
133
|
|
126
134
|
private
|
127
135
|
|
128
|
-
def baseline=(
|
129
|
-
|
130
|
-
|
136
|
+
def baseline=(b)
|
137
|
+
set_baseline(b)
|
138
|
+
stack << TimeStackItem.new(:travel, b)
|
139
|
+
end
|
140
|
+
|
141
|
+
def baseline
|
142
|
+
if @thread_safe
|
143
|
+
Thread.current[:timecop_baseline]
|
144
|
+
else
|
145
|
+
@baseline
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def set_baseline(b)
|
150
|
+
if @thread_safe
|
151
|
+
Thread.current[:timecop_baseline] = b
|
152
|
+
else
|
153
|
+
@baseline = b
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def stack
|
158
|
+
if @thread_safe
|
159
|
+
Thread.current[:timecop_stack] ||= []
|
160
|
+
Thread.current[:timecop_stack]
|
161
|
+
else
|
162
|
+
@stack
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def set_stack(s)
|
167
|
+
if @thread_safe
|
168
|
+
Thread.current[:timecop_stack] = s
|
169
|
+
else
|
170
|
+
@stack = s
|
171
|
+
end
|
131
172
|
end
|
132
173
|
|
133
174
|
def initialize #:nodoc:
|
134
|
-
@
|
175
|
+
@stack = []
|
176
|
+
@safe = nil
|
177
|
+
@thread_safe = false
|
178
|
+
end
|
179
|
+
|
180
|
+
def thread_safe=(t)
|
181
|
+
initialize
|
182
|
+
@thread_safe = t
|
183
|
+
end
|
184
|
+
|
185
|
+
def thread_safe
|
186
|
+
@thread_safe
|
135
187
|
end
|
136
188
|
|
137
189
|
def travel(mock_type, *args, &block) #:nodoc:
|
138
|
-
raise SafeModeException if Timecop.safe_mode? && !block_given?
|
190
|
+
raise SafeModeException if Timecop.safe_mode? && !block_given? && !@safe
|
139
191
|
|
140
192
|
stack_item = TimeStackItem.new(mock_type, *args)
|
141
193
|
|
142
|
-
stack_backup =
|
143
|
-
|
194
|
+
stack_backup = stack.dup
|
195
|
+
stack << stack_item
|
144
196
|
|
145
197
|
if block_given?
|
198
|
+
safe_backup = @safe
|
199
|
+
@safe = true
|
146
200
|
begin
|
147
201
|
yield stack_item.time
|
148
202
|
ensure
|
149
|
-
@
|
203
|
+
@stack.replace stack_backup
|
204
|
+
@safe = safe_backup
|
150
205
|
end
|
151
206
|
end
|
152
207
|
end
|
153
208
|
|
154
209
|
def return(&block)
|
155
|
-
current_stack =
|
156
|
-
current_baseline =
|
210
|
+
current_stack = stack
|
211
|
+
current_baseline = baseline
|
157
212
|
unmock!
|
158
213
|
yield
|
159
214
|
ensure
|
160
|
-
|
161
|
-
|
215
|
+
set_stack current_stack
|
216
|
+
set_baseline current_baseline
|
162
217
|
end
|
163
218
|
|
164
219
|
def unmock! #:nodoc:
|
165
|
-
|
166
|
-
|
220
|
+
set_baseline nil
|
221
|
+
set_stack []
|
167
222
|
end
|
168
223
|
|
169
224
|
def return_to_baseline
|
170
|
-
if
|
171
|
-
|
225
|
+
if baseline
|
226
|
+
set_stack [stack.shift]
|
172
227
|
else
|
173
228
|
unmock!
|
174
229
|
end
|
data/lib/timecop/version.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'date'
|
2
2
|
require_relative "test_helper"
|
3
3
|
require 'timecop'
|
4
|
+
|
4
5
|
require 'active_support/all'
|
5
6
|
|
6
7
|
class TestTimeStackItem < Minitest::Test
|
@@ -191,6 +192,15 @@ class TestTimeStackItem < Minitest::Test
|
|
191
192
|
end
|
192
193
|
end
|
193
194
|
|
195
|
+
def test_timezones_with_parsed_string
|
196
|
+
Time.zone = "Europe/Zurich"
|
197
|
+
time_string = "2012-12-27 12:12"
|
198
|
+
expected_time = Time.zone.parse(time_string)
|
199
|
+
Timecop.freeze(time_string) do |frozen_time|
|
200
|
+
assert_equal expected_time, frozen_time
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
194
204
|
def test_timezones_apply_dates
|
195
205
|
Time.zone = "Central Time (US & Canada)"
|
196
206
|
time = Time.zone.local(2013,1,3)
|
data/test/timecop_test.rb
CHANGED
@@ -251,7 +251,7 @@ class TestTimecop < Minitest::Test
|
|
251
251
|
new_now = Time.now
|
252
252
|
assert_times_effectively_equal(new_now, t, 1, "Looks like we failed to actually travel time")
|
253
253
|
sleep(0.25)
|
254
|
-
assert_times_effectively_not_equal new_now, Time.now, 0.
|
254
|
+
assert_times_effectively_not_equal new_now, Time.now, 0.24, "Looks like time is not moving"
|
255
255
|
end
|
256
256
|
end
|
257
257
|
|
@@ -495,6 +495,18 @@ class TestTimecop < Minitest::Test
|
|
495
495
|
end
|
496
496
|
end
|
497
497
|
|
498
|
+
def test_raises_when_safe_mode_and_no_block_though_previously_block_given
|
499
|
+
Timecop.freeze do
|
500
|
+
Timecop.freeze
|
501
|
+
end
|
502
|
+
|
503
|
+
with_safe_mode do
|
504
|
+
assert_raises Timecop::SafeModeException do
|
505
|
+
Timecop.freeze
|
506
|
+
end
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
498
510
|
def test_no_raise_when_safe_mode_and_block_used
|
499
511
|
with_safe_mode do
|
500
512
|
Timecop.freeze {}
|
@@ -507,6 +519,14 @@ class TestTimecop < Minitest::Test
|
|
507
519
|
end
|
508
520
|
end
|
509
521
|
|
522
|
+
def test_no_raise_when_safe_mode_and_no_block_and_in_block_context
|
523
|
+
with_safe_mode do
|
524
|
+
Timecop.freeze do
|
525
|
+
Timecop.freeze
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
510
530
|
def test_date_strptime_without_year
|
511
531
|
Timecop.freeze(Time.new(1984,2,28)) do
|
512
532
|
assert_equal Date.strptime('04-14', '%m-%d'), Date.new(1984, 4, 14)
|
@@ -536,6 +556,23 @@ class TestTimecop < Minitest::Test
|
|
536
556
|
assert !Timecop.frozen?
|
537
557
|
end
|
538
558
|
|
559
|
+
def test_thread_safe_timecop
|
560
|
+
Timecop.thread_safe = true
|
561
|
+
date = Time.local(2011, 01, 02)
|
562
|
+
thread = Thread.new do
|
563
|
+
Timecop.freeze(date) do
|
564
|
+
sleep 1 #give main thread time to run
|
565
|
+
assert_equal date, Time.now
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
sleep 0.25
|
570
|
+
assert Time.now != date
|
571
|
+
thread.join
|
572
|
+
ensure
|
573
|
+
Timecop.thread_safe = false
|
574
|
+
end
|
575
|
+
|
539
576
|
private
|
540
577
|
|
541
578
|
def with_safe_mode(enabled=true)
|
@@ -70,7 +70,7 @@ class TestTimecopWithoutDate < Minitest::Test
|
|
70
70
|
new_now = Time.now
|
71
71
|
assert_times_effectively_equal new_now, t, 1, "Looks like we failed to actually travel time" # 0.1 seconds
|
72
72
|
sleep(0.25)
|
73
|
-
assert_times_effectively_not_equal new_now, Time.now, 0.
|
73
|
+
assert_times_effectively_not_equal new_now, Time.now, 0.24, "Looks like time is not moving"
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timecop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Travis Jeffery
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2017-06-22 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: A gem providing "time travel" and "time freezing" capabilities, making
|
15
15
|
it dead simple to test time-dependent code. It provides a unified method to mock
|