Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ stopping the time, use the `freeze` parameter:
>>> pendulum.travel_to(pendulum.yesterday(), freeze=True)
```

Passing `freeze=True` also stops a clock that is already travelling. Once frozen,
subsequent calls to `travel()` or `travel_to()` keep the clock frozen, even when
`freeze` is omitted. Use `travel_back()` before starting a new ticking clock.

## Travelling back to the present

Using any of the travel helpers will keep you in the past, or future, until you decide
Expand Down
7 changes: 5 additions & 2 deletions src/pendulum/testing/traveller.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,18 @@ def travel(
minutes=minutes,
seconds=seconds,
microseconds=microseconds,
)
),
tick=False if freeze else None,
)

return self

def travel_to(self, dt: DateTime, *, freeze: bool = False) -> Self:
self._start(freeze=freeze)

cast("time_machine.Traveller", self._traveller).move_to(dt)
cast("time_machine.Traveller", self._traveller).move_to(
dt, tick=False if freeze else None
)

return self

Expand Down
82 changes: 82 additions & 0 deletions tests/testing/test_time_travel.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from __future__ import annotations

from itertools import count
from time import sleep
from typing import TYPE_CHECKING

import pytest

import pendulum

from pendulum.testing.traveller import Traveller
from pendulum.utils._compat import PYPY


Expand Down Expand Up @@ -84,3 +86,83 @@ def test_freeze() -> None:
sleep(0.01)

assert now != pendulum.now()


@pytest.fixture
def ticking_clock(monkeypatch: pytest.MonkeyPatch) -> None:
# Advance time-machine's real clock deterministically, without sleeping.
ticks = count(1_700_000_000_000_000_000, 1_000_000_000)
monkeypatch.setattr(
"time_machine._time_machine.original_time_ns", lambda: next(ticks)
)


@pytest.mark.skipif(PYPY, reason="Time travelling not available on PyPy")
@pytest.mark.parametrize("method", ["travel", "travel_to"])
@pytest.mark.parametrize("initial_freeze", [None, False, True])
@pytest.mark.parametrize("freeze", [False, True])
def test_travel_freeze_state(
ticking_clock: None, method: str, initial_freeze: bool | None, freeze: bool
) -> None:
if initial_freeze is not None:
pendulum.travel_to(pendulum.datetime(2024, 1, 1, 12), freeze=initial_freeze)

if method == "travel":
pendulum.travel(days=2, freeze=freeze)
else:
pendulum.travel_to(pendulum.datetime(2024, 2, 1, 12), freeze=freeze)

first = pendulum.now("UTC")
second = pendulum.now("UTC")
if freeze or initial_freeze:
assert second == first
else:
assert second == first.add(seconds=1)

if method == "travel_to":
assert first == pendulum.datetime(2024, 2, 1, 12)


@pytest.mark.skipif(PYPY, reason="Time travelling not available on PyPy")
@pytest.mark.parametrize("method", ["travel", "travel_to"])
def test_freeze_after_repeated_travel(ticking_clock: None, method: str) -> None:
target = pendulum.datetime(2024, 1, 1, 12)
pendulum.travel_to(target)
pendulum.travel(days=1)
if method == "travel":
pendulum.travel(days=1, freeze=True)
else:
pendulum.travel_to(target, freeze=True)

frozen = pendulum.now("UTC")
pendulum.travel(hours=2)
assert pendulum.now("UTC") == frozen.add(hours=2)
assert pendulum.now("UTC") == frozen.add(hours=2)
pendulum.travel_to(target)
assert pendulum.now("UTC") == target
assert pendulum.now("UTC") == target

pendulum.travel_back()
pendulum.travel_to(target)
assert pendulum.now("UTC") == target
assert pendulum.now("UTC") == target.add(seconds=1)


@pytest.mark.skipif(PYPY, reason="Time travelling not available on PyPy")
@pytest.mark.parametrize("method", ["travel", "travel_to"])
def test_freeze_active_traveller_in_nested_context(
ticking_clock: None, method: str
) -> None:
outer_target = pendulum.datetime(2024, 1, 1, 12)
inner_target = pendulum.datetime(2024, 2, 1, 12)
with Traveller().travel_to(outer_target):
with Traveller().travel_to(inner_target) as inner:
if method == "travel":
assert inner.travel(hours=2, freeze=True) is inner
else:
assert inner.travel_to(inner_target, freeze=True) is inner
assert pendulum.now("UTC") == pendulum.now("UTC")

first = pendulum.now("UTC")
assert first.date() == outer_target.date()
assert pendulum.now("UTC") == first.add(seconds=1)
Loading