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 (24696B)


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