Chrono Mock
LanguageEnglish

Command line reference

Every flag of both commands, both exit code tables, and how to read what comes back. The narrative version, with the reasoning, is on the CLI and CI page.

Two commands

chrono run starts an application at a different date and time. chrono calc works out a date and tells you what makes it interesting. They share one step grammar, so a shift written for one means the same in the other.

chrono version answers a third and much smaller question: which build this is, which of the two cores the executable is, and the protocol version it speaks. One line on stdout, so a script can capture it.

chrono license answers the fourth: the licence this build is distributed under, the warranty disclaimer, and the third-party components linked into it. It also says where the full licence text and the third-party notices are on disk, or admits it when the copy you are running does not have them beside it. --components lists every bundled component with its version instead of a summary.

chrono presets lists the preset catalogue the way --preset reads it: every preset in the folder, written in the words the calculator's grammar reads, and every file it would refuse, with the reason. --json writes chronomock.presets/1 and --dir names another folder.

chrono run <target> [options]
chrono calc [options]
chrono version        # chrono 0.4.0 (x64, protocol 1)
chrono license        # GPL-3.0-only, and what is bundled
chrono presets        # the catalogue, as --preset reads it
chrono --help          # usage for all of them

chrono run - options

FlagWhat it does
--at <moment> The moment to start at, in the session zone. Absolute (2038-01-19T03:14:07) or relative with a leading sign (+1d, -2h). Units: s m h d w mo q y - m is minutes and mo is months. Without this flag the session clock starts at the real current time, so --mode xN on its own simply runs the target faster.
--zone +HH:MM The session zone: the zone the moment is written in, and the one the target's local-time calls report. A fixed offset, with no daylight saving. Without it, the host's own zone.
--mode flow|frozen|xN flow runs at real speed, frozen holds the clock still, xN runs it N times faster (x60 turns an hour into a minute, x1440 a day).
--scale-duration Also speed up the duration axis: tick counts, the interrupt time, sleeps and timers. Object waits are counted and keep their real length. Off by default, because a wall clock and a stopwatch are different questions. A timer or a delay set for a date and time follows the session clock with or without it.
--scale-qpc Also scale the high-resolution counter, which is where Python 3.13+ monotonic, .NET Stopwatch and Java nanoTime read elapsed time. Off by default: an application that times its rendering off that counter can look distorted.
--ticks N End the session after N heartbeats, one real second each, and report. Without it the tool stays attached until the target, and every program it started that runs on the session clock, has exited - a launcher that ends at once leaves the session to the application it started.
--timeout <s> Give up after this many seconds and exit 6, for a pipeline that must not hang. Independently of this flag, a core that says nothing for 15 seconds is stopped and also exits 6 - in both cases the run is cut short, so it carries no verdict.
--args "..." Arguments for the target, as one string, split by the same rule Windows uses - so an argument containing spaces survives. For a Chromium target, --user-data-dir and --remote-debugging-port are refused rather than overridden: the session needs its own isolated profile, and quietly running on your real browser profile is the one thing that must not happen.
--cwd <dir> Start the target in that directory. Without it the target inherits ours, which is what every run did before this flag existed. An empty value is a usage error rather than "no directory" - absent and empty are different things, and only absent is something Windows can be given. A directory that does not exist stops the session with target.cwd_missing before anything is launched, instead of surfacing later as a target that would not start.
--preset <id> Take the moment and the speed from presets/<id>.json. Exclusive with --at, --mode and --scale-duration, since the preset is then the single source of both.
--param id=value Fill a preset parameter. Repeatable, requires --preset. In run only, a trial start date defaults to the target executable's own creation date, so a trial preset counts from the day the application was installed without you typing it.
--set-after T:M At heartbeat T, change the speed to M. In flight the multiplier is at least 1 - freezing mid-session is not offered here.
--jump-after T:moment At heartbeat T, jump the clock to that moment, absolute or relative, in the session zone.
--force Keep the session even when the opening verdict says the substitution did not take effect - by default the target is stopped instead, because a session that proved nothing is worse than no session. The verdict does not change, and neither does the exit code. This only decides whether the target keeps running.
--no-embedded Leave the web pages inside the application (WebView2, Qt WebEngine) on the real clock and open no debugging port. Without it the session asks such an engine to open a local port and puts its pages on the session clock through it - while the port is open, any other program on this computer can use it to reach those pages.
--elevated-embedded For an application that runs as administrator, from a Chrono Mock that runs as administrator too. WebView2 ignores the setting the session reaches its pages through for such an application, so the session writes one value to the machine registry (HKLM, named after the application's .exe) for its duration and removes it at the end. While the application runs, any other program on this computer can reach its pages through the port, and the application has administrator rights. Not with --no-embedded.
--keep-background-timers Start the web engine - the browser of a Chromium or Electron application, or the engine inside an application - with its slowdown of timers in hidden windows switched off, so a minimised window keeps the session speed. The application then also works at full pace while it is hidden. An application started as administrator through --elevated-embedded does not get it.
--dry-run Print what the session would be and start nothing: the resolved moment and zone and where they came from, what a preset filled each parameter with, the arguments after splitting, and which of the two mechanisms the target would take. No process is started and no file is written, including the one --report names. It exits 0 for a workable plan and 2 for a target that is not there, which is the code a real run gives for the same fact. A plan carries no verdict and says so - only a real session has one. With --json the plan is data, including session.elevated_embedded and session.elevated_embedded_value, the file name the registry value would be named after when the option applies to an .exe target, null otherwise. A name there is not a promise of a write: the session writes the value only when Chrono Mock itself runs as administrator.
--report <path> Write the human report to a file. A verdict other than works leads that file with an unreliable-evidence banner, because evidence that hides its own doubt is worse than none. A works session gets this banner too when the application read none of the clocks the session replaces, or when it is a Go program, whose wall clock Chrono Mock cannot reach. The file also names the run itself: the version, when it started, the target, its folder and the flags.
--report-args Together with --report, also write the target's arguments into that file, word for word. Off by default, because an argument can carry a password - the file then says only how many there were.
--json Emit the session as machine-readable records instead of the report - the same stream the window itself consumes. A console application's own output goes to standard error, so standard output holds the records and nothing else. An application still running when the session ends keeps writing there, so whatever reads standard error to its end waits for it, and the run says so.

chrono run - exit codes

The exit code is the session verdict, not what the application returned. The application's own exit code is reported separately, in the report and in the session data - the CLI and CI page explains why round that way.

CodeMeaning
0works - every process that read time saw the session clock
10works partially - some channels covered, some not
11does not work - the substitution did not reach the key channels
4undetermined - coverage could not be established. An honest "cannot tell", never a pretended pass
12the target vanished right after injection and left nothing running on the session clock, which suggests a single-instance application handing over to a copy already running, or a hand-off to a program the session could not enter, usually one of the other bitness. A target that starts the application and ends is a launcher, and the session goes on for the application
1usage error - a bad flag, a bad expression, or a preset this command will not accept
2the target could not be launched or injected
3internal error - the engine could not start, hit an unexpected state, or stopped before it closed the session (whatever its own exit code). Such a run carries no verdict
5the operation is not built in this release - for example a preset whose moment needs a calendar, which run cannot load yet. Deliberately distinct from a usage error: you asked for something that does not exist yet, not for something wrong
6the run was cut short - --timeout expired, or the engine stopped answering for 15 seconds and was stopped. This is not a verdict: the session was interrupted, so it proves nothing about the target either way
7another session is already running - one session at a time. Trying again once it has ended is enough
8the session reached a verdict, but its evidence is not in the file --report names - the disk was full, or another program held the old file open, which then stays as it was. The verdict is still in the report the command prints

Reading the report

Without --json the session ends in a plain report. Every line is either a fact the session observed or absent - nothing is filled in with a plausible default.

Chrono Mock - session report
  target:   .\build\test.exe
  verdict:  WORKS - time substitution took effect  (processes: 1)
            every process that read time saw the session clock
  session:  fake clock reached 2038-01-19T03:14:08
            real elapsed 1.3s, fake elapsed 1.3s
  exited:   the target closed itself with code 3
  covered channels (substituted, with call counts, 10 calls in all):
            - pid 21240: GetSystemTimeAsFileTime (5 calls)
            - pid 21240: GetLocalTime (5 calls)
  observed channels (hooked but left real):
            - pid 21240: NtCreateUserProcess (0 calls)
  warnings:
            - the target opened a network connection ...
  • verdict - the family verdict over the parent and every child process, with the process count. This is the number the exit code carries.
  • exited - the application's own exit code, and only when it closed itself. A run bounded by --ticks leaves the target running, a Chromium session is one the tool closes itself, and a session that never started has no such code - the line is then absent rather than showing a made-up zero. A crash code arrives as a large negative number, so it also gets its hexadecimal form.
  • covered - channels the substitution reached, with how many times each was actually called, per process. Counts are never summed across processes: one process's audit must not borrow another's numbers. A count stops at 1048576 and then reads at least that many.
  • observed - channels hooked but deliberately left real, such as object waits and multimedia timers. Their own bucket, so they are never read as substituted.
  • uncovered - channels the application asked for and the session did not cover. This is what turns a verdict into partially.
  • not fully cleaned up - anything teardown could not remove. Absent on a clean end, which is the normal case.

chrono calc - options

No natural language: each flag is one step, and the order of the flags is the order of the steps. The result after every step is printed, so a wrong step is visible rather than buried.

FlagWhat it does
--base today|now|<moment> Where to start, default today. Resolved in the session zone. An impossible day such as 2025-02-31 is refused, never quietly normalised into the next month.
--base-utc <moment> Like --base, but the start is an instant in UTC, with an optional trailing Z, instead of a wall-clock reading in the session zone.
--shift ±N<unit> Repeatable. The sign is required. Units: s m h d w mo q y bd, or their full names. m is minutes, mo is months. Months, quarters and years fold onto the calendar date with a clamp, so a month after 31 January is the end of February, not a fixed number of ticks. bd (business days) needs --calendar.
--set-time HH:MM:SSSet the time of day on the current result.
--snap <target> Jump to a period boundary: som eom soq eoq soy eoy (start or end of month, quarter, year). A start is the first day at 00:00:00, an end the last day at 23:59:59.
--nearest nbd|pbd The nearest business day forward or back, the current day included when it already is one. Needs --calendar.
--to-zone +HH:MM A step: re-express the same instant in another fixed offset. The instant is preserved, so the epoch value does not move and only the civil fields do. Not to be confused with --zone.
--zone +HH:MM The session zone, used to resolve today and now and to render the result. Without it, the host's zone.
--calendar us-banking|us-federal|pl Load a holiday calendar. It adds business day and holiday to the metadata and enables bd and --nearest. The two American calendars differ in how they observe a holiday falling at a weekend, which is exactly the kind of off-by-one day a test is meant to find. Each calendar answers for a range of years: pl from 2002, us-federal from 1960 and us-banking from 2008. A business-day step that reaches a day before it is refused with exit 1, and a date before it is shown as not judged. When a calendar's holidays were last checked against the law more than a year ago, a later day it judged is marked calendar_outdated, because a newer holiday may be missing.
--preset <id> --param id=value A named moment from presets/<id>.json, with its parameters filled in. Exclusive with the step flags and with --analyze, and it composes with --calendar, --zone and --format.
--analyze <date> Read a pasted date back and say what it is. An ambiguous 04/08 is shown in both readings, American and Polish, rather than guessed - that ambiguity is a real source of wrong bug reports.
--format <mask> Print the result in your own mask as well, so it matches the format of the application under test: yyyy-MM-dd HH:mm:ss. Tokens are case sensitive in the .NET and Java sense - M is month, m is minute.
--json Machine output (chronomock.calc/1) for any of the above, so a pipeline can compute a date and feed it straight back into chrono run.
# Last business day of the quarter, American banking calendar
chrono calc --base today --snap eoq --nearest pbd --calendar us-banking

chrono calc - exit codes

A separate, smaller table: the calculator has no coverage verdict to report.

CodeMeaning
0computed
1usage error - a bad flag, an impossible date, a range overflow, or a calendar file with no business day in it
5the step or unit is not built in this release, or the step needs a calendar you did not pass. Separate from a usage error on purpose, so a script can tell "not yet" from "wrong"

Next