#ifndef DISFLUID_TESTS_INCLUDED # define DISFLUID_TESTS_INCLUDED struct disfluid_tests_report; static inline char *run_tests (size_t *n_tests, size_t *n_errors); static inline int test_append_init (int *fd, char **filename); static inline int test_append_hello (const char *filename, size_t *n_before, size_t *n_after); static inline int test_append_count (const char *filename, size_t *n); # include # include "disfluid-trie-node.h" # include "disfluid-init.h" # include "disfluid-version.h" # include "disfluid-cache-entry.h" # include "disfluid-cache-entry-key.h" # include "disfluid-db.h" # include "string-desc.h" # include "disfluid-append-only-file.h" # include "disfluid-trie.h" # define BYTES * 1 # define KILOBYTES * 1024 BYTES # define MEGABYTES * 1024 KILOBYTES # define THOUSAND * 1000 # define MILLION THOUSAND THOUSAND # define BILLION THOUSAND MILLION static void prepare_database (char **filename) { if (ALLOC_N (*filename, strlen ("/tmp/check-disfluid-db-XXXXXX") + 1) < 0) { abort (); } strcpy (*filename, "/tmp/check-disfluid-db-XXXXXX"); if (mkdtemp (*filename) == NULL) { abort (); } } static int cleanup_database (char **filename) { int error = 0; ck_assert_int_eq (db_unmark (*filename), 0); ck_assert_int_eq (db_collect (*filename), 0); for (size_t i = 0; i < 256; i++) { char *group_name = NULL; ck_assert_int_ge (ALLOC_N (group_name, strlen (*filename) + strlen ("/") + 2 + 1), 0); strcpy (group_name, *filename); strcat (group_name, "/"); static const char *alpha[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; strcat (group_name, alpha[i / 16]); strcat (group_name, alpha[i % 16]); rmdir (group_name); FREE (group_name); } rmdir (*filename); FREE (*filename); } /* *INDENT-OFF* */ START_TEST (test_check_version) /* *INDENT-ON* */ { const char *lib_version = version (); ck_assert_str_eq (lib_version, VERSION); } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_read_invalid_magic_number) /* *INDENT-ON* */ { char *database; prepare_database (&database); string_desc_t file_id = { 0 }; string_desc_t lit_id = { 0 }; char data[16 + 12 + 12 + 32 + 32 + 32 + 1] = { 0 }; memcpy (data, "invalid magic no", 16); const string_desc_t file_data = {._nbytes = sizeof (data),._data = data }; static const char *lit_data = "hello :)"; const string_desc_t lit = {._nbytes = strlen (lit_data),._data = (char *) lit_data }; ck_assert_int_eq (db_write (database, &lit_id, &lit), 0); memcpy (data + 16 + 12 + 12, lit_id._data, lit_id._nbytes); memcpy (data + 16 + 12 + 12 + 32, lit_id._data, lit_id._nbytes); memcpy (data + 16 + 12 + 12 + 32 + 32, lit_id._data, lit_id._nbytes); FREE (lit_id._data); ck_assert_int_eq (db_write (database, &file_id, &file_data), 0); struct timespec request_date, response_date; bool invalidated; string_desc_t key_id = { 0 }, response_header_id = { 0 }, response_body_id = { 0 }; ck_assert_int_lt (db_read_cache_entry (database, &file_id, &request_date, &response_date, &invalidated, &key_id, &response_header_id, &response_body_id), 0); FREE (key_id._data); FREE (response_header_id._data); FREE (response_body_id._data); FREE (file_id._data); cleanup_database (&database); } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_read_normal) /* *INDENT-ON* */ { static const char *key_value = "GET http://example.com\r\n"; static const char *header_value = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n"; static const char *body_value = "Hello, world!"; const string_desc_t key = {._nbytes = strlen (key_value),._data = (char *) key_value }; const string_desc_t header = {._nbytes = strlen (header_value),._data = (char *) header_value }; const string_desc_t body = {._nbytes = strlen (body_value),._data = (char *) body_value }; string_desc_t key_id = { 0 }; string_desc_t header_id = { 0 }; string_desc_t body_id = { 0 }; string_desc_t entry_id = { 0 }; const struct timespec request_date = {.tv_sec = 12345,.tv_nsec = 678910 }; const struct timespec response_date = {.tv_sec = 111213,.tv_nsec = 141516 }; char *database; prepare_database (&database); ck_assert_int_eq (db_write (database, &key_id, &key), 0); ck_assert_int_eq (db_write (database, &header_id, &header), 0); ck_assert_int_eq (db_write (database, &body_id, &body), 0); ck_assert_int_eq (db_write_cache_entry (database, &entry_id, &request_date, &response_date, true, &key_id, &header_id, &body_id), 0); string_desc_t check_key_id = { 0 }; string_desc_t check_header_id = { 0 }; string_desc_t check_body_id = { 0 }; struct timespec check_request_date, check_response_date; bool check_invalidated; ck_assert_int_eq (db_read_cache_entry (database, &entry_id, &check_request_date, &check_response_date, &check_invalidated, &check_key_id, &check_header_id, &check_body_id), 0); ck_assert_int_eq (check_request_date.tv_sec, 12345); ck_assert_int_eq (check_request_date.tv_nsec, 678910); ck_assert_int_eq (check_response_date.tv_sec, 111213); ck_assert_int_eq (check_response_date.tv_nsec, 141516); ck_assert (check_invalidated); ck_assert_int_eq (check_key_id._nbytes, 32); ck_assert_int_eq (check_header_id._nbytes, 32); ck_assert_int_eq (check_body_id._nbytes, 32); ck_assert_int_eq (memcmp (check_key_id._data, key_id._data, 32), 0); ck_assert_int_eq (memcmp (check_header_id._data, header_id._data, 32), 0); ck_assert_int_eq (memcmp (check_body_id._data, body_id._data, 32), 0); FREE (check_key_id._data); FREE (check_header_id._data); FREE (check_body_id._data); FREE (key_id._data); FREE (header_id._data); FREE (body_id._data); FREE (entry_id._data); cleanup_database (&database); } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_cache_key_no_host) /* *INDENT-ON* */ { static const char *request = "\ GET /example HTTP/1.1\r\n\ If-None-Match: W/\"hello\", \"world\"\r\n\ If-None-Match: W/\"hi :)\"\r\n\ \r\n"; static const char *response = "\ HTTP/1.1 200 Hi\r\n\ Content-Type: text/plain\r\n\ \r\n\ Hi :)"; char memory[512]; int error = compute_cache_key ("https", request, response, sizeof (memory), memory); ck_assert_int_eq (error, -1); } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_cache_key_invalid_request_line) /* *INDENT-ON* */ { static const char *request = "\ GET /example HTTP/1.1\r\n\ Hello\r\n\ Host: example.com\r\n\ If-None-Match: W/\"hello\", \"world\"\r\n\ If-None-Match: W/\"hi :)\"\r\n\ \r\n"; static const char *response = "\ HTTP/1.1 200 Hi\r\n\ Content-Type: text/plain\r\n\ \r\n\ Hi :)"; char memory[512]; int error = compute_cache_key ("https", request, response, sizeof (memory), memory); ck_assert_int_eq (error, -1); } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_cache_key_invalid_response_line) /* *INDENT-ON* */ { static const char *request = "\ GET /example HTTP/1.1\r\n\ Host: example.com\r\n\ If-None-Match: W/\"hello\", \"world\"\r\n\ If-None-Match: W/\"hi :)\"\r\n\ \r\n"; static const char *response = "\ HTTP/1.1 200 Hi\r\n\ Content-Type: text/plain\r\n\ This line is not HTTP/1.1.\r\n\ \r\n\ Hi :)"; char memory[512]; int error = compute_cache_key ("https", request, response, sizeof (memory), memory); ck_assert_int_eq (error, -1); } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_simple_cache_key) /* *INDENT-ON* */ { static const char *request = "\ GET /example HTTP/1.1\r\n\ Host: example.com\r\n\ If-None-Match: W/\"hello\", \"world\"\r\n\ If-None-Match: W/\"hi :)\"\r\n\ \r\n"; static const char *response = "\ HTTP/1.1 200 Hi\r\n\ Content-Type: text/plain\r\n\ \r\n\ Hi :)"; char memory[512]; int error = compute_cache_key ("https", request, response, 0, memory); ck_assert_int_eq (error, -2); error = compute_cache_key ("https", request, response, sizeof (memory), memory); ck_assert_int_eq (error, 0); ck_assert_str_eq (memory, "GET https://example.com/example\r\n"); } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_simply_varied_cache_key) /* *INDENT-ON* */ { static const char *request = "\ GET /example HTTP/1.1\r\n\ Host: example.com\r\n\ Accept: text/plain\r\n\ Foo: bar\r\n\ Foo: other, thing\r\n\ If-None-Match: W/\"hello\", \"world\"\r\n\ If-None-Match: W/\"hi :)\"\r\n\ \r\n"; static const char *response = "\ HTTP/1.1 200 Hi\r\n\ Content-Type: text/plain\r\n\ ETag: W/\"hello\"\r\n\ Vary: accept, foo\r\n\ \r\n\ Hi :)"; char memory[512]; int error = compute_cache_key ("https", request, response, 0, memory); ck_assert_int_eq (error, -2); error = compute_cache_key ("https", request, response, sizeof (memory), memory); ck_assert_int_eq (error, 0); ck_assert_str_eq (memory, "\ GET https://example.com/example\r\n\ text/plain\r\n\ bar,other, thing\r\n"); /* FIXME: "other, thing" is not touched so the space is kept. Should disfluid try and parse comma-separated lists? Also see test_multiply_varied_cache_key for another occurence of the same problem. */ } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_multiply_varied_cache_key) /* *INDENT-ON* */ { static const char *request = "\ GET /example HTTP/1.1\r\n\ Host: example.com\r\n\ Accept: text/plain\r\n\ Foo: bar\r\n\ Foo: other, thing\r\n\ If-None-Match: W/\"hello\", \"world\"\r\n\ If-None-Match: W/\"hi :)\"\r\n\ \r\n"; static const char *response = "\ HTTP/1.1 200 Hi\r\n\ Content-Type: text/plain\r\n\ ETag: W/\"hello\"\r\n\ Vary: accept, foo\r\n\ Vary: accept\r\n\ \r\n\ Hi :)"; char memory[512]; int error = compute_cache_key ("https", request, response, 0, memory); ck_assert_int_eq (error, -2); error = compute_cache_key ("https", request, response, sizeof (memory), memory); ck_assert_int_eq (error, 0); ck_assert_str_eq (memory, "\ GET https://example.com/example\r\n\ text/plain\r\n\ bar,other, thing\r\n\ text/plain\r\n"); /* FIXME: see the test_simply_varied_cache_key test. */ } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_partial_cache_key) /* *INDENT-ON* */ { static const char *request = "\ GET /example HTTP/1.1\r\n\ Host: example.com\r\n\ Accept: text/plain\r\n\ \r\n"; static const char *response = "\ HTTP/1.1 200 Hi\r\n\ Content-Type: text/plain\r\n\ Vary: a\r\n\ \r\n\ Hi :)"; char memory[512]; int error = compute_cache_key ("https", request, response, sizeof (memory), memory); ck_assert_int_eq (error, 0); ck_assert_str_eq (memory, "\ GET https://example.com/example\r\n\ \r\n"); /* Notice how the "a" header is not present in the request, so the value is just empty. */ } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_cache_hash) /* *INDENT-ON* */ { static const char *method = "GET"; static const char *uri = "https://example.com"; static const char *password = "hello :)"; char output[65]; int error = hash_primary_cache_key (method, uri, password, strlen (password), sizeof (output), output); ck_assert_int_eq (error, 0); ck_assert_str_eq (output, "\ 3ee6f03368423b1e7da285a42f049a8e\ 9af16f60337cc02fb1bfe5d7aeab6fc8"); } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_db) /* *INDENT-ON* */ { ck_assert_int_eq (db_check (), 0); } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_db_trie_inner) /* *INDENT-ON* */ { char *database; prepare_database (&database); static const char *hello = "hello"; string_desc_t common_part = { ._nbytes = 5, ._data = (char *) hello }; static const char series[32] = { 1, 2, 3, 4, 5 }; string_desc_t interesting_branch = { ._nbytes = 32, ._data = (char *) series }; ck_assert_int_eq (common_part._nbytes, strlen (hello)); ck_assert_int_eq (interesting_branch._nbytes, sizeof (series)); const struct trie_node node = { .common_part = common_part, .branches = {{0}, interesting_branch, {0}}, .is_leaf = false }; string_desc_t node_id = { 0 }; ck_assert_int_eq (db_write_trie_node (database, &node_id, &node), 0); struct trie_node *actual_node = NULL; ck_assert_int_eq (db_read_trie_node (database, node_id, &actual_node), 0); ck_assert (!trie_node_is_leaf (actual_node)); const string_desc_t actual_common_part = trie_node_common_part (actual_node); const string_desc_t actual_branches[2] = { trie_node_branch (actual_node, 0), trie_node_branch (actual_node, 1) }; ck_assert_int_eq (actual_common_part._nbytes, common_part._nbytes); ck_assert_int_eq (memcmp (actual_common_part._data, common_part._data, common_part._nbytes), 0); ck_assert_int_eq (actual_branches[0]._nbytes, 0); ck_assert_ptr_null (actual_branches[0]._data); ck_assert_int_eq (actual_branches[1]._nbytes, 32); ck_assert_int_eq (memcmp (actual_branches[1]._data, series, 32), 0); trie_node_free (&actual_node); FREE (node_id._data); cleanup_database (&database); } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_db_trie_leaf) /* *INDENT-ON* */ { char *database; prepare_database (&database); static const char *hello = "hello"; const string_desc_t common_part = { ._nbytes = 5, ._data = (char *) hello }; static const char series[32] = { 1, 2, 3, 4, 5 }; const string_desc_t leaf_id = { ._nbytes = 32, ._data = (char *) series }; ck_assert_int_eq (common_part._nbytes, strlen (hello)); ck_assert_int_eq (leaf_id._nbytes, sizeof (series)); const struct trie_node node = { .common_part = common_part, .branches = {leaf_id, {0}}, .is_leaf = true }; string_desc_t node_id = { 0 }; ck_assert_int_eq (db_write_trie_node (database, &node_id, &node), 0); struct trie_node *actual_node = NULL; ck_assert_int_eq (db_read_trie_node (database, node_id, &actual_node), 0); const string_desc_t actual_common_part = trie_node_common_part (actual_node); ck_assert (trie_node_is_leaf (actual_node)); const string_desc_t actual_leaf_id = trie_node_leaf_id (actual_node); ck_assert_int_eq (actual_common_part._nbytes, common_part._nbytes); ck_assert_int_eq (memcmp (actual_common_part._data, common_part._data, common_part._nbytes), 0); ck_assert_int_eq (memcmp (actual_leaf_id._data, leaf_id._data, leaf_id._nbytes), 0); trie_node_free (&actual_node); FREE (node_id._data); cleanup_database (&database); } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_aof_recover) /* *INDENT-ON* */ { /* Here we have a partially updated file. The top was offset 5, but is in the process of being updated to 13. In the mean time, the first offset is garbage. */ static const uint8_t partial_update[] = { /* Magic: */ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, /* Status: */ 1, 42, 42, 42, 42, 42, 42, 42, /* Top before: */ 42, 42, 42, 42, 42, 42, 42, 42, /* Top after: */ 0, 0, 0, 0, 0, 0, 0, 13, /* Data: */ 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33 }; char filename[] = "/tmp/test-partial-update-XXXXXX"; int file = mkstemp (filename); ck_assert_int_ge (file, 0); size_t n_to_write = sizeof (partial_update); const uint8_t *to_write = partial_update; while (n_to_write > 0) { ssize_t n_written = write (file, to_write, n_to_write); ck_assert_int_gt (n_written, 0); ck_assert_int_le (n_written, n_to_write); n_to_write -= n_written; to_write += n_written; } size_t top = 42; int error = ao_file_read_top (file, &top); ck_assert_int_eq (error, 0); ck_assert_int_eq (top, 13); if (lseek (file, 0, SEEK_SET) == -1) { ck_assert (false); } static const uint8_t after_update_expected[] = { /* Magic: */ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, /* Status: */ 0, 42, 42, 42, 42, 42, 42, 42, /* Top before: */ 0, 0, 0, 0, 0, 0, 0, 13, /* Top after: */ 0, 0, 0, 0, 0, 0, 0, 13, /* Data: */ 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33 }; uint8_t actual_data[sizeof (after_update_expected)] = { 0 }; size_t n_to_read = sizeof (after_update_expected); uint8_t *read_ptr = actual_data; while (n_to_read > 0) { ssize_t n_read = read (file, read_ptr, n_to_read); ck_assert_int_gt (n_read, 0); ck_assert_int_le (n_read, n_to_read); n_to_read -= n_read; read_ptr += n_read; } for (size_t i = 0; i < sizeof (after_update_expected); i++) { ck_assert_int_eq (actual_data[i], after_update_expected[i]); } remove (filename); close (file); } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ /* *INDENT-OFF* */ START_TEST (test_aof_can_read_locked_file) /* *INDENT-ON* */ { /* Here we construct a file with "Hello", and lock it. We should still be able to call ao_file_read_top. */ char filename[] = "/tmp/test-lock-read-XXXXXX"; int file = mkstemp (filename); ck_assert_int_ge (file, 0); static const char *magic_data = "disfluid test ao"; const string_desc_t file_magic = { ._data = (char *) magic_data, ._nbytes = strlen (magic_data) }; int error = ao_file_prepare (file, file_magic); ck_assert_int_eq (error, 0); size_t top; error = ao_file_lock_for_writing (file, &top); ck_assert_int_eq (error, 0); ck_assert_int_eq (top, 0); static const char *hello_data = "Hello"; const string_desc_t hello = { ._data = (char *) hello_data, ._nbytes = strlen (hello_data) }; error = ao_file_push_data (file, hello, &top); ck_assert_int_eq (error, 0); ck_assert_int_eq (top, 5); error = ao_file_commit_transaction (file); ck_assert_int_eq (error, 0); static const char *world_data = ", world!"; const string_desc_t world = { ._data = (char *) world_data, ._nbytes = strlen (world_data) }; error = ao_file_lock_for_writing (file, &top); ck_assert_int_eq (error, 0); ck_assert_int_eq (top, 5); error = ao_file_push_data (file, world, &top); ck_assert_int_eq (error, 0); ck_assert_int_eq (top, 13); /* file is still locked. */ error = ao_file_read_top (file, &top); ck_assert_int_eq (error, 0); ck_assert_int_eq (top, 5); ao_file_abort_transaction (file); remove (filename); close (file); } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ enum test_aof_trie_fold_step { TEST_AOF_TRIE_FOLD_STEP_DOWN_A = 0, TEST_AOF_TRIE_FOLD_STEP_DOWN_AA, TEST_AOF_TRIE_FOLD_STEP_DOWN_AAA, /* Skip */ TEST_AOF_TRIE_FOLD_STEP_UP_FROM_AAA, TEST_AOF_TRIE_FOLD_STEP_DOWN_AAB, TEST_AOF_TRIE_FOLD_STEP_DOWN_AABNUL, TEST_AOF_TRIE_FOLD_STEP_UP_FROM_AABNUL, TEST_AOF_TRIE_FOLD_STEP_UP_FROM_AAB, TEST_AOF_TRIE_FOLD_STEP_UP_FROM_AA, TEST_AOF_TRIE_FOLD_STEP_UP_FROM_A, TEST_AOF_TRIE_FOLD_STEP_DOWN_B, TEST_AOF_TRIE_FOLD_STEP_DOWN_BA, TEST_AOF_TRIE_FOLD_STEP_DOWN_BAB, TEST_AOF_TRIE_FOLD_STEP_DOWN_BABA, TEST_AOF_TRIE_FOLD_STEP_DOWN_BABANUL, TEST_AOF_TRIE_FOLD_STEP_UP_FROM_BABANUL, TEST_AOF_TRIE_FOLD_STEP_UP_FROM_BABA, TEST_AOF_TRIE_FOLD_STEP_UP_FROM_BAB, TEST_AOF_TRIE_FOLD_STEP_UP_FROM_BA, TEST_AOF_TRIE_FOLD_STEP_UP_FROM_B, TEST_AOF_TRIE_FOLD_DONE }; static int test_aof_trie_fold_down (void *context, char key, size_t offset, bool *explore) { enum test_aof_trie_fold_step *step = context; *explore = true; switch (*step) { case TEST_AOF_TRIE_FOLD_STEP_DOWN_A: ck_assert_int_eq (key, 'a'); ck_assert_int_lt (offset, 8000); break; case TEST_AOF_TRIE_FOLD_STEP_DOWN_AA: ck_assert_int_eq (key, 'a'); ck_assert_int_lt (offset, 8000); break; case TEST_AOF_TRIE_FOLD_STEP_DOWN_AAA: ck_assert_int_eq (key, 'a'); ck_assert_int_lt (offset, 8000); *explore = false; break; case TEST_AOF_TRIE_FOLD_STEP_DOWN_AAB: ck_assert_int_eq (key, 'b'); ck_assert_int_lt (offset, 8000); break; case TEST_AOF_TRIE_FOLD_STEP_DOWN_AABNUL: ck_assert_int_eq (key, '\0'); ck_assert_int_eq (offset, 8003); *explore = false; break; case TEST_AOF_TRIE_FOLD_STEP_DOWN_B: ck_assert_int_eq (key, 'b'); ck_assert_int_lt (offset, 8000); break; case TEST_AOF_TRIE_FOLD_STEP_DOWN_BA: ck_assert_int_eq (key, 'a'); ck_assert_int_lt (offset, 8000); break; case TEST_AOF_TRIE_FOLD_STEP_DOWN_BAB: ck_assert_int_eq (key, 'b'); ck_assert_int_lt (offset, 8000); break; case TEST_AOF_TRIE_FOLD_STEP_DOWN_BABA: ck_assert_int_eq (key, 'a'); ck_assert_int_lt (offset, 8000); break; case TEST_AOF_TRIE_FOLD_STEP_DOWN_BABANUL: ck_assert_int_eq (key, '\0'); ck_assert_int_eq (offset, 8001); *explore = false; break; default: ck_assert (false); } *step += 1; return 0; } static int test_aof_trie_fold_up (void *context) { enum test_aof_trie_fold_step *step = context; switch (*step) { case TEST_AOF_TRIE_FOLD_STEP_UP_FROM_AAA: case TEST_AOF_TRIE_FOLD_STEP_UP_FROM_AABNUL: case TEST_AOF_TRIE_FOLD_STEP_UP_FROM_AAB: case TEST_AOF_TRIE_FOLD_STEP_UP_FROM_AA: case TEST_AOF_TRIE_FOLD_STEP_UP_FROM_A: case TEST_AOF_TRIE_FOLD_STEP_UP_FROM_BABANUL: case TEST_AOF_TRIE_FOLD_STEP_UP_FROM_BABA: case TEST_AOF_TRIE_FOLD_STEP_UP_FROM_BAB: case TEST_AOF_TRIE_FOLD_STEP_UP_FROM_BA: case TEST_AOF_TRIE_FOLD_STEP_UP_FROM_B: break; default: ck_assert (false); } *step += 1; return 0; } /* *INDENT-OFF* */ START_TEST (test_aof_trie_fold) /* *INDENT-ON* */ { /* The trie has: aaab (offset 8000), baba (8001), aaa (8002) and aab (8003). We want to skip "aaa*". We should find in order: aab and baba. */ char filename[] = "/tmp/test-ao-trie-fold-XXXXXX"; int file = mkstemp (filename); ck_assert_int_ge (file, 0); static const char *magic_data = "disfluid trie ao"; const string_desc_t file_magic = { ._data = (char *) magic_data, ._nbytes = strlen (magic_data) }; int error = ao_file_prepare (file, file_magic); ck_assert_int_eq (error, 0); size_t top; error = ao_file_lock_for_writing (file, &top); ck_assert_int_eq (error, 0); ck_assert_int_eq (top, 0); /* Push aaab$. */ static const uint8_t aaab_key = '\0'; static const size_t aaab_value = 8000; size_t aaab_offset; error = ao_trie_push (file, &aaab_offset, 1, &aaab_key, &aaab_value); ck_assert_int_eq (error, 0); /* Push aaa*. */ static const uint8_t aaa_keys[] = { 'b', '\0' }; const size_t aaa_values[] = { aaab_offset, 8002 }; size_t aaa_offset; error = ao_trie_push (file, &aaa_offset, 2, aaa_keys, aaa_values); ck_assert_int_eq (error, 0); /* Push aab$. */ static const uint8_t aab_key = '\0'; static const size_t aab_value = 8003; size_t aab_offset; error = ao_trie_push (file, &aab_offset, 1, &aab_key, &aab_value); ck_assert_int_eq (error, 0); /* Push aa*. */ static const uint8_t aa_keys[] = { 'b', 'a' }; const size_t aa_values[] = { aab_offset, aaa_offset }; size_t aa_offset; error = ao_trie_push (file, &aa_offset, 2, aa_keys, aa_values); ck_assert_int_eq (error, 0); /* Push a*. */ static const uint8_t a_keys[] = { 'a' }; const size_t a_values[] = { aa_offset }; size_t a_offset; error = ao_trie_push (file, &a_offset, 1, a_keys, a_values); ck_assert_int_eq (error, 0); /* Push baba$. */ static const uint8_t baba_keys[] = { '\0' }; const size_t baba_values[] = { 8001 }; size_t baba_offset; error = ao_trie_push (file, &baba_offset, 1, baba_keys, baba_values); ck_assert_int_eq (error, 0); /* Push bab*. */ static const uint8_t bab_keys[] = { 'a' }; const size_t bab_values[] = { baba_offset }; size_t bab_offset; error = ao_trie_push (file, &bab_offset, 1, bab_keys, bab_values); ck_assert_int_eq (error, 0); /* Push ba*. */ static const uint8_t ba_keys[] = { 'b' }; const size_t ba_values[] = { bab_offset }; size_t ba_offset; error = ao_trie_push (file, &ba_offset, 1, ba_keys, ba_values); ck_assert_int_eq (error, 0); /* Push b*. */ static const uint8_t b_keys[] = { 'a' }; const size_t b_values[] = { ba_offset }; size_t b_offset; error = ao_trie_push (file, &b_offset, 1, b_keys, b_values); ck_assert_int_eq (error, 0); /* Push the root. */ static const uint8_t root_keys[] = { 'b', 'a' }; const size_t root_values[] = { b_offset, a_offset }; size_t root_offset; error = ao_trie_push (file, &root_offset, 2, root_keys, root_values); ck_assert_int_eq (error, 0); /* Commit */ error = ao_file_commit_transaction (file); ck_assert_int_eq (error, 0); /* Check */ enum test_aof_trie_fold_step step = 0; error = ao_trie_fold (file, root_offset, test_aof_trie_fold_down, test_aof_trie_fold_up, &step); ck_assert_int_eq (error, 0); ck_assert_int_eq (step, TEST_AOF_TRIE_FOLD_DONE); remove (filename); close (file); } /* *INDENT-OFF* */ END_TEST /* *INDENT-ON* */ static inline char * tests_read_whole_file (int file) { off_t size = lseek (file, 0, SEEK_END); lseek (file, 0, SEEK_SET); char *ret = malloc (size + 1); if (ret == NULL) { return NULL; } size_t n_total = 0; while ((off_t) n_total < size) { ssize_t next = read (file, ret + n_total, size - n_total); if (next <= 0) { free (ret); return NULL; } n_total += next; } ret[size] = '\0'; return ret; } static inline char * run_tests (size_t *n_tests, size_t *n_errors) { ensure_init (); Suite *suite = suite_create (_("disfluid unit tests")); TCase *general = tcase_create (_("disfluid general tests")); tcase_add_test (general, test_check_version); suite_add_tcase (suite, general); TCase *cache_entry = tcase_create (_("disfluid cache entry files")); tcase_add_test (cache_entry, test_read_normal); tcase_add_test (cache_entry, test_read_invalid_magic_number); suite_add_tcase (suite, cache_entry); TCase *cache_key = tcase_create (_("disfluid cache key")); tcase_add_test (cache_key, test_cache_key_no_host); tcase_add_test (cache_key, test_cache_key_invalid_request_line); tcase_add_test (cache_key, test_cache_key_invalid_response_line); tcase_add_test (cache_key, test_simple_cache_key); tcase_add_test (cache_key, test_simply_varied_cache_key); tcase_add_test (cache_key, test_multiply_varied_cache_key); tcase_add_test (cache_key, test_partial_cache_key); suite_add_tcase (suite, cache_key); TCase *cache_hash = tcase_create (_("disfluid cache hash key")); tcase_add_test (cache_hash, test_cache_hash); suite_add_tcase (suite, cache_hash); TCase *db = tcase_create (_("disfluid database")); tcase_add_test (db, test_db); tcase_add_test (db, test_db_trie_inner); tcase_add_test (db, test_db_trie_leaf); suite_add_tcase (suite, db); TCase *aof = tcase_create (_("append-only file")); tcase_add_test (aof, test_aof_recover); tcase_add_test (aof, test_aof_can_read_locked_file); tcase_add_test (aof, test_aof_trie_fold); suite_add_tcase (suite, aof); SRunner *runner = srunner_create (suite); char log_file_name[] = "/tmp/disfluid-unit-tests-XXXXXX"; int log_file = mkstemp (log_file_name); srunner_set_log (runner, log_file_name); srunner_run_all (runner, CK_NORMAL); char *result = tests_read_whole_file (log_file); if (n_tests) { *n_tests = srunner_ntests_run (runner); } if (n_errors) { *n_errors = srunner_ntests_failed (runner); } srunner_free (runner); close (log_file); remove (log_file_name); return result; } static inline int test_append_init (int *fd, char **filename) { ensure_init (); int error = 0; *fd = -1; *filename = NULL; static const char model[] = "/tmp/disfluid-test-append-XXXXXX"; if (ALLOC_N (*filename, strlen (model) + 1) < 0) { error = -2; goto cleanup; } strcpy (*filename, model); *fd = mkstemp (*filename); if (*fd == -1) { error = -1; FREE (*filename); goto cleanup; } static const char *magic_data = "disfluid appendt"; const string_desc_t magic = { ._data = (char *) magic_data, ._nbytes = strlen (magic_data) }; if (ao_file_prepare (*fd, magic) < 0) { error = -1; goto cleanup; } cleanup: if (error != 0) { FREE (*filename); if (*fd >= 0) { close (*fd); *fd = -1; } } return error; } static inline int test_append_hello (const char *filename, size_t *n_before, size_t *n_after) { ensure_init (); if (test_append_count (filename, n_before) < 0) { return -1; } int file = open (filename, O_RDWR); int error = 0; if (file == -1) { return -1; } size_t top; if (ao_file_lock_for_writing (file, &top) < 0) { error = -1; goto cleanup; } static const char *hello_data = "Hello!"; const string_desc_t hello = { ._nbytes = strlen (hello_data), ._data = (char *) hello_data }; if (ao_file_push_data (file, hello, &top) < 0) { error = -1; goto cleanup; } if (ao_file_commit_transaction (file) < 0) { error = -1; goto cleanup; } cleanup: close (file); if (error == 0) { if (test_append_count (filename, n_after) < 0) { return -1; } if (n_before >= n_after) { return -1; } } return error; } static inline int test_append_count (const char *filename, size_t *n) { ensure_init (); *n = 0; size_t top; int error = 0; int file = open (filename, O_RDWR); if (file == -1) { return -1; } if (ao_file_read_top (file, &top) < 0) { error = -1; goto cleanup; } if (top % strlen ("Hello!") != 0) { error = -2; goto cleanup; } while (top != 0) { char data[6]; assert (sizeof (data) == strlen ("Hello!")); string_desc_t next = {._nbytes = sizeof (data),._data = data }; if (ao_file_read (file, top, next) < 0) { error = -2; goto cleanup; } if (memcmp (data, "Hello!", sizeof (data)) != 0) { error = -2; goto cleanup; } top -= sizeof (data); *n += 1; } cleanup: close (file); return error; } #endif /* DISFLUID_TESTS_INCLUDED */