Branch data Line data Source code
1 : : // Copyright 2011-2025 David Robillard <d@drobilla.net>
2 : : // SPDX-License-Identifier: ISC
3 : :
4 : : #undef NDEBUG
5 : :
6 : : #include "int_test_utils.h"
7 : : #include "num_test_utils.h"
8 : : #include "write_test_utils.h"
9 : :
10 : : #include <exess/exess.h>
11 : :
12 : : #include <assert.h>
13 : : #include <stdbool.h>
14 : : #include <stdint.h>
15 : : #include <stdio.h>
16 : : #include <string.h>
17 : :
18 : : static const ExessDuration zero = {0, 0, 0};
19 : : static const ExessDuration lowest = {-INT32_MAX, -INT32_MAX, -999999999};
20 : : static const ExessDuration highest = {INT32_MAX, INT32_MAX, 999999999};
21 : : static const ExessDuration longest = {-2119579475, -1093803875, -999999999};
22 : : static const ExessDuration longest_ym = {-2119579475, 0, 0};
23 : : static const ExessDuration longest_dt = {0, -1093803875, -999999999};
24 : : static const ExessDuration year = {12, 0, 0};
25 : : static const ExessDuration month = {1, 0, 0};
26 : : static const ExessDuration day = {0, 24 * 60 * 60, 0};
27 : : static const ExessDuration hour = {0, 60 * 60, 0};
28 : : static const ExessDuration minute = {0, 60, 0};
29 : : static const ExessDuration second = {0, 1, 0};
30 : : static const ExessDuration nanosecond = {0, 0, 1};
31 : :
32 : : static const ExessDuration n_year = {-12, 0, 0};
33 : : static const ExessDuration n_month = {-1, 0, 0};
34 : : static const ExessDuration n_day = {0, -24 * 60 * 60, 0};
35 : : static const ExessDuration n_hour = {0, -60 * 60, 0};
36 : : static const ExessDuration n_minute = {0, -60, 0};
37 : : static const ExessDuration n_second = {0, -1, 0};
38 : : static const ExessDuration n_nanosecond = {0, 0, -1};
39 : :
40 : : static const ExessDuration garbage1 = {1, 1, -1};
41 : : static const ExessDuration garbage2 = {1, -1, 1};
42 : : static const ExessDuration garbage3 = {1, -1, -1};
43 : : static const ExessDuration garbage4 = {-1, 1, 1};
44 : : static const ExessDuration garbage5 = {-1, 1, -1};
45 : : static const ExessDuration garbage6 = {-1, -1, 1};
46 : : static const ExessDuration garbage7 = {INT32_MIN, 0, -999999999};
47 : : static const ExessDuration garbage8 = {0, INT32_MIN, -999999999};
48 : : static const ExessDuration garbage9 = {INT32_MIN, INT32_MIN, -999999999};
49 : :
50 : : static void
51 : 69 : check_read(const char* const string,
52 : : const ExessStatus expected_status,
53 : : const size_t expected_count,
54 : : const int32_t expected_years,
55 : : const int32_t expected_months,
56 : : const int32_t expected_days,
57 : : const int32_t expected_hours,
58 : : const int32_t expected_minutes,
59 : : const int32_t expected_seconds,
60 : : const int32_t expected_nanoseconds,
61 : : const bool expected_is_negative)
62 : : {
63 : 69 : ExessDuration value = {0, 0, 0};
64 : 69 : const ExessResult r = exess_read_duration(string, &value);
65 : :
66 [ - + ]: 69 : assert(r.status == expected_status);
67 [ - + ]: 69 : assert(r.count == expected_count);
68 : :
69 [ + + - + ]: 69 : assert(value.months ==
70 : : ((expected_is_negative ? -1 : 1) * 12 * expected_years) +
71 : : expected_months);
72 : :
73 [ + + - + ]: 69 : assert(value.seconds ==
74 : : (expected_is_negative ? -1 : 1) *
75 : : ((expected_seconds + (60 * expected_minutes) +
76 : : (60 * 60 * expected_hours) + (24 * 60 * 60 * expected_days))));
77 : :
78 [ - + ]: 69 : assert(value.nanoseconds == expected_nanoseconds);
79 : 69 : }
80 : :
81 : : static void
82 : 1 : test_read_duration(void)
83 : : {
84 : : // No input
85 : 1 : check_read("", EXESS_EXPECTED_DURATION, 0, 0, 0, 0, 0, 0, 0, 0, false);
86 : 1 : check_read("\t\n\r ", EXESS_EXPECTED_DURATION, 4, 0, 0, 0, 0, 0, 0, 0, false);
87 : :
88 : : // Good values
89 : :
90 : 1 : check_read(
91 : : "P2Y6M5DT12H35M30S", EXESS_SUCCESS, 17, 2, 6, 5, 12, 35, 30, 0, false);
92 : :
93 : 1 : check_read("P1DT2H", EXESS_SUCCESS, 6, 0, 0, 1, 2, 0, 0, 0, false);
94 : 1 : check_read("P20M", EXESS_SUCCESS, 4, 0, 20, 0, 0, 0, 0, 0, false);
95 : 1 : check_read("PT20M", EXESS_SUCCESS, 5, 0, 0, 0, 0, 20, 0, 0, false);
96 : 1 : check_read("P0Y20M0D", EXESS_SUCCESS, 8, 0, 20, 0, 0, 0, 0, 0, false);
97 : 1 : check_read("P0Y", EXESS_SUCCESS, 3, 0, 0, 0, 0, 0, 0, 0, false);
98 : 1 : check_read("-P60D", EXESS_SUCCESS, 5, 0, 0, 60, 0, 0, 0, 0, true);
99 : 1 : check_read(
100 : : "PT1M30.5S", EXESS_SUCCESS, 9, 0, 0, 0, 0, 1, 30, 500000000, false);
101 : :
102 : : // Leading and trailing whitespace
103 : 1 : check_read("\t\n\r P1Y", EXESS_SUCCESS, 7, 1, 0, 0, 0, 0, 0, 0, false);
104 : 1 : check_read("P1MT2H\t\n\r ", EXESS_SUCCESS, 6, 0, 1, 0, 2, 0, 0, 0, false);
105 : 1 : check_read("\t\n\r P1Y", EXESS_SUCCESS, 7, 1, 0, 0, 0, 0, 0, 0, false);
106 : 1 : check_read("P1YT2H\t\n\r ", EXESS_SUCCESS, 6, 1, 0, 0, 2, 0, 0, 0, false);
107 : :
108 : : // Non-canonical form
109 : 1 : check_read("P06D", EXESS_SUCCESS, 4, 0, 0, 6, 0, 0, 0, 0, false);
110 : 1 : check_read("PT7.0S", EXESS_SUCCESS, 6, 0, 0, 0, 0, 0, 7, 0, false);
111 : 1 : check_read(
112 : : "P0Y0M01DT06H00M00S", EXESS_SUCCESS, 18, 0, 0, 1, 6, 0, 0, 0, false);
113 : :
114 : : // Maximum field values
115 : 1 : check_read(
116 : : "P178956970Y", EXESS_SUCCESS, 11, 178956970, 0, 0, 0, 0, 0, 0, false);
117 : 1 : check_read(
118 : : "P2147483646M", EXESS_SUCCESS, 12, 0, 2147483646, 0, 0, 0, 0, 0, false);
119 : 1 : check_read("P24855D", EXESS_SUCCESS, 7, 0, 0, 24855, 0, 0, 0, 0, false);
120 : 1 : check_read("PT596523H", EXESS_SUCCESS, 9, 0, 0, 0, 596523, 0, 0, 0, false);
121 : 1 : check_read(
122 : : "PT35791394M", EXESS_SUCCESS, 11, 0, 0, 0, 0, 35791394, 0, 0, false);
123 : 1 : check_read(
124 : : "PT2147483646S", EXESS_SUCCESS, 13, 0, 0, 0, 0, 0, 2147483646, 0, false);
125 : 1 : check_read(
126 : : "PT0.999999999S", EXESS_SUCCESS, 14, 0, 0, 0, 0, 0, 0, 999999999, false);
127 : :
128 : : // Out of range fields
129 : 1 : check_read("P178956971Y", EXESS_OUT_OF_RANGE, 11, 0, 0, 0, 0, 0, 0, 0, false);
130 : 1 : check_read(
131 : : "P2147483647M", EXESS_OUT_OF_RANGE, 12, 0, 0, 0, 0, 0, 0, 0, false);
132 : 1 : check_read("P24856D", EXESS_OUT_OF_RANGE, 7, 0, 0, 0, 0, 0, 0, 0, false);
133 : 1 : check_read("PT596524H", EXESS_OUT_OF_RANGE, 9, 0, 0, 0, 0, 0, 0, 0, false);
134 : 1 : check_read("PT35791395M", EXESS_OUT_OF_RANGE, 11, 0, 0, 0, 0, 0, 0, 0, false);
135 : 1 : check_read(
136 : : "PT2147483647S", EXESS_OUT_OF_RANGE, 13, 0, 0, 0, 0, 0, 0, 0, false);
137 : 1 : check_read("PT0.1000000000S",
138 : : EXESS_EXPECTED_SECOND_TAG,
139 : : 13,
140 : : 0,
141 : : 0,
142 : : 0,
143 : : 0,
144 : : 0,
145 : : 0,
146 : : 0,
147 : : false);
148 : :
149 : : // Repeated fields
150 : 1 : check_read("P1Y2Y", EXESS_BAD_ORDER, 5, 1, 0, 0, 0, 0, 0, 0, false);
151 : 1 : check_read("P1M2M", EXESS_BAD_ORDER, 5, 0, 1, 0, 0, 0, 0, 0, false);
152 : 1 : check_read("PT1H2H", EXESS_BAD_ORDER, 6, 0, 0, 0, 1, 0, 0, 0, false);
153 : 1 : check_read("PT1M2M", EXESS_BAD_ORDER, 6, 0, 0, 0, 0, 1, 0, 0, false);
154 : :
155 : : // Trailing garbage
156 : 1 : check_read("P1YM5D", EXESS_SUCCESS, 3, 1, 0, 0, 0, 0, 0, 0, false);
157 : 1 : check_read("PT1S2", EXESS_SUCCESS, 4, 0, 0, 0, 0, 0, 1, 0, false);
158 : :
159 : : // Out of range fields
160 : 1 : check_read("P18446744073709551616Y",
161 : : EXESS_OUT_OF_RANGE,
162 : : 20,
163 : : 0,
164 : : 0,
165 : : 0,
166 : : 0,
167 : : 0,
168 : : 0,
169 : : 0,
170 : : false);
171 : 1 : check_read("PT18446744073709551616H",
172 : : EXESS_OUT_OF_RANGE,
173 : : 21,
174 : : 0,
175 : : 0,
176 : : 0,
177 : : 0,
178 : : 0,
179 : : 0,
180 : : 0,
181 : : false);
182 : :
183 : : // Garbage
184 : 1 : check_read("P-20M", EXESS_EXPECTED_DIGIT, 1, 0, 0, 0, 0, 0, 0, 0, false);
185 : 1 : check_read("P20MT", EXESS_EXPECTED_DIGIT, 5, 0, 20, 0, 0, 0, 0, 0, false);
186 : 1 : check_read("P15.5Y", EXESS_EXPECTED_DATE_TAG, 3, 0, 0, 0, 0, 0, 0, 0, false);
187 : 1 : check_read("P1.5Y", EXESS_EXPECTED_DATE_TAG, 2, 0, 0, 0, 0, 0, 0, 0, false);
188 : 1 : check_read("P1.5M", EXESS_EXPECTED_DATE_TAG, 2, 0, 0, 0, 0, 0, 0, 0, false);
189 : 1 : check_read("P1.5D", EXESS_EXPECTED_DATE_TAG, 2, 0, 0, 0, 0, 0, 0, 0, false);
190 : 1 : check_read(
191 : : "PT1.5H", EXESS_EXPECTED_SECOND_TAG, 5, 0, 0, 0, 0, 0, 0, 0, false);
192 : 1 : check_read(
193 : : "PT1.5M", EXESS_EXPECTED_SECOND_TAG, 5, 0, 0, 0, 0, 0, 0, 0, false);
194 : 1 : check_read("P1D2H", EXESS_SUCCESS, 3, 0, 0, 1, 0, 0, 0, 0, false);
195 : 1 : check_read("1Y2M", EXESS_EXPECTED_DURATION, 0, 0, 0, 0, 0, 0, 0, 0, false);
196 : 1 : check_read("P2M1Y", EXESS_BAD_ORDER, 5, 0, 2, 0, 0, 0, 0, 0, false);
197 : 1 : check_read("P2D1Y", EXESS_SUCCESS, 3, 0, 0, 2, 0, 0, 0, 0, false);
198 : 1 : check_read("P", EXESS_EXPECTED_DIGIT, 1, 0, 0, 0, 0, 0, 0, 0, false);
199 : 1 : check_read(
200 : : "PT15.5H", EXESS_EXPECTED_SECOND_TAG, 6, 0, 0, 0, 0, 0, 0, 0, false);
201 : 1 : check_read("PT2M1H", EXESS_BAD_ORDER, 6, 0, 0, 0, 0, 2, 0, 0, false);
202 : 1 : check_read("PT15.S", EXESS_EXPECTED_DIGIT, 5, 0, 0, 0, 0, 0, 0, 0, false);
203 : 1 : check_read("P1Q", EXESS_EXPECTED_DATE_TAG, 2, 0, 0, 0, 0, 0, 0, 0, false);
204 : 1 : check_read("PT1Q", EXESS_EXPECTED_TIME_TAG, 3, 0, 0, 0, 0, 0, 0, 0, false);
205 : 1 : check_read("P-1Y", EXESS_EXPECTED_DIGIT, 1, 0, 0, 0, 0, 0, 0, 0, false);
206 : 1 : check_read("P-1M", EXESS_EXPECTED_DIGIT, 1, 0, 0, 0, 0, 0, 0, 0, false);
207 : 1 : check_read("P-1D", EXESS_EXPECTED_DIGIT, 1, 0, 0, 0, 0, 0, 0, 0, false);
208 : 1 : check_read("PT-1H", EXESS_EXPECTED_DIGIT, 2, 0, 0, 0, 0, 0, 0, 0, false);
209 : 1 : check_read("PT-1M", EXESS_EXPECTED_DIGIT, 2, 0, 0, 0, 0, 0, 0, 0, false);
210 : 1 : check_read("PT-1S", EXESS_EXPECTED_DIGIT, 2, 0, 0, 0, 0, 0, 0, 0, false);
211 : 1 : check_read(
212 : : "P4294967296Y", EXESS_OUT_OF_RANGE, 12, 0, 0, 0, 0, 0, 0, 0, false);
213 : 1 : check_read(
214 : : "P4294967296M", EXESS_OUT_OF_RANGE, 12, 0, 0, 0, 0, 0, 0, 0, false);
215 : 1 : check_read(
216 : : "P4294967296D", EXESS_OUT_OF_RANGE, 12, 0, 0, 0, 0, 0, 0, 0, false);
217 : 1 : check_read(
218 : : "PT4294967296H", EXESS_OUT_OF_RANGE, 13, 0, 0, 0, 0, 0, 0, 0, false);
219 : 1 : check_read(
220 : : "PT4294967296M", EXESS_OUT_OF_RANGE, 13, 0, 0, 0, 0, 0, 0, 0, false);
221 : 1 : check_read("", EXESS_EXPECTED_DURATION, 0, 0, 0, 0, 0, 0, 0, 0, false);
222 : 1 : }
223 : :
224 : : static void
225 : 43 : check_write(const ExessDuration value,
226 : : const ExessStatus expected_status,
227 : : const size_t buf_size,
228 : : const char* const expected_string)
229 : : {
230 : 43 : char buf[EXESS_MAX_DURATION_LENGTH + 1] = {42};
231 : 43 : init_out_buf(sizeof(buf), buf);
232 : :
233 [ - + ]: 43 : assert(buf_size <= sizeof(buf));
234 : :
235 : 43 : const ExessResult r = exess_write_duration(value, buf_size, buf);
236 [ - + ]: 43 : assert(r.status == expected_status);
237 [ + + ]: 43 : if (!r.status) {
238 [ - + ]: 20 : assert(!strcmp(buf, expected_string));
239 [ - + ]: 20 : assert(r.count == strlen(buf));
240 [ - + ]: 20 : assert(exess_write_duration(value, 0, NULL).count == r.count);
241 : : }
242 : 43 : }
243 : :
244 : : static void
245 : 1 : test_write_duration(void)
246 : : {
247 : 1 : check_write(zero, EXESS_SUCCESS, 4, "P0Y");
248 : :
249 : 1 : check_write(
250 : : lowest, EXESS_SUCCESS, 39, "-P178956970Y7M24855DT3H14M7.999999999S");
251 : :
252 : 1 : check_write(
253 : : highest, EXESS_SUCCESS, 38, "P178956970Y7M24855DT3H14M7.999999999S");
254 : :
255 : 1 : check_write(
256 : : longest, EXESS_SUCCESS, 42, "-P176631622Y11M12659DT18H24M35.999999999S");
257 : :
258 : 1 : check_write(longest_ym, EXESS_SUCCESS, 16, "-P176631622Y11M");
259 : 1 : check_write(longest_dt, EXESS_SUCCESS, 29, "-P12659DT18H24M35.999999999S");
260 : :
261 : 1 : check_write(year, EXESS_SUCCESS, 4, "P1Y");
262 : 1 : check_write(month, EXESS_SUCCESS, 4, "P1M");
263 : 1 : check_write(day, EXESS_SUCCESS, 4, "P1D");
264 : 1 : check_write(hour, EXESS_SUCCESS, 5, "PT1H");
265 : 1 : check_write(minute, EXESS_SUCCESS, 5, "PT1M");
266 : 1 : check_write(second, EXESS_SUCCESS, 5, "PT1S");
267 : 1 : check_write(nanosecond, EXESS_SUCCESS, 15, "PT0.000000001S");
268 : :
269 : 1 : check_write(n_year, EXESS_SUCCESS, 5, "-P1Y");
270 : 1 : check_write(n_month, EXESS_SUCCESS, 5, "-P1M");
271 : 1 : check_write(n_day, EXESS_SUCCESS, 5, "-P1D");
272 : 1 : check_write(n_hour, EXESS_SUCCESS, 6, "-PT1H");
273 : 1 : check_write(n_minute, EXESS_SUCCESS, 6, "-PT1M");
274 : 1 : check_write(n_second, EXESS_SUCCESS, 6, "-PT1S");
275 : 1 : check_write(n_nanosecond, EXESS_SUCCESS, 16, "-PT0.000000001S");
276 : :
277 : 1 : check_write(garbage1, EXESS_BAD_VALUE, 42, "");
278 : 1 : check_write(garbage2, EXESS_BAD_VALUE, 42, "");
279 : 1 : check_write(garbage3, EXESS_BAD_VALUE, 42, "");
280 : 1 : check_write(garbage4, EXESS_BAD_VALUE, 42, "");
281 : 1 : check_write(garbage5, EXESS_BAD_VALUE, 42, "");
282 : 1 : check_write(garbage6, EXESS_BAD_VALUE, 42, "");
283 : 1 : check_write(garbage7, EXESS_OUT_OF_RANGE, 42, "");
284 : 1 : check_write(garbage8, EXESS_OUT_OF_RANGE, 42, "");
285 : 1 : check_write(garbage9, EXESS_OUT_OF_RANGE, 42, "");
286 : :
287 : 1 : check_write(zero, EXESS_NO_SPACE, 3, "");
288 : 1 : check_write(lowest, EXESS_NO_SPACE, 24, "");
289 : 1 : check_write(highest, EXESS_NO_SPACE, 4, "");
290 : 1 : check_write(highest, EXESS_NO_SPACE, 10, "");
291 : 1 : check_write(highest, EXESS_NO_SPACE, 13, "");
292 : 1 : check_write(highest, EXESS_NO_SPACE, 16, "");
293 : 1 : check_write(highest, EXESS_NO_SPACE, 20, "");
294 : 1 : check_write(highest, EXESS_NO_SPACE, 23, "");
295 : 1 : check_write(year, EXESS_NO_SPACE, 3, "");
296 : 1 : check_write(month, EXESS_NO_SPACE, 3, "");
297 : 1 : check_write(day, EXESS_NO_SPACE, 3, "");
298 : 1 : check_write(hour, EXESS_NO_SPACE, 4, "");
299 : 1 : check_write(minute, EXESS_NO_SPACE, 4, "");
300 : 1 : check_write(second, EXESS_NO_SPACE, 4, "");
301 : :
302 : : // Check that nothing is written when there isn't enough space
303 : 1 : char c = 42;
304 : 1 : const ExessResult r = exess_write_duration(zero, 0, &c);
305 [ - + ]: 1 : assert(c == 42);
306 [ - + ]: 1 : assert(r.status == EXESS_NO_SPACE);
307 [ - + ]: 1 : assert(r.count == 3);
308 : 1 : }
309 : :
310 : : static void
311 : 1024 : check_round_trip(const ExessDuration value)
312 : : {
313 : 1024 : ExessDuration parsed_value = {0, 0, 0};
314 : 1024 : char buf[EXESS_MAX_DURATION_LENGTH + 1] = {42};
315 : 1024 : init_out_buf(sizeof(buf), buf);
316 : :
317 [ - + ]: 1024 : assert(exess_write_duration(value, 0, NULL).count <=
318 : : EXESS_MAX_DURATION_LENGTH);
319 : :
320 [ - + ]: 1024 : assert(!exess_write_duration(value, sizeof(buf), buf).status);
321 [ - + ]: 1024 : assert(!exess_read_duration(buf, &parsed_value).status);
322 [ - + ]: 1024 : assert(parsed_value.months == value.months);
323 [ - + ]: 1024 : assert(parsed_value.seconds == value.seconds);
324 [ - + ]: 1024 : assert(parsed_value.nanoseconds == value.nanoseconds);
325 : 1024 : }
326 : :
327 : : static void
328 : 1 : test_round_trip(const ExessNumTestOptions opts)
329 : : {
330 : 1 : fprintf(stderr, "Testing xsd:duration randomly with seed %u\n", opts.seed);
331 : :
332 : 1 : uint32_t rng = opts.seed;
333 [ + + ]: 1025 : for (size_t i = 0; i < opts.n_tests; ++i) {
334 : 1024 : rng = lcg32(rng);
335 : :
336 : 1024 : const int32_t months = (int32_t)rng;
337 : :
338 : 1024 : rng = lcg32(rng);
339 : :
340 [ + + ]: 1024 : const int32_t seconds = (months < 0 ? -1 : 1) * (int32_t)(rng % INT32_MAX);
341 : :
342 : 1024 : rng = lcg32(rng);
343 : :
344 : 1024 : const int32_t nanoseconds =
345 [ + + ]: 1024 : (months < 0 ? -1 : 1) * (int32_t)(rng % 1000000000);
346 : :
347 : 1024 : const ExessDuration value = {months, seconds, nanoseconds};
348 : 1024 : check_round_trip(value);
349 : :
350 : 1024 : print_num_test_progress(i, opts.n_tests);
351 : : }
352 : 1 : }
353 : :
354 : : int
355 : 1 : main(int argc, char** argv)
356 : : {
357 : : ExessNumTestOptions opts =
358 : 1 : parse_num_test_options(argc, argv, 16384U, 0U, INT32_MAX);
359 : :
360 [ + - ]: 1 : opts.n_tests = (opts.n_tests >= 4096U) ? (opts.n_tests / 16U) : 256U;
361 : :
362 [ + - ]: 1 : if (!opts.error) {
363 : 1 : test_read_duration();
364 : 1 : test_write_duration();
365 : 1 : test_round_trip(opts);
366 : : }
367 : :
368 : 1 : return (int)opts.error;
369 : : }
|