timecop 0.6.1 → 0.9.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +1 -1
- data/README.markdown +31 -16
- data/Rakefile +10 -4
- data/lib/timecop/time_extensions.rb +120 -47
- data/lib/timecop/time_stack_item.rb +109 -127
- data/lib/timecop/timecop.rb +104 -24
- data/lib/timecop/version.rb +1 -1
- data/test/test_helper.rb +26 -21
- data/test/time_stack_item_test.rb +98 -74
- data/test/timecop_test.rb +215 -31
- data/test/timecop_without_date_but_with_time_test.rb +4 -8
- data/test/timecop_without_date_test.rb +22 -23
- metadata +13 -17
- data/test/run_tests.sh +0 -6
data/lib/timecop/timecop.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'singleton'
|
2
|
-
require File.join(File.dirname(__FILE__), "time_extensions")
|
3
2
|
require File.join(File.dirname(__FILE__), "time_stack_item")
|
4
3
|
|
5
4
|
# Timecop
|
@@ -14,7 +13,7 @@ class Timecop
|
|
14
13
|
include Singleton
|
15
14
|
|
16
15
|
class << self
|
17
|
-
|
16
|
+
private :instance
|
18
17
|
|
19
18
|
# Allows you to run a block of code and "fake" a time throughout the execution of that block.
|
20
19
|
# This is particularly useful for writing test methods where the passage of time is critical to the business
|
@@ -33,6 +32,7 @@ class Timecop
|
|
33
32
|
# 3. Timecop.freeze(date_inst)
|
34
33
|
# 4. Timecop.freeze(offset_in_seconds)
|
35
34
|
# 5. Timecop.freeze(year, month, day, hour=0, minute=0, second=0)
|
35
|
+
# 6. Timecop.freeze() # Defaults to Time.now
|
36
36
|
#
|
37
37
|
# When a block is also passed, Time.now, DateTime.now and Date.today are all reset to their
|
38
38
|
# previous values after the block has finished executing. This allows us to nest multiple
|
@@ -76,11 +76,11 @@ class Timecop
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def baseline
|
79
|
-
instance.
|
79
|
+
instance.baseline
|
80
80
|
end
|
81
81
|
|
82
82
|
def baseline=(baseline)
|
83
|
-
instance.
|
83
|
+
instance.baseline = baseline
|
84
84
|
end
|
85
85
|
|
86
86
|
# Reverts back to system's Time.now, Date.today and DateTime.now (if it exists) permamently when
|
@@ -88,73 +88,153 @@ class Timecop
|
|
88
88
|
# the given block.
|
89
89
|
def return(&block)
|
90
90
|
if block_given?
|
91
|
-
instance.
|
91
|
+
instance.return(&block)
|
92
92
|
else
|
93
|
-
instance.
|
93
|
+
instance.unmock!
|
94
94
|
nil
|
95
95
|
end
|
96
96
|
end
|
97
|
+
alias :unfreeze :return
|
97
98
|
|
98
99
|
def return_to_baseline
|
99
|
-
instance.
|
100
|
+
instance.return_to_baseline
|
100
101
|
Time.now
|
101
102
|
end
|
102
103
|
|
103
104
|
def top_stack_item #:nodoc:
|
104
|
-
instance.
|
105
|
+
instance.stack.last
|
106
|
+
end
|
107
|
+
|
108
|
+
def safe_mode=(safe)
|
109
|
+
@safe_mode = safe
|
110
|
+
end
|
111
|
+
|
112
|
+
def safe_mode?
|
113
|
+
@safe_mode ||= false
|
114
|
+
end
|
115
|
+
|
116
|
+
def thread_safe=(t)
|
117
|
+
instance.thread_safe = t
|
118
|
+
end
|
119
|
+
|
120
|
+
def thread_safe
|
121
|
+
instance.thread_safe
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns whether or not Timecop is currently frozen/travelled
|
125
|
+
def frozen?
|
126
|
+
!instance.stack.empty?
|
105
127
|
end
|
106
128
|
|
107
129
|
private
|
108
130
|
def send_travel(mock_type, *args, &block)
|
109
|
-
val = instance.
|
131
|
+
val = instance.travel(mock_type, *args, &block)
|
110
132
|
block_given? ? val : Time.now
|
111
133
|
end
|
112
134
|
end
|
113
135
|
|
114
|
-
|
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
|
115
165
|
|
116
|
-
def
|
117
|
-
@
|
118
|
-
|
166
|
+
def set_stack(s)
|
167
|
+
if @thread_safe
|
168
|
+
Thread.current[:timecop_stack] = s
|
169
|
+
else
|
170
|
+
@stack = s
|
171
|
+
end
|
119
172
|
end
|
120
173
|
|
121
174
|
def initialize #:nodoc:
|
122
|
-
@
|
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
|
123
187
|
end
|
124
188
|
|
125
189
|
def travel(mock_type, *args, &block) #:nodoc:
|
190
|
+
raise SafeModeException if Timecop.safe_mode? && !block_given? && !@safe
|
191
|
+
|
126
192
|
stack_item = TimeStackItem.new(mock_type, *args)
|
127
193
|
|
128
|
-
|
194
|
+
stack_backup = stack.dup
|
195
|
+
stack << stack_item
|
129
196
|
|
130
197
|
if block_given?
|
198
|
+
safe_backup = @safe
|
199
|
+
@safe = true
|
131
200
|
begin
|
132
201
|
yield stack_item.time
|
133
202
|
ensure
|
134
|
-
|
203
|
+
stack.replace stack_backup
|
204
|
+
@safe = safe_backup
|
135
205
|
end
|
136
206
|
end
|
137
207
|
end
|
138
208
|
|
139
209
|
def return(&block)
|
140
|
-
current_stack =
|
141
|
-
current_baseline =
|
210
|
+
current_stack = stack
|
211
|
+
current_baseline = baseline
|
142
212
|
unmock!
|
143
213
|
yield
|
144
|
-
|
145
|
-
|
214
|
+
ensure
|
215
|
+
set_stack current_stack
|
216
|
+
set_baseline current_baseline
|
146
217
|
end
|
147
218
|
|
148
219
|
def unmock! #:nodoc:
|
149
|
-
|
150
|
-
|
220
|
+
set_baseline nil
|
221
|
+
set_stack []
|
151
222
|
end
|
152
223
|
|
153
224
|
def return_to_baseline
|
154
|
-
if
|
155
|
-
|
225
|
+
if baseline
|
226
|
+
set_stack [stack.shift]
|
156
227
|
else
|
157
228
|
unmock!
|
158
229
|
end
|
159
230
|
end
|
231
|
+
|
232
|
+
class SafeModeException < StandardError
|
233
|
+
def initialize
|
234
|
+
super "Safe mode is enabled, only calls passing a block are allowed."
|
235
|
+
end
|
236
|
+
end
|
160
237
|
end
|
238
|
+
|
239
|
+
# This must be done after TimeCop is available
|
240
|
+
require File.join(File.dirname(__FILE__), "time_extensions")
|
data/lib/timecop/version.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -1,38 +1,39 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'bundler/setup'
|
3
|
-
require '
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
end
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'minitest/rg'
|
4
|
+
require 'pry'
|
5
|
+
|
6
|
+
$VERBOSE = true # enable ruby warnings
|
9
7
|
|
10
|
-
|
8
|
+
require 'mocha/minitest'
|
11
9
|
|
10
|
+
class Minitest::Test
|
12
11
|
private
|
13
12
|
# Tests to see that two times are within the given distance,
|
14
13
|
# in seconds, from each other.
|
15
14
|
def times_effectively_equal(time1, time2, seconds_interval = 1)
|
16
15
|
(time1 - time2).abs <= seconds_interval
|
17
16
|
end
|
18
|
-
|
17
|
+
|
19
18
|
def assert_times_effectively_equal(time1, time2, seconds_interval = 1, msg = nil)
|
20
19
|
assert times_effectively_equal(time1, time2, seconds_interval), "#{msg}: time1 = #{time1.to_s}, time2 = #{time2.to_s}"
|
21
20
|
end
|
22
|
-
|
21
|
+
|
23
22
|
def assert_times_effectively_not_equal(time1, time2, seconds_interval = 1, msg = nil)
|
24
23
|
assert !times_effectively_equal(time1, time2, seconds_interval), "#{msg}: time1 = #{time1.to_s}, time2 = #{time2.to_s}"
|
25
24
|
end
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
|
26
|
+
# Gets the local offset (supplied by ENV['TZ'] or your computer's clock)
|
27
|
+
# At the given timestamp, or Time.now if not time is given.
|
28
|
+
def local_offset(time = Time.now)
|
29
|
+
Time.at(time.to_i).to_datetime.offset
|
29
30
|
end
|
30
|
-
|
31
|
-
TIMEZONES = ["Europe/Paris", "UTC", "
|
32
|
-
|
31
|
+
|
32
|
+
TIMEZONES = ["Pacific/Midway", "Europe/Paris", "UTC", "America/Chicago"]
|
33
|
+
|
33
34
|
def each_timezone
|
34
35
|
old_tz = ENV["TZ"]
|
35
|
-
|
36
|
+
|
36
37
|
begin
|
37
38
|
TIMEZONES.each do |timezone|
|
38
39
|
ENV["TZ"] = timezone
|
@@ -42,13 +43,17 @@ class Test::Unit::TestCase
|
|
42
43
|
ENV["TZ"] = old_tz
|
43
44
|
end
|
44
45
|
end
|
45
|
-
|
46
|
+
|
46
47
|
def a_time_stack_item
|
47
48
|
Timecop::TimeStackItem.new(:freeze, 2008, 1, 1, 0, 0, 0)
|
48
49
|
end
|
49
|
-
|
50
|
+
|
50
51
|
def assert_date_times_equal(dt1, dt2)
|
51
|
-
|
52
|
+
assert_in_delta dt1.to_time.to_f, dt2.to_time.to_f, 0.01, "Failed for timezone: #{ENV['TZ']}: #{dt1.to_s} not equal to #{dt2.to_s}"
|
52
53
|
end
|
53
|
-
|
54
|
+
|
55
|
+
def jruby?
|
56
|
+
RUBY_PLATFORM == "java"
|
57
|
+
end
|
58
|
+
|
54
59
|
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
require 'date'
|
2
|
-
|
3
|
-
require
|
2
|
+
require_relative "test_helper"
|
3
|
+
require 'timecop'
|
4
4
|
|
5
|
-
|
5
|
+
require 'active_support/all'
|
6
|
+
|
7
|
+
class TestTimeStackItem < Minitest::Test
|
6
8
|
def teardown
|
7
|
-
Timecop.active_support = nil
|
8
9
|
Timecop.return
|
10
|
+
Time.zone = nil
|
9
11
|
end
|
10
12
|
|
11
13
|
def test_new_with_time
|
@@ -85,6 +87,19 @@ class TestTimeStackItem < Test::Unit::TestCase
|
|
85
87
|
assert_equal s, stack_item.sec
|
86
88
|
end
|
87
89
|
|
90
|
+
def test_new_with_float
|
91
|
+
t = Time.now
|
92
|
+
y, m, d, h, min, s = t.year, t.month, t.day, t.hour, t.min, t.sec
|
93
|
+
stack_item = Timecop::TimeStackItem.new(:freeze, 0.0)
|
94
|
+
|
95
|
+
assert_equal y, stack_item.year
|
96
|
+
assert_equal m, stack_item.month
|
97
|
+
assert_equal d, stack_item.day
|
98
|
+
assert_equal h, stack_item.hour
|
99
|
+
assert_equal min, stack_item.min
|
100
|
+
assert_equal s, stack_item.sec
|
101
|
+
end
|
102
|
+
|
88
103
|
def test_new_with_individual_arguments
|
89
104
|
y, m, d, h, min, s = 2008, 10, 10, 10, 10, 10
|
90
105
|
stack_item = Timecop::TimeStackItem.new(:freeze, y, m, d, h, min, s)
|
@@ -111,49 +126,35 @@ class TestTimeStackItem < Test::Unit::TestCase
|
|
111
126
|
assert_equal Rational(1, 24), a_time_stack_item.send(:utc_offset_to_rational, 3600)
|
112
127
|
end
|
113
128
|
|
114
|
-
def
|
115
|
-
|
116
|
-
|
117
|
-
tsi = Timecop::TimeStackItem.new(:freeze, t)
|
118
|
-
return if !(Time.now.dst? && tsi.time.dst?)
|
119
|
-
|
120
|
-
assert_equal 0, tsi.send(:dst_adjustment)
|
121
|
-
end
|
122
|
-
|
123
|
-
def test_compute_dst_adjustment_for_non_dst_to_non_dst
|
124
|
-
Timecop.freeze(DateTime.parse("2009-12-1 00:38:00 -0400"))
|
125
|
-
t = DateTime.parse("2009-12-11 00:00:00 -0400")
|
126
|
-
tsi = Timecop::TimeStackItem.new(:freeze, t)
|
127
|
-
return if Time.now.dst? || tsi.time.dst?
|
128
|
-
|
129
|
-
assert_equal 0, tsi.send(:dst_adjustment)
|
130
|
-
end
|
129
|
+
def test_datetime_in_presence_of_activesupport_timezone
|
130
|
+
skip('requires ActiveSupport') unless Time.respond_to? :zone
|
131
|
+
backed_up_zone, backed_up_tzvar = Time.zone, ENV['TZ']
|
131
132
|
|
132
|
-
|
133
|
-
|
134
|
-
t = DateTime.parse("2009-12-11 00:00:00 -0400")
|
133
|
+
Time.zone = ENV['TZ'] = 'America/Los_Angeles'
|
134
|
+
t = DateTime.new(2001, 2, 28, 23, 59, 59.5)
|
135
135
|
tsi = Timecop::TimeStackItem.new(:freeze, t)
|
136
|
-
return if !Time.now.dst? || tsi.time.dst?
|
137
136
|
|
138
|
-
|
137
|
+
assert_date_times_equal t, tsi.datetime
|
138
|
+
ensure
|
139
|
+
Time.zone, ENV['TZ'] = backed_up_zone, backed_up_tzvar
|
139
140
|
end
|
140
141
|
|
141
|
-
|
142
|
-
|
142
|
+
# Ensure DateTimes handle changing DST properly
|
143
|
+
def test_datetime_for_dst_to_non_dst
|
144
|
+
Timecop.freeze(DateTime.parse("2009-12-1 00:38:00 -0500"))
|
143
145
|
t = DateTime.parse("2009-10-11 00:00:00 -0400")
|
144
146
|
tsi = Timecop::TimeStackItem.new(:freeze, t)
|
145
|
-
return if Time.now.dst? || !tsi.time.dst?
|
146
147
|
|
147
|
-
|
148
|
+
assert_date_times_equal t, tsi.datetime
|
148
149
|
end
|
149
150
|
|
150
|
-
# Ensure DateTimes handle changing DST properly
|
151
|
-
def
|
151
|
+
# Ensure DateTimes handle changing DST properly when changing from DateTime to Time
|
152
|
+
def test_datetime_for_dst_to_time_for_non_dst
|
152
153
|
Timecop.freeze(DateTime.parse("2009-12-1 00:38:00 -0500"))
|
153
154
|
t = DateTime.parse("2009-10-11 00:00:00 -0400")
|
154
155
|
tsi = Timecop::TimeStackItem.new(:freeze, t)
|
155
156
|
|
156
|
-
assert_date_times_equal t, tsi.
|
157
|
+
assert_date_times_equal t.to_time, tsi.time
|
157
158
|
end
|
158
159
|
|
159
160
|
def test_datetime_for_non_dst_to_dst
|
@@ -180,11 +181,10 @@ class TestTimeStackItem < Test::Unit::TestCase
|
|
180
181
|
t = Time.local(2009, 10, 1, 0, 0, 30)
|
181
182
|
tsi = Timecop::TimeStackItem.new(:freeze, t)
|
182
183
|
|
183
|
-
|
184
|
+
assert_nil tsi.send(:travel_offset)
|
184
185
|
end
|
185
186
|
|
186
187
|
def test_timezones
|
187
|
-
require 'active_support/all'
|
188
188
|
Time.zone = "Europe/Zurich"
|
189
189
|
time = Time.zone.parse("2012-12-27T12:12:12+08:00")
|
190
190
|
Timecop.freeze(time) do |frozen_time|
|
@@ -192,13 +192,21 @@ class TestTimeStackItem < Test::Unit::TestCase
|
|
192
192
|
end
|
193
193
|
end
|
194
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
|
+
|
195
204
|
def test_timezones_apply_dates
|
196
|
-
|
197
|
-
Time.zone = "Marshall Is."
|
205
|
+
Time.zone = "Central Time (US & Canada)"
|
198
206
|
time = Time.zone.local(2013,1,3)
|
199
207
|
|
200
208
|
Timecop.freeze(time) do
|
201
|
-
assert_equal time.to_date,
|
209
|
+
assert_equal time.to_date, Time.zone.now.to_date
|
202
210
|
end
|
203
211
|
end
|
204
212
|
|
@@ -212,48 +220,18 @@ class TestTimeStackItem < Test::Unit::TestCase
|
|
212
220
|
assert_equal tsi.send(:scaling_factor), 4, "Scaling factor not set"
|
213
221
|
end
|
214
222
|
|
215
|
-
def test_parse_string_date_with_active_support
|
216
|
-
date = '2012-01-02'
|
217
|
-
Time.expects(:parse).with(date).returns(Time.local(2012, 01, 02))
|
218
|
-
Timecop.freeze(date)
|
219
|
-
end
|
220
|
-
|
221
223
|
def test_parse_only_string_with_active_support
|
222
224
|
Time.expects(:parse).never
|
223
225
|
Timecop.freeze(2011, 01, 02, hour=0, minute=0, second=0)
|
224
226
|
end
|
225
227
|
|
226
|
-
def test_parse_with_active_support_off
|
227
|
-
date = '2012-01-02'
|
228
|
-
Timecop.active_support = false
|
229
|
-
Time.expects(:parse).never
|
230
|
-
Timecop.freeze(date)
|
231
|
-
end
|
232
|
-
|
233
|
-
def test_uses_active_supports_in_time_zone
|
234
|
-
time = Time.now
|
235
|
-
Time.any_instance.expects(:in_time_zone).returns(time)
|
236
|
-
Timecop::TimeStackItem.new(:freeze, time)
|
237
|
-
end
|
238
|
-
|
239
|
-
def test_configured_off_active_support_in_time_zone_xxx
|
240
|
-
Timecop.active_support = false
|
241
|
-
Time.any_instance.expects(:in_time_zone).never
|
242
|
-
Timecop::TimeStackItem.new(:freeze, Time.now)
|
243
|
-
end
|
244
|
-
|
245
228
|
def test_parse_date
|
246
|
-
|
247
|
-
Timecop.freeze(Date.new(2012, 6, 9))
|
248
|
-
end
|
229
|
+
Timecop.freeze(Date.new(2012, 6, 9))
|
249
230
|
end
|
250
231
|
|
251
232
|
def test_time_zone_returns_nil
|
252
|
-
|
253
|
-
|
254
|
-
assert_nothing_raised do
|
255
|
-
Timecop.freeze
|
256
|
-
end
|
233
|
+
Time.zone = nil
|
234
|
+
Timecop.freeze
|
257
235
|
end
|
258
236
|
|
259
237
|
def test_nsecs_are_set
|
@@ -263,13 +241,59 @@ class TestTimeStackItem < Test::Unit::TestCase
|
|
263
241
|
assert_equal time.nsec, Time.now.nsec if (Time.now.respond_to?(:nsec))
|
264
242
|
end
|
265
243
|
|
266
|
-
def
|
267
|
-
|
244
|
+
def test_time_with_different_timezone_keeps_nsec
|
245
|
+
Time.zone = "Tokyo"
|
246
|
+
t = Time.now
|
247
|
+
Timecop.freeze(t) do
|
248
|
+
assert_equal t, Time.now
|
249
|
+
assert_equal t.nsec, Time.now.nsec if (Time.now.respond_to?(:nsec))
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def test_time_now_always_returns_local_time
|
254
|
+
Time.zone = "Tokyo"
|
255
|
+
t = Time.utc(2000, 1, 1)
|
256
|
+
Timecop.freeze(t) do
|
257
|
+
assert_equal t.getlocal.zone, Time.now.zone
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_time_zone_now_returns_time_in_that_zone
|
262
|
+
Time.zone = "Hawaii"
|
263
|
+
t = Time.utc(2000, 1, 1)
|
264
|
+
Timecop.freeze(t) do
|
265
|
+
assert_equal t, Time.zone.now
|
266
|
+
assert_equal 'HST', Time.zone.now.zone
|
267
|
+
end
|
268
|
+
end
|
268
269
|
|
270
|
+
def test_freezing_a_time_leaves_timezone_intact
|
269
271
|
Time.zone = "Tokyo"
|
270
272
|
t = Time.now
|
273
|
+
t_dup = t.dup
|
274
|
+
Timecop.freeze(t) {}
|
275
|
+
assert_equal t_dup.zone, t.zone
|
276
|
+
end
|
277
|
+
|
278
|
+
def test_freezing_a_time_with_zone_returns_proper_zones
|
279
|
+
Time.zone = "Hawaii"
|
280
|
+
t = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Tokyo'])
|
271
281
|
Timecop.freeze(t) do
|
272
|
-
|
282
|
+
local_now = Time.now
|
283
|
+
assert_equal t, local_now
|
284
|
+
assert_equal t.getlocal.zone, local_now.zone
|
285
|
+
|
286
|
+
zoned_now = Time.zone.now
|
287
|
+
assert_equal t, zoned_now
|
288
|
+
assert_equal 'HST', zoned_now.zone
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def test_datetime_timezones
|
293
|
+
dt = DateTime.new(2011,1,3,15,25,0,"-6")
|
294
|
+
Timecop.freeze(dt) do
|
295
|
+
now = DateTime.now
|
296
|
+
assert_equal dt, now, "#{dt.to_f}, #{now.to_f}"
|
273
297
|
end
|
274
298
|
end
|
275
299
|
end
|