Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2026 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file exchangedb/test_work_shards.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `work_shards`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_begin_shard(),
23 : * #TALER_EXCHANGEDB_update_shard_progress(),
24 : * #TALER_EXCHANGEDB_abort_shard() and, for its `work_shards` half,
25 : * #TALER_EXCHANGEDB_delete_shard_locks().
26 : *
27 : * `work_shards` has no foreign keys. Every check uses a job name of its
28 : * own so that the shard numbering of one check cannot confuse the next; the
29 : * delete-locks check runs last because it empties the table.
30 : */
31 : #include "test_common.h"
32 : #include "exchange-database/abort_shard.h"
33 : #include "exchange-database/begin_shard.h"
34 : #include "exchange-database/delete_shard_locks.h"
35 : #include "exchange-database/update_shard_progress.h"
36 :
37 :
38 : /**
39 : * Lease we ask for; long enough that no check can lose its shard while it
40 : * is running.
41 : */
42 : #define LEASE GNUNET_TIME_UNIT_HOURS
43 :
44 :
45 : /**
46 : * Nothing to abort and no progress to record while the table is empty.
47 : *
48 : * @param pg the database context
49 : * @return 0 on success
50 : */
51 : static int
52 1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
53 : {
54 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
55 : TALER_EXCHANGEDB_abort_shard (pg,
56 : "ws-nothing",
57 : 0,
58 : 10));
59 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
60 : TALER_EXCHANGEDB_update_shard_progress (pg,
61 : "ws-nothing",
62 : 0,
63 : 10,
64 : 5,
65 : LEASE));
66 1 : FAILIF (0 != TDB_count (pg,
67 : "FROM work_shards"));
68 1 : return 0;
69 : }
70 :
71 :
72 : /**
73 : * The first shard of a job starts at 0, and a job whose only shard is
74 : * leased gets the next range rather than the leased one.
75 : *
76 : * @param pg the database context
77 : * @return 0 on success
78 : */
79 : static int
80 1 : check_begin (struct TALER_EXCHANGEDB_PostgresContext *pg)
81 : {
82 : uint64_t start;
83 : uint64_t end;
84 : uint64_t progress;
85 :
86 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
87 : TALER_EXCHANGEDB_begin_shard (pg,
88 : "ws-begin",
89 : LEASE,
90 : 100,
91 : &start,
92 : &end,
93 : &progress));
94 1 : FAILIF (0 != start);
95 1 : FAILIF (100 != end);
96 1 : FAILIF (0 != progress);
97 :
98 : /* the shard we just took is leased, so this must open the next one */
99 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
100 : TALER_EXCHANGEDB_begin_shard (pg,
101 : "ws-begin",
102 : LEASE,
103 : 100,
104 : &start,
105 : &end,
106 : &progress));
107 1 : FAILIF (100 != start);
108 1 : FAILIF (200 != end);
109 1 : FAILIF (100 != progress);
110 1 : FAILIF (2 != TDB_count (pg,
111 : "FROM work_shards"
112 : " WHERE job_name='ws-begin'"));
113 : /* a different job has its own numbering */
114 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
115 : TALER_EXCHANGEDB_begin_shard (pg,
116 : "ws-begin-other",
117 : LEASE,
118 : 7,
119 : &start,
120 : &end,
121 : &progress));
122 1 : FAILIF (0 != start);
123 1 : FAILIF (7 != end);
124 1 : return 0;
125 : }
126 :
127 :
128 : /**
129 : * Progress is recorded, never rewound, and completes the shard exactly
130 : * when it reaches the end of the range.
131 : *
132 : * @param pg the database context
133 : * @return 0 on success
134 : */
135 : static int
136 1 : check_progress (struct TALER_EXCHANGEDB_PostgresContext *pg)
137 : {
138 : uint64_t start;
139 : uint64_t end;
140 : uint64_t progress;
141 :
142 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
143 : TALER_EXCHANGEDB_begin_shard (pg,
144 : "ws-progress",
145 : LEASE,
146 : 100,
147 : &start,
148 : &end,
149 : &progress));
150 1 : FAILIF (0 != start);
151 :
152 : /* partial progress leaves the shard open */
153 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
154 : TALER_EXCHANGEDB_update_shard_progress (pg,
155 : "ws-progress",
156 : 0,
157 : 100,
158 : 50,
159 : LEASE));
160 1 : FAILIF (1 != TDB_count (pg,
161 : "FROM work_shards"
162 : " WHERE job_name='ws-progress'"
163 : " AND progress_row=50"
164 : " AND NOT completed"));
165 :
166 : /* ...and cannot be pushed back by a straggler */
167 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
168 : TALER_EXCHANGEDB_update_shard_progress (pg,
169 : "ws-progress",
170 : 0,
171 : 100,
172 : 30,
173 : LEASE));
174 1 : FAILIF (1 != TDB_count (pg,
175 : "FROM work_shards"
176 : " WHERE job_name='ws-progress'"
177 : " AND progress_row=50"));
178 :
179 : /* reaching the end completes it */
180 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
181 : TALER_EXCHANGEDB_update_shard_progress (pg,
182 : "ws-progress",
183 : 0,
184 : 100,
185 : 100,
186 : LEASE));
187 1 : FAILIF (1 != TDB_count (pg,
188 : "FROM work_shards"
189 : " WHERE job_name='ws-progress'"
190 : " AND completed"));
191 :
192 : /* a shard range that does not exist is not silently created */
193 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
194 : TALER_EXCHANGEDB_update_shard_progress (pg,
195 : "ws-progress",
196 : 1000,
197 : 1100,
198 : 1050,
199 : LEASE));
200 : /* neither is one belonging to another job */
201 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
202 : TALER_EXCHANGEDB_update_shard_progress (pg,
203 : "ws-progress-elsewhere",
204 : 0,
205 : 100,
206 : 100,
207 : LEASE));
208 1 : FAILIF (1 != TDB_count (pg,
209 : "FROM work_shards"
210 : " WHERE job_name='ws-progress'"));
211 1 : return 0;
212 : }
213 :
214 :
215 : /**
216 : * An aborted shard is handed out again, together with the progress the
217 : * previous worker had committed. A completed shard is not.
218 : *
219 : * @param pg the database context
220 : * @return 0 on success
221 : */
222 : static int
223 1 : check_abort_and_takeover (struct TALER_EXCHANGEDB_PostgresContext *pg)
224 : {
225 : uint64_t start;
226 : uint64_t end;
227 : uint64_t progress;
228 :
229 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
230 : TALER_EXCHANGEDB_begin_shard (pg,
231 : "ws-abort",
232 : LEASE,
233 : 10,
234 : &start,
235 : &end,
236 : &progress));
237 1 : FAILIF (0 != start);
238 1 : FAILIF (10 != end);
239 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
240 : TALER_EXCHANGEDB_update_shard_progress (pg,
241 : "ws-abort",
242 : 0,
243 : 10,
244 : 4,
245 : LEASE));
246 :
247 : /* aborting a range that does not exist changes nothing */
248 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
249 : TALER_EXCHANGEDB_abort_shard (pg,
250 : "ws-abort",
251 : 0,
252 : 11));
253 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
254 : TALER_EXCHANGEDB_abort_shard (pg,
255 : "ws-abort-elsewhere",
256 : 0,
257 : 10));
258 :
259 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
260 : TALER_EXCHANGEDB_abort_shard (pg,
261 : "ws-abort",
262 : 0,
263 : 10));
264 : /* the abandoned shard comes back, resuming where it was left off */
265 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
266 : TALER_EXCHANGEDB_begin_shard (pg,
267 : "ws-abort",
268 : LEASE,
269 : 10,
270 : &start,
271 : &end,
272 : &progress));
273 1 : FAILIF (0 != start);
274 1 : FAILIF (10 != end);
275 1 : FAILIF (4 != progress);
276 1 : FAILIF (1 != TDB_count (pg,
277 : "FROM work_shards"
278 : " WHERE job_name='ws-abort'"));
279 :
280 : /* once completed, aborting it still works but it is never handed out */
281 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
282 : TALER_EXCHANGEDB_update_shard_progress (pg,
283 : "ws-abort",
284 : 0,
285 : 10,
286 : 10,
287 : LEASE));
288 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
289 : TALER_EXCHANGEDB_abort_shard (pg,
290 : "ws-abort",
291 : 0,
292 : 10));
293 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
294 : TALER_EXCHANGEDB_begin_shard (pg,
295 : "ws-abort",
296 : LEASE,
297 : 10,
298 : &start,
299 : &end,
300 : &progress));
301 1 : FAILIF (10 != start);
302 1 : FAILIF (20 != end);
303 1 : return 0;
304 : }
305 :
306 :
307 : /**
308 : * Deleting the shard locks empties the table and restarts the numbering.
309 : * Runs last, as it discards what the other checks built.
310 : *
311 : * @param pg the database context
312 : * @return 0 on success
313 : */
314 : static int
315 1 : check_delete_locks (struct TALER_EXCHANGEDB_PostgresContext *pg)
316 : {
317 : uint64_t start;
318 : uint64_t end;
319 : uint64_t progress;
320 :
321 1 : FAILIF (0 == TDB_count (pg,
322 : "FROM work_shards"));
323 1 : FAILIF (GNUNET_OK !=
324 : TALER_EXCHANGEDB_delete_shard_locks (pg));
325 1 : FAILIF (0 != TDB_count (pg,
326 : "FROM work_shards"));
327 : /* ...and on an already empty table it is a no-op, not an error */
328 1 : FAILIF (GNUNET_OK !=
329 : TALER_EXCHANGEDB_delete_shard_locks (pg));
330 :
331 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
332 : TALER_EXCHANGEDB_begin_shard (pg,
333 : "ws-begin",
334 : LEASE,
335 : 100,
336 : &start,
337 : &end,
338 : &progress));
339 1 : FAILIF (0 != start);
340 1 : return 0;
341 : }
342 :
343 :
344 : /**
345 : * The checks to run, in order.
346 : */
347 : static const struct TDB_Test tests[] = {
348 : { "work-shards-empty",
349 : &check_empty },
350 : { "work-shards-begin",
351 : &check_begin },
352 : { "work-shards-progress",
353 : &check_progress },
354 : { "work-shards-abort-and-takeover",
355 : &check_abort_and_takeover },
356 : { "work-shards-delete-locks",
357 : &check_delete_locks },
358 : { NULL, NULL }
359 : };
360 :
361 :
362 : int
363 1 : main (int argc,
364 : char *const *argv)
365 : {
366 1 : return TDB_main (argc,
367 : argv,
368 : "test-work-shards",
369 : "Tests for the exchangedb `work_shards' table",
370 : tests);
371 : }
372 :
373 :
374 : /* end of test_work_shards.c */
|