Skip to content

About

Re-run a test suite with the clock moved to dates human intuition misses, and report which ones fail.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

chronofuzz

Runs an existing test suite with the system clock moved to dates that human intuition does not cover, and reports which of those dates change the outcome.

It answers one question: on which dates does this codebase stop behaving?

What it does not claim

  • It does not find bugs. It finds dates. A failing date is evidence that something is date-sensitive; deciding what to do about it is a human call.
  • It cannot tell whether a failure lives in the test or in the code under test. Both are real problems and both are worth knowing about, but they are fixed in different places, so the report prints the raw output next to every failure.
  • A clean sweep is not proof of correctness. It means no tested behaviour depends on the wall clock in a way these particular dates disturb.

Why this exists

Human intuition spans roughly one human life. It does not span these:

Date Hazard
1970-01-01 the epoch itself; zero, negative and "unset" collide
2000-02-29 a real leap day; day- and year-counting arithmetic breaks here
2038-01-19 signed 32-bit seconds overflow
2100-02-28 2100 is not a leap year, so 2100-02-29 must not exist
2262-04-11 int64 nanoseconds overflow
9999-12-31 far edge of four-digit-year formatting
2027-03-14 DST spring-forward: 02:30 local does not exist
2027-11-07 DST fall-back: 01:30 local happens twice
2026-12-31 year rollover; off-by-one in %Y and ISO week numbers
2016-12-31 nearest representable instant to the 2016 leap second

None of these are exotic, and all of them are invisible to code review, because the reviewer is a human with the same blind spots as the author.

One example found by hand while building this, which is what convinced me the class is real. Converting the last second of the year 9999 to local time:

Timezone datetime.fromtimestamp(253402300799)
UTC works
Asia/Shanghai (UTC+8) ValueError: year 10000 is out of range
America/New_York works

The same instant is fine in New York and raises in Shanghai. Code that only ever runs in one timezone will never reveal this.

Install

pip install chronofuzz            # clock-shift engines are detected, not bundled
pip install "chronofuzz[python-engine]"   # pre-install freezegun for the python engine

Or just run the file, which has no dependencies of its own:

python3 chronofuzz.py --version

Use

chronofuzz list                                         # show the clock targets
chronofuzz run -- pytest -q                             # sweep the whole list
chronofuzz run --targets-filter quick -- pytest -q      # the three cheap ones
chronofuzz run --markdown report.md -- npm test         # other languages too
chronofuzz selftest                                     # verify the harness itself

run exits non-zero when at least one shifted clock changes the outcome, and also when the baseline run fails, so it drops straight into a pipeline.

Engines

Engine Mechanism Affects Verified
python freezegun, injected through sitecustomize on PYTHONPATH, so no project file is modified clock reads made by Python code and by the test runner yes, on macOS with Python 3.13
faketime the faketime wrapper prefixes your command any process, any language implemented; exercised on Linux by the workflow in this repo, not verified on macOS
libfaketime LD_PRELOAD / DYLD_INSERT_LIBRARIES any process, any language compiles on macOS; not verified, the authoring machine forbids the shared memory it needs
none no shift used for the baseline run yes

--engine auto picks python when the command looks like Python, otherwise the best libc shim available on the machine.

The libc engines deliberately keep time.monotonic() real (FAKETIME_DONT_FAKE_MONOTONIC=1; freezegun never patches it). Without that, every sleep, retry and timeout in the suite would fire spuriously and the report would be noise.

Reading a failure

### y2038 - 2038-01-19T03:14:08+00:00

`E  AssertionError: assert True is False | FAILED test_retention.py::test_yesterday_record_is_kept`

    >   assert should_purge(now - datetime.timedelta(days=1), now) is False
    E   AssertionError: assert True is False
    E    +  where True = should_purge(FakeDatetime(2038, 1, 19, 3, 14, 8, ...))

That is a defect in the code: a retention policy that deletes everything once the clock passes 2038. The same shape of output can also mean the test itself compares against now(), which is a defect in the suite. The tail is printed so you can tell which one you are looking at.

chronofuzz selftest plants exactly the defect above and asserts that the sweep catches it, so the harness is checked rather than assumed.

Use as a GitHub Action

- uses: laodaixp/chronofuzz@v0.1.0
  with:
    command: pytest -q
    targets: quick

The action installs the clock shim, runs the sweep, writes the report into the job summary, and fails the job if a shifted clock changed the outcome.

Limits worth knowing

  • The python engine shifts Python-level clock reads only. C extensions that call time() themselves, and subprocesses that are not Python, are unaffected. Use a libc engine if that matters.
  • The python engine prepends freezegun to PYTHONPATH. A project that already ships its own freezegun or python-dateutil may see this copy first.
  • Each clock shift re-runs the whole suite, so cost is the suite's runtime times the number of targets. --targets-filter quick exists for that reason.
  • Timeouts are per run and default to 300 seconds; a slow suite needs a larger --timeout or fewer targets.

Development

python3 chronofuzz.py selftest --engine python
python3 chronofuzz.py selftest --engine faketime   # needs `faketime` installed

Both run in CI on every push and once a month on a schedule.

License

MIT.

About

Re-run a test suite with the clock moved to dates human intuition misses, and report which ones fail.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages