Skip to content

Commit bdf4fb7

Browse files
committed
test: add a test callback for cloudsync_payload_apply_callback
1 parent 38edf74 commit bdf4fb7

File tree

1 file changed

+195
-6
lines changed

1 file changed

+195
-6
lines changed

test/unit.c

Lines changed: 195 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,16 @@ sqlite3 *close_db (sqlite3 *db) {
252252
return NULL;
253253
}
254254

255+
int close_db_v2 (sqlite3 *db) {
256+
int counter = 0;
257+
if (db) {
258+
sqlite3_exec(db, "SELECT cloudsync_terminate();", NULL, NULL, NULL);
259+
counter = dbutils_debug_stmt(db, true);
260+
sqlite3_close(db);
261+
}
262+
return counter;
263+
}
264+
255265
bool file_delete (const char *path) {
256266
#ifdef _WIN32
257267
if (DeleteFile(path) == 0) return false;
@@ -264,6 +274,142 @@ bool file_delete (const char *path) {
264274

265275
// MARK: -
266276

277+
#ifndef UNITTEST_OMIT_RLS_VALIDATION
278+
typedef struct {
279+
bool in_savepoint;
280+
bool is_approved;
281+
bool last_is_delete;
282+
char *last_tbl;
283+
void *last_pk;
284+
int64_t last_pk_len;
285+
int64_t last_db_version;
286+
} unittest_payload_apply_rls_status;
287+
288+
bool unittest_validate_changed_row(sqlite3 *db, cloudsync_context *data, char *tbl_name, void *pk, int64_t pklen) {
289+
// verify row
290+
bool ret = false;
291+
bool vm_persistent;
292+
sqlite3_stmt *vm = cloudsync_col_value_stmt(db, data, tbl_name, &vm_persistent);
293+
if (!vm) goto cleanup;
294+
295+
// bind primary key values (the return code is the pk count)
296+
int rc = pk_decode_prikey((char *)pk, (size_t)pklen, pk_decode_bind_callback, (void *)vm);
297+
if (rc < 0) goto cleanup;
298+
299+
// execute vm
300+
rc = sqlite3_step(vm);
301+
if (rc == SQLITE_DONE) {
302+
rc = SQLITE_OK;
303+
} else if (rc == SQLITE_ROW) {
304+
rc = SQLITE_OK;
305+
ret = true;
306+
}
307+
308+
cleanup:
309+
if (vm_persistent) sqlite3_reset(vm);
310+
else sqlite3_finalize(vm);
311+
312+
return ret;
313+
}
314+
315+
int unittest_payload_apply_reset_transaction(sqlite3 *db, unittest_payload_apply_rls_status *s, bool create_new) {
316+
int rc = SQLITE_OK;
317+
318+
if (s->in_savepoint == true) {
319+
if (s->is_approved) rc = sqlite3_exec(db, "RELEASE unittest_payload_apply_transaction", NULL, NULL, NULL);
320+
else rc = sqlite3_exec(db, "ROLLBACK TO unittest_payload_apply_transaction; RELEASE unittest_payload_apply_transaction", NULL, NULL, NULL);
321+
if (rc == SQLITE_OK) s->in_savepoint = false;
322+
}
323+
if (create_new) {
324+
rc = sqlite3_exec(db, "SAVEPOINT unittest_payload_apply_transaction", NULL, NULL, NULL);
325+
if (rc == SQLITE_OK) s->in_savepoint = true;
326+
}
327+
return rc;
328+
}
329+
330+
bool unittest_payload_apply_rls_callback(void **xdata, cloudsync_pk_decode_bind_context *d, sqlite3 *db, cloudsync_context *data, int step, int rc) {
331+
unittest_payload_apply_rls_status *s;
332+
if (*xdata) {
333+
s = (unittest_payload_apply_rls_status *)*xdata;
334+
} else {
335+
s = cloudsync_memory_zeroalloc(sizeof(unittest_payload_apply_rls_status));
336+
s->is_approved = true;
337+
*xdata = s;
338+
}
339+
340+
switch (step) {
341+
case CLOUDSYNC_PAYLOAD_APPLY_WILL_APPLY: {
342+
// if the tbl name or the prikey has changed, then verify if the row is valid
343+
// must use strncmp because strings in xdata are not zero-terminated
344+
bool tbl_changed = (s->last_tbl && (strlen(s->last_tbl) != (size_t)d->tbl_len || strncmp(s->last_tbl, d->tbl, (size_t)d->tbl_len) != 0));
345+
bool pk_changed = (s->last_pk && d->pk && cloudsync_blob_compare(s->last_pk, s->last_pk_len, d->pk, d->pk_len) != 0);
346+
if (s->is_approved
347+
&& !s->last_is_delete
348+
&& (tbl_changed || pk_changed)) {
349+
s->is_approved = unittest_validate_changed_row(db, data, s->last_tbl, s->last_pk, s->last_pk_len);
350+
}
351+
352+
s->last_is_delete = ((size_t)d->col_name_len == strlen(CLOUDSYNC_TOMBSTONE_VALUE) &&
353+
strncmp(d->col_name, CLOUDSYNC_TOMBSTONE_VALUE, (size_t)d->col_name_len) == 0
354+
) && d->cl % 2 == 0;
355+
356+
// update the last_tbl value, if needed
357+
if (!s->last_tbl ||
358+
!d->tbl ||
359+
(strlen(s->last_tbl) != (size_t)d->tbl_len) ||
360+
strncmp(s->last_tbl, d->tbl, (size_t)d->tbl_len) != 0) {
361+
if (s->last_tbl) cloudsync_memory_free(s->last_tbl);
362+
if (d->tbl && d->tbl_len > 0) s->last_tbl = cloudsync_string_ndup(d->tbl, d->tbl_len, false);
363+
else s->last_tbl = NULL;
364+
}
365+
366+
// update the last_prikey and len values, if needed
367+
if (!s->last_pk || !d->pk || cloudsync_blob_compare(s->last_pk, s->last_pk_len, d->pk, d->pk_len) != 0) {
368+
if (s->last_pk) cloudsync_memory_free(s->last_pk);
369+
if (d->pk && d->pk_len > 0) {
370+
s->last_pk = cloudsync_memory_alloc(d->pk_len);
371+
memcpy(s->last_pk, d->pk, d->pk_len);
372+
s->last_pk_len = d->pk_len;
373+
} else {
374+
s->last_pk = NULL;
375+
s->last_pk_len = 0;
376+
}
377+
}
378+
379+
// commit the previous transaction, if any
380+
// begin new transacion, if needed
381+
if (s->last_db_version != d->db_version) {
382+
rc = unittest_payload_apply_reset_transaction(db, s, true);
383+
if (rc != SQLITE_OK) printf("unittest_payload_apply error in reset_transaction: (%d) %s\n", rc, sqlite3_errmsg(db));
384+
385+
// reset local variables
386+
s->last_db_version = d->db_version;
387+
s->is_approved = true;
388+
}
389+
break;
390+
}
391+
case CLOUDSYNC_PAYLOAD_APPLY_DID_APPLY:
392+
break;
393+
case CLOUDSYNC_PAYLOAD_APPLY_CLEANUP:
394+
if (s->is_approved && !s->last_is_delete) s->is_approved = unittest_validate_changed_row(db, data, s->last_tbl, s->last_pk, s->last_pk_len);
395+
rc = unittest_payload_apply_reset_transaction(db, s, false);
396+
if (s->last_tbl) cloudsync_memory_free(s->last_tbl);
397+
if (s->last_pk) {
398+
cloudsync_memory_free(s->last_pk);
399+
s->last_pk_len = 0;
400+
}
401+
402+
cloudsync_memory_free(s);
403+
*xdata = NULL;
404+
break;
405+
}
406+
407+
return s->is_approved;
408+
}
409+
#endif
410+
411+
// MARK: -
412+
267413
#ifndef CLOUDSYNC_OMIT_PRINT_RESULT
268414
int do_query_cb (void *type, int argc, char **argv, char **azColName) {
269415
int query_type = 0;
@@ -2028,6 +2174,8 @@ sqlite3 *do_create_database_file (int i, time_t timestamp, int ntest) {
20282174
return NULL;
20292175
}
20302176

2177+
sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL);
2178+
20312179
// manually load extension
20322180
sqlite3_cloudsync_init(db, NULL, NULL);
20332181

@@ -2093,8 +2241,24 @@ bool do_test_merge (int nclients, bool print_result, bool cleanup_databases) {
20932241

20942242
finalize:
20952243
for (int i=0; i<nclients; ++i) {
2096-
if (rc != SQLITE_OK && db[i] && (sqlite3_errcode(db[i]) != SQLITE_OK)) printf("do_test_merge error: %s\n", sqlite3_errmsg(db[i]));
2097-
if (db[i]) close_db(db[i]);
2244+
if (rc != SQLITE_OK && db[i] && (sqlite3_errcode(db[i]) != SQLITE_OK)) {
2245+
result = false;
2246+
printf("do_test_merge error: %s\n", sqlite3_errmsg(db[i]));
2247+
}
2248+
2249+
if (db[i]) {
2250+
if (sqlite3_get_autocommit(db[i]) == 0) {
2251+
result = false;
2252+
printf("do_test_merge error: db %d is in transaction\n", i);
2253+
}
2254+
2255+
int counter = close_db_v2(db[i]);
2256+
if (counter > 0) {
2257+
result = false;
2258+
printf("do_test_merge error: db %d has %d unterminated statements\n", i, counter);
2259+
}
2260+
}
2261+
20982262
if (cleanup_databases) {
20992263
char buf[256];
21002264
do_build_database_path(buf, i, timestamp, saved_counter++);
@@ -2171,12 +2335,12 @@ bool do_test_merge_2 (int nclients, int table_mask, bool print_result, bool clea
21712335

21722336
// deleta data in the first customer
21732337
do_delete(db[0], table_mask, print_result);
2174-
2338+
21752339
// merge all changes
21762340
if (do_merge(db, nclients, false) == false) {
21772341
goto finalize;
21782342
}
2179-
2343+
21802344
// compare results
21812345
for (int i=1; i<nclients; ++i) {
21822346
if (table_mask & TEST_PRIKEYS) {
@@ -2206,7 +2370,18 @@ bool do_test_merge_2 (int nclients, int table_mask, bool print_result, bool clea
22062370
finalize:
22072371
for (int i=0; i<nclients; ++i) {
22082372
if (rc != SQLITE_OK && db[i] && (sqlite3_errcode(db[i]) != SQLITE_OK)) printf("do_test_merge error: %s\n", sqlite3_errmsg(db[i]));
2209-
if (db[i]) close_db(db[i]);
2373+
if (db[i]) {
2374+
if (sqlite3_get_autocommit(db[i]) == 0) {
2375+
result = false;
2376+
printf("do_test_merge error: db %d is in transaction\n", i);
2377+
}
2378+
2379+
int counter = close_db_v2(db[i]);
2380+
if (counter > 0) {
2381+
result = false;
2382+
printf("do_test_merge error: db %d has %d unterminated statements\n", i, counter);
2383+
}
2384+
}
22102385
if (cleanup_databases) {
22112386
char buf[256];
22122387
do_build_database_path(buf, i, timestamp, saved_counter++);
@@ -2939,7 +3114,20 @@ bool do_test_fill_initial_data(int nclients, bool print_result, bool cleanup_dat
29393114
finalize:
29403115
for (int i=0; i<nclients; ++i) {
29413116
if (rc != SQLITE_OK && db[i] && (sqlite3_errcode(db[i]) != SQLITE_OK)) printf("do_test_merge error: %s\n", sqlite3_errmsg(db[i]));
2942-
if (db[i]) close_db(db[i]);
3117+
3118+
if (db[i]) {
3119+
if (sqlite3_get_autocommit(db[i]) == 0) {
3120+
result = false;
3121+
printf("do_test_merge error: db %d is in transaction\n", i);
3122+
}
3123+
3124+
int counter = close_db_v2(db[i]);
3125+
if (counter > 0) {
3126+
result = false;
3127+
printf("do_test_merge error: db %d has %d unterminated statements\n", i, counter);
3128+
}
3129+
}
3130+
29433131
if (cleanup_databases) {
29443132
char buf[256];
29453133
do_build_database_path(buf, i, timestamp, saved_counter++);
@@ -3081,6 +3269,7 @@ int main(int argc, const char * argv[]) {
30813269

30823270
// manually load extension
30833271
sqlite3_cloudsync_init(db, NULL, NULL);
3272+
cloudsync_payload_apply_callback(unittest_payload_apply_rls_callback);
30843273

30853274
printf("Testing CloudSync version %s\n", CLOUDSYNC_VERSION);
30863275
printf("===============================\n");

0 commit comments

Comments
 (0)