1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
"""Reading a nextest duration exactly as ``humantime`` reads it.
Split from ``timeout_budgets`` so neither outgrows the 400-line limit
``AGENTS.md`` sets, and because this is one self-contained translation:
the grammar and the arithmetic of ``humantime`` 2.3.0, the version
``cargo-nextest`` 0.9.122 resolves through ``humantime_serde``. The
grammar itself lives in ``nextest_duration_grammar``; this module is the
arithmetic that applies it.
The arithmetic uses integers, and splits into seconds and nanoseconds
the way ``humantime``'s does because reading a component as a float
accepts two classes of text it refuses.
Sub-nanosecond precision is one. ``humantime`` scales a fraction's
numerator by its unit and divides by the denominator, and that division
errors unless it is exact, so ``"0.0000000002s"`` is an error there and
a small positive number to ``float``. Out-of-range values are the
other: every step is checked against the range of a ``u64``, the
denominator included, so twenty fractional digits overflow whatever the
numerator is.
The split matters as much as the integers. ``humantime`` counts whole
hours, days, weeks, months, and years in *seconds*, so a duration of
several centuries is fine there while a single nanosecond accumulator
would overflow and refuse it.
"""
=
"""Return `value`, or raise when it leaves the range of a `u64`.
Parameters
----------
value : int
The intermediate to check.
duration : str
The whole duration, for the message.
Returns
-------
int
`value` unchanged.
Raises
------
DurationGrammarError
If `value` is outside the range `humantime` computes in.
"""
=
return
"""Return `scaled // denominator`, or raise when it is not exact.
`humantime`'s division errors on any remainder, which is what makes
sub-nanosecond precision an error rather than a rounding.
Parameters
----------
scaled : int
The numerator scaled by its unit.
denominator : int
The fraction's denominator.
duration : str
The whole duration, for the message.
Returns
-------
int
The exact quotient.
Raises
------
DurationGrammarError
If the division leaves a remainder.
"""
=
return //
"""Return one fraction's contribution as (seconds, nanoseconds).
Parameters
----------
unit : str
The canonical unit name.
digits : str
The digits after the point, whitespace already removed.
duration : str
The whole duration, for the message.
Returns
-------
tuple[int, int]
Seconds and nanoseconds contributed.
Raises
------
DurationGrammarError
If the unit takes no fraction, or the division is not exact.
"""
=
=
=
=
, =
=
return
"""Return one component's contributions, whole part before fraction.
Two contributions rather than one sum, in that order, because
``humantime`` adds them to the running total separately and the
running total is normalized between the two. Summing them here and
adding once would accept totals it refuses and refuse totals it
accepts.
Parameters
----------
component : re.Match[str]
A match of `COMPONENT`.
duration : str
The whole duration, for the message.
Returns
-------
list[tuple[int, int]]
One or two (seconds, nanoseconds) pairs, in the order
`humantime` adds them.
Raises
------
DurationGrammarError
If the unit is unknown, or the value is out of range or finer
than `humantime` allows.
"""
=
=
=
, =
=
=
return
"""Add one contribution to the running total the way `humantime` does.
`humantime` keeps whole seconds and a nanosecond part, both `u64`,
and normalizes after every contribution rather than at the end. That
is the whole of the difference between this and a single nanosecond
accumulator: `"18446744073709551615ns 1ns"` is a fraction over
eighteen seconds short of nineteen billion, which `humantime` reads,
and which one accumulator refuses because the two values together
pass the `u64` ceiling before anything carries.
The carry is deliberately in two parts, as `humantime`'s is. Its own
normalization runs only when the nanosecond part is *above* a
second, so a part of exactly one second reaches `Duration::new`,
which carries it and aborts if that carry overflows. Both halves
check, so the abort is a refusal here.
Parameters
----------
total : tuple[int, int]
The running (seconds, nanoseconds).
contribution : tuple[int, int]
What this whole part or fraction adds.
duration : str
The whole duration, for the message.
Returns
-------
tuple[int, int]
The new running total, its nanosecond part below one second.
Raises
------
DurationGrammarError
If any sum leaves the range `humantime` computes in.
"""
, =
, =
=
=
%=
=
=
%=
return ,
"""Convert a nextest duration to seconds.
Parameters
----------
duration : str
A duration as nextest spells it, such as ``"120s"``, the
multi-component ``"1m 30s"`` or the fractional ``"1.5m"``.
Returns
-------
Fraction
The duration in seconds, exactly. A ``Fraction`` is returned
rather than a ``float`` because the seconds and nanoseconds are
accumulated as integers and a float discards that exactness
above two to the fifty-third.
Raises
------
DurationGrammarError
If the text is not a duration nextest would accept.
Examples
--------
>>> read_exact("120s") == 120
True
>>> read_exact("1m 30s") == 90
True
>>> read_exact("1.5m") == 90
True
>>> read_exact("1 0s") == 10
True
>>> read_exact("0") == 0
True
"""
return
=
=
=
= 0
=
=
=
=
, =
return +