test_work_shards.c (13697B)
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 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 53 { 54 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 55 TALER_EXCHANGEDB_abort_shard (pg, 56 "ws-nothing", 57 0, 58 10)); 59 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 FAILIF (0 != TDB_count (pg, 67 "FROM work_shards")); 68 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 check_begin (struct TALER_EXCHANGEDB_PostgresContext *pg) 81 { 82 uint64_t start; 83 uint64_t end; 84 uint64_t progress; 85 86 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 FAILIF (0 != start); 95 FAILIF (100 != end); 96 FAILIF (0 != progress); 97 98 /* the shard we just took is leased, so this must open the next one */ 99 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 FAILIF (100 != start); 108 FAILIF (200 != end); 109 FAILIF (100 != progress); 110 FAILIF (2 != TDB_count (pg, 111 "FROM work_shards" 112 " WHERE job_name='ws-begin'")); 113 /* a different job has its own numbering */ 114 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 FAILIF (0 != start); 123 FAILIF (7 != end); 124 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 check_progress (struct TALER_EXCHANGEDB_PostgresContext *pg) 137 { 138 uint64_t start; 139 uint64_t end; 140 uint64_t progress; 141 142 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 FAILIF (0 != start); 151 152 /* partial progress leaves the shard open */ 153 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 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 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 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 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 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 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 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 FAILIF (1 != TDB_count (pg, 209 "FROM work_shards" 210 " WHERE job_name='ws-progress'")); 211 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 check_abort_and_takeover (struct TALER_EXCHANGEDB_PostgresContext *pg) 224 { 225 uint64_t start; 226 uint64_t end; 227 uint64_t progress; 228 229 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 FAILIF (0 != start); 238 FAILIF (10 != end); 239 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 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 249 TALER_EXCHANGEDB_abort_shard (pg, 250 "ws-abort", 251 0, 252 11)); 253 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 254 TALER_EXCHANGEDB_abort_shard (pg, 255 "ws-abort-elsewhere", 256 0, 257 10)); 258 259 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 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 FAILIF (0 != start); 274 FAILIF (10 != end); 275 FAILIF (4 != progress); 276 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 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 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 289 TALER_EXCHANGEDB_abort_shard (pg, 290 "ws-abort", 291 0, 292 10)); 293 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 FAILIF (10 != start); 302 FAILIF (20 != end); 303 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 check_delete_locks (struct TALER_EXCHANGEDB_PostgresContext *pg) 316 { 317 uint64_t start; 318 uint64_t end; 319 uint64_t progress; 320 321 FAILIF (0 == TDB_count (pg, 322 "FROM work_shards")); 323 FAILIF (GNUNET_OK != 324 TALER_EXCHANGEDB_delete_shard_locks (pg)); 325 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 FAILIF (GNUNET_OK != 329 TALER_EXCHANGEDB_delete_shard_locks (pg)); 330 331 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 FAILIF (0 != start); 340 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 main (int argc, 364 char *const *argv) 365 { 366 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 */