LCOV - code coverage report
Current view: top level - tests/src - test0015.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 92.3 % 555 512
Test Date: 2026-03-31 13:51:38 Functions: 100.0 % 30 30
Branches: 49.5 % 208 103

             Branch data     Line data    Source code
       1                 :             : #include "sute.h"
       2                 :             : #include "db_upgrade.h"
       3                 :             : 
       4                 :             : /**
       5                 :             :  * @brief Open SQLite database from TMPDIR by relative filename
       6                 :             :  *
       7                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
       8                 :             :  * @param[in] open_flags Flags passed to sqlite3_open_v2
       9                 :             :  * @param[out] db_out Opened database handle
      10                 :             :  *
      11                 :             :  * @return Return status code:
      12                 :             :  *         - SUCCESS: Database opened successfully
      13                 :             :  *         - FAILURE: Validation, path construction, or open failed
      14                 :             :  */
      15                 :          26 : static Return open_db_from_tmpdir(
      16                 :             :         const char *db_filename,
      17                 :             :         const int  open_flags,
      18                 :             :         sqlite3    **db_out)
      19                 :             : {
      20                 :             :         /* Status returned by this function through provide()
      21                 :             :            Default value assumes successful completion */
      22                 :          26 :         Return status = SUCCESS;
      23                 :             : 
      24                 :          26 :         create(char,db_path);
      25                 :             : 
      26   [ +  -  -  + ]:          26 :         if(db_filename == NULL || db_out == NULL)
      27                 :             :         {
      28                 :           0 :                 status = FAILURE;
      29                 :             :         }
      30                 :             : 
      31         [ +  - ]:          26 :         if(SUCCESS == status)
      32                 :             :         {
      33                 :          26 :                 status = construct_path(db_filename,db_path);
      34                 :             :         }
      35                 :             : 
      36         [ +  - ]:          26 :         if(SUCCESS == status)
      37                 :             :         {
      38                 :          26 :                 *db_out = NULL;
      39                 :             : 
      40         [ -  + ]:          26 :                 if(SQLITE_OK != sqlite3_open_v2(getcstring(db_path),db_out,open_flags,NULL))
      41                 :             :                 {
      42                 :           0 :                         status = FAILURE;
      43                 :             : 
      44         [ #  # ]:           0 :                         if(*db_out != NULL)
      45                 :             :                         {
      46                 :           0 :                                 (void)sqlite3_close(*db_out);
      47                 :           0 :                                 *db_out = NULL;
      48                 :             :                         }
      49                 :             :                 }
      50                 :             :         }
      51                 :             : 
      52                 :          26 :         del(db_path);
      53                 :             : 
      54                 :          26 :         return(status);
      55                 :             : }
      56                 :             : 
      57                 :             : /**
      58                 :             :  * @brief Read first row ID from files table
      59                 :             :  *
      60                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
      61                 :             :  * @param[out] row_id_out Output row ID
      62                 :             :  *
      63                 :             :  * @return Return status code:
      64                 :             :  *         - SUCCESS: First row ID was read
      65                 :             :  *         - FAILURE: Validation, DB access, or query execution failed
      66                 :             :  */
      67                 :           6 : static Return db_read_first_row_id(
      68                 :             :         const char    *db_filename,
      69                 :             :         sqlite3_int64 *row_id_out)
      70                 :             : {
      71                 :             :         /* Status returned by this function through provide()
      72                 :             :            Default value assumes successful completion */
      73                 :           6 :         Return status = SUCCESS;
      74                 :           6 :         sqlite3 *db = NULL;
      75                 :           6 :         sqlite3_stmt *stmt = NULL;
      76                 :           6 :         const char *sql = "SELECT ID FROM files ORDER BY ID ASC LIMIT 1;";
      77                 :             : 
      78   [ +  -  -  + ]:           6 :         if(db_filename == NULL || row_id_out == NULL)
      79                 :             :         {
      80                 :           0 :                 status = FAILURE;
      81                 :             :         }
      82                 :             : 
      83         [ +  - ]:           6 :         if(SUCCESS == status)
      84                 :             :         {
      85                 :           6 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READONLY,&db);
      86                 :             :         }
      87                 :             : 
      88   [ +  -  -  + ]:           6 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
      89                 :             :         {
      90                 :           0 :                 status = FAILURE;
      91                 :             :         }
      92                 :             : 
      93         [ +  - ]:           6 :         if(SUCCESS == status)
      94                 :             :         {
      95                 :           6 :                 int rc = sqlite3_step(stmt);
      96                 :             : 
      97         [ +  - ]:           6 :                 if(SQLITE_ROW == rc)
      98                 :             :                 {
      99                 :           6 :                         *row_id_out = sqlite3_column_int64(stmt,0);
     100                 :           6 :                         rc = sqlite3_step(stmt);
     101                 :             : 
     102         [ -  + ]:           6 :                         if(SQLITE_DONE != rc)
     103                 :             :                         {
     104                 :           0 :                                 status = FAILURE;
     105                 :             :                         }
     106                 :             :                 } else {
     107                 :           0 :                         status = FAILURE;
     108                 :             :                 }
     109                 :             :         }
     110                 :             : 
     111         [ +  - ]:           6 :         if(stmt != NULL)
     112                 :             :         {
     113                 :           6 :                 (void)sqlite3_finalize(stmt);
     114                 :             :         }
     115                 :             : 
     116         [ +  - ]:           6 :         if(db != NULL)
     117                 :             :         {
     118                 :           6 :                 (void)sqlite3_close(db);
     119                 :             :         }
     120                 :             : 
     121                 :           6 :         return(status);
     122                 :             : }
     123                 :             : 
     124                 :             : /**
     125                 :             :  * @brief Overwrite stat blob for a specific files row ID
     126                 :             :  *
     127                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     128                 :             :  * @param[in] row_id Row ID in files table
     129                 :             :  * @param[in] blob New blob bytes
     130                 :             :  * @param[in] blob_size Size of blob in bytes
     131                 :             :  *
     132                 :             :  * @return Return status code:
     133                 :             :  *         - SUCCESS: Blob was updated
     134                 :             :  *         - FAILURE: Validation, DB access, bind, step, or change check failed
     135                 :             :  */
     136                 :           4 : static Return db_overwrite_stat_blob_by_row_id(
     137                 :             :         const char          *db_filename,
     138                 :             :         const sqlite3_int64 row_id,
     139                 :             :         const void          *blob,
     140                 :             :         const int           blob_size)
     141                 :             : {
     142                 :             :         /* Status returned by this function through provide()
     143                 :             :            Default value assumes successful completion */
     144                 :           4 :         Return status = SUCCESS;
     145                 :           4 :         sqlite3 *db = NULL;
     146                 :           4 :         sqlite3_stmt *stmt = NULL;
     147                 :           4 :         const char *sql = "UPDATE files SET stat = ?1 WHERE ID = ?2;";
     148                 :             : 
     149   [ +  -  +  -  :           4 :         if(db_filename == NULL || blob == NULL || blob_size < 0)
                   -  + ]
     150                 :             :         {
     151                 :           0 :                 status = FAILURE;
     152                 :             :         }
     153                 :             : 
     154         [ +  - ]:           4 :         if(SUCCESS == status)
     155                 :             :         {
     156                 :           4 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READWRITE,&db);
     157                 :             :         }
     158                 :             : 
     159   [ +  -  -  + ]:           4 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     160                 :             :         {
     161                 :           0 :                 status = FAILURE;
     162                 :             :         }
     163                 :             : 
     164   [ +  -  -  + ]:           4 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_blob(stmt,1,blob,blob_size,SQLITE_TRANSIENT))
     165                 :             :         {
     166                 :           0 :                 status = FAILURE;
     167                 :             :         }
     168                 :             : 
     169   [ +  -  -  + ]:           4 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_int64(stmt,2,row_id))
     170                 :             :         {
     171                 :           0 :                 status = FAILURE;
     172                 :             :         }
     173                 :             : 
     174   [ +  -  -  + ]:           4 :         if(SUCCESS == status && SQLITE_DONE != sqlite3_step(stmt))
     175                 :             :         {
     176                 :           0 :                 status = FAILURE;
     177                 :             :         }
     178                 :             : 
     179   [ +  -  -  + ]:           4 :         if(SUCCESS == status && sqlite3_changes(db) < 1)
     180                 :             :         {
     181                 :           0 :                 status = FAILURE;
     182                 :             :         }
     183                 :             : 
     184         [ +  - ]:           4 :         if(stmt != NULL)
     185                 :             :         {
     186                 :           4 :                 (void)sqlite3_finalize(stmt);
     187                 :             :         }
     188                 :             : 
     189         [ +  - ]:           4 :         if(db != NULL)
     190                 :             :         {
     191                 :           4 :                 (void)sqlite3_close(db);
     192                 :             :         }
     193                 :             : 
     194                 :           4 :         return(status);
     195                 :             : }
     196                 :             : 
     197                 :             : /**
     198                 :             :  * @brief Read number of files rows whose stat blob has exact size
     199                 :             :  *
     200                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     201                 :             :  * @param[in] blob_size Expected stat blob size
     202                 :             :  * @param[out] count_out Output row count
     203                 :             :  *
     204                 :             :  * @return Return status code:
     205                 :             :  *         - SUCCESS: Count value was read
     206                 :             :  *         - FAILURE: Validation, DB access, bind, or query execution failed
     207                 :             :  */
     208                 :           4 : static Return db_read_files_count_with_blob_size(
     209                 :             :         const char *db_filename,
     210                 :             :         const int  blob_size,
     211                 :             :         int        *count_out)
     212                 :             : {
     213                 :             :         /* Status returned by this function through provide()
     214                 :             :            Default value assumes successful completion */
     215                 :           4 :         Return status = SUCCESS;
     216                 :           4 :         sqlite3 *db = NULL;
     217                 :           4 :         sqlite3_stmt *stmt = NULL;
     218                 :           4 :         const char *sql = "SELECT COUNT(*) FROM files WHERE length(stat) = ?1;";
     219                 :             : 
     220   [ +  -  +  -  :           4 :         if(db_filename == NULL || blob_size < 0 || count_out == NULL)
                   -  + ]
     221                 :             :         {
     222                 :           0 :                 status = FAILURE;
     223                 :             :         }
     224                 :             : 
     225         [ +  - ]:           4 :         if(SUCCESS == status)
     226                 :             :         {
     227                 :           4 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READONLY,&db);
     228                 :             :         }
     229                 :             : 
     230   [ +  -  -  + ]:           4 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     231                 :             :         {
     232                 :           0 :                 status = FAILURE;
     233                 :             :         }
     234                 :             : 
     235   [ +  -  -  + ]:           4 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_int(stmt,1,blob_size))
     236                 :             :         {
     237                 :           0 :                 status = FAILURE;
     238                 :             :         }
     239                 :             : 
     240         [ +  - ]:           4 :         if(SUCCESS == status)
     241                 :             :         {
     242                 :           4 :                 int rc = sqlite3_step(stmt);
     243                 :             : 
     244         [ +  - ]:           4 :                 if(SQLITE_ROW == rc)
     245                 :             :                 {
     246                 :           4 :                         *count_out = sqlite3_column_int(stmt,0);
     247                 :           4 :                         rc = sqlite3_step(stmt);
     248                 :             : 
     249         [ -  + ]:           4 :                         if(SQLITE_DONE != rc)
     250                 :             :                         {
     251                 :           0 :                                 status = FAILURE;
     252                 :             :                         }
     253                 :             :                 } else {
     254                 :           0 :                         status = FAILURE;
     255                 :             :                 }
     256                 :             :         }
     257                 :             : 
     258         [ +  - ]:           4 :         if(stmt != NULL)
     259                 :             :         {
     260                 :           4 :                 (void)sqlite3_finalize(stmt);
     261                 :             :         }
     262                 :             : 
     263         [ +  - ]:           4 :         if(db != NULL)
     264                 :             :         {
     265                 :           4 :                 (void)sqlite3_close(db);
     266                 :             :         }
     267                 :             : 
     268                 :           4 :         return(status);
     269                 :             : }
     270                 :             : 
     271                 :             : /**
     272                 :             :  * @brief Update db_version value in metadata table
     273                 :             :  *
     274                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     275                 :             :  * @param[in] db_version Version value to store
     276                 :             :  *
     277                 :             :  * @return Return status code:
     278                 :             :  *         - SUCCESS: Version value was updated
     279                 :             :  *         - FAILURE: Validation, DB access, bind, step, or change check failed
     280                 :             :  */
     281                 :           2 : static Return set_db_version_in_metadata(
     282                 :             :         const char *db_filename,
     283                 :             :         const int  db_version)
     284                 :             : {
     285                 :             :         /* Status returned by this function through provide()
     286                 :             :            Default value assumes successful completion */
     287                 :           2 :         Return status = SUCCESS;
     288                 :           2 :         sqlite3 *db = NULL;
     289                 :           2 :         sqlite3_stmt *stmt = NULL;
     290                 :           2 :         const char *sql = "UPDATE metadata SET db_version = ?1;";
     291                 :             : 
     292   [ +  -  -  + ]:           2 :         if(db_filename == NULL || db_version < 0)
     293                 :             :         {
     294                 :           0 :                 status = FAILURE;
     295                 :             :         }
     296                 :             : 
     297         [ +  - ]:           2 :         if(SUCCESS == status)
     298                 :             :         {
     299                 :           2 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READWRITE,&db);
     300                 :             :         }
     301                 :             : 
     302   [ +  -  -  + ]:           2 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     303                 :             :         {
     304                 :           0 :                 status = FAILURE;
     305                 :             :         }
     306                 :             : 
     307   [ +  -  -  + ]:           2 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_int(stmt,1,db_version))
     308                 :             :         {
     309                 :           0 :                 status = FAILURE;
     310                 :             :         }
     311                 :             : 
     312   [ +  -  -  + ]:           2 :         if(SUCCESS == status && SQLITE_DONE != sqlite3_step(stmt))
     313                 :             :         {
     314                 :           0 :                 status = FAILURE;
     315                 :             :         }
     316                 :             : 
     317   [ +  -  -  + ]:           2 :         if(SUCCESS == status && sqlite3_changes(db) < 1)
     318                 :             :         {
     319                 :           0 :                 status = FAILURE;
     320                 :             :         }
     321                 :             : 
     322         [ +  - ]:           2 :         if(stmt != NULL)
     323                 :             :         {
     324                 :           2 :                 (void)sqlite3_finalize(stmt);
     325                 :             :         }
     326                 :             : 
     327         [ +  - ]:           2 :         if(db != NULL)
     328                 :             :         {
     329                 :           2 :                 (void)sqlite3_close(db);
     330                 :             :         }
     331                 :             : 
     332                 :           2 :         return(status);
     333                 :             : }
     334                 :             : 
     335                 :             : /**
     336                 :             :  * @brief Read raw stat blob for a specific files row ID
     337                 :             :  *
     338                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     339                 :             :  * @param[in] row_id Row ID in files table
     340                 :             :  * @param[out] blob_out Optional output buffer for blob bytes
     341                 :             :  * @param[in] blob_out_size Size of blob_out buffer in bytes
     342                 :             :  * @param[out] blob_size_out Output blob size from DB
     343                 :             :  *
     344                 :             :  * @return Return status code:
     345                 :             :  *         - SUCCESS: Blob size and optional bytes were read
     346                 :             :  *         - FAILURE: Validation, DB access, bind, or row parsing failed
     347                 :             :  */
     348                 :           8 : static Return db_read_stat_blob_by_row_id(
     349                 :             :         const char          *db_filename,
     350                 :             :         const sqlite3_int64 row_id,
     351                 :             :         unsigned char       *blob_out,
     352                 :             :         const size_t        blob_out_size,
     353                 :             :         int                 *blob_size_out)
     354                 :             : {
     355                 :             :         /* Status returned by this function through provide()
     356                 :             :            Default value assumes successful completion */
     357                 :           8 :         Return status = SUCCESS;
     358                 :           8 :         sqlite3 *db = NULL;
     359                 :           8 :         sqlite3_stmt *stmt = NULL;
     360                 :           8 :         const char *sql = "SELECT stat FROM files WHERE ID = ?1;";
     361                 :             : 
     362   [ +  -  -  + ]:           8 :         if(db_filename == NULL || blob_size_out == NULL)
     363                 :             :         {
     364                 :           0 :                 status = FAILURE;
     365                 :             :         }
     366                 :             : 
     367         [ +  - ]:           8 :         if(SUCCESS == status)
     368                 :             :         {
     369                 :           8 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READONLY,&db);
     370                 :             :         }
     371                 :             : 
     372   [ +  -  -  + ]:           8 :         if(SUCCESS == status && SQLITE_OK != sqlite3_prepare_v2(db,sql,-1,&stmt,NULL))
     373                 :             :         {
     374                 :           0 :                 status = FAILURE;
     375                 :             :         }
     376                 :             : 
     377   [ +  -  -  + ]:           8 :         if(SUCCESS == status && SQLITE_OK != sqlite3_bind_int64(stmt,1,row_id))
     378                 :             :         {
     379                 :           0 :                 status = FAILURE;
     380                 :             :         }
     381                 :             : 
     382         [ +  - ]:           8 :         if(SUCCESS == status)
     383                 :             :         {
     384                 :           8 :                 int rc = sqlite3_step(stmt);
     385                 :             : 
     386         [ +  - ]:           8 :                 if(SQLITE_ROW == rc)
     387                 :             :                 {
     388                 :           8 :                         const void *blob = sqlite3_column_blob(stmt,0);
     389                 :           8 :                         const int blob_size = sqlite3_column_bytes(stmt,0);
     390                 :             : 
     391         [ -  + ]:           8 :                         if(blob_size < 0)
     392                 :             :                         {
     393                 :           0 :                                 status = FAILURE;
     394                 :             :                         }
     395                 :             : 
     396   [ +  -  +  -  :           8 :                         if(SUCCESS == status && blob_size > 0 && blob == NULL)
                   -  + ]
     397                 :             :                         {
     398                 :           0 :                                 status = FAILURE;
     399                 :             :                         }
     400                 :             : 
     401   [ +  -  +  - ]:           8 :                         if(SUCCESS == status && blob_out != NULL)
     402                 :             :                         {
     403         [ -  + ]:           8 :                                 if((size_t)blob_size > blob_out_size)
     404                 :             :                                 {
     405                 :           0 :                                         status = FAILURE;
     406         [ +  - ]:           8 :                                 } else if(blob_size > 0){
     407                 :           8 :                                         memcpy(blob_out,blob,(size_t)blob_size);
     408                 :             :                                 }
     409                 :             :                         }
     410                 :             : 
     411         [ +  - ]:           8 :                         if(SUCCESS == status)
     412                 :             :                         {
     413                 :           8 :                                 *blob_size_out = blob_size;
     414                 :             :                         }
     415                 :             : 
     416                 :           8 :                         rc = sqlite3_step(stmt);
     417                 :             : 
     418   [ +  -  -  + ]:           8 :                         if(SUCCESS == status && SQLITE_DONE != rc)
     419                 :             :                         {
     420                 :           0 :                                 status = FAILURE;
     421                 :             :                         }
     422                 :             :                 } else {
     423                 :           0 :                         status = FAILURE;
     424                 :             :                 }
     425                 :             :         }
     426                 :             : 
     427         [ +  - ]:           8 :         if(stmt != NULL)
     428                 :             :         {
     429                 :           8 :                 (void)sqlite3_finalize(stmt);
     430                 :             :         }
     431                 :             : 
     432         [ +  - ]:           8 :         if(db != NULL)
     433                 :             :         {
     434                 :           8 :                 (void)sqlite3_close(db);
     435                 :             :         }
     436                 :             : 
     437                 :           8 :         return(status);
     438                 :             : }
     439                 :             : 
     440                 :             : /**
     441                 :             :  * @brief Read CmpctStat struct from files row by ID
     442                 :             :  *
     443                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     444                 :             :  * @param[in] row_id Row ID in files table
     445                 :             :  * @param[out] stat_out Output compact stat structure
     446                 :             :  *
     447                 :             :  * @return Return status code:
     448                 :             :  *         - SUCCESS: CmpctStat value was read
     449                 :             :  *         - FAILURE: Validation, blob read, size check, or conversion failed
     450                 :             :  */
     451                 :           4 : static Return db_read_cmpctstat_by_row_id(
     452                 :             :         const char          *db_filename,
     453                 :             :         const sqlite3_int64 row_id,
     454                 :             :         CmpctStat           *stat_out)
     455                 :             : {
     456                 :             :         /* Status returned by this function through provide()
     457                 :             :            Default value assumes successful completion */
     458                 :           4 :         Return status = SUCCESS;
     459                 :             :         unsigned char raw[sizeof(CmpctStat)];
     460                 :           4 :         int blob_size = 0;
     461                 :             : 
     462         [ -  + ]:           4 :         if(stat_out == NULL)
     463                 :             :         {
     464                 :           0 :                 status = FAILURE;
     465                 :             :         }
     466                 :             : 
     467         [ +  - ]:           4 :         if(SUCCESS == status)
     468                 :             :         {
     469                 :           4 :                 status = db_read_stat_blob_by_row_id(db_filename,row_id,raw,sizeof(raw),&blob_size);
     470                 :             :         }
     471                 :             : 
     472   [ +  -  -  + ]:           4 :         if(SUCCESS == status && blob_size != (int)sizeof(CmpctStat))
     473                 :             :         {
     474                 :           0 :                 status = FAILURE;
     475                 :             :         }
     476                 :             : 
     477         [ +  - ]:           4 :         if(SUCCESS == status)
     478                 :             :         {
     479                 :           4 :                 memcpy(stat_out,raw,sizeof(CmpctStat));
     480                 :             :         }
     481                 :             : 
     482                 :           4 :         return(status);
     483                 :             : }
     484                 :             : 
     485                 :             : /**
     486                 :             :  * @brief Validate zero-converted CmpctStat values after migration fixups
     487                 :             :  *
     488                 :             :  * @param[in] stat Compact stat structure to verify
     489                 :             :  *
     490                 :             :  * @return Return status code:
     491                 :             :  *         - SUCCESS: Structure matches expected zero-converted values
     492                 :             :  *         - FAILURE: One or more fields differ from expected values
     493                 :             :  */
     494                 :           4 : static Return db_verify_zero_converted_cmpctstat(
     495                 :             :         const CmpctStat *stat)
     496                 :             : {
     497         [ -  + ]:           4 :         if(stat == NULL)
     498                 :             :         {
     499                 :           0 :                 return FAILURE;
     500                 :             :         }
     501                 :             : 
     502         [ -  + ]:           4 :         if(stat->st_size != 0)
     503                 :             :         {
     504                 :           0 :                 return FAILURE;
     505                 :             :         }
     506                 :             : 
     507         [ -  + ]:           4 :         if(stat->st_blocks != BLKCNT_UNKNOWN)
     508                 :             :         {
     509                 :           0 :                 return FAILURE;
     510                 :             :         }
     511                 :             : 
     512   [ +  -  -  + ]:           4 :         if(stat->st_dev != 0 || stat->st_ino != 0)
     513                 :             :         {
     514                 :           0 :                 return FAILURE;
     515                 :             :         }
     516                 :             : 
     517   [ +  -  -  + ]:           4 :         if(stat->mtim_tv_sec != 0 || stat->mtim_tv_nsec != 0)
     518                 :             :         {
     519                 :           0 :                 return FAILURE;
     520                 :             :         }
     521                 :             : 
     522   [ +  -  -  + ]:           4 :         if(stat->ctim_tv_sec != 0 || stat->ctim_tv_nsec != 0)
     523                 :             :         {
     524                 :           0 :                 return FAILURE;
     525                 :             :         }
     526                 :             : 
     527                 :           4 :         return SUCCESS;
     528                 :             : }
     529                 :             : 
     530                 :             : /**
     531                 :             :  * @brief Corrupt stat blob for first files row with one-byte payload
     532                 :             :  *
     533                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     534                 :             :  * @param[out] row_id_out Optional output for affected row ID
     535                 :             :  *
     536                 :             :  * @return Return status code:
     537                 :             :  *         - SUCCESS: First row stat blob was corrupted
     538                 :             :  *         - FAILURE: Row lookup or blob overwrite failed
     539                 :             :  */
     540                 :           4 : static Return db_corrupt_first_row_stat_blob(
     541                 :             :         const char    *db_filename,
     542                 :             :         sqlite3_int64 *row_id_out)
     543                 :             : {
     544                 :             :         /* Status returned by this function through provide()
     545                 :             :            Default value assumes successful completion */
     546                 :           4 :         Return status = SUCCESS;
     547                 :           4 :         sqlite3_int64 row_id = 0;
     548                 :           4 :         const unsigned char corrupt_blob[] = {0xA5};
     549                 :             : 
     550         [ +  - ]:           4 :         if(SUCCESS == status)
     551                 :             :         {
     552                 :           4 :                 status = db_read_first_row_id(db_filename,&row_id);
     553                 :             :         }
     554                 :             : 
     555         [ +  - ]:           4 :         if(SUCCESS == status)
     556                 :             :         {
     557                 :           4 :                 status = db_overwrite_stat_blob_by_row_id(db_filename,row_id,corrupt_blob,(int)sizeof(corrupt_blob));
     558                 :             :         }
     559                 :             : 
     560   [ +  -  +  - ]:           4 :         if(SUCCESS == status && row_id_out != NULL)
     561                 :             :         {
     562                 :           4 :                 *row_id_out = row_id;
     563                 :             :         }
     564                 :             : 
     565                 :           4 :         return(status);
     566                 :             : }
     567                 :             : 
     568                 :             : /**
     569                 :             :  * @brief Create trigger that aborts on second stat update in files table
     570                 :             :  *
     571                 :             :  * @param[in] db_filename Database filename relative to TMPDIR
     572                 :             :  *
     573                 :             :  * @return Return status code:
     574                 :             :  *         - SUCCESS: Trigger and helper table were created
     575                 :             :  *         - FAILURE: Validation, DB open, or SQL execution failed
     576                 :             :  */
     577                 :           2 : static Return db_create_abort_on_second_stat_update_trigger(
     578                 :             :         const char *db_filename)
     579                 :             : {
     580                 :             :         /* Status returned by this function through provide()
     581                 :             :            Default value assumes successful completion */
     582                 :           2 :         Return status = SUCCESS;
     583                 :           2 :         sqlite3 *db = NULL;
     584                 :           2 :         const char *sql =
     585                 :             :                 "DROP TRIGGER IF EXISTS __test_abort_on_second_update;"
     586                 :             :                 "DROP TABLE IF EXISTS __test_fail_counter;"
     587                 :             :                 "CREATE TABLE __test_fail_counter(n INTEGER NOT NULL);"
     588                 :             :                 "INSERT INTO __test_fail_counter(n) VALUES(0);"
     589                 :             :                 "CREATE TRIGGER __test_abort_on_second_update "
     590                 :             :                 "BEFORE UPDATE OF stat ON files "
     591                 :             :                 "BEGIN "
     592                 :             :                 "UPDATE __test_fail_counter SET n = n + 1;"
     593                 :             :                 "SELECT CASE WHEN (SELECT n FROM __test_fail_counter LIMIT 1) >= 2 "
     594                 :             :                 "THEN RAISE(ABORT,'forced rollback test failure') END;"
     595                 :             :                 "END;";
     596                 :             : 
     597         [ -  + ]:           2 :         if(db_filename == NULL)
     598                 :             :         {
     599                 :           0 :                 status = FAILURE;
     600                 :             :         }
     601                 :             : 
     602         [ +  - ]:           2 :         if(SUCCESS == status)
     603                 :             :         {
     604                 :           2 :                 status = open_db_from_tmpdir(db_filename,SQLITE_OPEN_READWRITE,&db);
     605                 :             :         }
     606                 :             : 
     607   [ +  -  -  + ]:           2 :         if(SUCCESS == status && SQLITE_OK != sqlite3_exec(db,sql,NULL,NULL,NULL))
     608                 :             :         {
     609                 :           0 :                 status = FAILURE;
     610                 :             :         }
     611                 :             : 
     612         [ +  - ]:           2 :         if(db != NULL)
     613                 :             :         {
     614                 :           2 :                 (void)sqlite3_close(db);
     615                 :             :         }
     616                 :             : 
     617                 :           2 :         return(status);
     618                 :             : }
     619                 :             : 
     620                 :             : /**
     621                 :             :  * @brief Reject using a legacy v0 DB as the primary database without --update
     622                 :             :  *
     623                 :             :  * Verify the run fails and prints the expected warning for the upgrade path
     624                 :             :  */
     625                 :           2 : Return test0015_1(void)
     626                 :             : {
     627                 :           2 :         INITTEST;
     628                 :             : 
     629                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
     630                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v0.db","0015_database_v0.db"));
     631                 :             : 
     632                 :           2 :         const char *arguments = "--database=./0015_database_v0.db tests/fixtures/diffs/diff1";
     633                 :             : 
     634                 :           2 :         create(char,result);
     635                 :           2 :         create(char,pattern);
     636                 :             : 
     637                 :           2 :         const char *filename = "templates/0015_001.txt";
     638                 :             : 
     639                 :           2 :         ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
     640                 :             : 
     641                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
     642                 :             : 
     643                 :             :         // Match the result against the pattern
     644                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
     645                 :             : 
     646                 :             :         // Clean to use it iteratively
     647                 :           2 :         del(pattern);
     648                 :           2 :         del(result);
     649                 :             : 
     650                 :             :         // Clean up test results
     651                 :           2 :         ASSERT(SUCCESS == delete_path("0015_database_v0.db"));
     652                 :             : 
     653                 :           2 :         RETURN_STATUS;
     654                 :             : }
     655                 :             : 
     656                 :             : /**
     657                 :             :  * @brief Upgrade a legacy v0 DB as the primary database with --update
     658                 :             :  *
     659                 :             :  * Verify the upgrade succeeds and produces the expected output
     660                 :             :  */
     661                 :           2 : Return test0015_2(void)
     662                 :             : {
     663                 :           2 :         INITTEST;
     664                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v0.db","0015_database_v0.db"));
     665                 :             : 
     666                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
     667                 :             : 
     668                 :           2 :         const char *arguments = "--update --database=0015_database_v0.db "
     669                 :             :                 "tests/fixtures/diffs/diff1";
     670                 :             : 
     671                 :           2 :         create(char,result);
     672                 :           2 :         create(char,pattern);
     673                 :             : 
     674                 :           2 :         const char *filename = "templates/0015_002_1.txt";
     675                 :             : 
     676                 :           2 :         ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
     677                 :             : 
     678                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
     679                 :             : 
     680                 :             :         // Match the result against the pattern
     681                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
     682                 :             : 
     683                 :             :         // Clean up test results
     684                 :           2 :         ASSERT(SUCCESS == delete_path("0015_database_v0.db"));
     685                 :             : 
     686                 :             :         // Clean to use it iteratively
     687                 :           2 :         del(pattern);
     688                 :           2 :         del(result);
     689                 :             : 
     690                 :           2 :         RETURN_STATUS;
     691                 :             : }
     692                 :             : 
     693                 :             : /**
     694                 :             :  * @brief Upgrade a legacy v0 DB with --watch-timestamps enabled
     695                 :             :  *
     696                 :             :  * Verify the upgrade succeeds and reports timestamp details in the output
     697                 :             :  */
     698                 :           2 : Return test0015_3(void)
     699                 :             : {
     700                 :           2 :         INITTEST;
     701                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v0.db","0015_database_v0.db"));
     702                 :             : 
     703                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
     704                 :             : 
     705                 :           2 :         const char *arguments = "--watch-timestamps --update --database=0015_database_v0.db "
     706                 :             :                 "tests/fixtures/diffs/diff1";
     707                 :             : 
     708                 :           2 :         create(char,result);
     709                 :           2 :         create(char,pattern);
     710                 :             : 
     711                 :           2 :         const char *filename = "templates/0015_002_2.txt";
     712                 :             : 
     713                 :           2 :         ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
     714                 :             : 
     715                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
     716                 :             : 
     717                 :             :         // Match the result against the pattern
     718                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
     719                 :             : 
     720                 :             :         // Clean to use it iteratively
     721                 :           2 :         del(pattern);
     722                 :           2 :         del(result);
     723                 :             : 
     724                 :           2 :         RETURN_STATUS;
     725                 :             : }
     726                 :             : 
     727                 :             : /**
     728                 :             :  * @brief Re-run upgrade for the already upgraded v0 DB
     729                 :             :  *
     730                 :             :  * Verify the database is treated as current and the run stays successful
     731                 :             :  */
     732                 :           2 : Return test0015_4(void)
     733                 :             : {
     734                 :           2 :         INITTEST;
     735                 :             : 
     736                 :           2 :         create(char,result);
     737                 :           2 :         create(char,pattern);
     738                 :             : 
     739                 :           2 :         const char *filename = "templates/0015_003.txt";
     740                 :             : 
     741                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
     742                 :             : 
     743                 :           2 :         const char *arguments = "--update --database=./0015_database_v0.db "
     744                 :             :                 "tests/fixtures/diffs/diff1";
     745                 :             : 
     746                 :           2 :         ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
     747                 :             : 
     748                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
     749                 :             : 
     750                 :             :         // Match the result against the pattern
     751                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
     752                 :             : 
     753                 :             :         // Clean to use it iteratively
     754                 :           2 :         del(pattern);
     755                 :           2 :         del(result);
     756                 :             : 
     757                 :             :         // Clean up test results
     758                 :           2 :         ASSERT(SUCCESS == delete_path("0015_database_v0.db"));
     759                 :             : 
     760                 :           2 :         RETURN_STATUS;
     761                 :             : }
     762                 :             : 
     763                 :             : /**
     764                 :             :  * @brief Create a fresh DB with the default generated filename
     765                 :             :  *
     766                 :             :  * Verify the application reports the generated DB name in the expected output
     767                 :             :  */
     768                 :           2 : Return test0015_5(void)
     769                 :             : {
     770                 :           2 :         INITTEST;
     771                 :             : 
     772                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
     773                 :             : 
     774                 :             :         // Run the application without an explicit DB name
     775                 :           2 :         const char *arguments = "tests/fixtures/diffs/diff1";
     776                 :             : 
     777                 :           2 :         const char *filename = "templates/0015_004.txt"; // File name
     778                 :           2 :         const char *template = "%DB_NAME%";
     779                 :             : 
     780                 :           2 :         const char *replacement = getenv("DBNAME"); // Database name
     781                 :             : 
     782                 :           2 :         ASSERT(replacement != NULL);
     783                 :             : 
     784                 :           2 :         ASSERT(SUCCESS == match_app_output(arguments,filename,template,replacement,COMPLETED));
     785                 :             : 
     786                 :           2 :         RETURN_STATUS;
     787                 :             : }
     788                 :             : 
     789                 :             : /**
     790                 :             :  * @brief Compare a current DB against a legacy v0 DB without --update
     791                 :             :  *
     792                 :             :  * Verify the application warns that the legacy DB must be upgraded first
     793                 :             :  */
     794                 :           2 : Return test0015_6(void)
     795                 :             : {
     796                 :           2 :         INITTEST;
     797                 :             : 
     798                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
     799                 :             : 
     800                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v0.db","0015_database_v0.db"));
     801                 :             : 
     802                 :           2 :         const char *arguments = "--compare $DBNAME 0015_database_v0.db";
     803                 :             : 
     804                 :           2 :         const char *filename = "templates/0015_005.txt"; // File name
     805                 :           2 :         const char *template = "%DB_NAME%";
     806                 :             : 
     807                 :           2 :         const char *replacement = getenv("DBNAME"); // Database name
     808                 :             : 
     809                 :           2 :         ASSERT(replacement != NULL);
     810                 :             : 
     811                 :           2 :         ASSERT(SUCCESS == match_app_output(arguments,filename,template,replacement,WARNING));
     812                 :             : 
     813                 :           2 :         RETURN_STATUS;
     814                 :             : }
     815                 :             : 
     816                 :             : /**
     817                 :             :  * @brief Compare and upgrade a legacy v0 DB with --compare --update
     818                 :             :  *
     819                 :             :  * Verify the legacy DB is upgraded during comparison and the run completes
     820                 :             :  */
     821                 :           2 : Return test0015_7(void)
     822                 :             : {
     823                 :           2 :         INITTEST;
     824                 :             : 
     825                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
     826                 :             : 
     827                 :             :         // Run the comparison and upgrade flow
     828                 :           2 :         const char *arguments = "--compare --update $DBNAME 0015_database_v0.db";
     829                 :             : 
     830                 :           2 :         const char *filename = "templates/0015_006.txt"; // File name
     831                 :           2 :         const char *template = "%DB_NAME%";
     832                 :             : 
     833                 :           2 :         const char *replacement = getenv("DBNAME"); // Database name
     834                 :             : 
     835                 :           2 :         ASSERT(replacement != NULL);
     836                 :             : 
     837                 :           2 :         ASSERT(SUCCESS == match_app_output(arguments,filename,template,replacement,COMPLETED));
     838                 :             : 
     839                 :             :         // Clean up test results
     840                 :           2 :         ASSERT(SUCCESS == delete_path("0015_database_v0.db"));
     841                 :             : 
     842                 :           2 :         RETURN_STATUS;
     843                 :             : }
     844                 :             : 
     845                 :             : /**
     846                 :             :  * @brief Upgrade a legacy v1 DB as the primary database with --update
     847                 :             :  *
     848                 :             :  * Verify the upgrade succeeds and produces the expected output
     849                 :             :  */
     850                 :           2 : Return test0015_8(void)
     851                 :             : {
     852                 :           2 :         INITTEST;
     853                 :             : 
     854                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
     855                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v1.db","0015_database_v1.db"));
     856                 :             : 
     857                 :           2 :         const char *arguments = "--update --database=0015_database_v1.db "
     858                 :             :                 "tests/fixtures/diffs/diff1";
     859                 :             : 
     860                 :           2 :         create(char,result);
     861                 :           2 :         create(char,pattern);
     862                 :             : 
     863                 :           2 :         const char *filename = "templates/0015_007.txt";
     864                 :             : 
     865                 :           2 :         ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
     866                 :             : 
     867                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
     868                 :             : 
     869                 :             :         // Match the result against the pattern
     870                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
     871                 :             : 
     872                 :             :         // Clean to use it iteratively
     873                 :           2 :         del(pattern);
     874                 :           2 :         del(result);
     875                 :             : 
     876                 :           2 :         RETURN_STATUS;
     877                 :             : }
     878                 :             : 
     879                 :             : /**
     880                 :             :  * @brief Re-run upgrade for the already upgraded v1 DB
     881                 :             :  *
     882                 :             :  * Verify the database is treated as current and the run stays successful
     883                 :             :  */
     884                 :           2 : Return test0015_9(void)
     885                 :             : {
     886                 :           2 :         INITTEST;
     887                 :             : 
     888                 :           2 :         create(char,result);
     889                 :           2 :         create(char,pattern);
     890                 :             : 
     891                 :           2 :         const char *filename = "templates/0015_008.txt";
     892                 :             : 
     893                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
     894                 :             : 
     895                 :           2 :         const char *arguments = "--update --database=./0015_database_v1.db "
     896                 :             :                 "tests/fixtures/diffs/diff1";
     897                 :             : 
     898                 :           2 :         ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
     899                 :             : 
     900                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
     901                 :             : 
     902                 :             :         // Match the result against the pattern
     903                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
     904                 :             : 
     905                 :             :         // Clean to use it iteratively
     906                 :           2 :         del(pattern);
     907                 :           2 :         del(result);
     908                 :             : 
     909                 :             :         // Clean up test results
     910                 :           2 :         ASSERT(SUCCESS == delete_path("0015_database_v1.db"));
     911                 :             : 
     912                 :           2 :         RETURN_STATUS;
     913                 :             : }
     914                 :             : 
     915                 :             : /**
     916                 :             :  * @brief Compare and upgrade a legacy v1 DB with --compare --update
     917                 :             :  *
     918                 :             :  * Verify the legacy DB is upgraded during comparison and the run completes
     919                 :             :  */
     920                 :           2 : Return test0015_10(void)
     921                 :             : {
     922                 :           2 :         INITTEST;
     923                 :             : 
     924                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
     925                 :             : 
     926                 :             :         // Run the comparison and upgrade flow
     927                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v1.db","0015_database_v1.db"));
     928                 :             : 
     929                 :           2 :         const char *arguments = "--compare --update $DBNAME 0015_database_v1.db";
     930                 :             : 
     931                 :           2 :         const char *filename = "templates/0015_009.txt"; // File name
     932                 :           2 :         const char *template = "%DB_NAME%";
     933                 :             : 
     934                 :           2 :         const char *replacement = getenv("DBNAME"); // Database name
     935                 :             : 
     936                 :           2 :         ASSERT(replacement != NULL);
     937                 :             : 
     938                 :           2 :         ASSERT(SUCCESS == match_app_output(arguments,filename,template,replacement,COMPLETED));
     939                 :             : 
     940                 :             :         // Clean up test results
     941                 :           2 :         ASSERT(SUCCESS == delete_path("0015_database_v1.db"));
     942                 :             : 
     943                 :           2 :         RETURN_STATUS;
     944                 :             : }
     945                 :             : 
     946                 :             : /**
     947                 :             :  * @brief Upgrade a legacy v2 DB as the primary database with --update
     948                 :             :  *
     949                 :             :  * Verify the upgrade succeeds and prints the verbose migration details
     950                 :             :  */
     951                 :           2 : Return test0015_11(void)
     952                 :             : {
     953                 :           2 :         INITTEST;
     954                 :             : 
     955                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","false"));
     956                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v2.db","0015_database_v2.db"));
     957                 :             : 
     958                 :           2 :         const char *arguments = "--update --database=0015_database_v2.db --verbose "
     959                 :             :                 "tests/fixtures/diffs/diff1";
     960                 :             : 
     961                 :           2 :         create(char,result);
     962                 :           2 :         create(char,pattern);
     963                 :             : 
     964                 :           2 :         ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
     965                 :             : 
     966                 :           2 :         const char *filename = "templates/0015_010.txt";
     967                 :             : 
     968                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
     969                 :             : 
     970                 :             :         // Match the result against the pattern
     971                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
     972                 :             : 
     973                 :             :         // Clean up test results
     974                 :           2 :         ASSERT(SUCCESS == delete_path("0015_database_v2.db"));
     975                 :             : 
     976                 :             :         // Clean to use it iteratively
     977                 :           2 :         del(pattern);
     978                 :           2 :         del(result);
     979                 :             : 
     980                 :           2 :         RETURN_STATUS;
     981                 :             : }
     982                 :             : 
     983                 :             : /**
     984                 :             :  * @brief Compare and upgrade a legacy v2 DB with --compare --update
     985                 :             :  *
     986                 :             :  * Verify the legacy DB is upgraded during comparison and the run completes
     987                 :             :  */
     988                 :           2 : Return test0015_12(void)
     989                 :             : {
     990                 :           2 :         INITTEST;
     991                 :             : 
     992                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
     993                 :             : 
     994                 :             :         // Run the comparison and upgrade flow
     995                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v2.db","0015_database_v2.db"));
     996                 :             : 
     997                 :           2 :         const char *arguments = "--compare --update $DBNAME 0015_database_v2.db";
     998                 :             : 
     999                 :           2 :         const char *filename = "templates/0015_011.txt"; // File name
    1000                 :           2 :         const char *template = "%DB_NAME%";
    1001                 :             : 
    1002                 :           2 :         const char *replacement = getenv("DBNAME"); // Database name
    1003                 :             : 
    1004                 :           2 :         ASSERT(replacement != NULL);
    1005                 :             : 
    1006                 :           2 :         ASSERT(SUCCESS == match_app_output(arguments,filename,template,replacement,COMPLETED));
    1007                 :             : 
    1008                 :             :         // Clean up test results
    1009                 :           2 :         ASSERT(SUCCESS == delete_path(replacement));
    1010                 :           2 :         ASSERT(SUCCESS == delete_path("0015_database_v2.db"));
    1011                 :             : 
    1012                 :           2 :         RETURN_STATUS;
    1013                 :             : }
    1014                 :             : 
    1015                 :             : /**
    1016                 :             :  * @brief Upgrade a legacy v3 DB whose filename contains UTF-8 characters
    1017                 :             :  *
    1018                 :             :  * Verify the primary DB upgrade works with spaces and non-ASCII characters
    1019                 :             :  */
    1020                 :           2 : Return test0015_13(void)
    1021                 :             : {
    1022                 :           2 :         INITTEST;
    1023                 :             : 
    1024                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
    1025                 :             : 
    1026                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v3 это база данных с пробелами и символами UTF-8.db","0015_database_v3 это база данных с пробелами и символами UTF-8.db"));
    1027                 :             : 
    1028                 :           2 :         const char *arguments = "--update --database=\"0015_database_v3 это база данных с пробелами и символами UTF-8.db\" "
    1029                 :             :                 "tests/fixtures/diffs/diff1";
    1030                 :             : 
    1031                 :           2 :         create(char,result);
    1032                 :             : 
    1033                 :           2 :         ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
    1034                 :             : 
    1035                 :           2 :         const char *filename = "templates/0015_012.txt";
    1036                 :             : 
    1037                 :           2 :         create(char,pattern);
    1038                 :             : 
    1039                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
    1040                 :             : 
    1041                 :             :         // Match the result against the pattern
    1042                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
    1043                 :             : 
    1044                 :             :         // Clean up test results
    1045                 :           2 :         ASSERT(SUCCESS == delete_path("0015_database_v3 это база данных с пробелами и символами UTF-8.db"));
    1046                 :             : 
    1047                 :             :         // Clean to use it iteratively
    1048                 :           2 :         del(pattern);
    1049                 :           2 :         del(result);
    1050                 :             : 
    1051                 :           2 :         RETURN_STATUS;
    1052                 :             : }
    1053                 :             : 
    1054                 :             : /**
    1055                 :             :  * @brief Compare and upgrade a legacy v3 UTF-8 DB with --compare --update
    1056                 :             :  *
    1057                 :             :  * Verify the upgrade path works when both compared DB filenames contain UTF-8
    1058                 :             :  */
    1059                 :           2 : Return test0015_14(void)
    1060                 :             : {
    1061                 :           2 :         INITTEST;
    1062                 :             : 
    1063                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
    1064                 :             : 
    1065                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v3 это база данных с пробелами и символами UTF-8.db","0015_database_v3 это база данных с пробелами и символами UTF-8.db"));
    1066                 :             : 
    1067                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v4 это база данных с пробелами и символами UTF-8.db","0015_database_v4 это база данных с пробелами и символами UTF-8.db"));
    1068                 :             : 
    1069                 :           2 :         const char *arguments = "--compare --update "
    1070                 :             :                 "\"0015_database_v3 это база данных с пробелами и символами UTF-8.db\" "
    1071                 :             :                 "\"0015_database_v4 это база данных с пробелами и символами UTF-8.db\"";
    1072                 :             : 
    1073                 :           2 :         create(char,result);
    1074                 :           2 :         create(char,pattern);
    1075                 :             : 
    1076                 :           2 :         ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
    1077                 :             : 
    1078                 :           2 :         const char *filename = "templates/0015_013.txt";
    1079                 :             : 
    1080                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
    1081                 :             : 
    1082                 :             :         // Match the result against the pattern
    1083                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
    1084                 :             : 
    1085                 :           2 :         del(result);
    1086                 :           2 :         del(pattern);
    1087                 :             : 
    1088                 :           2 :         ASSERT(SUCCESS == delete_path("0015_database_v3 это база данных с пробелами и символами UTF-8.db"));
    1089                 :           2 :         ASSERT(SUCCESS == delete_path("0015_database_v4 это база данных с пробелами и символами UTF-8.db"));
    1090                 :             : 
    1091                 :           2 :         RETURN_STATUS;
    1092                 :             : }
    1093                 :             : 
    1094                 :             : /**
    1095                 :             :  * @brief Compare a fresh UTF-8 DB against a legacy UTF-8 v4 reference DB
    1096                 :             :  *
    1097                 :             :  * Verify the application can create, read, and compare DB filenames with
    1098                 :             :  * spaces and non-ASCII characters while keeping checksum compatibility
    1099                 :             :  */
    1100                 :           2 : Return test0015_15(void)
    1101                 :             : {
    1102                 :           2 :         INITTEST;
    1103                 :             : 
    1104                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
    1105                 :             : 
    1106                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v4 это база данных с пробелами и символами UTF-8.db","0015_database_v4 это база данных с пробелами и символами UTF-8.db"));
    1107                 :             : 
    1108                 :           2 :         create(char,pattern);
    1109                 :           2 :         create(char,result);
    1110                 :           2 :         create(char,chunk);
    1111                 :             : 
    1112                 :           2 :         const char *arguments = "--database=\"Это новая база данных.db\" "
    1113                 :             :                 "tests/fixtures/diffs/diff1";
    1114                 :             : 
    1115                 :           2 :         ASSERT(SUCCESS == runit(arguments,chunk,NULL,COMPLETED,ALLOW_BOTH));
    1116                 :           2 :         ASSERT(SUCCESS == copy(result,chunk));
    1117                 :             : 
    1118                 :           2 :         arguments = "--compare \"Это новая база данных.db\" "
    1119                 :             :                 "\"0015_database_v4 это база данных с пробелами и символами UTF-8.db\"";
    1120                 :             : 
    1121                 :           2 :         ASSERT(SUCCESS == runit(arguments,chunk,NULL,COMPLETED,ALLOW_BOTH));
    1122                 :           2 :         ASSERT(SUCCESS == concat_strings(result,chunk));
    1123                 :             : 
    1124                 :           2 :         const char *filename = "templates/0015_014.txt";
    1125                 :             : 
    1126                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
    1127                 :             : 
    1128                 :             :         // Match the result against the pattern
    1129                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
    1130                 :             : 
    1131                 :             :         // Clean to use it iteratively
    1132                 :           2 :         del(pattern);
    1133                 :           2 :         del(result);
    1134                 :           2 :         del(chunk);
    1135                 :             : 
    1136                 :             :         // Clean up test results
    1137                 :           2 :         ASSERT(SUCCESS == delete_path("Это новая база данных.db"));
    1138                 :           2 :         ASSERT(SUCCESS == delete_path("0015_database_v4 это база данных с пробелами и символами UTF-8.db"));
    1139                 :             : 
    1140                 :           2 :         RETURN_STATUS;
    1141                 :             : }
    1142                 :             : 
    1143                 :             : /**
    1144                 :             :  * @brief Upgrade a v0 DB with one corrupted legacy stat blob
    1145                 :             :  *
    1146                 :             :  * Verify the full upgrade still completes and the corrupted row is converted
    1147                 :             :  * into the expected zero-source compact stat representation
    1148                 :             :  */
    1149                 :           2 : Return test0015_16(void)
    1150                 :             : {
    1151                 :           2 :         INITTEST;
    1152                 :           2 :         const char *corrupted_db_filename = "0015_database_v0_corrupt.db";
    1153                 :           2 :         const char *reference_db_filename = "0015_database_v4_reference.db";
    1154                 :             : 
    1155                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
    1156                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v0.db",corrupted_db_filename));
    1157                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v4 это база данных с пробелами и символами UTF-8.db",reference_db_filename));
    1158                 :             : 
    1159                 :           2 :         sqlite3_int64 row_id = 0;
    1160                 :             : 
    1161                 :           2 :         ASSERT(SUCCESS == db_corrupt_first_row_stat_blob(corrupted_db_filename,&row_id));
    1162                 :             : 
    1163                 :           2 :         create(char,result);
    1164                 :           2 :         create(char,pattern);
    1165                 :             : 
    1166                 :           2 :         const char *arguments = "--compare --update 0015_database_v4_reference.db 0015_database_v0_corrupt.db";
    1167                 :             : 
    1168                 :           2 :         ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
    1169                 :           2 :         const char *filename = "templates/0015_015.txt";
    1170                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
    1171                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
    1172                 :             : 
    1173                 :           2 :         int db_version = 0;
    1174                 :           2 :         ASSERT(SUCCESS == read_db_version_from_metadata(corrupted_db_filename,&db_version));
    1175                 :           2 :         ASSERT(db_version == 4);
    1176                 :             : 
    1177                 :           2 :         CmpctStat stat = {0};
    1178                 :           2 :         ASSERT(SUCCESS == db_read_cmpctstat_by_row_id(corrupted_db_filename,row_id,&stat));
    1179                 :           2 :         ASSERT(SUCCESS == db_verify_zero_converted_cmpctstat(&stat));
    1180                 :             : 
    1181                 :           2 :         ASSERT(SUCCESS == delete_path(corrupted_db_filename));
    1182                 :           2 :         ASSERT(SUCCESS == delete_path(reference_db_filename));
    1183                 :             : 
    1184                 :           2 :         del(result);
    1185                 :           2 :         del(pattern);
    1186                 :             : 
    1187                 :           2 :         RETURN_STATUS;
    1188                 :             : }
    1189                 :             : 
    1190                 :             : /**
    1191                 :             :  * @brief Upgrade a v3 DB with one corrupted legacy stat blob
    1192                 :             :  *
    1193                 :             :  * Verify the full upgrade still completes and the corrupted row is stored
    1194                 :             :  * using the zero-source conversion logic
    1195                 :             :  */
    1196                 :           2 : Return test0015_17(void)
    1197                 :             : {
    1198                 :           2 :         INITTEST;
    1199                 :           2 :         const char *corrupted_db_filename = "0015_database_v3_corrupt.db";
    1200                 :           2 :         const char *reference_db_filename = "0015_database_v4_reference.db";
    1201                 :             : 
    1202                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
    1203                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v3 это база данных с пробелами и символами UTF-8.db",corrupted_db_filename));
    1204                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v4 это база данных с пробелами и символами UTF-8.db",reference_db_filename));
    1205                 :             : 
    1206                 :           2 :         sqlite3_int64 row_id = 0;
    1207                 :           2 :         ASSERT(SUCCESS == db_corrupt_first_row_stat_blob(corrupted_db_filename,&row_id));
    1208                 :             : 
    1209                 :           2 :         const char *arguments = "--compare --update 0015_database_v4_reference.db 0015_database_v3_corrupt.db";
    1210                 :             : 
    1211                 :           2 :         create(char,result);
    1212                 :           2 :         create(char,pattern);
    1213                 :             : 
    1214                 :           2 :         ASSERT(SUCCESS == runit(arguments,result,NULL,COMPLETED,ALLOW_BOTH));
    1215                 :           2 :         const char *filename = "templates/0015_016.txt";
    1216                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
    1217                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
    1218                 :             : 
    1219                 :           2 :         int db_version = 0;
    1220                 :           2 :         ASSERT(SUCCESS == read_db_version_from_metadata(corrupted_db_filename,&db_version));
    1221                 :           2 :         ASSERT(db_version == 4);
    1222                 :             : 
    1223                 :           2 :         CmpctStat stat = {0};
    1224                 :           2 :         ASSERT(SUCCESS == db_read_cmpctstat_by_row_id(corrupted_db_filename,row_id,&stat));
    1225                 :           2 :         ASSERT(SUCCESS == db_verify_zero_converted_cmpctstat(&stat));
    1226                 :             : 
    1227                 :           2 :         ASSERT(SUCCESS == delete_path(corrupted_db_filename));
    1228                 :           2 :         ASSERT(SUCCESS == delete_path(reference_db_filename));
    1229                 :             : 
    1230                 :           2 :         del(result);
    1231                 :           2 :         del(pattern);
    1232                 :             : 
    1233                 :           2 :         RETURN_STATUS;
    1234                 :             : }
    1235                 :             : 
    1236                 :             : /**
    1237                 :             :  * @brief Roll back 3->4 migration when SQLite fails during stat conversion
    1238                 :             :  *
    1239                 :             :  * Verify the transaction is rolled back and the DB content stays unchanged
    1240                 :             :  */
    1241                 :           2 : Return test0015_18(void)
    1242                 :             : {
    1243                 :           2 :         INITTEST;
    1244                 :           2 :         const char *rollback_db_filename = "0015_database_v3_rollback.db";
    1245                 :           2 :         const char *reference_db_filename = "0015_database_v4_reference.db";
    1246                 :             : 
    1247                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
    1248                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v3 это база данных с пробелами и символами UTF-8.db",rollback_db_filename));
    1249                 :           2 :         ASSERT(SUCCESS == copy_path("tests/templates/0015_database_v4 это база данных с пробелами и символами UTF-8.db",reference_db_filename));
    1250                 :             : 
    1251                 :           2 :         int files_count = 0;
    1252                 :           2 :         ASSERT(SUCCESS == db_read_files_count(rollback_db_filename,&files_count));
    1253                 :           2 :         ASSERT(files_count >= 2);
    1254                 :             : 
    1255                 :           2 :         sqlite3_int64 row_id = 0;
    1256                 :           2 :         ASSERT(SUCCESS == db_read_first_row_id(rollback_db_filename,&row_id));
    1257                 :             : 
    1258                 :             :         unsigned char before_blob[512];
    1259                 :           2 :         int before_blob_size = 0;
    1260                 :           2 :         ASSERT(SUCCESS == db_read_stat_blob_by_row_id(rollback_db_filename,
    1261                 :             :                                                    row_id,
    1262                 :             :                                                    before_blob,
    1263                 :             :                                                    sizeof(before_blob),
    1264                 :             :                                                    &before_blob_size));
    1265                 :             : 
    1266                 :           2 :         int v1_rows_before = 0;
    1267                 :           2 :         ASSERT(SUCCESS == db_read_files_count_with_blob_size(rollback_db_filename,
    1268                 :             :                                                           (int)sizeof(CmpctStat_v1),
    1269                 :             :                                                           &v1_rows_before));
    1270                 :             : 
    1271                 :           2 :         ASSERT(SUCCESS == db_create_abort_on_second_stat_update_trigger(rollback_db_filename));
    1272                 :             : 
    1273                 :           2 :         const char *arguments = "--compare --update 0015_database_v4_reference.db 0015_database_v3_rollback.db";
    1274                 :             : 
    1275                 :           2 :         create(char,result);
    1276                 :           2 :         create(char,pattern);
    1277                 :             : 
    1278                 :           2 :         ASSERT(SUCCESS == runit(arguments,result,NULL,FAILURE,ALLOW_BOTH));
    1279                 :           2 :         const char *filename = "templates/0015_017.txt";
    1280                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
    1281                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
    1282                 :             : 
    1283                 :           2 :         int db_version = 0;
    1284                 :           2 :         ASSERT(SUCCESS == read_db_version_from_metadata(rollback_db_filename,&db_version));
    1285                 :           2 :         ASSERT(db_version == 3);
    1286                 :             : 
    1287                 :             :         unsigned char after_blob[512];
    1288                 :           2 :         int after_blob_size = 0;
    1289                 :           2 :         ASSERT(SUCCESS == db_read_stat_blob_by_row_id(rollback_db_filename,
    1290                 :             :                                                    row_id,
    1291                 :             :                                                    after_blob,
    1292                 :             :                                                    sizeof(after_blob),
    1293                 :             :                                                    &after_blob_size));
    1294                 :             : 
    1295                 :           2 :         ASSERT(before_blob_size == after_blob_size);
    1296                 :             : 
    1297         [ +  - ]:           2 :         if(before_blob_size > 0)
    1298                 :             :         {
    1299                 :           2 :                 ASSERT(0 == memcmp(before_blob,after_blob,(size_t)before_blob_size));
    1300                 :             :         }
    1301                 :             : 
    1302                 :           2 :         int v1_rows_after = 0;
    1303                 :           2 :         ASSERT(SUCCESS == db_read_files_count_with_blob_size(rollback_db_filename,
    1304                 :             :                                                           (int)sizeof(CmpctStat_v1),
    1305                 :             :                                                           &v1_rows_after));
    1306                 :           2 :         ASSERT(v1_rows_before == v1_rows_after);
    1307                 :             : 
    1308                 :           2 :         ASSERT(SUCCESS == delete_path(rollback_db_filename));
    1309                 :           2 :         ASSERT(SUCCESS == delete_path(reference_db_filename));
    1310                 :             : 
    1311                 :           2 :         del(result);
    1312                 :           2 :         del(pattern);
    1313                 :             : 
    1314                 :           2 :         RETURN_STATUS;
    1315                 :             : }
    1316                 :             : 
    1317                 :             : /**
    1318                 :             :  * @brief Warn when metadata declares a DB version newer than supported
    1319                 :             :  *
    1320                 :             :  * Verify both testing and non-testing modes keep the future DB unchanged
    1321                 :             :  */
    1322                 :           2 : Return test0015_19(void)
    1323                 :             : {
    1324                 :           2 :         INITTEST;
    1325                 :             : 
    1326                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","true"));
    1327                 :             : 
    1328                 :           2 :         const char *db_filename = "0015_database_future_version.db";
    1329                 :           2 :         const char *arguments = "--database=0015_database_future_version.db "
    1330                 :             :                 "tests/fixtures/diffs/diff1";
    1331                 :             : 
    1332                 :           2 :         create(char,result);
    1333                 :           2 :         create(char,pattern);
    1334                 :             : 
    1335                 :           2 :         ASSERT(SUCCESS == runit(arguments,NULL,NULL,COMPLETED,ALLOW_BOTH));
    1336                 :           2 :         ASSERT(SUCCESS == set_db_version_in_metadata(db_filename,CURRENT_DB_VERSION + 1));
    1337                 :             : 
    1338                 :           2 :         int db_version = 0;
    1339                 :           2 :         ASSERT(SUCCESS == read_db_version_from_metadata(db_filename,&db_version));
    1340                 :           2 :         ASSERT(db_version == CURRENT_DB_VERSION + 1);
    1341                 :             : 
    1342                 :           2 :         ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
    1343                 :             : 
    1344                 :           2 :         const char *filename = "templates/0015_018_1.txt";
    1345                 :             : 
    1346                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
    1347                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
    1348                 :             : 
    1349                 :           2 :         ASSERT(SUCCESS == read_db_version_from_metadata(db_filename,&db_version));
    1350                 :           2 :         ASSERT(db_version == CURRENT_DB_VERSION + 1);
    1351                 :             : 
    1352                 :           2 :         ASSERT(SUCCESS == set_environment_variable("TESTING","false"));
    1353                 :           2 :         ASSERT(SUCCESS == runit(arguments,result,NULL,WARNING,ALLOW_BOTH));
    1354                 :             : 
    1355                 :           2 :         filename = "templates/0015_018_2.txt";
    1356                 :             : 
    1357                 :           2 :         ASSERT(SUCCESS == get_file_content(filename,pattern));
    1358                 :           2 :         ASSERT(SUCCESS == match_pattern(result,pattern,filename));
    1359                 :             : 
    1360                 :           2 :         ASSERT(SUCCESS == delete_path(db_filename));
    1361                 :             : 
    1362                 :           2 :         del(result);
    1363                 :           2 :         del(pattern);
    1364                 :             : 
    1365                 :           2 :         RETURN_STATUS;
    1366                 :             : }
    1367                 :             : 
    1368                 :             : /**
    1369                 :             :  * @brief Exercise DB upgrade and migration regressions across legacy formats
    1370                 :             :  *
    1371                 :             :  * This scenario covers primary DB upgrades from legacy versions 0 through 3,
    1372                 :             :  * comparison-driven upgrades, default DB creation, UTF-8 filename handling,
    1373                 :             :  * checksum compatibility against a legacy v4 reference DB, resilient handling
    1374                 :             :  * of corrupted legacy stat blobs, rollback on forced SQLite migration failure,
    1375                 :             :  * and warning behavior for a DB that reports a future metadata version
    1376                 :             :  */
    1377                 :           2 : Return test0015(void)
    1378                 :             : {
    1379                 :           2 :         INITTEST;
    1380                 :             : 
    1381                 :           2 :         TEST(test0015_1,"Upgrade a DB from v0 to the current version. Error handling…");
    1382                 :           2 :         TEST(test0015_2,"Upgrade a DB from v0 to the current version as the primary database…");
    1383                 :           2 :         TEST(test0015_3,"Upgrade a DB from v0 to the current version with --watch-timestamps…");
    1384                 :           2 :         TEST(test0015_4,"Verify that the upgraded v0 DB is actually at the current version…");
    1385                 :           2 :         TEST(test0015_5,"Create default name database…");
    1386                 :           2 :         TEST(test0015_6,"Attempting an upgrade with a single --compare parameter…");
    1387                 :           2 :         TEST(test0015_7,"Upgrading from 0 to the last version using the --compare and --update…");
    1388                 :           2 :         TEST(test0015_8,"Upgrade a DB from v1 to the current version as the primary database…");
    1389                 :           2 :         TEST(test0015_9,"Verify that the upgraded v1 DB is actually at the current version…");
    1390                 :           2 :         TEST(test0015_10,"Upgrading from 1 to the last version using the --compare and --update…");
    1391                 :           2 :         TEST(test0015_11,"Upgrade a DB from v2 to the current version as the primary database…");
    1392                 :           2 :         TEST(test0015_12,"Upgrading from 2 to the last version using the --compare and --update…");
    1393                 :           2 :         TEST(test0015_13,"Upgrading from 3 with UTF-8 name to the last version using the --update…");
    1394                 :           2 :         TEST(test0015_14,"Upgrading from 3 to the last version using the --compare and --update…");
    1395                 :           2 :         TEST(test0015_15,"Create and compare DBs with UTF-8 names and checksums from legacy DB…");
    1396                 :           2 :         TEST(test0015_16,"Corrupted v0 stat blob does not break the full upgrade…");
    1397                 :           2 :         TEST(test0015_17,"Corrupted v3 stat blob does not break the full upgrade…");
    1398                 :           2 :         TEST(test0015_18,"Forced SQLite failure triggers rollback during 3->4 migration…");
    1399                 :           2 :         TEST(test0015_19,"Fresh DB with forced future version returns warning and stays unchanged…");
    1400                 :             : 
    1401                 :           2 :         RETURN_STATUS;
    1402                 :             : }
        

Generated by: LCOV version 2.0-1