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 "double_test_utils.h"
7 : : #include "int_test_utils.h"
8 : : #include "time_test_utils.h"
9 : :
10 : : #include <exess/exess.h>
11 : :
12 : : #include <assert.h>
13 : : #include <stdbool.h>
14 : : #include <stdint.h>
15 : : #include <string.h>
16 : :
17 : : static const ExessTime nozone = {EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
18 : : static const ExessTime utc = {EXESS_TIMEZONE_UTC, 12, 15, 1, 250000000};
19 : : static const ExessTime zoned = {INIT_ZONE(11, 30), 23, 59, 59, 1000000};
20 : : static const ExessTime high = {INIT_ZONE(11, 30), 23, 59, 59, 999999999};
21 : : static const ExessTime garbage1 = {INIT_ZONE(11, 30), 0, 0, 0, 1000000000};
22 : : static const ExessTime garbage2 = {INIT_ZONE(11, 30), 0, 0, 60, 0};
23 : : static const ExessTime garbage3 = {INIT_ZONE(11, 30), 0, 60, 0, 0};
24 : : static const ExessTime garbage4 = {INIT_ZONE(11, 30), 24, 0, 0, 1};
25 : : static const ExessTime garbage5 = {INIT_ZONE(11, 30), 24, 0, 1, 0};
26 : : static const ExessTime garbage6 = {INIT_ZONE(11, 30), 24, 1, 0, 0};
27 : : static const ExessTime garbage7 = {INIT_ZONE(11, 30), 25, 0, 0, 0};
28 : : static const ExessTime garbage8 = {INIT_ZONE(11, 30), 24, 0, 0, 0};
29 : :
30 : : static void
31 : 40 : check_read(const char* const string,
32 : : const ExessStatus expected_status,
33 : : const uint8_t expected_hour,
34 : : const uint8_t expected_minute,
35 : : const uint8_t expected_second,
36 : : const uint32_t expected_nanosecond,
37 : : const int8_t expected_tz_hour,
38 : : const int8_t expected_tz_minute,
39 : : const bool expected_tz_is_present,
40 : : const size_t expected_count)
41 : : {
42 : 40 : ExessTime value = {EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
43 : :
44 : 40 : const ExessResult r = exess_read_time(string, &value);
45 [ - + ]: 40 : assert(r.status == expected_status);
46 [ - + ]: 40 : assert(r.count == expected_count);
47 [ - + ]: 40 : assert(value.hour == expected_hour);
48 [ - + ]: 40 : assert(value.minute == expected_minute);
49 [ - + ]: 40 : assert(value.second == expected_second);
50 [ - + ]: 40 : assert(value.nanosecond == expected_nanosecond);
51 [ + + + + : 40 : assert((!expected_tz_is_present && value.zone == EXESS_TIMEZONE_LOCAL) ||
- + ]
52 : : value.zone == (4 * expected_tz_hour) + (expected_tz_minute / 15));
53 : 40 : }
54 : :
55 : : static void
56 : 1 : test_read_time(void)
57 : : {
58 : : // No value
59 : 1 : check_read("", EXESS_EXPECTED_DIGIT, 0, 0, 0, 0, 0, 0, false, 0);
60 : 1 : check_read("\t\n\r ", EXESS_EXPECTED_DIGIT, 0, 0, 0, 0, 0, 0, false, 4);
61 : :
62 : : // Good values
63 : 1 : check_read("13:20:00", EXESS_SUCCESS, 13, 20, 0, 0, 0, 0, false, 8);
64 : 1 : check_read(
65 : : "13:20:30.5555", EXESS_SUCCESS, 13, 20, 30, 555500000, 0, 0, false, 13);
66 : 1 : check_read("13:20:00-05:00", EXESS_SUCCESS, 13, 20, 0, 0, -5, 0, true, 14);
67 : 1 : check_read("13:20:00Z", EXESS_SUCCESS, 13, 20, 0, 0, 0, 0, true, 9);
68 : 1 : check_read("00:00:00", EXESS_SUCCESS, 0, 0, 0, 0, 0, 0, false, 8);
69 : 1 : check_read("24:00:00", EXESS_SUCCESS, 0, 0, 0, 0, 0, 0, false, 8);
70 : 1 : check_read("21:32:52", EXESS_SUCCESS, 21, 32, 52, 0, 0, 0, false, 8);
71 : 1 : check_read("21:32:52+02:00", EXESS_SUCCESS, 21, 32, 52, 0, 2, 0, true, 14);
72 : 1 : check_read("19:32:52Z", EXESS_SUCCESS, 19, 32, 52, 0, 0, 0, true, 9);
73 : 1 : check_read("19:32:52+00:00", EXESS_SUCCESS, 19, 32, 52, 0, 0, 0, true, 14);
74 : 1 : check_read(
75 : : "21:32:52.12679", EXESS_SUCCESS, 21, 32, 52, 126790000, 0, 0, false, 14);
76 : :
77 : : // Longest possible string
78 : 1 : check_read("23:59:59.000000001-14:00",
79 : : EXESS_SUCCESS,
80 : : 23,
81 : : 59,
82 : : 59,
83 : : 1,
84 : : -14,
85 : : 0,
86 : : true,
87 : : EXESS_MAX_TIME_LENGTH);
88 : :
89 : : // Non-canonical form
90 : 1 : check_read("\t\n\r 13:20:00 ", EXESS_SUCCESS, 13, 20, 0, 0, 0, 0, false, 12);
91 : :
92 : : // Trailing whitespace
93 : 1 : check_read("13:20:45 ", EXESS_SUCCESS, 13, 20, 45, 0, 0, 0, false, 8);
94 : 1 : check_read("13:20:45\f", EXESS_SUCCESS, 13, 20, 45, 0, 0, 0, false, 8);
95 : 1 : check_read("13:20:45\n", EXESS_SUCCESS, 13, 20, 45, 0, 0, 0, false, 8);
96 : 1 : check_read("13:20:45\r", EXESS_SUCCESS, 13, 20, 45, 0, 0, 0, false, 8);
97 : 1 : check_read("13:20:45\t", EXESS_SUCCESS, 13, 20, 45, 0, 0, 0, false, 8);
98 : 1 : check_read("13:20:45\v", EXESS_SUCCESS, 13, 20, 45, 0, 0, 0, false, 8);
99 : :
100 : : // Trailing garbage
101 : 1 : check_read("13:20:00junk", EXESS_SUCCESS, 13, 20, 0, 0, 0, 0, false, 8);
102 : 1 : check_read("13:20:00Zjunk", EXESS_SUCCESS, 13, 20, 0, 0, 0, 0, true, 9);
103 : 1 : check_read("13:20:00A", EXESS_SUCCESS, 13, 20, 0, 0, 0, 0, false, 8);
104 : :
105 : : // Too many nanosecond digits (expecting a timezone after 9 digits)
106 : 1 : check_read("01:02:03.123456789123",
107 : : EXESS_OUT_OF_RANGE,
108 : : 1,
109 : : 2,
110 : : 3,
111 : : 123456789,
112 : : 0,
113 : : 0,
114 : : false,
115 : : 18);
116 : :
117 : : // Garbage
118 : 1 : check_read("13.20.00", EXESS_EXPECTED_COLON, 13, 0, 0, 0, 0, 0, false, 2);
119 : 1 : check_read("13:20:", EXESS_EXPECTED_DIGIT, 13, 20, 0, 0, 0, 0, false, 6);
120 : 1 : check_read("5:20:00", EXESS_EXPECTED_DIGIT, 5, 0, 0, 0, 0, 0, false, 1);
121 : 1 : check_read("13:20", EXESS_EXPECTED_COLON, 13, 20, 0, 0, 0, 0, false, 5);
122 : 1 : check_read("13:20.5:00", EXESS_EXPECTED_COLON, 13, 20, 0, 0, 0, 0, false, 5);
123 : 1 : check_read("13:65:00", EXESS_OUT_OF_RANGE, 13, 65, 0, 0, 0, 0, false, 5);
124 : 1 : check_read("21:32", EXESS_EXPECTED_COLON, 21, 32, 0, 0, 0, 0, false, 5);
125 : 1 : check_read("25:25:10", EXESS_OUT_OF_RANGE, 25, 0, 0, 0, 0, 0, false, 2);
126 : 1 : check_read("-10:00:00", EXESS_EXPECTED_DIGIT, 0, 0, 0, 0, 0, 0, false, 0);
127 : 1 : check_read("1:20:10", EXESS_EXPECTED_DIGIT, 1, 0, 0, 0, 0, 0, false, 1);
128 : 1 : check_read("13:20:00.", EXESS_EXPECTED_DIGIT, 13, 20, 0, 0, 0, 0, false, 9);
129 : 1 : check_read("24:01:00", EXESS_OUT_OF_RANGE, 24, 1, 0, 0, 0, 0, false, 5);
130 : 1 : check_read("24:00:01", EXESS_OUT_OF_RANGE, 24, 0, 1, 0, 0, 0, false, 8);
131 : 1 : check_read(
132 : : "24:00:00.000000001", EXESS_OUT_OF_RANGE, 24, 0, 0, 1, 0, 0, false, 18);
133 : 1 : check_read("24:00:00.000000001+02:00",
134 : : EXESS_OUT_OF_RANGE,
135 : : 24,
136 : : 0,
137 : : 0,
138 : : 1,
139 : : 0,
140 : : 0,
141 : : false,
142 : : 18);
143 : 1 : }
144 : :
145 : : static void
146 : 17 : check_write(const ExessTime value,
147 : : const ExessStatus expected_status,
148 : : const size_t buf_size,
149 : : const char* const expected_string)
150 : : {
151 : 17 : char buf[EXESS_MAX_TIME_LENGTH + 1] = {1, 2, 3, 4, 5, 6, 7, 8, 9,
152 : : 10, 11, 12, 13, 14, 15, 16, 17, 18,
153 : : 19, 20, 21, 22, 23, 24, 25};
154 : :
155 [ - + ]: 17 : assert(buf_size <= sizeof(buf));
156 : :
157 : 17 : const ExessResult r = exess_write_time(value, buf_size, buf);
158 [ - + ]: 17 : assert(r.status == expected_status);
159 [ + + ]: 17 : if (!r.status) {
160 [ - + ]: 4 : assert(!strcmp(buf, expected_string));
161 [ - + ]: 4 : assert(r.count == strlen(buf));
162 [ - + ]: 4 : assert(exess_write_time(value, 0, NULL).count == r.count);
163 : : }
164 : 17 : }
165 : :
166 : : static void
167 : 1 : test_write_time(void)
168 : : {
169 : 1 : check_write(nozone, EXESS_SUCCESS, 9, "00:00:00");
170 : 1 : check_write(utc, EXESS_SUCCESS, 13, "12:15:01.25Z");
171 : 1 : check_write(zoned, EXESS_SUCCESS, 19, "23:59:59.001+11:30");
172 : 1 : check_write(high, EXESS_SUCCESS, 25, "23:59:59.999999999+11:30");
173 : :
174 : 1 : check_write(garbage1, EXESS_BAD_VALUE, 19, "");
175 : 1 : check_write(garbage2, EXESS_BAD_VALUE, 19, "");
176 : 1 : check_write(garbage3, EXESS_BAD_VALUE, 19, "");
177 : 1 : check_write(garbage4, EXESS_BAD_VALUE, 19, "");
178 : 1 : check_write(garbage5, EXESS_BAD_VALUE, 19, "");
179 : 1 : check_write(garbage6, EXESS_BAD_VALUE, 19, "");
180 : 1 : check_write(garbage7, EXESS_BAD_VALUE, 19, "");
181 : 1 : check_write(garbage8, EXESS_BAD_VALUE, 19, "");
182 : :
183 : 1 : check_write(nozone, EXESS_NO_SPACE, 8, "");
184 : 1 : check_write(utc, EXESS_NO_SPACE, 12, "");
185 : 1 : check_write(zoned, EXESS_NO_SPACE, 18, "");
186 : 1 : check_write(zoned, EXESS_NO_SPACE, 12, "");
187 : 1 : check_write(high, EXESS_NO_SPACE, 14, "");
188 : :
189 : : // Check that nothing is written when there isn't enough space
190 : 1 : char c = 42;
191 : 1 : const ExessResult r = exess_write_time(nozone, 0, &c);
192 [ - + ]: 1 : assert(c == 42);
193 [ - + ]: 1 : assert(r.status == EXESS_NO_SPACE);
194 [ - + ]: 1 : assert(r.count == 8);
195 : 1 : }
196 : :
197 : : static void
198 : 5760 : check_round_trip(const ExessTime value)
199 : : {
200 : 5760 : char buf[EXESS_MAX_TIME_LENGTH + 1] = {1, 2, 3, 4, 5, 6, 7, 8, 9,
201 : : 10, 11, 12, 13, 14, 15, 16, 17, 18,
202 : : 19, 20, 21, 22, 23, 24, 25};
203 : :
204 : 5760 : ExessTime parsed_value = {EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
205 [ - + ]: 5760 : assert(!exess_write_time(value, sizeof(buf), buf).status);
206 [ - + ]: 5760 : assert(!exess_read_time(buf, &parsed_value).status);
207 [ - + ]: 5760 : assert(parsed_value.hour == value.hour);
208 [ - + ]: 5760 : assert(parsed_value.minute == value.minute);
209 [ - + ]: 5760 : assert(double_matches(parsed_value.second, value.second));
210 [ - + ]: 5760 : assert(parsed_value.zone == value.zone);
211 : 5760 : }
212 : :
213 : : static void
214 : 1 : test_round_trip(void)
215 : : {
216 : 1 : uint32_t rng = 0;
217 [ + + ]: 25 : for (uint8_t h = 0; h < 24; ++h) {
218 [ + + ]: 1464 : for (uint8_t m = 0; m < 60; ++m) {
219 : 1440 : rng = lcg32(rng);
220 : :
221 : 1440 : const uint32_t ns = rng % 1000000000U;
222 : :
223 : 1440 : rng = lcg32(rng);
224 : :
225 : 1440 : const uint8_t s = (uint8_t)(rng % 60U);
226 : 1440 : const ExessTime no_zone = {EXESS_TIMEZONE_LOCAL, h, m, s, ns};
227 : 1440 : const ExessTime lowest_zone = {INIT_ZONE(-14, 0), h, m, s, ns};
228 : 1440 : const ExessTime highest_zone = {INIT_ZONE(14, 0), h, m, s, ns};
229 : :
230 : 1440 : check_round_trip(no_zone);
231 : 1440 : check_round_trip(lowest_zone);
232 : 1440 : check_round_trip(highest_zone);
233 : :
234 : 1440 : const ExessTime value = {random_timezone(&rng), h, m, s, ns};
235 : 1440 : check_round_trip(value);
236 : : }
237 : : }
238 : 1 : }
239 : :
240 : : int
241 : 1 : main(void)
242 : : {
243 : 1 : test_read_time();
244 : 1 : test_write_time();
245 : 1 : test_round_trip();
246 : :
247 : 1 : return 0;
248 : : }
|