LCOV - code coverage report
Current view: top level - tests/src - test0045.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 384 384
Test Date: 2026-10-20 14:27:43 Functions: 100.0 % 20 20
Branches: 78.8 % 66 52

             Branch data     Line data    Source code
       1                 :             : #include "sute.h"
       2                 :             : #include <sysexits.h>
       3                 :             : 
       4                 :             : typedef struct
       5                 :             : {
       6                 :             :         const char *path;
       7                 :             :         bool required;
       8                 :             :         bool has_entries;
       9                 :             :         Return result;
      10                 :             : } ReaderCapture;
      11                 :             : 
      12                 :             : static ReaderCapture *active_reader = NULL;
      13                 :             : 
      14                 :             : /**
      15                 :             :  * @brief Preserve the reader's return value while capturing its diagnostics
      16                 :             :  */
      17                 :          90 : static void capture_reader(void)
      18                 :             : {
      19                 :          90 :         char program_name[] = "testitall";
      20                 :          90 :         struct argp_state state = {.name = program_name,.err_stream = stderr,.flags = ARGP_NO_EXIT};
      21                 :         180 :         active_reader->result = read_config_file(active_reader->path,
      22                 :          90 :                 active_reader->required,&active_reader->has_entries,&state);
      23                 :          90 : }
      24                 :             : 
      25                 :             : /**
      26                 :             :  * @brief Apply a file to fresh defaults and check the complete diagnostic output
      27                 :             :  * @param relative_path Configuration path under TMPDIR
      28                 :             :  * @param required Whether absence must cause an error
      29                 :             :  * @param expected_result Expected reader return status
      30                 :             :  * @param expected_entries Whether a valid record must have been encountered
      31                 :             :  * @param error_template Complete stderr template, or NULL for silence
      32                 :             :  * @return SUCCESS when the reader and all captured output match
      33                 :             :  */
      34                 :          90 : static Return check_reader(
      35                 :             :         const char *relative_path,
      36                 :             :         bool       required,
      37                 :             :         Return     expected_result,
      38                 :             :         bool       expected_entries,
      39                 :             :         const char *error_template)
      40                 :             : {
      41                 :             :         /* Status returned by this function through deliver()
      42                 :             :            Default value assumes successful completion */
      43                 :          90 :         Return status = SUCCESS;
      44                 :             :         /* Cleanup must discard a pending diagnostic line before the next run */
      45                 :          90 :         config->config_file_error_line = 12;
      46                 :          90 :         free_config();
      47                 :          90 :         ASSERT(config->config_file_error_line == -1);
      48                 :          90 :         ASSERT(SUCCESS == init_config_and_check_output());
      49                 :          90 :         ASSERT(config->config_file_error_line == -1);
      50                 :          90 :         m_create(char,path,MEMORY_STRING);
      51                 :          90 :         m_create(char,captured_stdout,MEMORY_STRING);
      52                 :          90 :         m_create(char,captured_stderr,MEMORY_STRING);
      53                 :          90 :         m_create(char,pattern,MEMORY_STRING);
      54                 :          90 :         ReaderCapture capture = {
      55                 :             :                 .required = required,
      56                 :             :                 .has_entries = true,
      57                 :             :                 .result = FAILURE
      58                 :             :         };
      59                 :             : 
      60                 :          90 :         ASSERT(SUCCESS == construct_path(relative_path,path));
      61                 :          90 :         IF(SUCCESS == status)
      62                 :             :         {
      63                 :          90 :                 capture.path = m_text(path);
      64                 :          90 :                 active_reader = &capture;
      65                 :             :         }
      66                 :          90 :         ASSERT(SUCCESS == function_capture(capture_reader,captured_stdout,captured_stderr));
      67                 :          90 :         active_reader = NULL;
      68                 :          90 :         ASSERT(capture.result == expected_result);
      69                 :          90 :         ASSERT(capture.has_entries == expected_entries);
      70                 :          90 :         ASSERT(config->config_file_error_line == -1);
      71                 :          90 :         ASSERT(captured_stdout->length == 0U);
      72                 :             : 
      73         [ +  + ]:          90 :         if(error_template != NULL)
      74                 :             :         {
      75                 :          44 :                 ASSERT(SUCCESS == get_file_content(error_template,pattern));
      76                 :          44 :                 ASSERT(SUCCESS == match_pattern(captured_stderr,pattern,error_template));
      77                 :             :         } else {
      78                 :          46 :                 ASSERT(captured_stderr->length == 0U);
      79                 :             :         }
      80                 :             : 
      81                 :          90 :         call(m_del(pattern));
      82                 :          90 :         call(m_del(captured_stderr));
      83                 :          90 :         call(m_del(captured_stdout));
      84                 :          90 :         call(m_del(path));
      85                 :          90 :         deliver(status);
      86                 :             : }
      87                 :             : 
      88                 :             : /**
      89                 :             :  * @brief Apply repeated settings across sections and retain their strings after reading finishes
      90                 :             :  * @return Return status for scalar, list, alias, and output-setting application
      91                 :             :  */
      92                 :           1 : static Return test0045_1(void)
      93                 :             : {
      94                 :           1 :         INITTEST;
      95                 :           1 :         const LOGMODES default_logger_mode = atomic_load_explicit(&rational_logger_mode,memory_order_relaxed);
      96                 :           1 :         ASSERT(SUCCESS == copy_from_origin("tests/templates/0045_001.conf",
      97                 :             :                 "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
      98                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,true,NULL));
      99                 :           1 :         ASSERT(config->roots.length == 4U);
     100                 :           1 :         memory *roots = m_data(memory,conf(roots));
     101                 :           1 :         ASSERT(strcmp(m_text(&roots[0]),"/data/first") == 0);
     102                 :           1 :         ASSERT(strcmp(m_text(&roots[1]),"/data/second#suffix") == 0);
     103                 :           1 :         ASSERT(strcmp(m_text(&roots[2]),"root with spaces") == 0);
     104                 :           1 :         ASSERT(strcmp(m_text(&roots[3]),"[literal continuation]") == 0);
     105                 :             : 
     106                 :           1 :         const char *expected_ignore[] = {
     107                 :             :                 "^cache/","^tmp/","^something_eles/","^other/","^things/","^before#after"
     108                 :             :         };
     109                 :           1 :         ASSERT(config->ignore != NULL);
     110   [ +  +  +  - ]:           7 :         for(size_t i = 0; i < sizeof(expected_ignore) / sizeof(expected_ignore[0]) && SUCCESS == status; ++i)
     111                 :             :         {
     112                 :           6 :                 ASSERT(config->ignore[i] != NULL);
     113                 :           6 :                 ASSERT(strcmp(config->ignore[i],expected_ignore[i]) == 0);
     114                 :             :         }
     115                 :           1 :         ASSERT(config->ignore[6] == NULL);
     116                 :           1 :         ASSERT(config->include != NULL);
     117                 :           1 :         ASSERT(config->include[0] != NULL && config->include[1] != NULL);
     118                 :           1 :         ASSERT(strcmp(config->include[0],"^one;two=three:four/") == 0);
     119                 :           1 :         ASSERT(strcmp(config->include[1],"^following/") == 0);
     120                 :           1 :         ASSERT(config->include[2] == NULL);
     121                 :           1 :         ASSERT(config->include_specified == true);
     122                 :           1 :         ASSERT(config->db_drop_ignored == false);
     123                 :           1 :         ASSERT(config->db_drop_inaccessible == false);
     124                 :           1 :         ASSERT(config->one_file_system == false);
     125                 :           1 :         ASSERT(config->db_check_level == QUICK);
     126                 :           1 :         ASSERT(config->maxdepth == 7);
     127                 :           1 :         ASSERT(strcmp(confstr(db_primary_file_path),"/second/final.db") == 0);
     128                 :           1 :         ASSERT(strcmp(confstr(db_file_name),"final.db") == 0);
     129                 :           1 :         ASSERT(config->compare_filter == (CF_CHECKSUM_MISMATCH | CF_FIRST_SOURCE));
     130                 :           1 :         ASSERT(config->dry_run == true && config->dry_run_with_checksums == true);
     131                 :           1 :         ASSERT(config->verbose == false);
     132                 :           1 :         ASSERT(rational_logger_mode == default_logger_mode);
     133                 :           1 :         ASSERT(rational_color_mode == COLOR_MODE_NEVER);
     134                 :             : 
     135                 :             :         /* Mixed-case FULL replaces QUICK instead of retaining the earlier value */
     136                 :           1 :         ASSERT(SUCCESS == copy_path("tests/templates/0045_001_5.conf","0045_reader.conf"));
     137                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,true,NULL));
     138                 :           1 :         ASSERT(config->db_check_level == FULL);
     139                 :             : 
     140                 :             :         /* The largest supported traversal depth is accepted without truncation */
     141                 :           1 :         ASSERT(SUCCESS == copy_path("tests/templates/0045_001_6.conf","0045_reader.conf"));
     142                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,true,NULL));
     143                 :           1 :         ASSERT(config->maxdepth == 32767);
     144                 :             : 
     145                 :             :         /* BOM, CRLF, initial indentation, and an unterminated continuation */
     146                 :           1 :         ASSERT(SUCCESS == copy_from_origin("tests/templates/0045_001_1.conf",
     147                 :             :                 "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     148                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,true,NULL));
     149                 :           1 :         ASSERT(config->roots.length == 2U);
     150                 :           1 :         roots = m_data(memory,conf(roots));
     151                 :           1 :         ASSERT(strcmp(m_text(&roots[0]),"first") == 0);
     152                 :           1 :         ASSERT(strcmp(m_text(&roots[1]),"second") == 0);
     153                 :             : 
     154                 :             :         /* A selected file can contain no records at all */
     155                 :           1 :         ASSERT(SUCCESS == copy_from_origin("tests/templates/0045_001_2.conf",
     156                 :             :                 "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     157                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,false,NULL));
     158                 :           1 :         ASSERT(config->roots.length == 0U);
     159                 :           1 :         ASSERT(SUCCESS == copy_from_origin("tests/templates/0045_001_3.conf",
     160                 :             :                 "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     161                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,false,NULL));
     162                 :           1 :         ASSERT(config->roots.length == 0U);
     163                 :             : 
     164                 :             :         /* Section headers alone do not count as settings */
     165                 :           1 :         ASSERT(SUCCESS == copy_from_origin("tests/templates/0045_001_4.conf",
     166                 :             :                 "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     167                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,false,NULL));
     168                 :           1 :         ASSERT(config->roots.length == 0U);
     169                 :             : 
     170                 :           1 :         call(delete_path_if_present("0045_reader.conf"));
     171                 :           1 :         RETURN_STATUS;
     172                 :             : }
     173                 :             : 
     174                 :             : /**
     175                 :             :  * @brief Reject invalid syntax and keys before consuming any later records
     176                 :             :  * @return Return status for first-error handling and original line numbers
     177                 :             :  */
     178                 :           1 : static Return test0045_2(void)
     179                 :             : {
     180                 :           1 :         INITTEST;
     181                 :             :         static const struct {
     182                 :             :                 const char *config_template;
     183                 :             :                 const char *error_template;
     184                 :             :         } cases[] = {
     185                 :             :                 {"tests/templates/0045_002_1.conf","templates/0045_002.txt"},
     186                 :             :                 {"tests/templates/0045_002_2.conf","templates/0045_003.txt"},
     187                 :             :                 {"tests/templates/0045_002_3.conf","templates/0045_003.txt"},
     188                 :             :                 {"tests/templates/0045_002_4.conf","templates/0045_003.txt"},
     189                 :             :                 {"tests/templates/0045_002_5.conf","templates/0045_004.txt"},
     190                 :             :                 {"tests/templates/0045_002_6.conf","templates/0045_024.txt"},
     191                 :             :                 {"tests/templates/0045_002_7.conf","templates/0045_025.txt"},
     192                 :             :                 {"tests/templates/0045_002_8.conf","templates/0045_005.txt"},
     193                 :             :                 {"tests/templates/0045_002_9.conf","templates/0045_026.txt"},
     194                 :             :                 {"tests/templates/0045_002_10.conf","templates/0045_027.txt"},
     195                 :             :                 {"tests/templates/0045_002_11.conf","templates/0045_028.txt"}
     196                 :             :         };
     197                 :             : 
     198   [ +  +  +  - ]:          12 :         for(size_t i = 0; i < sizeof(cases) / sizeof(cases[0]) && SUCCESS == status; ++i)
     199                 :             :         {
     200                 :          11 :                 ASSERT(SUCCESS == copy_from_origin(cases[i].config_template,
     201                 :             :                         "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     202                 :          11 :                 ASSERT(SUCCESS == check_reader("0045_reader.conf",true,FAILURE,true,
     203                 :             :                         cases[i].error_template));
     204                 :          11 :                 ASSERT(config->roots.length == 1U);
     205                 :             :         }
     206                 :             : 
     207                 :             :         /* A first-line failure must prevent the later valid key from setting the flag */
     208                 :           1 :         ASSERT(SUCCESS == copy_from_origin("tests/templates/0045_002_12.conf",
     209                 :             :                 "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     210                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,FAILURE,false,
     211                 :             :                 "templates/0045_006.txt"));
     212                 :           1 :         ASSERT(config->roots.length == 0U);
     213                 :             : 
     214                 :           1 :         call(delete_path_if_present("0045_reader.conf"));
     215                 :           1 :         RETURN_STATUS;
     216                 :             : }
     217                 :             : 
     218                 :             : /**
     219                 :             :  * @brief Reject an oversized line and stop before applying subsequent settings
     220                 :             :  * @return Return status for the parser's long-line error and unread later settings
     221                 :             :  */
     222                 :           1 : static Return test0045_3(void)
     223                 :             : {
     224                 :           1 :         INITTEST;
     225                 :           1 :         ASSERT(SUCCESS == copy_from_origin("tests/templates/0045_003.conf",
     226                 :             :                 "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     227                 :             :         /* inih reports the error after recognizing the key from the buffered prefix */
     228                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,FAILURE,true,
     229                 :             :                 "templates/0045_007.txt"));
     230                 :             :         /* The valid path after the oversized line must not be applied */
     231                 :           1 :         ASSERT(config->roots.length == 0U);
     232                 :             : 
     233                 :           1 :         call(delete_path_if_present("0045_reader.conf"));
     234                 :           1 :         RETURN_STATUS;
     235                 :             : }
     236                 :             : 
     237                 :             : /**
     238                 :             :  * @brief Distinguish optional absence from required-file and read failures
     239                 :             :  * @return Return status for filesystem failure handling
     240                 :             :  */
     241                 :           1 : static Return test0045_4(void)
     242                 :             : {
     243                 :           1 :         INITTEST;
     244                 :             : 
     245                 :           1 :         ASSERT(SUCCESS == delete_path_if_present("0045_reader.conf"));
     246                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",false,SUCCESS,false,NULL));
     247                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,FAILURE,false,
     248                 :             :                 "templates/0045_008.txt"));
     249                 :             : 
     250                 :             :         /* A regular file used as a parent produces ENOTDIR even with elevated privileges */
     251                 :           1 :         ASSERT(SUCCESS == copy_from_origin("tests/templates/0045_001_2.conf",
     252                 :             :                 "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     253                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf/child",false,FAILURE,false,
     254                 :             :                 "templates/0045_009.txt"));
     255                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf/child",true,FAILURE,false,
     256                 :             :                 "templates/0045_009.txt"));
     257                 :             : 
     258                 :             :         /* A directory opens successfully but causes a read error on the first read */
     259                 :           1 :         ASSERT(SUCCESS == create_directory("0045_config_directory"));
     260                 :           1 :         ASSERT(SUCCESS == check_reader("0045_config_directory",true,FAILURE,false,
     261                 :             :                 "templates/0045_010.txt"));
     262                 :           1 :         call(delete_path_if_present("0045_config_directory"));
     263                 :             : 
     264                 :           1 :         call(delete_path_if_present("0045_reader.conf"));
     265                 :           1 :         RETURN_STATUS;
     266                 :             : }
     267                 :             : 
     268                 :             : /**
     269                 :             :  * @brief Apply the last boolean value and update the flag's related settings
     270                 :             :  * @return Return status for boolean spellings, repeated flags, and dry-run modes
     271                 :             :  */
     272                 :           1 : static Return test0045_5(void)
     273                 :             : {
     274                 :           1 :         INITTEST;
     275                 :           1 :         free_config();
     276                 :           1 :         ASSERT(SUCCESS == init_config_and_check_output());
     277                 :           1 :         const LOGMODES default_logger_mode = atomic_load_explicit(&rational_logger_mode,memory_order_relaxed);
     278                 :             :         const struct {
     279                 :             :                 const char *config_template;
     280                 :             :                 bool enabled;
     281                 :           1 :         } cases[] = {
     282                 :             :                 {"tests/templates/0045_005_1.conf",true},
     283                 :             :                 {"tests/templates/0045_005_2.conf",false},
     284                 :             :                 {"tests/templates/0045_005_3.conf",false},
     285                 :             :                 {"tests/templates/0045_005_4.conf",true},
     286                 :             :                 {"tests/templates/0045_005_5.conf",false},
     287                 :             :                 {"tests/templates/0045_005_6.conf",false},
     288                 :             :                 {"tests/templates/0045_005_7.conf",true},
     289                 :             :                 {"tests/templates/0045_005_8.conf",false},
     290                 :             :                 {"tests/templates/0045_005_9.conf",false},
     291                 :             :                 {"tests/templates/0045_005_10.conf",true},
     292                 :             :                 {"tests/templates/0045_005_11.conf",false},
     293                 :             :                 {"tests/templates/0045_005_12.conf",false},
     294                 :             :                 {"tests/templates/0045_005_13.conf",true},
     295                 :             :                 {"tests/templates/0045_005_14.conf",false},
     296                 :             :                 {"tests/templates/0045_005_15.conf",false},
     297                 :             :                 {"tests/templates/0045_005_16.conf",true}
     298                 :             :         };
     299                 :             : 
     300   [ +  +  +  - ]:          17 :         for(size_t i = 0; i < sizeof(cases) / sizeof(cases[0]) && SUCCESS == status; ++i)
     301                 :             :         {
     302                 :          16 :                 ASSERT(SUCCESS == copy_from_origin(cases[i].config_template,
     303                 :             :                         "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     304                 :          16 :                 ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,true,NULL));
     305                 :          16 :                 ASSERT(config->progress == cases[i].enabled);
     306                 :          16 :                 ASSERT(config->update == cases[i].enabled);
     307                 :          16 :                 ASSERT(config->dry_run == cases[i].enabled);
     308                 :          16 :                 ASSERT(config->rehash_locked == cases[i].enabled);
     309                 :          16 :                 ASSERT(config->one_file_system == cases[i].enabled);
     310                 :          16 :                 ASSERT(config->db_drop_ignored == cases[i].enabled);
     311                 :          16 :                 ASSERT(config->db_drop_inaccessible == cases[i].enabled);
     312                 :          16 :                 ASSERT(config->watch_timestamps == cases[i].enabled);
     313                 :          16 :                 ASSERT(config->force == cases[i].enabled);
     314                 :          16 :                 ASSERT(config->quiet_ignored == cases[i].enabled);
     315                 :          16 :                 ASSERT(config->verbose == cases[i].enabled);
     316                 :          16 :                 ASSERT(config->show_remembered_messages_at_exit == cases[i].enabled);
     317                 :          16 :                 ASSERT(config->dry_run_with_checksums == false);
     318                 :             : 
     319         [ +  + ]:          16 :                 if(cases[i].enabled == true)
     320                 :             :                 {
     321                 :           6 :                         ASSERT(rational_logger_mode == VERBOSE);
     322                 :             :                 } else {
     323                 :          10 :                         ASSERT(rational_logger_mode == default_logger_mode);
     324                 :             :                 }
     325                 :             :         }
     326                 :             : 
     327                 :             :         /* A plain true value replaces the checksum mode; a later named mode enables it */
     328                 :           1 :         ASSERT(SUCCESS == copy_from_origin("tests/templates/0045_005_17.conf",
     329                 :             :                 "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     330                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,true,NULL));
     331                 :           1 :         ASSERT(config->dry_run == true && config->dry_run_with_checksums == false);
     332                 :           1 :         ASSERT(SUCCESS == copy_from_origin("tests/templates/0045_005_18.conf",
     333                 :             :                 "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     334                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,true,NULL));
     335                 :           1 :         ASSERT(config->dry_run == true && config->dry_run_with_checksums == true);
     336                 :             : 
     337                 :             :         /* Disabling an output mode preserves a different active mode */
     338                 :             :         const struct {
     339                 :             :                 const char *config_template;
     340                 :             :                 LOGMODES logger_mode;
     341                 :             :                 bool verbose;
     342                 :           1 :         } output_cases[] = {
     343                 :             :                 {"tests/templates/0045_005_19.conf",default_logger_mode,false},
     344                 :             :                 {"tests/templates/0045_005_20.conf",default_logger_mode,false},
     345                 :             :                 {"tests/templates/0045_005_21.conf",SILENT,false},
     346                 :             :                 {"tests/templates/0045_005_22.conf",VERBOSE,true}
     347                 :             :         };
     348                 :             : 
     349   [ +  +  +  - ]:           5 :         for(size_t i = 0; i < sizeof(output_cases) / sizeof(output_cases[0]) && SUCCESS == status; ++i)
     350                 :             :         {
     351                 :           4 :                 ASSERT(SUCCESS == copy_from_origin(output_cases[i].config_template,
     352                 :             :                         "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     353                 :           4 :                 ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,true,NULL));
     354                 :           4 :                 ASSERT(rational_logger_mode == output_cases[i].logger_mode);
     355                 :           4 :                 ASSERT(config->verbose == output_cases[i].verbose);
     356                 :             :         }
     357                 :             : 
     358                 :           1 :         call(delete_path_if_present("0045_reader.conf"));
     359                 :           1 :         RETURN_STATUS;
     360                 :             : }
     361                 :             : 
     362                 :             : /**
     363                 :             :  * @brief Attribute shared option errors to their original configuration lines
     364                 :             :  * @return Return status for invalid values and immediate parsing termination
     365                 :             :  */
     366                 :           1 : static Return test0045_6(void)
     367                 :             : {
     368                 :           1 :         INITTEST;
     369                 :             :         static const struct {
     370                 :             :                 const char *config_template;
     371                 :             :                 const char *error_template;
     372                 :             :         } cases[] = {
     373                 :             :                 {"tests/templates/0045_006_1.conf","templates/0045_011.txt"},
     374                 :             :                 {"tests/templates/0045_006_2.conf","templates/0045_020.txt"},
     375                 :             :                 {"tests/templates/0045_006_3.conf","templates/0045_029.txt"},
     376                 :             :                 {"tests/templates/0045_006_4.conf","templates/0045_030.txt"},
     377                 :             :                 {"tests/templates/0045_006_5.conf","templates/0045_031.txt"},
     378                 :             :                 {"tests/templates/0045_006_6.conf","templates/0045_012.txt"},
     379                 :             :                 {"tests/templates/0045_006_7.conf","templates/0045_013.txt"},
     380                 :             :                 {"tests/templates/0045_006_8.conf","templates/0045_014.txt"},
     381                 :             :                 {"tests/templates/0045_006_9.conf","templates/0045_015.txt"},
     382                 :             :                 {"tests/templates/0045_006_10.conf","templates/0045_016.txt"},
     383                 :             :                 {"tests/templates/0045_006_11.conf","templates/0045_017.txt"},
     384                 :             :                 {"tests/templates/0045_006_12.conf","templates/0045_018.txt"},
     385                 :             :                 {"tests/templates/0045_006_13.conf","templates/0045_019.txt"},
     386                 :             :                 {"tests/templates/0045_006_14.conf","templates/0045_032.txt"},
     387                 :             :                 {"tests/templates/0045_006_15.conf","templates/0045_015.txt"},
     388                 :             :                 {"tests/templates/0045_006_16.conf","templates/0045_015.txt"},
     389                 :             :                 {"tests/templates/0045_006_17.conf","templates/0045_018.txt"},
     390                 :             :                 {"tests/templates/0045_006_18.conf","templates/0045_018.txt"}
     391                 :             :         };
     392                 :             : 
     393   [ +  +  +  - ]:          19 :         for(size_t i = 0; i < sizeof(cases) / sizeof(cases[0]) && SUCCESS == status; ++i)
     394                 :             :         {
     395                 :          18 :                 ASSERT(SUCCESS == copy_from_origin(cases[i].config_template,
     396                 :             :                         "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     397                 :          18 :                 ASSERT(SUCCESS == check_reader("0045_reader.conf",true,FAILURE,true,cases[i].error_template));
     398                 :          18 :                 ASSERT(config->roots.length == 1U);
     399                 :          18 :                 memory *roots = m_data(memory,conf(roots));
     400                 :          18 :                 ASSERT(strcmp(m_text(&roots[0]),"first") == 0);
     401                 :             :         }
     402                 :             : 
     403                 :           1 :         call(delete_path_if_present("0045_reader.conf"));
     404                 :           1 :         RETURN_STATUS;
     405                 :             : }
     406                 :             : 
     407                 :             : /**
     408                 :             :  * @brief Retain regex text from repeated keys and continuations for working compilation
     409                 :             :  * @return Return status for valid and empty expressions in all three regex lists
     410                 :             :  */
     411                 :           1 : static Return test0045_7(void)
     412                 :             : {
     413                 :           1 :         INITTEST;
     414                 :           1 :         ASSERT(SUCCESS == copy_from_origin("tests/templates/0045_007.conf",
     415                 :             :                 "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     416                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,true,NULL));
     417                 :             : 
     418                 :           1 :         char **patterns[] = {config->ignore,config->include,config->lock_checksum};
     419                 :             : 
     420   [ +  +  +  - ]:           4 :         for(size_t i = 0; i < sizeof(patterns) / sizeof(patterns[0]) && SUCCESS == status; ++i)
     421                 :             :         {
     422                 :           3 :                 ASSERT(patterns[i] != NULL);
     423                 :           3 :                 ASSERT(patterns[i][0] != NULL && patterns[i][1] != NULL && patterns[i][2] != NULL);
     424                 :           3 :                 ASSERT(strcmp(patterns[i][0],"^first/") == 0);
     425                 :           3 :                 ASSERT(strcmp(patterns[i][1],"^second/") == 0);
     426                 :           3 :                 ASSERT(strcmp(patterns[i][2],"") == 0);
     427                 :           3 :                 ASSERT(patterns[i][3] == NULL);
     428                 :             :         }
     429                 :             : 
     430                 :             :         /* Reading keeps no compiled objects; working compilation creates them */
     431                 :           1 :         ASSERT(config->ignore_pcre_compiled == NULL);
     432                 :           1 :         ASSERT(config->include_pcre_compiled == NULL);
     433                 :           1 :         ASSERT(config->lock_checksum_pcre_compiled == NULL);
     434                 :           1 :         ASSERT(SUCCESS == compile_patterns());
     435                 :           1 :         pcre2_code **compiled[] = {
     436                 :           1 :                 config->ignore_pcre_compiled,config->include_pcre_compiled,config->lock_checksum_pcre_compiled
     437                 :             :         };
     438                 :             : 
     439   [ +  +  +  - ]:           4 :         for(size_t i = 0; i < sizeof(compiled) / sizeof(compiled[0]) && SUCCESS == status; ++i)
     440                 :             :         {
     441                 :           3 :                 ASSERT(compiled[i] != NULL);
     442                 :           3 :                 ASSERT(compiled[i][0] != NULL && compiled[i][1] != NULL && compiled[i][2] != NULL);
     443                 :           3 :                 ASSERT(compiled[i][3] == NULL);
     444                 :             :         }
     445                 :             : 
     446                 :           1 :         call(delete_path_if_present("0045_reader.conf"));
     447                 :           1 :         RETURN_STATUS;
     448                 :             : }
     449                 :             : 
     450                 :             : /**
     451                 :             :  * @brief Check the compiler's failure status while its normal output is captured
     452                 :             :  * @return SUCCESS when working compilation rejects the stored expression
     453                 :             :  */
     454                 :           9 : static Return capture_pattern_compilation_failure(void)
     455                 :             : {
     456                 :           9 :         INITTEST;
     457                 :           9 :         ASSERT(FAILURE == compile_patterns());
     458                 :           9 :         deliver(status);
     459                 :             : }
     460                 :             : 
     461                 :             : /**
     462                 :             :  * @brief Defer regex errors until working compilation and prioritize configuration parsing errors
     463                 :             :  * @return Return status for first entries, repeated keys, and continuation values
     464                 :             :  */
     465                 :           1 : static Return test0045_8(void)
     466                 :             : {
     467                 :           1 :         INITTEST;
     468                 :           1 :         m_create(char,pattern,MEMORY_STRING);
     469                 :           1 :         ASSERT(SUCCESS == get_file_content("templates/0045_021.txt",pattern));
     470                 :             :         const struct {
     471                 :             :                 const char *config_template;
     472                 :             :                 /* Address of the Config list populated after check_reader() resets defaults */
     473                 :             :                 char ***patterns;
     474                 :             :                 bool has_valid_pattern;
     475                 :           1 :         } cases[] = {
     476                 :           1 :                 {"tests/templates/0045_008_1.conf",&config->ignore,false},
     477                 :           1 :                 {"tests/templates/0045_008_2.conf",&config->include,false},
     478                 :           1 :                 {"tests/templates/0045_008_3.conf",&config->lock_checksum,false},
     479                 :           1 :                 {"tests/templates/0045_008_4.conf",&config->ignore,true},
     480                 :           1 :                 {"tests/templates/0045_008_5.conf",&config->include,true},
     481                 :           1 :                 {"tests/templates/0045_008_6.conf",&config->lock_checksum,true},
     482                 :           1 :                 {"tests/templates/0045_008_7.conf",&config->ignore,true},
     483                 :           1 :                 {"tests/templates/0045_008_8.conf",&config->include,true},
     484                 :           1 :                 {"tests/templates/0045_008_9.conf",&config->lock_checksum,true}
     485                 :             :         };
     486                 :             : 
     487   [ +  +  +  - ]:          10 :         for(size_t i = 0; i < sizeof(cases) / sizeof(cases[0]) && SUCCESS == status; ++i)
     488                 :             :         {
     489                 :           9 :                 ASSERT(SUCCESS == copy_from_origin(cases[i].config_template,
     490                 :             :                         "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     491                 :           9 :                 ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,true,NULL));
     492                 :           9 :                 ASSERT(config->roots.length == 1U);
     493                 :           9 :                 memory *roots = m_data(memory,conf(roots));
     494                 :           9 :                 ASSERT(strcmp(m_text(&roots[0]),"after-pattern") == 0);
     495                 :           9 :                 ASSERT(*cases[i].patterns != NULL);
     496                 :           9 :                 ASSERT((*cases[i].patterns)[0] != NULL);
     497                 :             : 
     498         [ +  + ]:           9 :                 if(cases[i].has_valid_pattern == true)
     499                 :             :                 {
     500                 :           6 :                         ASSERT(strcmp((*cases[i].patterns)[0],"^first/") == 0);
     501                 :           6 :                         ASSERT((*cases[i].patterns)[1] != NULL);
     502                 :           6 :                         ASSERT(strcmp((*cases[i].patterns)[1],"(") == 0);
     503                 :           6 :                         ASSERT((*cases[i].patterns)[2] == NULL);
     504                 :             :                 } else {
     505                 :           3 :                         ASSERT(strcmp((*cases[i].patterns)[0],"(") == 0);
     506                 :           3 :                         ASSERT((*cases[i].patterns)[1] == NULL);
     507                 :             :                 }
     508                 :             : 
     509                 :             :                 /* Use the normal uncolored diagnostic, without verbose or testing traces */
     510                 :           9 :                 rational_logger_mode = REGULAR;
     511                 :           9 :                 rational_color_mode = COLOR_MODE_NEVER;
     512                 :           9 :                 ASSERT(SUCCESS == match_function_output(m_text(pattern),NULL,capture_pattern_compilation_failure));
     513                 :           9 :                 ASSERT(config->ignore_pcre_compiled == NULL);
     514                 :           9 :                 ASSERT(config->include_pcre_compiled == NULL);
     515                 :           9 :                 ASSERT(config->lock_checksum_pcre_compiled == NULL);
     516                 :             :         }
     517                 :             : 
     518                 :             :         /* A later option error is reported before regex compilation can start */
     519                 :           1 :         ASSERT(SUCCESS == copy_from_origin("tests/templates/0045_008_10.conf",
     520                 :             :                 "0045_reader.conf",REQUIRE_SOURCE_EXISTS));
     521                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,FAILURE,true,"templates/0045_015.txt"));
     522                 :           1 :         ASSERT(config->ignore != NULL && config->ignore[0] != NULL);
     523                 :           1 :         ASSERT(strcmp(config->ignore[0],"(") == 0);
     524                 :           1 :         ASSERT(config->roots.length == 0U);
     525                 :             : 
     526                 :           1 :         call(delete_path_if_present("0045_reader.conf"));
     527                 :           1 :         call(m_del(pattern));
     528                 :           1 :         RETURN_STATUS;
     529                 :             : }
     530                 :             : 
     531                 :             : /**
     532                 :             :  * @brief Check source decisions from preliminary results without running argp
     533                 :             :  * @return SUCCESS when source selection and discarded error results match
     534                 :             :  */
     535                 :           1 : static Return check_settings_source_choices(void)
     536                 :             : {
     537                 :           1 :         INITTEST;
     538                 :           1 :         char program_name[] = "testitall";
     539                 :             :         static const struct {
     540                 :             :                 int argc;
     541                 :             :                 error_t preliminary_error;
     542                 :             :                 const char *explicit_path;
     543                 :             :                 bool has_other_arguments;
     544                 :             :                 bool information_mode_requested;
     545                 :             :                 const char *expected_path;
     546                 :             :                 bool expected_required;
     547                 :             :         } cases[] = {
     548                 :             :                 {1,EX_OK,NULL,false,false,APP_NAME ".conf",false},
     549                 :             :                 {2,EX_OK,NULL,true,false,NULL,false},
     550                 :             :                 {2,EX_OK,NULL,false,false,NULL,false},
     551                 :             :                 {3,EX_OK,"profile.conf",false,false,"profile.conf",true},
     552                 :             :                 {2,EX_OK,"",false,false,"",true},
     553                 :             :                 {2,EX_OK,"--",false,false,"--",true},
     554                 :             :                 /* Preliminary parsing supplies the last path when --config is repeated */
     555                 :             :                 {5,EX_OK,"last.conf",false,false,"last.conf",true},
     556                 :             :                 {1,EINVAL,NULL,false,false,NULL,false},
     557                 :             :                 {3,ARGP_ERR_UNKNOWN,"profile.conf",false,false,NULL,false},
     558                 :             :                 {4,EINVAL,"profile.conf",true,false,NULL,false},
     559                 :             :                 {2,EINVAL,NULL,true,false,NULL,false},
     560                 :             :                 /* Information requests suppress both automatic and explicit file selection */
     561                 :             :                 {2,EX_OK,NULL,true,true,NULL,false},
     562                 :             :                 {4,EX_OK,"profile.conf",true,true,NULL,false},
     563                 :             :                 {3,EX_OK,"",true,true,NULL,false},
     564                 :             :                 {4,EINVAL,"profile.conf",true,true,NULL,false}
     565                 :             :         };
     566                 :             : 
     567   [ +  +  +  - ]:          16 :         for(size_t i = 0; i < sizeof(cases) / sizeof(cases[0]) && SUCCESS == status; ++i)
     568                 :             :         {
     569                 :          15 :                 config->config_file_path = cases[i].explicit_path;
     570                 :          15 :                 ArgumentParseContext context = {
     571                 :             :                         .config_required = true,
     572                 :          15 :                         .has_other_arguments = cases[i].has_other_arguments,
     573                 :          15 :                         .information_mode_requested = cases[i].information_mode_requested,
     574                 :             :                         .diagnostic_state = {.name = program_name,.err_stream = stderr}
     575                 :             :                 };
     576                 :          15 :                 ASSERT(SUCCESS == select_settings_source(cases[i].argc,cases[i].preliminary_error,&context));
     577                 :          15 :                 ASSERT(context.config_required == cases[i].expected_required);
     578                 :             : 
     579         [ +  + ]:          15 :                 if(cases[i].expected_path != NULL)
     580                 :             :                 {
     581                 :           5 :                         ASSERT(config->config_file_path != NULL);
     582                 :           5 :                         ASSERT(strcmp(config->config_file_path,cases[i].expected_path) == 0);
     583                 :             :                 } else {
     584                 :          10 :                         ASSERT(config->config_file_path == NULL);
     585                 :             :                 }
     586                 :             : 
     587         [ +  + ]:          15 :                 if(cases[i].preliminary_error != EX_OK)
     588                 :             :                 {
     589                 :           5 :                         ASSERT(context.has_other_arguments == false);
     590                 :           5 :                         ASSERT(context.information_mode_requested == false);
     591                 :             :                 }
     592                 :             :         }
     593                 :             : 
     594                 :           1 :         deliver(status);
     595                 :             : }
     596                 :             : 
     597                 :             : /**
     598                 :             :  * @brief Select settings sources silently and defer preliminary errors to CLI parsing
     599                 :             :  * @return Return status for source decisions and empty diagnostic streams
     600                 :             :  */
     601                 :           1 : static Return test0045_9(void)
     602                 :             : {
     603                 :           1 :         INITTEST;
     604                 :           1 :         ASSERT(SUCCESS == match_function_output(NULL,NULL,check_settings_source_choices));
     605                 :           1 :         RETURN_STATUS;
     606                 :             : }
     607                 :             : 
     608                 :             : /**
     609                 :             :  * @brief Reject an explicit configuration file combined with other arguments
     610                 :             :  * @return SUCCESS when the conflict fails without leaving a selected file
     611                 :             :  */
     612                 :           1 : static Return check_settings_source_conflict(void)
     613                 :             : {
     614                 :           1 :         INITTEST;
     615                 :           1 :         char program_name[] = "testitall";
     616                 :           1 :         config->config_file_path = "missing.conf";
     617                 :           1 :         ArgumentParseContext context = {
     618                 :             :                 .config_required = true,
     619                 :             :                 .has_other_arguments = true,
     620                 :             :                 .diagnostic_state = {.name = program_name,.err_stream = stderr}
     621                 :             :         };
     622                 :           1 :         ASSERT(FAILURE == select_settings_source(4,EX_OK,&context));
     623                 :           1 :         ASSERT(config->config_file_path == NULL);
     624                 :           1 :         ASSERT(context.config_required == false);
     625                 :           1 :         deliver(status);
     626                 :             : }
     627                 :             : 
     628                 :             : /**
     629                 :             :  * @brief Check the complete diagnostic for a config and CLI source conflict
     630                 :             :  * @return Return status for the expected conflict message and empty stdout
     631                 :             :  */
     632                 :           1 : static Return test0045_10(void)
     633                 :             : {
     634                 :           1 :         INITTEST;
     635                 :           1 :         m_create(char,pattern,MEMORY_STRING);
     636                 :           1 :         ASSERT(SUCCESS == get_file_content("templates/0045_022.txt",pattern));
     637                 :           1 :         ASSERT(SUCCESS == match_function_output(NULL,m_text(pattern),check_settings_source_conflict));
     638                 :           1 :         call(m_del(pattern));
     639                 :           1 :         RETURN_STATUS;
     640                 :             : }
     641                 :             : 
     642                 :             : /**
     643                 :             :  * @brief Apply quoted values while preserving spaces, backslashes, and literal contents
     644                 :             :  * @return Return status for quote removal and shared value interpretation
     645                 :             :  */
     646                 :           1 : static Return test0045_11(void)
     647                 :             : {
     648                 :           1 :         INITTEST;
     649                 :           1 :         ASSERT(SUCCESS == copy_path("tests/templates/0045_011.conf","0045_reader.conf"));
     650                 :           1 :         ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,true,NULL));
     651                 :           1 :         ASSERT(config->roots.length == 3U);
     652                 :           1 :         memory *roots = m_data(memory,conf(roots));
     653                 :           1 :         ASSERT(strcmp(m_text(&roots[0]),"/mnt/my files") == 0);
     654                 :           1 :         ASSERT(strcmp(m_text(&roots[1]),"  spaced path  ") == 0);
     655                 :           1 :         ASSERT(strcmp(m_text(&roots[2]),"$HOME/$(literal)") == 0);
     656                 :             : 
     657                 :           1 :         const char *expected_ignore[] = {
     658                 :             :                 "^proc/.*","^sys/.*","\\.log$","^\\w+\\s+\\d+$","\"literal quotes\"","^before#after","^literal\"$"
     659                 :             :         };
     660                 :           1 :         ASSERT(config->ignore != NULL);
     661   [ +  +  +  - ]:           8 :         for(size_t i = 0; i < sizeof(expected_ignore) / sizeof(expected_ignore[0]) && SUCCESS == status; ++i)
     662                 :             :         {
     663                 :           7 :                 ASSERT(config->ignore[i] != NULL);
     664                 :           7 :                 ASSERT(strcmp(config->ignore[i],expected_ignore[i]) == 0);
     665                 :             :         }
     666                 :           1 :         ASSERT(config->ignore[7] == NULL);
     667                 :           1 :         ASSERT(config->include != NULL && config->include[0] != NULL);
     668                 :           1 :         ASSERT(strcmp(config->include[0],"^dir with spaces/") == 0);
     669                 :           1 :         ASSERT(config->include[1] == NULL);
     670                 :           1 :         ASSERT(strcmp(confstr(db_primary_file_path),"/tmp/my data.db") == 0);
     671                 :           1 :         ASSERT(strcmp(confstr(db_file_name),"my data.db") == 0);
     672                 :           1 :         ASSERT(config->maxdepth == 3);
     673                 :           1 :         ASSERT(config->db_check_level == QUICK);
     674                 :           1 :         ASSERT(config->update == true);
     675                 :           1 :         ASSERT(config->quiet_ignored == false);
     676                 :           1 :         ASSERT(config->progress == false);
     677                 :           1 :         ASSERT(config->dry_run == true && config->dry_run_with_checksums == true);
     678                 :             : 
     679                 :           1 :         call(delete_path_if_present("0045_reader.conf"));
     680                 :           1 :         RETURN_STATUS;
     681                 :             : }
     682                 :             : 
     683                 :             : /**
     684                 :             :  * @brief Reject missing or mismatched closing quotes at the original line
     685                 :             :  * @return Return status for complete diagnostics and stopping before later records
     686                 :             :  */
     687                 :           1 : static Return test0045_12(void)
     688                 :             : {
     689                 :           1 :         INITTEST;
     690                 :           1 :         const char *cases[] = {
     691                 :             :                 "tests/templates/0045_012_1.conf",
     692                 :             :                 "tests/templates/0045_012_2.conf",
     693                 :             :                 "tests/templates/0045_012_3.conf",
     694                 :             :                 "tests/templates/0045_012_4.conf"
     695                 :             :         };
     696                 :             : 
     697   [ +  +  +  - ]:           5 :         for(size_t i = 0; i < sizeof(cases) / sizeof(cases[0]) && SUCCESS == status; ++i)
     698                 :             :         {
     699                 :           4 :                 ASSERT(SUCCESS == copy_path(cases[i],"0045_reader.conf"));
     700                 :           4 :                 ASSERT(SUCCESS == check_reader("0045_reader.conf",true,FAILURE,true,"templates/0045_012_1.txt"));
     701                 :           4 :                 ASSERT(config->roots.length == 1U);
     702                 :           4 :                 memory *roots = m_data(memory,conf(roots));
     703                 :           4 :                 ASSERT(strcmp(m_text(&roots[0]),"first") == 0);
     704                 :           4 :                 ASSERT(config->ignore == NULL);
     705                 :             :         }
     706                 :             : 
     707                 :           1 :         call(delete_path_if_present("0045_reader.conf"));
     708                 :           1 :         RETURN_STATUS;
     709                 :             : }
     710                 :             : 
     711                 :             : /**
     712                 :             :  * @brief Preserve comparison paths in file order across repeated keys and continuation lines
     713                 :             :  * @return Return status for owned paths, literal values, and configuration cleanup
     714                 :             :  */
     715                 :           1 : static Return test0045_13(void)
     716                 :             : {
     717                 :           1 :         INITTEST;
     718                 :             :         const struct {
     719                 :             :                 const char *config_template;
     720                 :             :                 const char *first_path;
     721                 :             :                 const char *second_path;
     722                 :           1 :         } cases[] = {
     723                 :             :                 {"tests/templates/0045_013_1.conf","first.db","second.db"},
     724                 :             :                 {"tests/templates/0045_013_2.conf","first.db","second.db"},
     725                 :             :                 {"tests/templates/0045_013_3.conf","first db.db","second db.db"},
     726                 :             :                 {"tests/templates/0045_013_4.conf","first db.db","second db.db"},
     727                 :             :                 {"tests/templates/0045_013_5.conf","true","false"}
     728                 :             :         };
     729                 :             : 
     730   [ +  +  +  - ]:           6 :         for(size_t i = 0; i < sizeof(cases) / sizeof(cases[0]) && SUCCESS == status; ++i)
     731                 :             :         {
     732                 :           5 :                 ASSERT(SUCCESS == copy_path(cases[i].config_template,"0045_reader.conf"));
     733                 :           5 :                 ASSERT(SUCCESS == check_reader("0045_reader.conf",true,SUCCESS,true,NULL));
     734                 :           5 :                 ASSERT(config->compare == true);
     735                 :           5 :                 ASSERT(config->roots.length == 0U);
     736                 :           5 :                 ASSERT(config->db_file_paths.length == 2U);
     737                 :           5 :                 memory *paths = m_data(memory,conf(db_file_paths));
     738                 :           5 :                 ASSERT(strcmp(m_text(&paths[0]),cases[i].first_path) == 0);
     739                 :           5 :                 ASSERT(strcmp(m_text(&paths[1]),cases[i].second_path) == 0);
     740                 :             :         }
     741                 :             : 
     742                 :             :         /* Release the populated array and verify the next configuration starts empty */
     743                 :           1 :         free_config();
     744                 :           1 :         ASSERT(config->db_file_paths.length == 0U);
     745                 :           1 :         ASSERT(SUCCESS == init_config_and_check_output());
     746                 :           1 :         ASSERT(config->compare == false);
     747                 :           1 :         ASSERT(config->db_file_paths.length == 0U);
     748                 :             : 
     749                 :           1 :         call(delete_path_if_present("0045_reader.conf"));
     750                 :           1 :         RETURN_STATUS;
     751                 :             : }
     752                 :             : 
     753                 :             : /**
     754                 :             :  * @brief Reject an empty comparison path before applying subsequent configuration entries
     755                 :             :  * @return Return status for line diagnostics and preservation of earlier paths
     756                 :             :  */
     757                 :           1 : static Return test0045_14(void)
     758                 :             : {
     759                 :           1 :         INITTEST;
     760                 :             :         const struct {
     761                 :             :                 const char *config_template;
     762                 :             :                 bool compare;
     763                 :             :                 size_t path_count;
     764                 :           1 :         } cases[] = {
     765                 :             :                 {"tests/templates/0045_014_1.conf",false,0U},
     766                 :             :                 {"tests/templates/0045_014_2.conf",true,1U},
     767                 :             :                 {"tests/templates/0045_014_3.conf",true,1U},
     768                 :             :                 {"tests/templates/0045_014_4.conf",true,1U}
     769                 :             :         };
     770                 :             : 
     771   [ +  +  +  - ]:           5 :         for(size_t i = 0; i < sizeof(cases) / sizeof(cases[0]) && SUCCESS == status; ++i)
     772                 :             :         {
     773                 :           4 :                 ASSERT(SUCCESS == copy_path(cases[i].config_template,"0045_reader.conf"));
     774                 :           4 :                 ASSERT(SUCCESS == check_reader("0045_reader.conf",true,FAILURE,true,"templates/0045_014_1.txt"));
     775                 :           4 :                 ASSERT(config->compare == cases[i].compare);
     776                 :           4 :                 ASSERT(config->db_file_paths.length == cases[i].path_count);
     777                 :           4 :                 ASSERT(config->update == false);
     778   [ +  +  +  - ]:           4 :                 if(cases[i].path_count != 0U && SUCCESS == status)
     779                 :             :                 {
     780                 :           3 :                         memory *paths = m_data(memory,conf(db_file_paths));
     781                 :           3 :                         ASSERT(strcmp(m_text(&paths[0]),"first.db") == 0);
     782                 :             :                 }
     783                 :             :         }
     784                 :             : 
     785                 :           1 :         call(delete_path_if_present("0045_reader.conf"));
     786                 :           1 :         RETURN_STATUS;
     787                 :             : }
     788                 :             : 
     789                 :             : /**
     790                 :             :  * @brief Exercise configuration reading and source selection independently of CLI startup
     791                 :             :  * @return Return status for the configuration reader and source selection tests
     792                 :             :  */
     793                 :           1 : Return test0045(void)
     794                 :             : {
     795                 :           1 :         INITTEST;
     796                 :           1 :         Config *initial_config = config;
     797                 :           1 :         Config test_config = {0};
     798                 :           1 :         const LOGMODES initial_logger_mode = atomic_load_explicit(&rational_logger_mode,memory_order_relaxed);
     799                 :           1 :         const RATIONAL_COLOR_MODE initial_color_mode = atomic_load_explicit(&rational_color_mode,memory_order_relaxed);
     800                 :           1 :         config = &test_config;
     801                 :           1 :         ASSERT(SUCCESS == init_config_and_check_output());
     802                 :             : 
     803                 :           1 :         TEST(test0045_1,"Configuration applies values and retains owned strings after reading");
     804                 :           1 :         TEST(test0045_2,"Invalid configuration syntax and keys stop at the first error");
     805                 :           1 :         TEST(test0045_3,"Oversized configuration lines report a parser error");
     806                 :           1 :         TEST(test0045_4,"Configuration absence, opening failures, and read failures remain distinct");
     807                 :           1 :         TEST(test0045_5,"Boolean values apply the last setting and update related modes");
     808                 :           1 :         TEST(test0045_6,"Shared option errors retain their text and configuration line numbers");
     809                 :           1 :         TEST(test0045_7,"Regex values retain their order and remain available for working compilation");
     810                 :           1 :         TEST(test0045_8,"Regex errors are deferred until working compilation, after configuration parsing");
     811                 :           1 :         TEST(test0045_9,"Settings source selection is silent and discards preliminary parsing errors");
     812                 :           1 :         TEST(test0045_10,"Explicit configuration and other arguments produce a source conflict");
     813                 :           1 :         TEST(test0045_11,"Quoted configuration values preserve literal contents and apply normal value rules");
     814                 :           1 :         TEST(test0045_12,"Unclosed configuration quotes report their line and stop reading");
     815                 :           1 :         TEST(test0045_13,"Comparison paths retain their order and literal contents until configuration cleanup");
     816                 :           1 :         TEST(test0045_14,"Empty comparison paths report their line and stop configuration reading");
     817                 :             : 
     818                 :             :         /* Restore the runner's configuration and output policy after any test result */
     819                 :           1 :         free_config();
     820                 :           1 :         config = initial_config;
     821                 :           1 :         rational_logger_mode = initial_logger_mode;
     822                 :           1 :         rational_color_mode = initial_color_mode;
     823                 :             : 
     824                 :           1 :         RETURN_STATUS;
     825                 :             : }
        

Generated by: LCOV version 2.0-1