LCOV - code coverage report
Current view: top level - libs/testitall/src - runit.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 64.5 % 107 69
Test Date: 2026-01-12 05:34:38 Functions: 100.0 % 2 2

            Line data    Source code
       1              : #include "testitall.h"
       2              : #include <limits.h>
       3              : #include <stdint.h>
       4              : #include <wordexp.h>
       5              : 
       6              : #ifndef ARG_MAX
       7              : #ifdef _POSIX_ARG_MAX
       8              : #define ARG_MAX _POSIX_ARG_MAX
       9              : #else
      10              : #define ARG_MAX 4096
      11              : #endif
      12              : #endif
      13              : 
      14              : extern int test_main(
      15              :         int  argc,
      16              :         char **argv) __attribute__((weak));
      17              : 
      18              : enum run_mode run_external = EXTERNAL_CALL;
      19              : 
      20              : static struct {
      21              :         int argc;
      22              :         char **argv;
      23              :         int result;
      24              : } test_main_context = {0};
      25              : 
      26          102 : static void test_main_wrapper(void)
      27              : {
      28          102 :         test_main_context.result = test_main(test_main_context.argc,test_main_context.argv);
      29          102 : }
      30              : 
      31          204 : Return runit(
      32              :         const char   *arguments,
      33              :         memory       *result,
      34              :         const int    expected_return_code,
      35              :         unsigned int buffer_policy)
      36              : {
      37              :         // Base status for the whole sequence; subsequent steps update it on errors.
      38              :         /** @var Return status
      39              :          *  @brief The status that will be passed to return() before exiting
      40              :          *  @details By default, the function worked without errors
      41              :          */
      42          204 :         Return status = SUCCESS;
      43              : 
      44              :         // Arguments string to parse and forward into test_main
      45          204 :         const char *safe_arguments = arguments;
      46          204 :         const bool suppress_stdout = (buffer_policy & STDOUT_SUPPRESS) != 0U;
      47          204 :         const bool suppress_stderr = (buffer_policy & STDERR_SUPPRESS) != 0U;
      48              : 
      49          204 :         if(NULL == safe_arguments || safe_arguments[0] == '\0')
      50              :         {
      51            2 :                 safe_arguments = "";
      52              :         }
      53              : 
      54              :         // External mode: build shell command and exit early.
      55          204 :         if(EXTERNAL_CALL == run_external)
      56              :         {
      57              :                 char command[ARG_MAX];
      58          102 :                 int written = snprintf(
      59              :                         command,
      60              :                         sizeof(command),
      61              :                         "cd ${TMPDIR} && ${BINDIR}/precizer %s",
      62              :                         safe_arguments);
      63              : 
      64          102 :                 if(written < 0 || (size_t)written >= sizeof(command))
      65              :                 {
      66            0 :                         echo(STDERR,"Command length exceeds ARG_MAX (%zu bytes)",sizeof(command));
      67            0 :                         status = FAILURE;
      68              : 
      69              :                 } else {
      70          102 :                         run(execute_command(command,result,expected_return_code,buffer_policy));
      71              :                 }
      72              : 
      73          102 :                 provide(status);
      74              :         }
      75              : 
      76              :         // Prepare wordexp and argv/argc for test_main.
      77          102 :         wordexp_t parsed_arguments = {0};
      78          102 :         bool words_allocated = false;
      79          102 :         size_t argc = 0U;
      80          102 :         char **argv = NULL;
      81              : 
      82              :         // Save current working directory to restore after test_main.
      83          102 :         char *previous_cwd = NULL;
      84          102 :         bool changed_directory = false;
      85              : 
      86              :         // Capture working directory before switching to TMPDIR.
      87          102 :         if(SUCCESS == status)
      88              :         {
      89          102 :                 previous_cwd = getcwd(NULL,0);
      90              : 
      91          102 :                 if(NULL == previous_cwd)
      92              :                 {
      93            0 :                         serp("Failed to get current working directory");
      94            0 :                         status = FAILURE;
      95              :                 }
      96              :         }
      97              : 
      98              :         // Switch into TMPDIR (tests expect to run precizer from a temp directory).
      99          102 :         const char *tmpdir = getenv("TMPDIR");
     100              : 
     101          102 :         if(SUCCESS == status)
     102              :         {
     103          102 :                 if(NULL == tmpdir)
     104              :                 {
     105            0 :                         echo(STDERR,"Environment variable TMPDIR is not set");
     106            0 :                         status = FAILURE;
     107              : 
     108              :                 } else {
     109          102 :                         if(0 != chdir(tmpdir))
     110              :                         {
     111            0 :                                 serp("Failed to change directory to TMPDIR");
     112            0 :                                 status = FAILURE;
     113              : 
     114              :                         } else {
     115          102 :                                 changed_directory = true;
     116              :                         }
     117              :                 }
     118              :         }
     119              : 
     120              :         // Parse argument string into words without command substitution.
     121          102 :         if(SUCCESS == status)
     122              :         {
     123          102 :                 int word_status = wordexp(safe_arguments,&parsed_arguments,WRDE_NOCMD);
     124              : 
     125          102 :                 if(0 != word_status)
     126              :                 {
     127            0 :                         echo(STDERR,"Failed to parse arguments \"%s\" (wordexp code %d)",safe_arguments,word_status);
     128            0 :                         status = FAILURE;
     129              : 
     130              :                 } else {
     131          102 :                         words_allocated = true;
     132              :                 }
     133              :         }
     134              : 
     135              :         // Ensure argument count fits into int and set argc.
     136          102 :         if(SUCCESS == status)
     137              :         {
     138          102 :                 argc = parsed_arguments.we_wordc + 1U;
     139              : 
     140          102 :                 if(argc > (size_t)INT_MAX)
     141              :                 {
     142            0 :                         echo(STDERR,"Too many arguments for test_main: %zu",argc);
     143            0 :                         status = FAILURE;
     144              :                 }
     145              :         }
     146              : 
     147              :         // Build argv: argv[0] is a program name, the rest are parsed words.
     148          102 :         if(SUCCESS == status)
     149              :         {
     150          102 :                 argv = calloc(argc + 1U,sizeof(char *));
     151              : 
     152          102 :                 if(NULL == argv)
     153              :                 {
     154            0 :                         report("Memory allocation failed, requested size: %zu bytes",(argc + 1U) * sizeof(char *));
     155            0 :                         status = FAILURE;
     156              : 
     157              :                 } else {
     158          102 :                         argv[0] = (char *)(uintptr_t)"precizer";
     159              : 
     160          443 :                         for(size_t i = 0U; i < parsed_arguments.we_wordc; i++)
     161              :                         {
     162          341 :                                 argv[i + 1U] = parsed_arguments.we_wordv[i];
     163              :                         }
     164              : 
     165          102 :                         argv[argc] = NULL;
     166              :                 }
     167              :         }
     168              : 
     169              :         // Run test_main in-process while capturing stdout/stderr.
     170          102 :         if(SUCCESS == status)
     171              :         {
     172          102 :                 call(del(STDERR));
     173          102 :                 call(del(STDOUT));
     174              : 
     175          102 :                 test_main_context.argc = (int)argc;
     176          102 :                 test_main_context.argv = argv;
     177          102 :                 test_main_context.result = 0;
     178              : 
     179          102 :                 run(function_capture(test_main_wrapper,STDOUT,STDERR));
     180              : 
     181          102 :                 if(SUCCESS == status)
     182              :                 {
     183          102 :                         int exit_code = test_main_context.result;
     184              : 
     185              :                         // Handle stderr: either suppress it or format a warning and fail.
     186          102 :                         if(STDERR->length > 0U)
     187              :                         {
     188            1 :                                 if(true == suppress_stderr)
     189              :                                 {
     190            1 :                                         call(del(STDERR));
     191              : 
     192              :                                 } else {
     193              :                                         char *str;
     194            0 :                                         const char *stderr_view = getcstring(STDERR);
     195            0 :                                         int rt = asprintf(&str,
     196              :                                                 YELLOW "Warning! STDERR buffer is not empty!\n"
     197              :                                                 "Internal call:\n" YELLOW ">>" RESET "precizer %s" YELLOW "<<" RESET "\n"
     198              :                                                 "Stderr output:\n" YELLOW ">>" RESET "%s" YELLOW "<<" RESET "\n",
     199              :                                                 safe_arguments,
     200              :                                                 stderr_view);
     201              : 
     202            0 :                                         if(rt > -1)
     203              :                                         {
     204            0 :                                                 if(SUCCESS == resize(STDERR,(size_t)rt + 1U))
     205              :                                                 {
     206            0 :                                                         run(copy_literal(STDERR,str));
     207              :                                                 }
     208              : 
     209              :                                         } else {
     210            0 :                                                 report("Memory allocation failed, requested size: %zu bytes",(size_t)rt + 1U);
     211            0 :                                                 status = FAILURE;
     212              :                                         }
     213              : 
     214            0 :                                         free(str);
     215              : 
     216            0 :                                         if(SUCCESS == status)
     217              :                                         {
     218            0 :                                                 status = FAILURE;
     219              :                                         }
     220              :                                 }
     221              :                         }
     222              : 
     223              :                         // Suppress stdout if requested.
     224          102 :                         if(STDOUT->length > 0U && true == suppress_stdout)
     225              :                         {
     226            0 :                                 call(del(STDOUT));
     227              :                         }
     228              : 
     229              :                         // Compare exit code with expected and format a report on mismatch.
     230          102 :                         if(SUCCESS == status)
     231              :                         {
     232          102 :                                 if(expected_return_code != exit_code)
     233              :                                 {
     234              :                                         char *str;
     235            0 :                                         const char *stderr_view = getcstring(STDERR);
     236            0 :                                         const char *stdout_view = getcstring(STDOUT);
     237            0 :                                         int rt = asprintf(&str,
     238              :                                                 YELLOW "ERROR: Unexpected exit code!" RESET "\n"
     239              :                                                 YELLOW "Internal call:" RESET "\n" YELLOW ">>" RESET "precizer %s" YELLOW "<<" RESET "\n"
     240              :                                                 YELLOW "Exited with code " RESET "%d" YELLOW " but expected " RESET "%d\n"
     241              :                                                 YELLOW "Stderr output:" RESET "\n" YELLOW ">>" RESET "%s" YELLOW "<<" RESET "\n"
     242              :                                                 YELLOW "Stdout output:" RESET "\n" YELLOW ">>" RESET "%s" YELLOW "<<" RESET "\n",
     243              :                                                 safe_arguments,
     244              :                                                 exit_code,
     245              :                                                 expected_return_code,
     246              :                                                 stderr_view,
     247              :                                                 stdout_view);
     248              : 
     249            0 :                                         if(rt > -1)
     250              :                                         {
     251            0 :                                                 if(SUCCESS == resize(STDERR,(size_t)rt + 1U))
     252              :                                                 {
     253            0 :                                                         run(copy_literal(STDERR,str));
     254              :                                                 }
     255              : 
     256              :                                         } else {
     257            0 :                                                 report("Memory allocation failed, requested size: %zu bytes",(size_t)rt + 1U);
     258            0 :                                                 status = FAILURE;
     259              :                                         }
     260              : 
     261            0 :                                         free(str);
     262              : 
     263            0 :                                         if(SUCCESS == status)
     264              :                                         {
     265            0 :                                                 status = FAILURE;
     266              :                                         }
     267              :                                 }
     268              :                         }
     269              :                 }
     270              :         }
     271              : 
     272              :         // Restore original working directory if changed.
     273          102 :         if(true == changed_directory && NULL != previous_cwd)
     274              :         {
     275          102 :                 if(0 != chdir(previous_cwd))
     276              :                 {
     277            0 :                         serp("Failed to restore working directory");
     278            0 :                         status = FAILURE;
     279              :                 }
     280              :         }
     281              : 
     282          102 :         free(previous_cwd);
     283          102 :         free(argv);
     284              : 
     285              :         // Copy stdout into caller-provided result buffer when requested.
     286          102 :         if(true == words_allocated)
     287              :         {
     288          102 :                 wordfree(&parsed_arguments);
     289              :         }
     290              : 
     291          102 :         if(NULL != result)
     292              :         {
     293           92 :                 if(STDOUT->length > 0U)
     294              :                 {
     295           82 :                         run(copy(result,STDOUT));
     296              :                 }
     297              :         }
     298              : 
     299          102 :         call(del(STDOUT));
     300              : 
     301          102 :         provide(status);
     302              : }
        

Generated by: LCOV version 2.0-1