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


      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;
    208 
    209   if (GNUNET_SYSERR == is->result)
    210     return; /* ignore, we already failed! */
    211   if (0 > is->ip)
    212   {
    213     /* We were rewound to before the first command; there is no
    214        command to finish, just (re)start at the beginning. */
    215     is->ip = 0;
    216     is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
    217                                          is);
    218     return;
    219   }
    220   cmd = &is->commands[is->ip];
    221   if (TALER_TESTING_cmd_is_batch (cmd))
    222   {
    223     if (TALER_TESTING_cmd_batch_next (is,
    224                                       cmd->cls))
    225     {
    226       /* batch is done */
    227       cmd->finish_time = GNUNET_TIME_absolute_get ();
    228       is->ip++;
    229     }
    230   }
    231   else
    232   {
    233     cmd->finish_time = GNUNET_TIME_absolute_get ();
    234     is->ip++;
    235   }
    236   if (0 == (ipc % 1000))
    237   {
    238     if (0 != ipc)
    239       GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    240                   "Interpreter executed 1000 instructions in %s\n",
    241                   GNUNET_STRINGS_relative_time_to_string (
    242                     GNUNET_TIME_absolute_get_duration (last_report),
    243                     true));
    244     last_report = GNUNET_TIME_absolute_get ();
    245   }
    246   ipc++;
    247   is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
    248                                        is);
    249 }
    250 
    251 
    252 void
    253 TALER_TESTING_interpreter_fail (struct TALER_TESTING_Interpreter *is)
    254 {
    255   struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
    256 
    257   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    258               "Failed at command `%s'\n",
    259               cmd->label);
    260   while (TALER_TESTING_cmd_is_batch (cmd))
    261   {
    262     cmd = TALER_TESTING_cmd_batch_get_current (cmd);
    263     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    264                 "Batch is at command `%s'\n",
    265                 cmd->label);
    266   }
    267   is->result = GNUNET_SYSERR;
    268   GNUNET_SCHEDULER_shutdown ();
    269 }
    270 
    271 
    272 const char *
    273 TALER_TESTING_interpreter_get_current_label (
    274   struct TALER_TESTING_Interpreter *is)
    275 {
    276   struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
    277 
    278   return cmd->label;
    279 }
    280 
    281 
    282 void
    283 TALER_TESTING_update_variables_ (
    284   struct TALER_TESTING_Interpreter *is,
    285   struct TALER_TESTING_Command *cmd)
    286 {
    287   struct GNUNET_HashCode h_name;
    288 
    289   if (NULL == cmd->name)
    290     return;
    291   GNUNET_CRYPTO_hash (cmd->name,
    292                       strlen (cmd->name),
    293                       &h_name);
    294   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    295               "Storing command %s under variable `%s'\n",
    296               cmd->label,
    297               cmd->name);
    298   (void) GNUNET_CONTAINER_multihashmap_put (
    299     is->vars,
    300     &h_name,
    301     cmd,
    302     GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE);
    303 }
    304 
    305 
    306 static void
    307 interpreter_run (void *cls)
    308 {
    309   struct TALER_TESTING_Interpreter *is = cls;
    310   struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
    311 
    312   is->task = NULL;
    313   if (NULL == cmd->label)
    314   {
    315 
    316     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    317                 "Running command END\n");
    318     is->result = GNUNET_OK;
    319     GNUNET_SCHEDULER_shutdown ();
    320     return;
    321   }
    322 
    323   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    324               "Running command `%s'\n",
    325               cmd->label);
    326   cmd->last_req_time
    327     = GNUNET_TIME_absolute_get ();
    328   if (0 == cmd->num_tries)
    329     cmd->start_time = cmd->last_req_time;
    330   cmd->num_tries++;
    331   TALER_TESTING_update_variables_ (is,
    332                                    cmd);
    333   cmd->run (cmd->cls,
    334             cmd,
    335             is);
    336 }
    337 
    338 
    339 /**
    340  * Function run when the test terminates (good or bad).
    341  * Cleans up our state.
    342  *
    343  * @param cls the interpreter state.
    344  */
    345 static void
    346 do_shutdown (void *cls)
    347 {
    348   struct TALER_TESTING_Interpreter *is = cls;
    349   struct TALER_TESTING_Command *cmd;
    350   const char *label;
    351 
    352   label = is->commands[is->ip].label;
    353   if (NULL == label)
    354     label = "END";
    355   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    356               "Executing shutdown at `%s'\n",
    357               label);
    358   for (unsigned int j = 0;
    359        NULL != (cmd = &is->commands[j])->label;
    360        j++)
    361     if (NULL != cmd->cleanup)
    362       cmd->cleanup (cmd->cls,
    363                     cmd);
    364   if (NULL != is->task)
    365   {
    366     GNUNET_SCHEDULER_cancel (is->task);
    367     is->task = NULL;
    368   }
    369   if (NULL != is->ctx)
    370   {
    371     GNUNET_CURL_fini (is->ctx);
    372     is->ctx = NULL;
    373   }
    374   if (NULL != is->rc)
    375   {
    376     GNUNET_CURL_gnunet_rc_destroy (is->rc);
    377     is->rc = NULL;
    378   }
    379   if (NULL != is->vars)
    380   {
    381     GNUNET_CONTAINER_multihashmap_destroy (is->vars);
    382     is->vars = NULL;
    383   }
    384   if (NULL != is->timeout_task)
    385   {
    386     GNUNET_SCHEDULER_cancel (is->timeout_task);
    387     is->timeout_task = NULL;
    388   }
    389   if (NULL != is->cwh)
    390   {
    391     GNUNET_wait_child_cancel (is->cwh);
    392     is->cwh = NULL;
    393   }
    394   GNUNET_free (is->commands);
    395 }
    396 
    397 
    398 /**
    399  * Function run when the test terminates (good or bad) with timeout.
    400  *
    401  * @param cls the `struct TALER_TESTING_Interpreter *`
    402  */
    403 static void
    404 do_timeout (void *cls)
    405 {
    406   struct TALER_TESTING_Interpreter *is = cls;
    407 
    408   is->timeout_task = NULL;
    409   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    410               "Terminating test due to timeout\n");
    411   GNUNET_SCHEDULER_shutdown ();
    412 }
    413 
    414 
    415 /**
    416  * Task triggered whenever we receive a SIGCHLD (child
    417  * process died).
    418  *
    419  * @param cls the `struct TALER_TESTING_Interpreter *`
    420  * @param type type of the process
    421  * @param code status code of the process
    422  */
    423 static void
    424 maint_child_death (void *cls,
    425                    enum GNUNET_OS_ProcessStatusType type,
    426                    long unsigned int code)
    427 {
    428   struct TALER_TESTING_Interpreter *is = cls;
    429   struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
    430   struct GNUNET_Process **processp;
    431 
    432   is->cwh = NULL;
    433   while (TALER_TESTING_cmd_is_batch (cmd))
    434     cmd = TALER_TESTING_cmd_batch_get_current (cmd);
    435   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    436               "Got SIGCHLD for `%s'.\n",
    437               cmd->label);
    438   if (GNUNET_OK !=
    439       TALER_TESTING_get_trait_process (cmd,
    440                                        &processp))
    441   {
    442     GNUNET_break (0);
    443     TALER_TESTING_interpreter_fail (is);
    444     return;
    445   }
    446   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    447               "Got the dead child process handle, waiting for termination ...\n");
    448   GNUNET_process_destroy (*processp);
    449   *processp = NULL;
    450   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    451               "... definitively terminated\n");
    452   switch (type)
    453   {
    454   case GNUNET_OS_PROCESS_UNKNOWN:
    455     GNUNET_break (0);
    456     TALER_TESTING_interpreter_fail (is);
    457     return;
    458   case GNUNET_OS_PROCESS_RUNNING:
    459     GNUNET_break (0);
    460     TALER_TESTING_interpreter_fail (is);
    461     return;
    462   case GNUNET_OS_PROCESS_STOPPED:
    463     GNUNET_break (0);
    464     TALER_TESTING_interpreter_fail (is);
    465     return;
    466   case GNUNET_OS_PROCESS_EXITED:
    467     if (0 != code)
    468     {
    469       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    470                   "Process exited with unexpected status %u\n",
    471                   (unsigned int) code);
    472       TALER_TESTING_interpreter_fail (is);
    473       return;
    474     }
    475     break;
    476   case GNUNET_OS_PROCESS_SIGNALED:
    477     GNUNET_break (0);
    478     TALER_TESTING_interpreter_fail (is);
    479     return;
    480   }
    481   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    482               "Dead child, go on with next command.\n");
    483   TALER_TESTING_interpreter_next (is);
    484 }
    485 
    486 
    487 void
    488 TALER_TESTING_wait_for_sigchld (struct TALER_TESTING_Interpreter *is)
    489 {
    490   struct GNUNET_Process **processp;
    491   struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
    492 
    493   while (TALER_TESTING_cmd_is_batch (cmd))
    494     cmd = TALER_TESTING_cmd_batch_get_current (cmd);
    495   if (GNUNET_OK !=
    496       TALER_TESTING_get_trait_process (cmd,
    497                                        &processp))
    498   {
    499     GNUNET_break (0);
    500     TALER_TESTING_interpreter_fail (is);
    501     return;
    502   }
    503   GNUNET_assert (NULL == is->cwh);
    504   is->cwh
    505     = GNUNET_wait_child (*processp,
    506                          &maint_child_death,
    507                          is);
    508 }
    509 
    510 
    511 void
    512 TALER_TESTING_run2 (struct TALER_TESTING_Interpreter *is,
    513                     struct TALER_TESTING_Command *commands,
    514                     struct GNUNET_TIME_Relative timeout)
    515 {
    516   unsigned int i;
    517 
    518   if (NULL != is->timeout_task)
    519   {
    520     GNUNET_SCHEDULER_cancel (is->timeout_task);
    521     is->timeout_task = NULL;
    522   }
    523   /* get the number of commands */
    524   for (i = 0; NULL != commands[i].label; i++)
    525     ;
    526   is->commands = GNUNET_malloc_large ( (i + 1)
    527                                        * sizeof (struct TALER_TESTING_Command));
    528   GNUNET_assert (NULL != is->commands);
    529   GNUNET_memcpy (is->commands,
    530                  commands,
    531                  sizeof (struct TALER_TESTING_Command) * i);
    532   is->timeout_task = GNUNET_SCHEDULER_add_delayed (
    533     timeout,
    534     &do_timeout,
    535     is);
    536   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    537                                  is);
    538   is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
    539                                        is);
    540 }
    541 
    542 
    543 #include "valgrind.h"
    544 
    545 void
    546 TALER_TESTING_run (struct TALER_TESTING_Interpreter *is,
    547                    struct TALER_TESTING_Command *commands)
    548 {
    549   TALER_TESTING_run2 (is,
    550                       commands,
    551                       0 == RUNNING_ON_VALGRIND
    552                       ? GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES,
    553                                                        5)
    554                       : GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES,
    555                                                        50));
    556 }
    557 
    558 
    559 /**
    560  * Information used by the wrapper around the main
    561  * "run" method.
    562  */
    563 struct MainContext
    564 {
    565   /**
    566    * Main "run" method.
    567    */
    568   TALER_TESTING_Main main_cb;
    569 
    570   /**
    571    * Closure for @e main_cb.
    572    */
    573   void *main_cb_cls;
    574 
    575   /**
    576    * Interpreter state.
    577    */
    578   struct TALER_TESTING_Interpreter *is;
    579 
    580   /**
    581    * URL of the exchange.
    582    */
    583   char *exchange_url;
    584 
    585 };
    586 
    587 
    588 /**
    589  * Initialize scheduler loop and curl context for the testcase,
    590  * and responsible to run the "run" method.
    591  *
    592  * @param cls closure, typically the "run" method, the
    593  *        interpreter state and a closure for "run".
    594  */
    595 static void
    596 main_wrapper (void *cls)
    597 {
    598   struct MainContext *main_ctx = cls;
    599 
    600   main_ctx->main_cb (main_ctx->main_cb_cls,
    601                      main_ctx->is);
    602 }
    603 
    604 
    605 enum GNUNET_GenericReturnValue
    606 TALER_TESTING_loop (TALER_TESTING_Main main_cb,
    607                     void *main_cb_cls)
    608 {
    609   struct TALER_TESTING_Interpreter is;
    610   struct MainContext main_ctx = {
    611     .main_cb = main_cb,
    612     .main_cb_cls = main_cb_cls,
    613     /* needed to init the curl ctx */
    614     .is = &is,
    615   };
    616 
    617   memset (&is,
    618           0,
    619           sizeof (is));
    620   is.ctx = GNUNET_CURL_init (
    621     &GNUNET_CURL_gnunet_scheduler_reschedule,
    622     &is.rc);
    623   GNUNET_CURL_enable_async_scope_header (is.ctx,
    624                                          "Taler-Correlation-Id");
    625   GNUNET_assert (NULL != is.ctx);
    626   is.rc = GNUNET_CURL_gnunet_rc_create (is.ctx);
    627   is.vars = GNUNET_CONTAINER_multihashmap_create (1024,
    628                                                   false);
    629   /* Blocking */
    630   GNUNET_SCHEDULER_run (&main_wrapper,
    631                         &main_ctx);
    632   return is.result;
    633 }
    634 
    635 
    636 int
    637 TALER_TESTING_main (char *const *argv,
    638                     const char *loglevel,
    639                     const char *cfg_file,
    640                     const char *exchange_account_section,
    641                     enum TALER_TESTING_BankSystem bs,
    642                     struct TALER_TESTING_Credentials *cred,
    643                     TALER_TESTING_Main main_cb,
    644                     void *main_cb_cls)
    645 {
    646   enum GNUNET_GenericReturnValue ret;
    647 
    648   unsetenv ("XDG_DATA_HOME");
    649   unsetenv ("XDG_CONFIG_HOME");
    650   GNUNET_log_setup (argv[0],
    651                     loglevel,
    652                     NULL);
    653   if (GNUNET_OK !=
    654       TALER_TESTING_get_credentials (cfg_file,
    655                                      exchange_account_section,
    656                                      bs,
    657                                      cred))
    658   {
    659     GNUNET_break (0);
    660     return 77;
    661   }
    662   if (GNUNET_OK !=
    663       TALER_TESTING_cleanup_files_cfg (NULL,
    664                                        cred->cfg))
    665   {
    666     GNUNET_break (0);
    667     return 77;
    668   }
    669   /* Initialize age restriction mask from [exchange] config */
    670   memset (&TALER_testing_age_restriction_mask,
    671           0,
    672           sizeof (TALER_testing_age_restriction_mask));
    673   if (GNUNET_YES ==
    674       GNUNET_CONFIGURATION_get_value_yesno (cred->cfg,
    675                                             "exchange",
    676                                             "AGE_RESTRICTION_ENABLED"))
    677   {
    678     char *groups = NULL;
    679 
    680     if (GNUNET_OK !=
    681         GNUNET_CONFIGURATION_get_value_string (cred->cfg,
    682                                                "exchange",
    683                                                "AGE_GROUPS",
    684                                                &groups))
    685       groups = GNUNET_strdup ("8:10:12:14:16:18:21");
    686     if (GNUNET_OK !=
    687         TALER_parse_age_group_string (groups,
    688                                       &TALER_testing_age_restriction_mask))
    689     {
    690       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    691                   "Invalid AGE_GROUPS in [exchange]: %s\n",
    692                   groups);
    693       GNUNET_free (groups);
    694       GNUNET_break (0);
    695       return 77;
    696     }
    697     GNUNET_free (groups);
    698   }
    699   ret = TALER_TESTING_loop (main_cb,
    700                             main_cb_cls);
    701   /* FIXME: should we free 'cred' resources here? */
    702   return (GNUNET_OK == ret) ? 0 : 1;
    703 }
    704 
    705 
    706 /* ************** iterate over commands ********* */
    707 
    708 
    709 void
    710 TALER_TESTING_iterate (struct TALER_TESTING_Interpreter *is,
    711                        bool asc,
    712                        TALER_TESTING_CommandIterator cb,
    713                        void *cb_cls)
    714 {
    715   unsigned int start;
    716   unsigned int end;
    717   int inc;
    718 
    719   if (asc)
    720   {
    721     inc = 1;
    722     start = 0;
    723     end = is->ip;
    724   }
    725   else
    726   {
    727     inc = -1;
    728     start = is->ip;
    729     end = 0;
    730   }
    731   for (unsigned int off = start; off != end + inc; off += inc)
    732   {
    733     const struct TALER_TESTING_Command *cmd = &is->commands[off];
    734 
    735     cb (cb_cls,
    736         cmd);
    737   }
    738 }
    739 
    740 
    741 /* ************** special commands ********* */
    742 
    743 
    744 struct TALER_TESTING_Command
    745 TALER_TESTING_cmd_end (void)
    746 {
    747   static struct TALER_TESTING_Command cmd;
    748   cmd.label = NULL;
    749 
    750   return cmd;
    751 }
    752 
    753 
    754 struct TALER_TESTING_Command
    755 TALER_TESTING_cmd_set_var (const char *name,
    756                            struct TALER_TESTING_Command cmd)
    757 {
    758   cmd.name = name;
    759   return cmd;
    760 }
    761 
    762 
    763 /**
    764  * State for a "rewind" CMD.
    765  */
    766 struct RewindIpState
    767 {
    768   /**
    769    * Instruction pointer to set into the interpreter.
    770    */
    771   const char *target_label;
    772 
    773   /**
    774    * How many times this set should take place.  However, this value lives at
    775    * the calling process, and this CMD is only in charge of checking and
    776    * decremeting it.
    777    */
    778   unsigned int counter;
    779 };
    780 
    781 
    782 /**
    783  * Seek for the @a target command in @a batch (and rewind to it
    784  * if successful).
    785  *
    786  * @param is the interpreter state (for failures)
    787  * @param cmd batch to search for @a target
    788  * @param target command to search for
    789  * @return #GNUNET_OK on success, #GNUNET_NO if target was not found,
    790  *         #GNUNET_SYSERR if target is in the future and we failed
    791  */
    792 static enum GNUNET_GenericReturnValue
    793 seek_batch (struct TALER_TESTING_Interpreter *is,
    794             const struct TALER_TESTING_Command *cmd,
    795             const struct TALER_TESTING_Command *target)
    796 {
    797   unsigned int new_ip;
    798   struct TALER_TESTING_Command *batch;
    799   struct TALER_TESTING_Command *current;
    800   struct TALER_TESTING_Command *icmd;
    801   struct TALER_TESTING_Command *match;
    802 
    803   current = TALER_TESTING_cmd_batch_get_current (cmd);
    804   GNUNET_assert (GNUNET_OK ==
    805                  TALER_TESTING_get_trait_batch_cmds (cmd,
    806                                                      &batch));
    807   match = NULL;
    808   for (new_ip = 0;
    809        NULL != (icmd = &batch[new_ip])->label;
    810        new_ip++)
    811   {
    812     if (current == target)
    813       current = NULL;
    814     if (icmd == target)
    815     {
    816       match = icmd;
    817       break;
    818     }
    819     if (TALER_TESTING_cmd_is_batch (icmd))
    820     {
    821       enum GNUNET_GenericReturnValue ret;
    822 
    823       ret = seek_batch (is,
    824                         icmd,
    825                         target);
    826       if (GNUNET_SYSERR == ret)
    827         return GNUNET_SYSERR; /* failure! */
    828       if (GNUNET_OK == ret)
    829       {
    830         match = icmd;
    831         break;
    832       }
    833     }
    834   }
    835   if (NULL == current)
    836   {
    837     /* refuse to jump forward */
    838     GNUNET_break (0);
    839     TALER_TESTING_interpreter_fail (is);
    840     return GNUNET_SYSERR;
    841   }
    842   if (NULL == match)
    843     return GNUNET_NO; /* not found */
    844   TALER_TESTING_cmd_batch_set_current (cmd,
    845                                        new_ip);
    846   return GNUNET_OK;
    847 }
    848 
    849 
    850 /**
    851  * Run the "rewind" CMD.
    852  *
    853  * @param cls closure.
    854  * @param cmd command being executed now.
    855  * @param is the interpreter state.
    856  */
    857 static void
    858 rewind_ip_run (void *cls,
    859                const struct TALER_TESTING_Command *cmd,
    860                struct TALER_TESTING_Interpreter *is)
    861 {
    862   struct RewindIpState *ris = cls;
    863   const struct TALER_TESTING_Command *target;
    864   unsigned int new_ip;
    865 
    866   (void) cmd;
    867   if (0 == ris->counter)
    868   {
    869     TALER_TESTING_interpreter_next (is);
    870     return;
    871   }
    872   target
    873     = TALER_TESTING_interpreter_lookup_command (is,
    874                                                 ris->target_label);
    875   if (NULL == target)
    876   {
    877     GNUNET_break (0);
    878     TALER_TESTING_interpreter_fail (is);
    879     return;
    880   }
    881   ris->counter--;
    882   for (new_ip = 0;
    883        NULL != is->commands[new_ip].label;
    884        new_ip++)
    885   {
    886     const struct TALER_TESTING_Command *ipcmd
    887       = &is->commands[new_ip];
    888 
    889     if (ipcmd == target)
    890       break;
    891     if (TALER_TESTING_cmd_is_batch (ipcmd))
    892     {
    893       int ret = seek_batch (is,
    894                             ipcmd,
    895                             target);
    896       if (GNUNET_SYSERR == ret)
    897         return;   /* failure! */
    898       if (GNUNET_OK == ret)
    899         break;
    900     }
    901   }
    902   if (new_ip > (unsigned int) is->ip)
    903   {
    904     /* refuse to jump forward */
    905     GNUNET_break (0);
    906     TALER_TESTING_interpreter_fail (is);
    907     return;
    908   }
    909   is->ip = new_ip - 1; /* -1 because the next function will advance by one */
    910   TALER_TESTING_interpreter_next (is);
    911 }
    912 
    913 
    914 struct TALER_TESTING_Command
    915 TALER_TESTING_cmd_rewind_ip (const char *label,
    916                              const char *target_label,
    917                              unsigned int counter)
    918 {
    919   struct RewindIpState *ris;
    920 
    921   ris = GNUNET_new (struct RewindIpState);
    922   ris->target_label = target_label;
    923   ris->counter = counter;
    924   {
    925     struct TALER_TESTING_Command cmd = {
    926       .cls = ris,
    927       .label = label,
    928       .run = &rewind_ip_run
    929     };
    930 
    931     return cmd;
    932   }
    933 }
    934 
    935 
    936 /**
    937  * State for a "authchange" CMD.
    938  */
    939 struct AuthchangeState
    940 {
    941 
    942   /**
    943    * What is the new authorization token to send?
    944    */
    945   const char *auth_token;
    946 
    947   /**
    948    * Old context, clean up on termination.
    949    */
    950   struct GNUNET_CURL_Context *old_ctx;
    951 };
    952 
    953 
    954 /**
    955  * Run the command.
    956  *
    957  * @param cls closure.
    958  * @param cmd the command to execute.
    959  * @param is the interpreter state.
    960  */
    961 static void
    962 authchange_run (void *cls,
    963                 const struct TALER_TESTING_Command *cmd,
    964                 struct TALER_TESTING_Interpreter *is)
    965 {
    966   struct AuthchangeState *ss = cls;
    967 
    968   (void) cmd;
    969   ss->old_ctx = is->ctx;
    970   if (NULL != is->rc)
    971   {
    972     GNUNET_CURL_gnunet_rc_destroy (is->rc);
    973     is->rc = NULL;
    974   }
    975   is->ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    976                               &is->rc);
    977   GNUNET_CURL_enable_async_scope_header (is->ctx,
    978                                          "Taler-Correlation-Id");
    979   GNUNET_assert (NULL != is->ctx);
    980   is->rc = GNUNET_CURL_gnunet_rc_create (is->ctx);
    981   if (NULL != ss->auth_token)
    982   {
    983     char *authorization;
    984 
    985     GNUNET_asprintf (&authorization,
    986                      "%s: %s",
    987                      MHD_HTTP_HEADER_AUTHORIZATION,
    988                      ss->auth_token);
    989     GNUNET_assert (GNUNET_OK ==
    990                    GNUNET_CURL_append_header (is->ctx,
    991                                               authorization));
    992     GNUNET_free (authorization);
    993   }
    994   TALER_TESTING_interpreter_next (is);
    995 }
    996 
    997 
    998 /**
    999  * Call GNUNET_CURL_fini(). Done as a separate task to
   1000  * ensure that all of the command's cleanups have been
   1001  * executed first.  See #7151.
   1002  *
   1003  * @param cls a `struct GNUNET_CURL_Context *` to clean up.
   1004  */
   1005 static void
   1006 deferred_cleanup_cb (void *cls)
   1007 {
   1008   struct GNUNET_CURL_Context *ctx = cls;
   1009 
   1010   GNUNET_CURL_fini (ctx);
   1011 }
   1012 
   1013 
   1014 /**
   1015  * Cleanup the state from a "authchange" CMD.
   1016  *
   1017  * @param cls closure.
   1018  * @param cmd the command which is being cleaned up.
   1019  */
   1020 static void
   1021 authchange_cleanup (void *cls,
   1022                     const struct TALER_TESTING_Command *cmd)
   1023 {
   1024   struct AuthchangeState *ss = cls;
   1025 
   1026   (void) cmd;
   1027   if (NULL != ss->old_ctx)
   1028   {
   1029     (void) GNUNET_SCHEDULER_add_now (&deferred_cleanup_cb,
   1030                                      ss->old_ctx);
   1031     ss->old_ctx = NULL;
   1032   }
   1033   GNUNET_free (ss);
   1034 }
   1035 
   1036 
   1037 struct TALER_TESTING_Command
   1038 TALER_TESTING_cmd_set_authorization (const char *label,
   1039                                      const char *auth_token)
   1040 {
   1041   struct AuthchangeState *ss;
   1042 
   1043   ss = GNUNET_new (struct AuthchangeState);
   1044   ss->auth_token = auth_token;
   1045 
   1046   {
   1047     struct TALER_TESTING_Command cmd = {
   1048       .cls = ss,
   1049       .label = label,
   1050       .run = &authchange_run,
   1051       .cleanup = &authchange_cleanup
   1052     };
   1053 
   1054     return cmd;
   1055   }
   1056 }
   1057 
   1058 
   1059 /* end of testing_api_loop.c */