exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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 */