exchange

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

testing_api_loop.c (25891B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2018-2023 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it
      6   under the terms of the GNU General Public License as published
      7   by the Free Software Foundation; either version 3, or (at your
      8   option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not, see
     17   <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file testing/testing_api_loop.c
     22  * @brief main interpreter loop for testcases
     23  * @author Christian Grothoff
     24  * @author Marcello Stanisci
     25  */
     26 #include "taler/taler_json_lib.h"
     27 #include <gnunet/gnunet_curl_lib.h>
     28 #include "taler/taler_util.h"
     29 #include "taler/taler_testing_lib.h"
     30 
     31 
     32 /**
     33  * Age restriction mask for the exchange under test.
     34  * Set during TALER_TESTING_run_with_fakebank by reading the [exchange] config.
     35  */
     36 struct TALER_AgeMask TALER_testing_age_restriction_mask;
     37 
     38 
     39 /**
     40  * The interpreter and its state
     41  */
     42 struct TALER_TESTING_Interpreter
     43 {
     44 
     45   /**
     46    * Commands the interpreter will run.
     47    */
     48   struct TALER_TESTING_Command *commands;
     49 
     50   /**
     51    * Interpreter task (if one is scheduled).
     52    */
     53   struct GNUNET_SCHEDULER_Task *task;
     54 
     55   /**
     56    * Handle for the child management.
     57    */
     58   struct GNUNET_ChildWaitHandle *cwh;
     59 
     60   /**
     61    * Main execution context for the main loop.
     62    */
     63   struct GNUNET_CURL_Context *ctx;
     64 
     65   /**
     66    * Context for running the CURL event loop.
     67    */
     68   struct GNUNET_CURL_RescheduleContext *rc;
     69 
     70   /**
     71    * Hash map mapping variable names to commands.
     72    */
     73   struct GNUNET_CONTAINER_MultiHashMap *vars;
     74 
     75   /**
     76    * Task run on timeout.
     77    */
     78   struct GNUNET_SCHEDULER_Task *timeout_task;
     79 
     80   /**
     81    * Instruction pointer.  Tells #interpreter_run() which instruction to run
     82    * next.  Need (signed) int because it gets -1 when rewinding the
     83    * interpreter to the first CMD.
     84    */
     85   int ip;
     86 
     87   /**
     88    * Result of the testcases, #GNUNET_OK on success
     89    */
     90   enum GNUNET_GenericReturnValue result;
     91 
     92 };
     93 
     94 
     95 const struct TALER_TESTING_Command *
     96 TALER_TESTING_interpreter_lookup_command (struct TALER_TESTING_Interpreter *is,
     97                                           const char *label)
     98 {
     99   if (NULL == label)
    100   {
    101     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    102                 "Attempt to lookup command for empty label\n");
    103     return NULL;
    104   }
    105   /* Search backwards as we most likely reference recent commands */
    106   for (int i = is->ip; i >= 0; i--)
    107   {
    108     const struct TALER_TESTING_Command *cmd = &is->commands[i];
    109 
    110     /* Give precedence to top-level commands.  */
    111     if ( (NULL != cmd->label) &&
    112          (0 == strcmp (cmd->label,
    113                        label)) )
    114       return cmd;
    115 
    116     if (TALER_TESTING_cmd_is_batch (cmd))
    117     {
    118       struct TALER_TESTING_Command *batch;
    119       struct TALER_TESTING_Command *current;
    120       struct TALER_TESTING_Command *icmd;
    121       const struct TALER_TESTING_Command *match;
    122 
    123       current = TALER_TESTING_cmd_batch_get_current (cmd);
    124       GNUNET_assert (GNUNET_OK ==
    125                      TALER_TESTING_get_trait_batch_cmds (cmd,
    126                                                          &batch));
    127       /* We must do the loop forward, but we can find the last match */
    128       match = NULL;
    129       for (unsigned int j = 0;
    130            NULL != (icmd = &batch[j])->label;
    131            j++)
    132       {
    133         if (current == icmd)
    134           break; /* do not go past current command */
    135         if ( (NULL != icmd->label) &&
    136              (0 == strcmp (icmd->label,
    137                            label)) )
    138           match = icmd;
    139       }
    140       if (NULL != match)
    141         return match;
    142     }
    143   }
    144   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    145               "Command not found: %s\n",
    146               label);
    147   return NULL;
    148 }
    149 
    150 
    151 const struct TALER_TESTING_Command *
    152 TALER_TESTING_interpreter_get_command (struct TALER_TESTING_Interpreter *is,
    153                                        const char *name)
    154 {
    155   const struct TALER_TESTING_Command *cmd;
    156   struct GNUNET_HashCode h_name;
    157 
    158   GNUNET_CRYPTO_hash (name,
    159                       strlen (name),
    160                       &h_name);
    161   cmd = GNUNET_CONTAINER_multihashmap_get (is->vars,
    162                                            &h_name);
    163   if (NULL == cmd)
    164     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    165                 "Command not found by name: %s\n",
    166                 name);
    167   return cmd;
    168 }
    169 
    170 
    171 struct GNUNET_CURL_Context *
    172 TALER_TESTING_interpreter_get_context (struct TALER_TESTING_Interpreter *is)
    173 {
    174   return is->ctx;
    175 }
    176 
    177 
    178 void
    179 TALER_TESTING_touch_cmd (struct TALER_TESTING_Interpreter *is)
    180 {
    181   is->commands[is->ip].last_req_time
    182     = GNUNET_TIME_absolute_get ();
    183 }
    184 
    185 
    186 void
    187 TALER_TESTING_inc_tries (struct TALER_TESTING_Interpreter *is)
    188 {
    189   is->commands[is->ip].num_tries++;
    190 }
    191 
    192 
    193 /**
    194  * Run the main interpreter loop that performs exchange operations.
    195  *
    196  * @param cls contains the `struct InterpreterState`
    197  */
    198 static void
    199 interpreter_run (void *cls);
    200 
    201 
    202 void
    203 TALER_TESTING_interpreter_next (struct TALER_TESTING_Interpreter *is)
    204 {
    205   static unsigned long long ipc;
    206   static struct GNUNET_TIME_Absolute last_report;
    207   struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
    208 
    209   if (GNUNET_SYSERR == is->result)
    210     return; /* ignore, we already failed! */
    211   if (TALER_TESTING_cmd_is_batch (cmd))
    212   {
    213     if (TALER_TESTING_cmd_batch_next (is,
    214                                       cmd->cls))
    215     {
    216       /* batch is done */
    217       cmd->finish_time = GNUNET_TIME_absolute_get ();
    218       is->ip++;
    219     }
    220   }
    221   else
    222   {
    223     cmd->finish_time = GNUNET_TIME_absolute_get ();
    224     is->ip++;
    225   }
    226   if (0 == (ipc % 1000))
    227   {
    228     if (0 != ipc)
    229       GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    230                   "Interpreter executed 1000 instructions in %s\n",
    231                   GNUNET_STRINGS_relative_time_to_string (
    232                     GNUNET_TIME_absolute_get_duration (last_report),
    233                     true));
    234     last_report = GNUNET_TIME_absolute_get ();
    235   }
    236   ipc++;
    237   is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
    238                                        is);
    239 }
    240 
    241 
    242 void
    243 TALER_TESTING_interpreter_fail (struct TALER_TESTING_Interpreter *is)
    244 {
    245   struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
    246 
    247   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    248               "Failed at command `%s'\n",
    249               cmd->label);
    250   while (TALER_TESTING_cmd_is_batch (cmd))
    251   {
    252     cmd = TALER_TESTING_cmd_batch_get_current (cmd);
    253     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    254                 "Batch is at command `%s'\n",
    255                 cmd->label);
    256   }
    257   is->result = GNUNET_SYSERR;
    258   GNUNET_SCHEDULER_shutdown ();
    259 }
    260 
    261 
    262 const char *
    263 TALER_TESTING_interpreter_get_current_label (
    264   struct TALER_TESTING_Interpreter *is)
    265 {
    266   struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
    267 
    268   return cmd->label;
    269 }
    270 
    271 
    272 void
    273 TALER_TESTING_update_variables_ (
    274   struct TALER_TESTING_Interpreter *is,
    275   struct TALER_TESTING_Command *cmd)
    276 {
    277   struct GNUNET_HashCode h_name;
    278 
    279   if (NULL == cmd->name)
    280     return;
    281   GNUNET_CRYPTO_hash (cmd->name,
    282                       strlen (cmd->name),
    283                       &h_name);
    284   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    285               "Storing command %s under variable `%s'\n",
    286               cmd->label,
    287               cmd->name);
    288   (void) GNUNET_CONTAINER_multihashmap_put (
    289     is->vars,
    290     &h_name,
    291     cmd,
    292     GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE);
    293 }
    294 
    295 
    296 static void
    297 interpreter_run (void *cls)
    298 {
    299   struct TALER_TESTING_Interpreter *is = cls;
    300   struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
    301 
    302   is->task = NULL;
    303   if (NULL == cmd->label)
    304   {
    305 
    306     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    307                 "Running command END\n");
    308     is->result = GNUNET_OK;
    309     GNUNET_SCHEDULER_shutdown ();
    310     return;
    311   }
    312 
    313   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    314               "Running command `%s'\n",
    315               cmd->label);
    316   cmd->last_req_time
    317     = GNUNET_TIME_absolute_get ();
    318   if (0 == cmd->num_tries)
    319     cmd->start_time = cmd->last_req_time;
    320   cmd->num_tries++;
    321   TALER_TESTING_update_variables_ (is,
    322                                    cmd);
    323   cmd->run (cmd->cls,
    324             cmd,
    325             is);
    326 }
    327 
    328 
    329 /**
    330  * Function run when the test terminates (good or bad).
    331  * Cleans up our state.
    332  *
    333  * @param cls the interpreter state.
    334  */
    335 static void
    336 do_shutdown (void *cls)
    337 {
    338   struct TALER_TESTING_Interpreter *is = cls;
    339   struct TALER_TESTING_Command *cmd;
    340   const char *label;
    341 
    342   label = is->commands[is->ip].label;
    343   if (NULL == label)
    344     label = "END";
    345   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    346               "Executing shutdown at `%s'\n",
    347               label);
    348   for (unsigned int j = 0;
    349        NULL != (cmd = &is->commands[j])->label;
    350        j++)
    351     if (NULL != cmd->cleanup)
    352       cmd->cleanup (cmd->cls,
    353                     cmd);
    354   if (NULL != is->task)
    355   {
    356     GNUNET_SCHEDULER_cancel (is->task);
    357     is->task = NULL;
    358   }
    359   if (NULL != is->ctx)
    360   {
    361     GNUNET_CURL_fini (is->ctx);
    362     is->ctx = NULL;
    363   }
    364   if (NULL != is->rc)
    365   {
    366     GNUNET_CURL_gnunet_rc_destroy (is->rc);
    367     is->rc = NULL;
    368   }
    369   if (NULL != is->vars)
    370   {
    371     GNUNET_CONTAINER_multihashmap_destroy (is->vars);
    372     is->vars = NULL;
    373   }
    374   if (NULL != is->timeout_task)
    375   {
    376     GNUNET_SCHEDULER_cancel (is->timeout_task);
    377     is->timeout_task = NULL;
    378   }
    379   if (NULL != is->cwh)
    380   {
    381     GNUNET_wait_child_cancel (is->cwh);
    382     is->cwh = NULL;
    383   }
    384   GNUNET_free (is->commands);
    385 }
    386 
    387 
    388 /**
    389  * Function run when the test terminates (good or bad) with timeout.
    390  *
    391  * @param cls the `struct TALER_TESTING_Interpreter *`
    392  */
    393 static void
    394 do_timeout (void *cls)
    395 {
    396   struct TALER_TESTING_Interpreter *is = cls;
    397 
    398   is->timeout_task = NULL;
    399   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    400               "Terminating test due to timeout\n");
    401   GNUNET_SCHEDULER_shutdown ();
    402 }
    403 
    404 
    405 /**
    406  * Task triggered whenever we receive a SIGCHLD (child
    407  * process died).
    408  *
    409  * @param cls the `struct TALER_TESTING_Interpreter *`
    410  * @param type type of the process
    411  * @param code status code of the process
    412  */
    413 static void
    414 maint_child_death (void *cls,
    415                    enum GNUNET_OS_ProcessStatusType type,
    416                    long unsigned int code)
    417 {
    418   struct TALER_TESTING_Interpreter *is = cls;
    419   struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
    420   struct GNUNET_Process **processp;
    421 
    422   is->cwh = NULL;
    423   while (TALER_TESTING_cmd_is_batch (cmd))
    424     cmd = TALER_TESTING_cmd_batch_get_current (cmd);
    425   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    426               "Got SIGCHLD for `%s'.\n",
    427               cmd->label);
    428   if (GNUNET_OK !=
    429       TALER_TESTING_get_trait_process (cmd,
    430                                        &processp))
    431   {
    432     GNUNET_break (0);
    433     TALER_TESTING_interpreter_fail (is);
    434     return;
    435   }
    436   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    437               "Got the dead child process handle, waiting for termination ...\n");
    438   GNUNET_process_destroy (*processp);
    439   *processp = NULL;
    440   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    441               "... definitively terminated\n");
    442   switch (type)
    443   {
    444   case GNUNET_OS_PROCESS_UNKNOWN:
    445     GNUNET_break (0);
    446     TALER_TESTING_interpreter_fail (is);
    447     return;
    448   case GNUNET_OS_PROCESS_RUNNING:
    449     GNUNET_break (0);
    450     TALER_TESTING_interpreter_fail (is);
    451     return;
    452   case GNUNET_OS_PROCESS_STOPPED:
    453     GNUNET_break (0);
    454     TALER_TESTING_interpreter_fail (is);
    455     return;
    456   case GNUNET_OS_PROCESS_EXITED:
    457     if (0 != code)
    458     {
    459       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    460                   "Process exited with unexpected status %u\n",
    461                   (unsigned int) code);
    462       TALER_TESTING_interpreter_fail (is);
    463       return;
    464     }
    465     break;
    466   case GNUNET_OS_PROCESS_SIGNALED:
    467     GNUNET_break (0);
    468     TALER_TESTING_interpreter_fail (is);
    469     return;
    470   }
    471   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    472               "Dead child, go on with next command.\n");
    473   TALER_TESTING_interpreter_next (is);
    474 }
    475 
    476 
    477 void
    478 TALER_TESTING_wait_for_sigchld (struct TALER_TESTING_Interpreter *is)
    479 {
    480   struct GNUNET_Process **processp;
    481   struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
    482 
    483   while (TALER_TESTING_cmd_is_batch (cmd))
    484     cmd = TALER_TESTING_cmd_batch_get_current (cmd);
    485   if (GNUNET_OK !=
    486       TALER_TESTING_get_trait_process (cmd,
    487                                        &processp))
    488   {
    489     GNUNET_break (0);
    490     TALER_TESTING_interpreter_fail (is);
    491     return;
    492   }
    493   GNUNET_assert (NULL == is->cwh);
    494   is->cwh
    495     = GNUNET_wait_child (*processp,
    496                          &maint_child_death,
    497                          is);
    498 }
    499 
    500 
    501 void
    502 TALER_TESTING_run2 (struct TALER_TESTING_Interpreter *is,
    503                     struct TALER_TESTING_Command *commands,
    504                     struct GNUNET_TIME_Relative timeout)
    505 {
    506   unsigned int i;
    507 
    508   if (NULL != is->timeout_task)
    509   {
    510     GNUNET_SCHEDULER_cancel (is->timeout_task);
    511     is->timeout_task = NULL;
    512   }
    513   /* get the number of commands */
    514   for (i = 0; NULL != commands[i].label; i++)
    515     ;
    516   is->commands = GNUNET_malloc_large ( (i + 1)
    517                                        * sizeof (struct TALER_TESTING_Command));
    518   GNUNET_assert (NULL != is->commands);
    519   GNUNET_memcpy (is->commands,
    520                  commands,
    521                  sizeof (struct TALER_TESTING_Command) * i);
    522   is->timeout_task = GNUNET_SCHEDULER_add_delayed (
    523     timeout,
    524     &do_timeout,
    525     is);
    526   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    527                                  is);
    528   is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
    529                                        is);
    530 }
    531 
    532 
    533 #include "valgrind.h"
    534 
    535 void
    536 TALER_TESTING_run (struct TALER_TESTING_Interpreter *is,
    537                    struct TALER_TESTING_Command *commands)
    538 {
    539   TALER_TESTING_run2 (is,
    540                       commands,
    541                       0 == RUNNING_ON_VALGRIND
    542                       ? GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES,
    543                                                        5)
    544                       : GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES,
    545                                                        50));
    546 }
    547 
    548 
    549 /**
    550  * Information used by the wrapper around the main
    551  * "run" method.
    552  */
    553 struct MainContext
    554 {
    555   /**
    556    * Main "run" method.
    557    */
    558   TALER_TESTING_Main main_cb;
    559 
    560   /**
    561    * Closure for @e main_cb.
    562    */
    563   void *main_cb_cls;
    564 
    565   /**
    566    * Interpreter state.
    567    */
    568   struct TALER_TESTING_Interpreter *is;
    569 
    570   /**
    571    * URL of the exchange.
    572    */
    573   char *exchange_url;
    574 
    575 };
    576 
    577 
    578 /**
    579  * Initialize scheduler loop and curl context for the testcase,
    580  * and responsible to run the "run" method.
    581  *
    582  * @param cls closure, typically the "run" method, the
    583  *        interpreter state and a closure for "run".
    584  */
    585 static void
    586 main_wrapper (void *cls)
    587 {
    588   struct MainContext *main_ctx = cls;
    589 
    590   main_ctx->main_cb (main_ctx->main_cb_cls,
    591                      main_ctx->is);
    592 }
    593 
    594 
    595 enum GNUNET_GenericReturnValue
    596 TALER_TESTING_loop (TALER_TESTING_Main main_cb,
    597                     void *main_cb_cls)
    598 {
    599   struct TALER_TESTING_Interpreter is;
    600   struct MainContext main_ctx = {
    601     .main_cb = main_cb,
    602     .main_cb_cls = main_cb_cls,
    603     /* needed to init the curl ctx */
    604     .is = &is,
    605   };
    606 
    607   memset (&is,
    608           0,
    609           sizeof (is));
    610   is.ctx = GNUNET_CURL_init (
    611     &GNUNET_CURL_gnunet_scheduler_reschedule,
    612     &is.rc);
    613   GNUNET_CURL_enable_async_scope_header (is.ctx,
    614                                          "Taler-Correlation-Id");
    615   GNUNET_assert (NULL != is.ctx);
    616   is.rc = GNUNET_CURL_gnunet_rc_create (is.ctx);
    617   is.vars = GNUNET_CONTAINER_multihashmap_create (1024,
    618                                                   false);
    619   /* Blocking */
    620   GNUNET_SCHEDULER_run (&main_wrapper,
    621                         &main_ctx);
    622   return is.result;
    623 }
    624 
    625 
    626 int
    627 TALER_TESTING_main (char *const *argv,
    628                     const char *loglevel,
    629                     const char *cfg_file,
    630                     const char *exchange_account_section,
    631                     enum TALER_TESTING_BankSystem bs,
    632                     struct TALER_TESTING_Credentials *cred,
    633                     TALER_TESTING_Main main_cb,
    634                     void *main_cb_cls)
    635 {
    636   enum GNUNET_GenericReturnValue ret;
    637 
    638   unsetenv ("XDG_DATA_HOME");
    639   unsetenv ("XDG_CONFIG_HOME");
    640   GNUNET_log_setup (argv[0],
    641                     loglevel,
    642                     NULL);
    643   if (GNUNET_OK !=
    644       TALER_TESTING_get_credentials (cfg_file,
    645                                      exchange_account_section,
    646                                      bs,
    647                                      cred))
    648   {
    649     GNUNET_break (0);
    650     return 77;
    651   }
    652   if (GNUNET_OK !=
    653       TALER_TESTING_cleanup_files_cfg (NULL,
    654                                        cred->cfg))
    655   {
    656     GNUNET_break (0);
    657     return 77;
    658   }
    659   /* Initialize age restriction mask from [exchange] config */
    660   memset (&TALER_testing_age_restriction_mask,
    661           0,
    662           sizeof (TALER_testing_age_restriction_mask));
    663   if (GNUNET_YES ==
    664       GNUNET_CONFIGURATION_get_value_yesno (cred->cfg,
    665                                             "exchange",
    666                                             "AGE_RESTRICTION_ENABLED"))
    667   {
    668     char *groups = NULL;
    669 
    670     if (GNUNET_OK !=
    671         GNUNET_CONFIGURATION_get_value_string (cred->cfg,
    672                                                "exchange",
    673                                                "AGE_GROUPS",
    674                                                &groups))
    675       groups = GNUNET_strdup ("8:10:12:14:16:18:21");
    676     if (GNUNET_OK !=
    677         TALER_parse_age_group_string (groups,
    678                                       &TALER_testing_age_restriction_mask))
    679     {
    680       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    681                   "Invalid AGE_GROUPS in [exchange]: %s\n",
    682                   groups);
    683       GNUNET_free (groups);
    684       GNUNET_break (0);
    685       return 77;
    686     }
    687     GNUNET_free (groups);
    688   }
    689   ret = TALER_TESTING_loop (main_cb,
    690                             main_cb_cls);
    691   /* FIXME: should we free 'cred' resources here? */
    692   return (GNUNET_OK == ret) ? 0 : 1;
    693 }
    694 
    695 
    696 /* ************** iterate over commands ********* */
    697 
    698 
    699 void
    700 TALER_TESTING_iterate (struct TALER_TESTING_Interpreter *is,
    701                        bool asc,
    702                        TALER_TESTING_CommandIterator cb,
    703                        void *cb_cls)
    704 {
    705   unsigned int start;
    706   unsigned int end;
    707   int inc;
    708 
    709   if (asc)
    710   {
    711     inc = 1;
    712     start = 0;
    713     end = is->ip;
    714   }
    715   else
    716   {
    717     inc = -1;
    718     start = is->ip;
    719     end = 0;
    720   }
    721   for (unsigned int off = start; off != end + inc; off += inc)
    722   {
    723     const struct TALER_TESTING_Command *cmd = &is->commands[off];
    724 
    725     cb (cb_cls,
    726         cmd);
    727   }
    728 }
    729 
    730 
    731 /* ************** special commands ********* */
    732 
    733 
    734 struct TALER_TESTING_Command
    735 TALER_TESTING_cmd_end (void)
    736 {
    737   static struct TALER_TESTING_Command cmd;
    738   cmd.label = NULL;
    739 
    740   return cmd;
    741 }
    742 
    743 
    744 struct TALER_TESTING_Command
    745 TALER_TESTING_cmd_set_var (const char *name,
    746                            struct TALER_TESTING_Command cmd)
    747 {
    748   cmd.name = name;
    749   return cmd;
    750 }
    751 
    752 
    753 /**
    754  * State for a "rewind" CMD.
    755  */
    756 struct RewindIpState
    757 {
    758   /**
    759    * Instruction pointer to set into the interpreter.
    760    */
    761   const char *target_label;
    762 
    763   /**
    764    * How many times this set should take place.  However, this value lives at
    765    * the calling process, and this CMD is only in charge of checking and
    766    * decremeting it.
    767    */
    768   unsigned int counter;
    769 };
    770 
    771 
    772 /**
    773  * Seek for the @a target command in @a batch (and rewind to it
    774  * if successful).
    775  *
    776  * @param is the interpreter state (for failures)
    777  * @param cmd batch to search for @a target
    778  * @param target command to search for
    779  * @return #GNUNET_OK on success, #GNUNET_NO if target was not found,
    780  *         #GNUNET_SYSERR if target is in the future and we failed
    781  */
    782 static enum GNUNET_GenericReturnValue
    783 seek_batch (struct TALER_TESTING_Interpreter *is,
    784             const struct TALER_TESTING_Command *cmd,
    785             const struct TALER_TESTING_Command *target)
    786 {
    787   unsigned int new_ip;
    788   struct TALER_TESTING_Command *batch;
    789   struct TALER_TESTING_Command *current;
    790   struct TALER_TESTING_Command *icmd;
    791   struct TALER_TESTING_Command *match;
    792 
    793   current = TALER_TESTING_cmd_batch_get_current (cmd);
    794   GNUNET_assert (GNUNET_OK ==
    795                  TALER_TESTING_get_trait_batch_cmds (cmd,
    796                                                      &batch));
    797   match = NULL;
    798   for (new_ip = 0;
    799        NULL != (icmd = &batch[new_ip]);
    800        new_ip++)
    801   {
    802     if (current == target)
    803       current = NULL;
    804     if (icmd == target)
    805     {
    806       match = icmd;
    807       break;
    808     }
    809     if (TALER_TESTING_cmd_is_batch (icmd))
    810     {
    811       int ret = seek_batch (is,
    812                             icmd,
    813                             target);
    814       if (GNUNET_SYSERR == ret)
    815         return GNUNET_SYSERR; /* failure! */
    816       if (GNUNET_OK == ret)
    817       {
    818         match = icmd;
    819         break;
    820       }
    821     }
    822   }
    823   if (NULL == current)
    824   {
    825     /* refuse to jump forward */
    826     GNUNET_break (0);
    827     TALER_TESTING_interpreter_fail (is);
    828     return GNUNET_SYSERR;
    829   }
    830   if (NULL == match)
    831     return GNUNET_NO; /* not found */
    832   TALER_TESTING_cmd_batch_set_current (cmd,
    833                                        new_ip);
    834   return GNUNET_OK;
    835 }
    836 
    837 
    838 /**
    839  * Run the "rewind" CMD.
    840  *
    841  * @param cls closure.
    842  * @param cmd command being executed now.
    843  * @param is the interpreter state.
    844  */
    845 static void
    846 rewind_ip_run (void *cls,
    847                const struct TALER_TESTING_Command *cmd,
    848                struct TALER_TESTING_Interpreter *is)
    849 {
    850   struct RewindIpState *ris = cls;
    851   const struct TALER_TESTING_Command *target;
    852   unsigned int new_ip;
    853 
    854   (void) cmd;
    855   if (0 == ris->counter)
    856   {
    857     TALER_TESTING_interpreter_next (is);
    858     return;
    859   }
    860   target
    861     = TALER_TESTING_interpreter_lookup_command (is,
    862                                                 ris->target_label);
    863   if (NULL == target)
    864   {
    865     GNUNET_break (0);
    866     TALER_TESTING_interpreter_fail (is);
    867     return;
    868   }
    869   ris->counter--;
    870   for (new_ip = 0;
    871        NULL != is->commands[new_ip].label;
    872        new_ip++)
    873   {
    874     const struct TALER_TESTING_Command *ipcmd
    875       = &is->commands[new_ip];
    876 
    877     if (ipcmd == target)
    878       break;
    879     if (TALER_TESTING_cmd_is_batch (ipcmd))
    880     {
    881       int ret = seek_batch (is,
    882                             ipcmd,
    883                             target);
    884       if (GNUNET_SYSERR == ret)
    885         return;   /* failure! */
    886       if (GNUNET_OK == ret)
    887         break;
    888     }
    889   }
    890   if (new_ip > (unsigned int) is->ip)
    891   {
    892     /* refuse to jump forward */
    893     GNUNET_break (0);
    894     TALER_TESTING_interpreter_fail (is);
    895     return;
    896   }
    897   is->ip = new_ip - 1; /* -1 because the next function will advance by one */
    898   TALER_TESTING_interpreter_next (is);
    899 }
    900 
    901 
    902 struct TALER_TESTING_Command
    903 TALER_TESTING_cmd_rewind_ip (const char *label,
    904                              const char *target_label,
    905                              unsigned int counter)
    906 {
    907   struct RewindIpState *ris;
    908 
    909   ris = GNUNET_new (struct RewindIpState);
    910   ris->target_label = target_label;
    911   ris->counter = counter;
    912   {
    913     struct TALER_TESTING_Command cmd = {
    914       .cls = ris,
    915       .label = label,
    916       .run = &rewind_ip_run
    917     };
    918 
    919     return cmd;
    920   }
    921 }
    922 
    923 
    924 /**
    925  * State for a "authchange" CMD.
    926  */
    927 struct AuthchangeState
    928 {
    929 
    930   /**
    931    * What is the new authorization token to send?
    932    */
    933   const char *auth_token;
    934 
    935   /**
    936    * Old context, clean up on termination.
    937    */
    938   struct GNUNET_CURL_Context *old_ctx;
    939 };
    940 
    941 
    942 /**
    943  * Run the command.
    944  *
    945  * @param cls closure.
    946  * @param cmd the command to execute.
    947  * @param is the interpreter state.
    948  */
    949 static void
    950 authchange_run (void *cls,
    951                 const struct TALER_TESTING_Command *cmd,
    952                 struct TALER_TESTING_Interpreter *is)
    953 {
    954   struct AuthchangeState *ss = cls;
    955 
    956   (void) cmd;
    957   ss->old_ctx = is->ctx;
    958   if (NULL != is->rc)
    959   {
    960     GNUNET_CURL_gnunet_rc_destroy (is->rc);
    961     is->rc = NULL;
    962   }
    963   is->ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    964                               &is->rc);
    965   GNUNET_CURL_enable_async_scope_header (is->ctx,
    966                                          "Taler-Correlation-Id");
    967   GNUNET_assert (NULL != is->ctx);
    968   is->rc = GNUNET_CURL_gnunet_rc_create (is->ctx);
    969   if (NULL != ss->auth_token)
    970   {
    971     char *authorization;
    972 
    973     GNUNET_asprintf (&authorization,
    974                      "%s: %s",
    975                      MHD_HTTP_HEADER_AUTHORIZATION,
    976                      ss->auth_token);
    977     GNUNET_assert (GNUNET_OK ==
    978                    GNUNET_CURL_append_header (is->ctx,
    979                                               authorization));
    980     GNUNET_free (authorization);
    981   }
    982   TALER_TESTING_interpreter_next (is);
    983 }
    984 
    985 
    986 /**
    987  * Call GNUNET_CURL_fini(). Done as a separate task to
    988  * ensure that all of the command's cleanups have been
    989  * executed first.  See #7151.
    990  *
    991  * @param cls a `struct GNUNET_CURL_Context *` to clean up.
    992  */
    993 static void
    994 deferred_cleanup_cb (void *cls)
    995 {
    996   struct GNUNET_CURL_Context *ctx = cls;
    997 
    998   GNUNET_CURL_fini (ctx);
    999 }
   1000 
   1001 
   1002 /**
   1003  * Cleanup the state from a "authchange" CMD.
   1004  *
   1005  * @param cls closure.
   1006  * @param cmd the command which is being cleaned up.
   1007  */
   1008 static void
   1009 authchange_cleanup (void *cls,
   1010                     const struct TALER_TESTING_Command *cmd)
   1011 {
   1012   struct AuthchangeState *ss = cls;
   1013 
   1014   (void) cmd;
   1015   if (NULL != ss->old_ctx)
   1016   {
   1017     (void) GNUNET_SCHEDULER_add_now (&deferred_cleanup_cb,
   1018                                      ss->old_ctx);
   1019     ss->old_ctx = NULL;
   1020   }
   1021   GNUNET_free (ss);
   1022 }
   1023 
   1024 
   1025 struct TALER_TESTING_Command
   1026 TALER_TESTING_cmd_set_authorization (const char *label,
   1027                                      const char *auth_token)
   1028 {
   1029   struct AuthchangeState *ss;
   1030 
   1031   ss = GNUNET_new (struct AuthchangeState);
   1032   ss->auth_token = auth_token;
   1033 
   1034   {
   1035     struct TALER_TESTING_Command cmd = {
   1036       .cls = ss,
   1037       .label = label,
   1038       .run = &authchange_run,
   1039       .cleanup = &authchange_cleanup
   1040     };
   1041 
   1042     return cmd;
   1043   }
   1044 }
   1045 
   1046 
   1047 /* end of testing_api_loop.c */