Skip to content

Commit 5d7bf9a

Browse files
committed
feat: timestamp_file implementation
1 parent 74f4197 commit 5d7bf9a

File tree

3 files changed

+59
-6
lines changed

3 files changed

+59
-6
lines changed

README.md

+16
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,22 @@ To avoid displaying the informative text when re-executing, you can set the
7878
reexec_if_needed(quiet=True)
7979
```
8080

81+
### timestamp_file
82+
83+
A common time can be shared between several execution contexts by using a file
84+
to store the time to mock, instead of environment variables. This is useful
85+
to control the time of a running process for instance. Here is a schematized
86+
use case:
87+
88+
```python
89+
reexec_if_needed(remove_vars=False)
90+
91+
with fake_time("1970-01-01 00:00:00", timestamp_file="/tmp/timestamp"):
92+
subprocess.run("/some/server/process")
93+
94+
with fake_time("2000-01-01 00:00:00", timestamp_file="/tmp/timestamp"):
95+
assert request_the_server_process_date() == "2000-01-01 00:00:00"
96+
8197
Performance
8298
-----------
8399

libfaketime/__init__.py

+28-6
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,18 @@ def end_callback(instance):
126126

127127

128128
class fake_time:
129-
def __init__(self, datetime_spec, only_main_thread=True, tz_offset=None):
129+
def __init__(self, datetime_spec=None, only_main_thread=True, tz_offset=None, timestamp_file=None):
130130
self.only_main_thread = only_main_thread
131131
self.timezone_str = 'UTC'
132132
if tz_offset is not None:
133133
self.timezone_str = 'Etc/GMT{0:+}'.format(-tz_offset)
134134

135+
if not datetime_spec and not timestamp_file:
136+
raise ValueError("Either 'datetime_spec' or 'timestamp_file' must be passed.")
137+
135138
self.time_to_freeze = datetime_spec
139+
self.timestamp_file = timestamp_file
140+
136141
if isinstance(datetime_spec, basestring):
137142
self.time_to_freeze = utc.localize(dateutil.parser.parse(datetime_spec)) \
138143
.astimezone(timezone(self.timezone_str))
@@ -164,21 +169,31 @@ def _should_patch_uuid(self):
164169
def _format_datetime(self, _datetime):
165170
return _datetime.strftime(_FAKETIME_FMT)
166171

172+
def _update_time(self, time):
173+
if not self.timestamp_file:
174+
os.environ['FAKETIME'] = self._format_datetime(time)
175+
else:
176+
if time:
177+
with open(self.timestamp_file, "w") as fd:
178+
fd.write(self._format_datetime(time))
179+
os.environ['FAKETIME_TIMESTAMP_FILE'] = self.timestamp_file
180+
167181
def tick(self, delta=datetime.timedelta(seconds=1)):
168182
self.time_to_freeze += delta
169-
os.environ['FAKETIME'] = self._format_datetime(self.time_to_freeze)
183+
self._update_time(self.time_to_freeze)
170184

171185
def __enter__(self):
172186
if self._should_fake():
173187
begin_callback(self)
174188
self._prev_spec = os.environ.get('FAKETIME')
175189
self._prev_tz = os.environ.get('TZ')
176190
self._prev_fmt = os.environ.get('FAKETIME_FMT')
191+
self._prev_timestamp_file = os.environ.get('FAKETIME_TIMESTAMP_FILE')
177192

178193
os.environ['TZ'] = self.timezone_str
179194

180195
time.tzset()
181-
os.environ['FAKETIME'] = self._format_datetime(self.time_to_freeze)
196+
self._update_time(self.time_to_freeze)
182197
os.environ['FAKETIME_FMT'] = _FAKETIME_FMT
183198

184199
func_name = self._should_patch_uuid()
@@ -200,10 +215,17 @@ def __exit__(self, *exc):
200215
del os.environ['TZ']
201216
time.tzset()
202217

203-
if self._prev_spec is not None:
204-
os.environ['FAKETIME'] = self._prev_spec
218+
if self.timestamp_file:
219+
if self._prev_timestamp_file is not None:
220+
os.environ['FAKETIME_TIMESTAMP_FILE'] = self._prev_timestamp_file
221+
elif 'FAKETIME_TIMESTAMP_FILE' in os.environ:
222+
del os.environ['FAKETIME_TIMESTAMP_FILE']
223+
205224
else:
206-
del os.environ['FAKETIME']
225+
if self._prev_spec is not None:
226+
os.environ['FAKETIME'] = self._prev_spec
227+
else:
228+
del os.environ['FAKETIME']
207229

208230
if self._prev_fmt is not None:
209231
os.environ['FAKETIME_FMT'] = self._prev_spec

test/test_faketime.py

+15
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,21 @@ def test_freeze_time_alias(self):
8787
def test_monotonic_not_mocked(self):
8888
assert os.environ['DONT_FAKE_MONOTONIC'] == '1'
8989

90+
def test_timestmap_file(self, tmpdir):
91+
file_path = str(tmpdir / "faketime.rc")
92+
93+
with fake_time('2000-01-01 10:00:05', timestamp_file=file_path) as fake:
94+
assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.now()
95+
with open(file_path) as fd:
96+
assert fd.read() == "2000-01-01 10:00:05.000000"
97+
98+
fake.tick(delta=datetime.timedelta(hours=1))
99+
assert datetime.datetime(2000, 1, 1, 11, 0, 5) == datetime.datetime.now()
100+
with open(file_path) as fd:
101+
assert fd.read() == "2000-01-01 11:00:05.000000"
102+
103+
with fake_time(timestamp_file=file_path):
104+
assert datetime.datetime(2000, 1, 1, 11, 0, 5) == datetime.datetime.now()
90105

91106
class TestUUID1Deadlock():
92107

0 commit comments

Comments
 (0)