#include #include #include #include #include /* third-party */ #include /* local */ #include "automerge.h" #include "../stack_utils.h" typedef struct { AMresultStack* stack; AMdoc* n1; AMdoc* n2; AMsyncState* s1; AMsyncState* s2; } TestState; static int setup(void** state) { TestState* test_state = test_calloc(1, sizeof(TestState)); test_state->n1 = AMpush(&test_state->stack, AMcreate(), AM_VALUE_DOC, cmocka_cb).doc; test_state->n2 = AMpush(&test_state->stack, AMcreate(), AM_VALUE_DOC, cmocka_cb).doc; test_state->s1 = AMpush(&test_state->stack, AMsyncStateInit(), AM_VALUE_SYNC_STATE, cmocka_cb).sync_state; test_state->s2 = AMpush(&test_state->stack, AMsyncStateInit(), AM_VALUE_SYNC_STATE, cmocka_cb).sync_state; *state = test_state; return 0; } static int teardown(void** state) { TestState* test_state = *state; AMfreeStack(&test_state->stack); test_free(test_state); return 0; } static void sync(AMdoc* a, AMdoc* b, AMsyncState* a_sync_state, AMsyncState* b_sync_state) { static size_t const MAX_ITER = 10; AMsyncMessage const* a2b_msg = NULL; AMsyncMessage const* b2a_msg = NULL; size_t iter = 0; do { AMresult* a2b_msg_result = AMgenerateSyncMessage(a, a_sync_state); AMresult* b2a_msg_result = AMgenerateSyncMessage(b, b_sync_state); AMvalue value = AMresultValue(a2b_msg_result); switch (value.tag) { case AM_VALUE_SYNC_MESSAGE: { a2b_msg = value.sync_message; AMfree(AMreceiveSyncMessage(b, b_sync_state, a2b_msg)); } break; case AM_VALUE_VOID: a2b_msg = NULL; break; } value = AMresultValue(b2a_msg_result); switch (value.tag) { case AM_VALUE_SYNC_MESSAGE: { b2a_msg = value.sync_message; AMfree(AMreceiveSyncMessage(a, a_sync_state, b2a_msg)); } break; case AM_VALUE_VOID: b2a_msg = NULL; break; } if (++iter > MAX_ITER) { fail_msg("Did not synchronize within %d iterations. " "Do you have a bug causing an infinite loop?", MAX_ITER); } } while(a2b_msg || b2a_msg); } static time_t const TIME_0 = 0; /** * \brief should send a sync message implying no local data */ static void test_should_send_a_sync_message_implying_no_local_data(void **state) { /* const doc = create() const s1 = initSyncState() */ TestState* test_state = *state; /* const m1 = doc.generateSyncMessage(s1) if (m1 === null) { throw new RangeError("message should not be null") } const message: DecodedSyncMessage = decodeSyncMessage(m1) */ AMsyncMessage const* const m1 = AMpush(&test_state->stack, AMgenerateSyncMessage( test_state->n1, test_state->s1), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* assert.deepStrictEqual(message.heads, []) */ AMchangeHashes heads = AMsyncMessageHeads(m1); assert_int_equal(AMchangeHashesSize(&heads), 0); /* assert.deepStrictEqual(message.need, []) */ AMchangeHashes needs = AMsyncMessageNeeds(m1); assert_int_equal(AMchangeHashesSize(&needs), 0); /* assert.deepStrictEqual(message.have.length, 1) */ AMsyncHaves haves = AMsyncMessageHaves(m1); assert_int_equal(AMsyncHavesSize(&haves), 1); /* assert.deepStrictEqual(message.have[0].lastSync, []) */ AMsyncHave const* have0 = AMsyncHavesNext(&haves, 1); AMchangeHashes last_sync = AMsyncHaveLastSync(have0); assert_int_equal(AMchangeHashesSize(&last_sync), 0); /* assert.deepStrictEqual(message.have[0].bloom.byteLength, 0) assert.deepStrictEqual(message.changes, []) */ AMchanges changes = AMsyncMessageChanges(m1); assert_int_equal(AMchangesSize(&changes), 0); } /** * \brief should not reply if we have no data as well */ static void test_should_not_reply_if_we_have_no_data_as_well(void **state) { /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; /* const m1 = n1.generateSyncMessage(s1) if (m1 === null) { throw new RangeError("message should not be null") }*/ AMsyncMessage const* const m1 = AMpush(&test_state->stack, AMgenerateSyncMessage( test_state->n1, test_state->s1), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* n2.receiveSyncMessage(s2, m1) */ AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, m1)); /* const m2 = n2.generateSyncMessage(s2) assert.deepStrictEqual(m2, null) */ AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n2, test_state->s2), AM_VALUE_VOID, cmocka_cb); } /** * \brief repos with equal heads do not need a reply message */ static void test_repos_with_equal_heads_do_not_need_a_reply_message(void **state) { /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; /* */ /* make two nodes with the same changes */ /* const list = n1.putObject("_root", "n", []) */ AMobjId const* const list = AMpush(&test_state->stack, AMmapPutObject(test_state->n1, AM_ROOT, "n", AM_OBJ_TYPE_LIST), AM_VALUE_OBJ_ID, cmocka_cb).obj_id; /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.insert(list, i, i) */ AMfree(AMlistPutUint(test_state->n1, AM_ROOT, i, true, i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* n2.applyChanges(n1.getChanges([])) */ AMchanges const changes = AMpush(&test_state->stack, AMgetChanges(test_state->n1, NULL), AM_VALUE_CHANGES, cmocka_cb).changes; AMfree(AMapplyChanges(test_state->n2, &changes)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); /* */ /* generate a naive sync message */ /* const m1 = n1.generateSyncMessage(s1) if (m1 === null) { throw new RangeError("message should not be null") }*/ AMsyncMessage const* m1 = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n1, test_state->s1), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* assert.deepStrictEqual(s1.lastSentHeads, n1.getHeads()) */ AMchangeHashes const last_sent_heads = AMsyncStateLastSentHeads( test_state->s1 ); AMchangeHashes const heads = AMpush(&test_state->stack, AMgetHeads(test_state->n1), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; assert_int_equal(AMchangeHashesCmp(&last_sent_heads, &heads), 0); /* */ /* heads are equal so this message should be null */ /* n2.receiveSyncMessage(s2, m1) */ AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, m1)); /* const m2 = n2.generateSyncMessage(s2) assert.strictEqual(m2, null) */ AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n2, test_state->s2), AM_VALUE_VOID, cmocka_cb); } /** * \brief n1 should offer all changes to n2 when starting from nothing */ static void test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing(void **state) { /* const n1 = create(), n2 = create() */ TestState* test_state = *state; /* make changes for n1 that n2 should request */ /* const list = n1.putObject("_root", "n", []) */ AMobjId const* const list = AMpush( &test_state->stack, AMmapPutObject(test_state->n1, AM_ROOT, "n", AM_OBJ_TYPE_LIST), AM_VALUE_OBJ_ID, cmocka_cb).obj_id; /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.insert(list, i, i) */ AMfree(AMlistPutUint(test_state->n1, AM_ROOT, i, true, i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ assert_false(AMequal(test_state->n1, test_state->n2)); /* sync(n1, n2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } /** * \brief should sync peers where one has commits the other does not */ static void test_should_sync_peers_where_one_has_commits_the_other_does_not(void **state) { /* const n1 = create(), n2 = create() */ TestState* test_state = *state; /* make changes for n1 that n2 should request */ /* const list = n1.putObject("_root", "n", []) */ AMobjId const* const list = AMpush( &test_state->stack, AMmapPutObject(test_state->n1, AM_ROOT, "n", AM_OBJ_TYPE_LIST), AM_VALUE_OBJ_ID, cmocka_cb).obj_id; /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.insert(list, i, i) */ AMfree(AMlistPutUint(test_state->n1, AM_ROOT, i, true, i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ assert_false(AMequal(test_state->n1, test_state->n2)); /* sync(n1, n2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } /** * \brief should work with prior sync state */ static void test_should_work_with_prior_sync_state(void **state) { /* create & synchronize two nodes */ /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; /* */ /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* modify the first node further */ /* for (let i = 5; i < 10; i++) { */ for (size_t i = 5; i != 10; ++i) { /* n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ assert_false(AMequal(test_state->n1, test_state->n2)); /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } /** * \brief should not generate messages once synced */ static void test_should_not_generate_messages_once_synced(void **state) { /* create & synchronize two nodes */ /* const n1 = create('abc123'), n2 = create('def456') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, AMactorIdInitStr("abc123"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, AMactorIdInitStr("def456"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); /* */ /* let message, patch for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { // n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); // n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n2.put("_root", "y", i) */ AMfree(AMmapPutUint(test_state->n2, AM_ROOT, "y", i)); /* n2.commit("", 0) */ AMfree(AMcommit(test_state->n2, "", &TIME_0)); /* { */ } /* */ /* n1 reports what it has */ /* message = n1.generateSyncMessage(s1) if (message === null) { throw new RangeError("message should not be null") }*/ AMsyncMessage const* message = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n1, test_state->s1), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* */ /* n2 receives that message and sends changes along with what it has */ /* n2.receiveSyncMessage(s2, message) */ AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, message)); /* message = n2.generateSyncMessage(s2) if (message === null) { throw new RangeError("message should not be null") }*/ message = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n2, test_state->s2), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; AMchanges message_changes = AMsyncMessageChanges(message); assert_int_equal(AMchangesSize(&message_changes), 5); /* */ /* n1 receives the changes and replies with the changes it now knows that * n2 needs */ /* n1.receiveSyncMessage(s1, message) */ AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, message)); /* message = n2.generateSyncMessage(s2) if (message === null) { throw new RangeError("message should not be null") }*/ message = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n1, test_state->s1), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; message_changes = AMsyncMessageChanges(message); assert_int_equal(AMchangesSize(&message_changes), 5); /* */ /* n2 applies the changes and sends confirmation ending the exchange */ /* n2.receiveSyncMessage(s2, message) */ AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, message)); /* message = n2.generateSyncMessage(s2) if (message === null) { throw new RangeError("message should not be null") }*/ message = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n2, test_state->s2), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* */ /* n1 receives the message and has nothing more to say */ /* n1.receiveSyncMessage(s1, message) */ AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, message)); /* message = n1.generateSyncMessage(s1) assert.deepStrictEqual(message, null) */ AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n1, test_state->s1), AM_VALUE_VOID, cmocka_cb); /* //assert.deepStrictEqual(patch, null) // no changes arrived */ /* */ /* n2 also has nothing left to say */ /* message = n2.generateSyncMessage(s2) assert.deepStrictEqual(message, null) */ AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n2, test_state->s2), AM_VALUE_VOID, cmocka_cb); } /** * \brief should allow simultaneous messages during synchronization */ static void test_should_allow_simultaneous_messages_during_synchronization(void **state) { /* create & synchronize two nodes */ /* const n1 = create('abc123'), n2 = create('def456') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, AMactorIdInitStr("abc123"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, AMactorIdInitStr("def456"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); /* */ /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n2.put("_root", "y", i) */ AMfree(AMmapPutUint(test_state->n2, AM_ROOT, "y", i)); /* n2.commit("", 0) */ AMfree(AMcommit(test_state->n2, "", &TIME_0)); /* { */ } /* const head1 = n1.getHeads()[0], head2 = n2.getHeads()[0] */ AMchangeHashes heads1 = AMpush(&test_state->stack, AMgetHeads(test_state->n1), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; AMbyteSpan const head1 = AMchangeHashesNext(&heads1, 1); AMchangeHashes heads2 = AMpush(&test_state->stack, AMgetHeads(test_state->n2), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; AMbyteSpan const head2 = AMchangeHashesNext(&heads2, 1); /* */ /* both sides report what they have but have no shared peer state */ /* let msg1to2, msg2to1 msg1to2 = n1.generateSyncMessage(s1) if (msg1to2 === null) { throw new RangeError("message should not be null") }*/ AMsyncMessage const* msg1to2 = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n1, test_state->s1), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* msg2to1 = n2.generateSyncMessage(s2) if (msg2to1 === null) { throw new RangeError("message should not be null") }*/ AMsyncMessage const* msg2to1 = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n2, test_state->s2), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).changes.length, 0) */ AMchanges msg1to2_changes = AMsyncMessageChanges(msg1to2); assert_int_equal(AMchangesSize(&msg1to2_changes), 0); /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).have[0].lastSync.length, 0)*/ AMsyncHaves msg1to2_haves = AMsyncMessageHaves(msg1to2); AMsyncHave const* msg1to2_have = AMsyncHavesNext(&msg1to2_haves, 1); AMchangeHashes msg1to2_last_sync = AMsyncHaveLastSync(msg1to2_have); assert_int_equal(AMchangeHashesSize(&msg1to2_last_sync), 0); /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).changes.length, 0) */ AMchanges msg2to1_changes = AMsyncMessageChanges(msg2to1); assert_int_equal(AMchangesSize(&msg2to1_changes), 0); /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).have[0].lastSync.length, 0)*/ AMsyncHaves msg2to1_haves = AMsyncMessageHaves(msg2to1); AMsyncHave const* msg2to1_have = AMsyncHavesNext(&msg2to1_haves, 1); AMchangeHashes msg2to1_last_sync = AMsyncHaveLastSync(msg2to1_have); assert_int_equal(AMchangeHashesSize(&msg2to1_last_sync), 0); /* */ /* n1 and n2 receive that message and update sync state but make no patch*/ /* n1.receiveSyncMessage(s1, msg2to1) */ AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1)); /* n2.receiveSyncMessage(s2, msg1to2) */ AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2)); /* */ /* now both reply with their local changes that the other lacks * (standard warning that 1% of the time this will result in a "needs" * message) */ /* msg1to2 = n1.generateSyncMessage(s1) if (msg1to2 === null) { throw new RangeError("message should not be null") }*/ msg1to2 = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n1, test_state->s1), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).changes.length, 5) */ msg1to2_changes = AMsyncMessageChanges(msg1to2); assert_int_equal(AMchangesSize(&msg1to2_changes), 5); /* msg2to1 = n2.generateSyncMessage(s2) if (msg2to1 === null) { throw new RangeError("message should not be null") }*/ msg2to1 = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n2, test_state->s2), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).changes.length, 5) */ msg2to1_changes = AMsyncMessageChanges(msg2to1); assert_int_equal(AMchangesSize(&msg2to1_changes), 5); /* */ /* both should now apply the changes and update the frontend */ /* n1.receiveSyncMessage(s1, msg2to1) */ AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1)); /* assert.deepStrictEqual(n1.getMissingDeps(), []) */ AMchangeHashes missing_deps = AMpush(&test_state->stack, AMgetMissingDeps(test_state->n1, NULL), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; assert_int_equal(AMchangeHashesSize(&missing_deps), 0); /* //assert.notDeepStrictEqual(patch1, null) assert.deepStrictEqual(n1.materialize(), { x: 4, y: 4 }) */ assert_int_equal(AMpush(&test_state->stack, AMmapGet(test_state->n1, AM_ROOT, "x", NULL), AM_VALUE_UINT, cmocka_cb).uint, 4); assert_int_equal(AMpush(&test_state->stack, AMmapGet(test_state->n1, AM_ROOT, "y", NULL), AM_VALUE_UINT, cmocka_cb).uint, 4); /* */ /* n2.receiveSyncMessage(s2, msg1to2) */ AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2)); /* assert.deepStrictEqual(n2.getMissingDeps(), []) */ missing_deps = AMpush(&test_state->stack, AMgetMissingDeps(test_state->n2, NULL), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; assert_int_equal(AMchangeHashesSize(&missing_deps), 0); /* //assert.notDeepStrictEqual(patch2, null) assert.deepStrictEqual(n2.materialize(), { x: 4, y: 4 }) */ assert_int_equal(AMpush(&test_state->stack, AMmapGet(test_state->n2, AM_ROOT, "x", NULL), AM_VALUE_UINT, cmocka_cb).uint, 4); assert_int_equal(AMpush(&test_state->stack, AMmapGet(test_state->n2, AM_ROOT, "y", NULL), AM_VALUE_UINT, cmocka_cb).uint, 4); /* */ /* The response acknowledges the changes received and sends no further * changes */ /* msg1to2 = n1.generateSyncMessage(s1) if (msg1to2 === null) { throw new RangeError("message should not be null") }*/ msg1to2 = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n1, test_state->s1), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).changes.length, 0) */ msg1to2_changes = AMsyncMessageChanges(msg1to2); assert_int_equal(AMchangesSize(&msg1to2_changes), 0); /* msg2to1 = n2.generateSyncMessage(s2) if (msg2to1 === null) { throw new RangeError("message should not be null") }*/ msg2to1 = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n2, test_state->s2), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).changes.length, 0) */ msg2to1_changes = AMsyncMessageChanges(msg2to1); assert_int_equal(AMchangesSize(&msg2to1_changes), 0); /* */ /* After receiving acknowledgements, their shared heads should be equal */ /* n1.receiveSyncMessage(s1, msg2to1) */ AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1)); /* n2.receiveSyncMessage(s2, msg1to2) */ AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2)); /* assert.deepStrictEqual(s1.sharedHeads, [head1, head2].sort()) */ AMchangeHashes s1_shared_heads = AMsyncStateSharedHeads(test_state->s1); assert_memory_equal(AMchangeHashesNext(&s1_shared_heads, 1).src, head1.src, head1.count); assert_memory_equal(AMchangeHashesNext(&s1_shared_heads, 1).src, head2.src, head2.count); /* assert.deepStrictEqual(s2.sharedHeads, [head1, head2].sort()) */ AMchangeHashes s2_shared_heads = AMsyncStateSharedHeads(test_state->s2); assert_memory_equal(AMchangeHashesNext(&s2_shared_heads, 1).src, head1.src, head1.count); assert_memory_equal(AMchangeHashesNext(&s2_shared_heads, 1).src, head2.src, head2.count); /* //assert.deepStrictEqual(patch1, null) //assert.deepStrictEqual(patch2, null) */ /* */ /* We're in sync, no more messages required */ /* msg1to2 = n1.generateSyncMessage(s1) assert.deepStrictEqual(msg1to2, null) */ AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n1, test_state->s1), AM_VALUE_VOID, cmocka_cb); /* msg2to1 = n2.generateSyncMessage(s2) assert.deepStrictEqual(msg2to1, null) */ AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n2, test_state->s2), AM_VALUE_VOID, cmocka_cb); /* */ /* If we make one more change and start another sync then its lastSync * should be updated */ /* n1.put("_root", "x", 5) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", 5)); /* msg1to2 = n1.generateSyncMessage(s1) if (msg1to2 === null) { throw new RangeError("message should not be null") }*/ msg1to2 = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n1, test_state->s1), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).have[0].lastSync, [head1, head2].sort()*/ msg1to2_haves = AMsyncMessageHaves(msg1to2); msg1to2_have = AMsyncHavesNext(&msg1to2_haves, 1); msg1to2_last_sync = AMsyncHaveLastSync(msg1to2_have); AMbyteSpan msg1to2_last_sync_next = AMchangeHashesNext(&msg1to2_last_sync, 1); assert_int_equal(msg1to2_last_sync_next.count, head1.count); assert_memory_equal(msg1to2_last_sync_next.src, head1.src, head1.count); msg1to2_last_sync_next = AMchangeHashesNext(&msg1to2_last_sync, 1); assert_int_equal(msg1to2_last_sync_next.count, head2.count); assert_memory_equal(msg1to2_last_sync_next.src, head2.src, head2.count); } /** * \brief should assume sent changes were received until we hear otherwise */ static void test_should_assume_sent_changes_were_received_until_we_hear_otherwise(void **state) { /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, AMactorIdInitStr("01234567"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, AMactorIdInitStr("89abcdef"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); /* let message = null */ /* */ /* const items = n1.putObject("_root", "items", []) */ AMobjId const* items = AMpush(&test_state->stack, AMmapPutObject(test_state->n1, AM_ROOT, "items", AM_OBJ_TYPE_LIST), AM_VALUE_OBJ_ID, cmocka_cb).obj_id; /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* n1.push(items, "x") */ AMfree(AMlistPutStr(test_state->n1, items, SIZE_MAX, true, "x")); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* message = n1.generateSyncMessage(s1) if (message === null) { throw new RangeError("message should not be null") }*/ AMsyncMessage const* message = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n1, test_state->s1), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(message).changes.length, 1) */ AMchanges message_changes = AMsyncMessageChanges(message); assert_int_equal(AMchangesSize(&message_changes), 1); /* */ /* n1.push(items, "y") */ AMfree(AMlistPutStr(test_state->n1, items, SIZE_MAX, true, "y")); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* message = n1.generateSyncMessage(s1) if (message === null) { throw new RangeError("message should not be null") }*/ message = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n1, test_state->s1), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(message).changes.length, 1) */ message_changes = AMsyncMessageChanges(message); assert_int_equal(AMchangesSize(&message_changes), 1); /* */ /* n1.push(items, "z") */ AMfree(AMlistPutStr(test_state->n1, items, SIZE_MAX, true, "z")); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* */ /* message = n1.generateSyncMessage(s1) if (message === null) { throw new RangeError("message should not be null") }*/ message = AMpush(&test_state->stack, AMgenerateSyncMessage(test_state->n1, test_state->s1), AM_VALUE_SYNC_MESSAGE, cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(message).changes.length, 1) */ message_changes = AMsyncMessageChanges(message); assert_int_equal(AMchangesSize(&message_changes), 1); } /** * \brief should work regardless of who initiates the exchange */ static void test_should_work_regardless_of_who_initiates_the_exchange(void **state) { /* create & synchronize two nodes */ /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; /* */ /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* modify the first node further */ /* for (let i = 5; i < 10; i++) { */ for (size_t i = 5; i != 10; ++i) { /* n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ assert_false(AMequal(test_state->n1, test_state->n2)); /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } /** * \brief should work without prior sync state */ static void test_should_work_without_prior_sync_state(void **state) { /* Scenario: ,-- c10 <-- c11 <-- c12 <-- c13 <-- c14 * c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 <-- c6 <-- c7 <-- c8 <-- c9 <-+ * `-- c15 <-- c16 <-- c17 * lastSync is undefined. */ /* */ /* create two peers both with divergent commits */ /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, AMactorIdInitStr("01234567"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, AMactorIdInitStr("89abcdef"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); /* */ /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* */ /* sync(n1, n2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* for (let i = 10; i < 15; i++) { */ for (size_t i = 10; i != 15; ++i) { /* n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* */ /* for (let i = 15; i < 18; i++) { */ for (size_t i = 15; i != 18; ++i) { /* n2.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n2, AM_ROOT, "x", i)); /* n2.commit("", 0) */ AMfree(AMcommit(test_state->n2, "", &TIME_0)); /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ assert_false(AMequal(test_state->n1, test_state->n2)); /* sync(n1, n2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ AMchangeHashes heads1 = AMpush(&test_state->stack, AMgetHeads(test_state->n1), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; AMchangeHashes heads2 = AMpush(&test_state->stack, AMgetHeads(test_state->n2), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } /** * \brief should work with prior sync state */ static void test_should_work_with_prior_sync_state_2(void **state) { /* Scenario: * ,-- c10 <-- c11 <-- c12 <-- c13 <-- c14 * c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 <-- c6 <-- c7 <-- c8 <-- c9 <-+ * `-- c15 <-- c16 <-- c17 * lastSync is c9. */ /* */ /* create two peers both with divergent commits */ /* const n1 = create('01234567'), n2 = create('89abcdef') let s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, AMactorIdInitStr("01234567"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, AMactorIdInitStr("89abcdef"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); /* */ /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* for (let i = 10; i < 15; i++) { */ for (size_t i = 10; i != 15; ++i) { /* n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* for (let i = 15; i < 18; i++) { */ for (size_t i = 15; i != 18; ++i) { /* n2.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n2, AM_ROOT, "x", i)); /* n2.commit("", 0) */ AMfree(AMcommit(test_state->n2, "", &TIME_0)); /* { */ } /* */ /* s1 = decodeSyncState(encodeSyncState(s1)) */ AMbyteSpan encoded = AMpush(&test_state->stack, AMsyncStateEncode(test_state->s1), AM_VALUE_BYTES, cmocka_cb).bytes; AMsyncState* s1 = AMpush(&test_state->stack, AMsyncStateDecode(encoded.src, encoded.count), AM_VALUE_SYNC_STATE, cmocka_cb).sync_state; /* s2 = decodeSyncState(encodeSyncState(s2)) */ encoded = AMpush(&test_state->stack, AMsyncStateEncode(test_state->s2), AM_VALUE_BYTES, cmocka_cb).bytes; AMsyncState* s2 = AMpush(&test_state->stack, AMsyncStateDecode(encoded.src, encoded.count), AM_VALUE_SYNC_STATE, cmocka_cb).sync_state; /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ assert_false(AMequal(test_state->n1, test_state->n2)); /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, s1, s2); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ AMchangeHashes heads1 = AMpush(&test_state->stack, AMgetHeads(test_state->n1), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; AMchangeHashes heads2 = AMpush(&test_state->stack, AMgetHeads(test_state->n2), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } /** * \brief should ensure non-empty state after sync */ static void test_should_ensure_non_empty_state_after_sync(void **state) { /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, AMactorIdInitStr("01234567"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, AMactorIdInitStr("89abcdef"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); /* */ /* for (let i = 0; i < 3; i++) { */ for (size_t i = 0; i != 3; ++i) { /* n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* assert.deepStrictEqual(s1.sharedHeads, n1.getHeads()) */ AMchangeHashes heads1 = AMpush(&test_state->stack, AMgetHeads(test_state->n1), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; AMchangeHashes shared_heads1 = AMsyncStateSharedHeads(test_state->s1); assert_int_equal(AMchangeHashesCmp(&shared_heads1, &heads1), 0); /* assert.deepStrictEqual(s2.sharedHeads, n1.getHeads()) */ AMchangeHashes shared_heads2 = AMsyncStateSharedHeads(test_state->s2); assert_int_equal(AMchangeHashesCmp(&shared_heads2, &heads1), 0); } /** * \brief should re-sync after one node crashed with data loss */ static void test_should_resync_after_one_node_crashed_with_data_loss(void **state) { /* Scenario: (r) (n2) (n1) * c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 <-- c6 <-- c7 <-- c8 * n2 has changes {c0, c1, c2}, n1's lastSync is c5, and n2's lastSync * is c2 * we want to successfully sync (n1) with (r), even though (n1) believes * it's talking to (n2) */ /* const n1 = create('01234567'), n2 = create('89abcdef') let s1 = initSyncState() const s2 = initSyncState() */ TestState* test_state = *state; AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, AMactorIdInitStr("01234567"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, AMactorIdInitStr("89abcdef"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); /* */ /* n1 makes three changes, which we sync to n2 */ /* for (let i = 0; i < 3; i++) { */ for (size_t i = 0; i != 3; ++i) { /* n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* save a copy of n2 as "r" to simulate recovering from a crash */ /* let r let rSyncState ;[r, rSyncState] = [n2.clone(), s2.clone()] */ AMdoc* r = AMpush(&test_state->stack, AMclone(test_state->n2), AM_VALUE_DOC, cmocka_cb).doc; AMbyteSpan const encoded_s2 = AMpush(&test_state->stack, AMsyncStateEncode(test_state->s2), AM_VALUE_BYTES, cmocka_cb).bytes; AMsyncState* sync_state_r = AMpush(&test_state->stack, AMsyncStateDecode(encoded_s2.src, encoded_s2.count), AM_VALUE_SYNC_STATE, cmocka_cb).sync_state; /* */ /* sync another few commits */ /* for (let i = 3; i < 6; i++) { */ for (size_t i = 3; i != 6; ++i) { /* n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* everyone should be on the same page here */ /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ AMchangeHashes heads1 = AMpush(&test_state->stack, AMgetHeads(test_state->n1), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; AMchangeHashes heads2 = AMpush(&test_state->stack, AMgetHeads(test_state->n2), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); /* */ /* now make a few more changes and then attempt to sync the fully * up-to-date n1 with with the confused r */ /* for (let i = 6; i < 9; i++) { */ for (size_t i = 6; i != 9; ++i) { /* n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* */ /* s1 = decodeSyncState(encodeSyncState(s1)) */ AMbyteSpan const encoded_s1 = AMpush(&test_state->stack, AMsyncStateEncode(test_state->s1), AM_VALUE_BYTES, cmocka_cb).bytes; AMsyncState* const s1 = AMpush(&test_state->stack, AMsyncStateDecode(encoded_s1.src, encoded_s1.count), AM_VALUE_SYNC_STATE, cmocka_cb).sync_state; /* rSyncState = decodeSyncState(encodeSyncState(rSyncState)) */ AMbyteSpan const encoded_r = AMpush(&test_state->stack, AMsyncStateEncode(sync_state_r), AM_VALUE_BYTES, cmocka_cb).bytes; sync_state_r = AMpush(&test_state->stack, AMsyncStateDecode(encoded_r.src, encoded_r.count), AM_VALUE_SYNC_STATE, cmocka_cb).sync_state; /* */ /* assert.notDeepStrictEqual(n1.getHeads(), r.getHeads()) */ heads1 = AMpush(&test_state->stack, AMgetHeads(test_state->n1), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; AMchangeHashes heads_r = AMpush(&test_state->stack, AMgetHeads(r), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; assert_int_not_equal(AMchangeHashesCmp(&heads1, &heads_r), 0); /* assert.notDeepStrictEqual(n1.materialize(), r.materialize()) */ assert_false(AMequal(test_state->n1, r)); /* assert.deepStrictEqual(n1.materialize(), { x: 8 }) */ assert_int_equal(AMpush(&test_state->stack, AMmapGet(test_state->n1, AM_ROOT, "x", NULL), AM_VALUE_UINT, cmocka_cb).uint, 8); /* assert.deepStrictEqual(r.materialize(), { x: 2 }) */ assert_int_equal(AMpush(&test_state->stack, AMmapGet(r, AM_ROOT, "x", NULL), AM_VALUE_UINT, cmocka_cb).uint, 2); /* sync(n1, r, s1, rSyncState) */ sync(test_state->n1, r, test_state->s1, sync_state_r); /* assert.deepStrictEqual(n1.getHeads(), r.getHeads()) */ heads1 = AMpush(&test_state->stack, AMgetHeads(test_state->n1), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; heads_r = AMpush(&test_state->stack, AMgetHeads(r), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; assert_int_equal(AMchangeHashesCmp(&heads1, &heads_r), 0); /* assert.deepStrictEqual(n1.materialize(), r.materialize()) */ assert_true(AMequal(test_state->n1, r)); } /** * \brief should re-sync after one node experiences data loss without disconnecting */ static void test_should_resync_after_one_node_experiences_data_loss_without_disconnecting(void **state) { /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, AMactorIdInitStr("01234567"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, AMactorIdInitStr("89abcdef"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); /* */ /* n1 makes three changes which we sync to n2 */ /* for (let i = 0; i < 3; i++) { */ for (size_t i = 0; i != 3; ++i) { /* n1.put("_root", "x", i) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", i)); /* n1.commit("", 0) */ AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* { */ } /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ AMchangeHashes heads1 = AMpush(&test_state->stack, AMgetHeads(test_state->n1), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; AMchangeHashes heads2 = AMpush(&test_state->stack, AMgetHeads(test_state->n2), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); /* */ /* const n2AfterDataLoss = create('89abcdef') */ AMdoc* n2_after_data_loss = AMpush(&test_state->stack, AMcreate(), AM_VALUE_DOC, cmocka_cb).doc; AMfree(AMsetActorId(n2_after_data_loss, AMpush(&test_state->stack, AMactorIdInitStr("89abcdef"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); /* */ /* "n2" now has no data, but n1 still thinks it does. Note we don't do * decodeSyncState(encodeSyncState(s1)) in order to simulate data loss * without disconnecting */ /* sync(n1, n2AfterDataLoss, s1, initSyncState()) */ AMsyncState* s2_after_data_loss = AMpush(&test_state->stack, AMsyncStateInit(), AM_VALUE_SYNC_STATE, cmocka_cb).sync_state; sync(test_state->n1, n2_after_data_loss, test_state->s1, s2_after_data_loss); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ heads1 = AMpush(&test_state->stack, AMgetHeads(test_state->n1), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; heads2 = AMpush(&test_state->stack, AMgetHeads(test_state->n2), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } /** * \brief should handle changes concurrent to the last sync heads */ static void test_should_handle_changes_concurrrent_to_the_last_sync_heads(void **state) { /* const n1 = create('01234567'), n2 = create('89abcdef'), n3 = create('fedcba98')*/ TestState* test_state = *state; AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, AMactorIdInitStr("01234567"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, AMactorIdInitStr("89abcdef"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); AMdoc* n3 = AMpush(&test_state->stack, AMcreate(), AM_VALUE_DOC, cmocka_cb).doc; AMfree(AMsetActorId(n3, AMpush(&test_state->stack, AMactorIdInitStr("fedcba98"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); /* const s12 = initSyncState(), s21 = initSyncState(), s23 = initSyncState(), s32 = initSyncState()*/ AMsyncState* s12 = test_state->s1; AMsyncState* s21 = test_state->s2; AMsyncState* s23 = AMpush(&test_state->stack, AMsyncStateInit(), AM_VALUE_SYNC_STATE, cmocka_cb).sync_state; AMsyncState* s32 = AMpush(&test_state->stack, AMsyncStateInit(), AM_VALUE_SYNC_STATE, cmocka_cb).sync_state; /* */ /* Change 1 is known to all three nodes */ /* //n1 = Automerge.change(n1, {time: 0}, doc => doc.x = 1) */ /* n1.put("_root", "x", 1); n1.commit("", 0) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", 1)); AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* */ /* sync(n1, n2, s12, s21) */ sync(test_state->n1, test_state->n2, s12, s21); /* sync(n2, n3, s23, s32) */ sync(test_state->n2, n3, s23, s32); /* */ /* Change 2 is known to n1 and n2 */ /* n1.put("_root", "x", 2); n1.commit("", 0) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", 2)); AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* */ /* sync(n1, n2, s12, s21) */ sync(test_state->n1, test_state->n2, s12, s21); /* */ /* Each of the three nodes makes one change (changes 3, 4, 5) */ /* n1.put("_root", "x", 3); n1.commit("", 0) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", 3)); AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* n2.put("_root", "x", 4); n2.commit("", 0) */ AMfree(AMmapPutUint(test_state->n2, AM_ROOT, "x", 4)); AMfree(AMcommit(test_state->n2, "", &TIME_0)); /* n3.put("_root", "x", 5); n3.commit("", 0) */ AMfree(AMmapPutUint(n3, AM_ROOT, "x", 5)); AMfree(AMcommit(n3, "", &TIME_0)); /* */ /* Apply n3's latest change to n2. */ /* let change = n3.getLastLocalChange() if (change === null) throw new RangeError("no local change") */ AMchanges changes = AMpush(&test_state->stack, AMgetLastLocalChange(n3), AM_VALUE_CHANGES, cmocka_cb).changes; /* n2.applyChanges([change]) */ AMfree(AMapplyChanges(test_state->n2, &changes)); /* */ /* Now sync n1 and n2. n3's change is concurrent to n1 and n2's last sync * heads */ /* sync(n1, n2, s12, s21) */ sync(test_state->n1, test_state->n2, s12, s21); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ AMchangeHashes heads1 = AMpush(&test_state->stack, AMgetHeads(test_state->n1), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; AMchangeHashes heads2 = AMpush(&test_state->stack, AMgetHeads(test_state->n2), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } /** * \brief should handle histories with lots of branching and merging */ static void test_should_handle_histories_with_lots_of_branching_and_merging(void **state) { /* const n1 = create('01234567'), n2 = create('89abcdef'), n3 = create('fedcba98') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, AMactorIdInitStr("01234567"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, AMactorIdInitStr("89abcdef"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); AMdoc* n3 = AMpush(&test_state->stack, AMcreate(), AM_VALUE_DOC, cmocka_cb).doc; AMfree(AMsetActorId(n3, AMpush(&test_state->stack, AMactorIdInitStr("fedcba98"), AM_VALUE_ACTOR_ID, cmocka_cb).actor_id)); /* n1.put("_root", "x", 0); n1.commit("", 0) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "x", 0)); AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* let change1 = n1.getLastLocalChange() if (change1 === null) throw new RangeError("no local change") */ AMchanges change1 = AMpush(&test_state->stack, AMgetLastLocalChange(test_state->n1), AM_VALUE_CHANGES, cmocka_cb).changes; /* n2.applyChanges([change1]) */ AMfree(AMapplyChanges(test_state->n2, &change1)); /* let change2 = n1.getLastLocalChange() if (change2 === null) throw new RangeError("no local change") */ AMchanges change2 = AMpush(&test_state->stack, AMgetLastLocalChange(test_state->n1), AM_VALUE_CHANGES, cmocka_cb).changes; /* n3.applyChanges([change2]) */ AMfree(AMapplyChanges(n3, &change2)); /* n3.put("_root", "x", 1); n3.commit("", 0) */ AMfree(AMmapPutUint(n3, AM_ROOT, "x", 1)); AMfree(AMcommit(n3, "", &TIME_0)); /* */ /* - n1c1 <------ n1c2 <------ n1c3 <-- etc. <-- n1c20 <------ n1c21 * / \/ \/ \/ * / /\ /\ /\ * c0 <---- n2c1 <------ n2c2 <------ n2c3 <-- etc. <-- n2c20 <------ n2c21 * \ / * ---------------------------------------------- n3c1 <----- */ /* for (let i = 1; i < 20; i++) { */ for (size_t i = 1; i != 20; ++i) { /* n1.put("_root", "n1", i); n1.commit("", 0) */ AMfree(AMmapPutUint(test_state->n1, AM_ROOT, "n1", i)); AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* n2.put("_root", "n2", i); n2.commit("", 0) */ AMfree(AMmapPutUint(test_state->n2, AM_ROOT, "n2", i)); AMfree(AMcommit(test_state->n2, "", &TIME_0)); /* const change1 = n1.getLastLocalChange() if (change1 === null) throw new RangeError("no local change") */ AMchanges change1 = AMpush(&test_state->stack, AMgetLastLocalChange(test_state->n1), AM_VALUE_CHANGES, cmocka_cb).changes; /* const change2 = n2.getLastLocalChange() if (change2 === null) throw new RangeError("no local change") */ AMchanges change2 = AMpush(&test_state->stack, AMgetLastLocalChange(test_state->n2), AM_VALUE_CHANGES, cmocka_cb).changes; /* n1.applyChanges([change2]) */ AMfree(AMapplyChanges(test_state->n1, &change2)); /* n2.applyChanges([change1]) */ AMfree(AMapplyChanges(test_state->n2, &change1)); /* { */ } /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* Having n3's last change concurrent to the last sync heads forces us into * the slower code path */ /* const change3 = n2.getLastLocalChange() if (change3 === null) throw new RangeError("no local change") */ AMchanges change3 = AMpush(&test_state->stack, AMgetLastLocalChange(n3), AM_VALUE_CHANGES, cmocka_cb).changes; /* n2.applyChanges([change3]) */ AMfree(AMapplyChanges(test_state->n2, &change3)); /* n1.put("_root", "n1", "final"); n1.commit("", 0) */ AMfree(AMmapPutStr(test_state->n1, AM_ROOT, "n1", "final")); AMfree(AMcommit(test_state->n1, "", &TIME_0)); /* n2.put("_root", "n2", "final"); n2.commit("", 0) */ AMfree(AMmapPutStr(test_state->n2, AM_ROOT, "n2", "final")); AMfree(AMcommit(test_state->n2, "", &TIME_0)); /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ AMchangeHashes heads1 = AMpush(&test_state->stack, AMgetHeads(test_state->n1), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; AMchangeHashes heads2 = AMpush(&test_state->stack, AMgetHeads(test_state->n2), AM_VALUE_CHANGE_HASHES, cmocka_cb).change_hashes; assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } int run_ported_wasm_sync_tests(void) { const struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown(test_should_send_a_sync_message_implying_no_local_data, setup, teardown), cmocka_unit_test_setup_teardown(test_should_not_reply_if_we_have_no_data_as_well, setup, teardown), cmocka_unit_test_setup_teardown(test_repos_with_equal_heads_do_not_need_a_reply_message, setup, teardown), cmocka_unit_test_setup_teardown(test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing, setup, teardown), cmocka_unit_test_setup_teardown(test_should_sync_peers_where_one_has_commits_the_other_does_not, setup, teardown), cmocka_unit_test_setup_teardown(test_should_work_with_prior_sync_state, setup, teardown), cmocka_unit_test_setup_teardown(test_should_not_generate_messages_once_synced, setup, teardown), cmocka_unit_test_setup_teardown(test_should_allow_simultaneous_messages_during_synchronization, setup, teardown), cmocka_unit_test_setup_teardown(test_should_assume_sent_changes_were_received_until_we_hear_otherwise, setup, teardown), cmocka_unit_test_setup_teardown(test_should_work_regardless_of_who_initiates_the_exchange, setup, teardown), cmocka_unit_test_setup_teardown(test_should_work_without_prior_sync_state, setup, teardown), cmocka_unit_test_setup_teardown(test_should_work_with_prior_sync_state_2, setup, teardown), cmocka_unit_test_setup_teardown(test_should_ensure_non_empty_state_after_sync, setup, teardown), cmocka_unit_test_setup_teardown(test_should_resync_after_one_node_crashed_with_data_loss, setup, teardown), cmocka_unit_test_setup_teardown(test_should_resync_after_one_node_experiences_data_loss_without_disconnecting, setup, teardown), cmocka_unit_test_setup_teardown(test_should_handle_changes_concurrrent_to_the_last_sync_heads, setup, teardown), cmocka_unit_test_setup_teardown(test_should_handle_histories_with_lots_of_branching_and_merging, setup, teardown), }; return cmocka_run_group_tests(tests, NULL, NULL); }