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 : : }
|