diff options
Diffstat (limited to 't/unit-tests')
| -rw-r--r-- | t/unit-tests/t-ctype.c | 4 | ||||
| -rw-r--r-- | t/unit-tests/t-reftable-basics.c | 228 | ||||
| -rw-r--r-- | t/unit-tests/t-reftable-readwrite.c | 974 | ||||
| -rw-r--r-- | t/unit-tests/t-strvec.c | 416 | ||||
| -rw-r--r-- | t/unit-tests/test-lib.c | 36 | ||||
| -rw-r--r-- | t/unit-tests/test-lib.h | 20 |
6 files changed, 1322 insertions, 356 deletions
diff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/t-ctype.c index 24cc566cfe..6043f0d9bc 100644 --- a/t/unit-tests/t-ctype.c +++ b/t/unit-tests/t-ctype.c @@ -4,15 +4,13 @@ size_t len = ARRAY_SIZE(string) - 1 + \ BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \ BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \ - int skip = test__run_begin(); \ - if (!skip) { \ + if_test (#class " works") { \ for (int i = 0; i < 256; i++) { \ if (!check_int(class(i), ==, !!memchr(string, i, len)))\ test_msg(" i: 0x%02x", i); \ } \ check(!class(EOF)); \ } \ - test__run_end(!skip, TEST_LOCATION(), #class " works"); \ } while (0) #define DIGIT "0123456789" diff --git a/t/unit-tests/t-reftable-basics.c b/t/unit-tests/t-reftable-basics.c index 7ffee74dca..e5556ebf52 100644 --- a/t/unit-tests/t-reftable-basics.c +++ b/t/unit-tests/t-reftable-basics.c @@ -20,141 +20,125 @@ static int integer_needle_lesseq(size_t i, void *_args) return args->needle <= args->haystack[i]; } -static void test_binsearch(void) +int cmd_main(int argc UNUSED, const char *argv[] UNUSED) { - int haystack[] = { 2, 4, 6, 8, 10 }; - struct { - int needle; - size_t expected_idx; - } testcases[] = { - {-9000, 0}, - {-1, 0}, - {0, 0}, - {2, 0}, - {3, 1}, - {4, 1}, - {7, 3}, - {9, 4}, - {10, 4}, - {11, 5}, - {9000, 5}, - }; - - for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) { - struct integer_needle_lesseq_args args = { - .haystack = haystack, - .needle = testcases[i].needle, + if_test ("binary search with binsearch works") { + int haystack[] = { 2, 4, 6, 8, 10 }; + struct { + int needle; + size_t expected_idx; + } testcases[] = { + {-9000, 0}, + {-1, 0}, + {0, 0}, + {2, 0}, + {3, 1}, + {4, 1}, + {7, 3}, + {9, 4}, + {10, 4}, + {11, 5}, + {9000, 5}, }; - size_t idx; - idx = binsearch(ARRAY_SIZE(haystack), &integer_needle_lesseq, &args); - check_int(idx, ==, testcases[i].expected_idx); + for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) { + struct integer_needle_lesseq_args args = { + .haystack = haystack, + .needle = testcases[i].needle, + }; + size_t idx; + + idx = binsearch(ARRAY_SIZE(haystack), + &integer_needle_lesseq, &args); + check_int(idx, ==, testcases[i].expected_idx); + } } -} -static void test_names_length(void) -{ - const char *a[] = { "a", "b", NULL }; - check_int(names_length(a), ==, 2); -} - -static void test_names_equal(void) -{ - const char *a[] = { "a", "b", "c", NULL }; - const char *b[] = { "a", "b", "d", NULL }; - const char *c[] = { "a", "b", NULL }; + if_test ("names_length retuns size of a NULL-terminated string array") { + const char *a[] = { "a", "b", NULL }; + check_int(names_length(a), ==, 2); + } - check(names_equal(a, a)); - check(!names_equal(a, b)); - check(!names_equal(a, c)); -} + if_test ("names_equal compares NULL-terminated string arrays") { + const char *a[] = { "a", "b", "c", NULL }; + const char *b[] = { "a", "b", "d", NULL }; + const char *c[] = { "a", "b", NULL }; -static void test_parse_names_normal(void) -{ - char in1[] = "line\n"; - char in2[] = "a\nb\nc"; - char **out = NULL; - parse_names(in1, strlen(in1), &out); - check_str(out[0], "line"); - check(!out[1]); - free_names(out); - - parse_names(in2, strlen(in2), &out); - check_str(out[0], "a"); - check_str(out[1], "b"); - check_str(out[2], "c"); - check(!out[3]); - free_names(out); -} + check(names_equal(a, a)); + check(!names_equal(a, b)); + check(!names_equal(a, c)); + } -static void test_parse_names_drop_empty(void) -{ - char in[] = "a\n\nb\n"; - char **out = NULL; - parse_names(in, strlen(in), &out); - check_str(out[0], "a"); - /* simply '\n' should be dropped as empty string */ - check_str(out[1], "b"); - check(!out[2]); - free_names(out); -} + if_test ("parse_names works for basic input") { + char in1[] = "line\n"; + char in2[] = "a\nb\nc"; + char **out = NULL; + parse_names(in1, strlen(in1), &out); + check_str(out[0], "line"); + check(!out[1]); + free_names(out); + + parse_names(in2, strlen(in2), &out); + check_str(out[0], "a"); + check_str(out[1], "b"); + check_str(out[2], "c"); + check(!out[3]); + free_names(out); + } -static void test_common_prefix(void) -{ - struct strbuf a = STRBUF_INIT; - struct strbuf b = STRBUF_INIT; - struct { - const char *a, *b; - int want; - } cases[] = { - {"abcdef", "abc", 3}, - { "abc", "ab", 2 }, - { "", "abc", 0 }, - { "abc", "abd", 2 }, - { "abc", "pqr", 0 }, - }; - - for (size_t i = 0; i < ARRAY_SIZE(cases); i++) { - strbuf_addstr(&a, cases[i].a); - strbuf_addstr(&b, cases[i].b); - check_int(common_prefix_size(&a, &b), ==, cases[i].want); - strbuf_reset(&a); - strbuf_reset(&b); + if_test ("parse_names drops empty string") { + char in[] = "a\n\nb\n"; + char **out = NULL; + parse_names(in, strlen(in), &out); + check_str(out[0], "a"); + /* simply '\n' should be dropped as empty string */ + check_str(out[1], "b"); + check(!out[2]); + free_names(out); } - strbuf_release(&a); - strbuf_release(&b); -} -static void test_u24_roundtrip(void) -{ - uint32_t in = 0x112233; - uint8_t dest[3]; - uint32_t out; - put_be24(dest, in); - out = get_be24(dest); - check_int(in, ==, out); -} + if_test ("common_prefix_size works") { + struct strbuf a = STRBUF_INIT; + struct strbuf b = STRBUF_INIT; + struct { + const char *a, *b; + int want; + } cases[] = { + {"abcdef", "abc", 3}, + { "abc", "ab", 2 }, + { "", "abc", 0 }, + { "abc", "abd", 2 }, + { "abc", "pqr", 0 }, + }; -static void test_u16_roundtrip(void) -{ - uint32_t in = 0xfef1; - uint8_t dest[3]; - uint32_t out; - put_be16(dest, in); - out = get_be16(dest); - check_int(in, ==, out); -} + for (size_t i = 0; i < ARRAY_SIZE(cases); i++) { + strbuf_addstr(&a, cases[i].a); + strbuf_addstr(&b, cases[i].b); + check_int(common_prefix_size(&a, &b), ==, cases[i].want); + strbuf_reset(&a); + strbuf_reset(&b); + } + strbuf_release(&a); + strbuf_release(&b); + } -int cmd_main(int argc UNUSED, const char *argv[] UNUSED) -{ - TEST(test_common_prefix(), "common_prefix_size works"); - TEST(test_parse_names_normal(), "parse_names works for basic input"); - TEST(test_parse_names_drop_empty(), "parse_names drops empty string"); - TEST(test_binsearch(), "binary search with binsearch works"); - TEST(test_names_length(), "names_length retuns size of a NULL-terminated string array"); - TEST(test_names_equal(), "names_equal compares NULL-terminated string arrays"); - TEST(test_u24_roundtrip(), "put_be24 and get_be24 work"); - TEST(test_u16_roundtrip(), "put_be16 and get_be16 work"); + if_test ("put_be24 and get_be24 work") { + uint32_t in = 0x112233; + uint8_t dest[3]; + uint32_t out; + put_be24(dest, in); + out = get_be24(dest); + check_int(in, ==, out); + } + + if_test ("put_be16 and get_be16 work") { + uint32_t in = 0xfef1; + uint8_t dest[3]; + uint32_t out; + put_be16(dest, in); + out = get_be16(dest); + check_int(in, ==, out); + } return test_done(); } diff --git a/t/unit-tests/t-reftable-readwrite.c b/t/unit-tests/t-reftable-readwrite.c new file mode 100644 index 0000000000..1eae36cc60 --- /dev/null +++ b/t/unit-tests/t-reftable-readwrite.c @@ -0,0 +1,974 @@ +/* +Copyright 2020 Google LLC + +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file or at +https://developers.google.com/open-source/licenses/bsd +*/ + +#include "test-lib.h" +#include "reftable/basics.h" +#include "reftable/blocksource.h" +#include "reftable/reader.h" +#include "reftable/reftable-error.h" +#include "reftable/reftable-writer.h" + +static const int update_index = 5; + +static void set_test_hash(uint8_t *p, int i) +{ + memset(p, (uint8_t)i, hash_size(GIT_SHA1_FORMAT_ID)); +} + +static ssize_t strbuf_add_void(void *b, const void *data, size_t sz) +{ + strbuf_add(b, data, sz); + return sz; +} + +static int noop_flush(void *arg) +{ + return 0; +} + +static void t_buffer(void) +{ + struct strbuf buf = STRBUF_INIT; + struct reftable_block_source source = { 0 }; + struct reftable_block out = { 0 }; + int n; + uint8_t in[] = "hello"; + strbuf_add(&buf, in, sizeof(in)); + block_source_from_strbuf(&source, &buf); + check_int(block_source_size(&source), ==, 6); + n = block_source_read_block(&source, &out, 0, sizeof(in)); + check_int(n, ==, sizeof(in)); + check(!memcmp(in, out.data, n)); + reftable_block_done(&out); + + n = block_source_read_block(&source, &out, 1, 2); + check_int(n, ==, 2); + check(!memcmp(out.data, "el", 2)); + + reftable_block_done(&out); + block_source_close(&source); + strbuf_release(&buf); +} + +static void write_table(char ***names, struct strbuf *buf, int N, + int block_size, uint32_t hash_id) +{ + struct reftable_write_options opts = { + .block_size = block_size, + .hash_id = hash_id, + }; + struct reftable_writer *w = + reftable_new_writer(&strbuf_add_void, &noop_flush, buf, &opts); + struct reftable_ref_record ref = { 0 }; + int i = 0, n; + struct reftable_log_record log = { 0 }; + const struct reftable_stats *stats = NULL; + + REFTABLE_CALLOC_ARRAY(*names, N + 1); + + reftable_writer_set_limits(w, update_index, update_index); + for (i = 0; i < N; i++) { + char name[100]; + int n; + + snprintf(name, sizeof(name), "refs/heads/branch%02d", i); + + ref.refname = name; + ref.update_index = update_index; + ref.value_type = REFTABLE_REF_VAL1; + set_test_hash(ref.value.val1, i); + (*names)[i] = xstrdup(name); + + n = reftable_writer_add_ref(w, &ref); + check_int(n, ==, 0); + } + + for (i = 0; i < N; i++) { + char name[100]; + int n; + + snprintf(name, sizeof(name), "refs/heads/branch%02d", i); + + log.refname = name; + log.update_index = update_index; + log.value_type = REFTABLE_LOG_UPDATE; + set_test_hash(log.value.update.new_hash, i); + log.value.update.message = (char *) "message"; + + n = reftable_writer_add_log(w, &log); + check_int(n, ==, 0); + } + + n = reftable_writer_close(w); + check_int(n, ==, 0); + + stats = reftable_writer_stats(w); + for (i = 0; i < stats->ref_stats.blocks; i++) { + int off = i * opts.block_size; + if (!off) + off = header_size((hash_id == GIT_SHA256_FORMAT_ID) ? 2 : 1); + check_char(buf->buf[off], ==, 'r'); + } + + check_int(stats->log_stats.blocks, >, 0); + reftable_writer_free(w); +} + +static void t_log_buffer_size(void) +{ + struct strbuf buf = STRBUF_INIT; + struct reftable_write_options opts = { + .block_size = 4096, + }; + int err; + int i; + struct reftable_log_record + log = { .refname = (char *) "refs/heads/master", + .update_index = 0xa, + .value_type = REFTABLE_LOG_UPDATE, + .value = { .update = { + .name = (char *) "Han-Wen Nienhuys", + .email = (char *) "hanwen@google.com", + .tz_offset = 100, + .time = 0x5e430672, + .message = (char *) "commit: 9\n", + } } }; + struct reftable_writer *w = + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); + + /* This tests buffer extension for log compression. Must use a random + hash, to ensure that the compressed part is larger than the original. + */ + for (i = 0; i < GIT_SHA1_RAWSZ; i++) { + log.value.update.old_hash[i] = (uint8_t)(git_rand() % 256); + log.value.update.new_hash[i] = (uint8_t)(git_rand() % 256); + } + reftable_writer_set_limits(w, update_index, update_index); + err = reftable_writer_add_log(w, &log); + check(!err); + err = reftable_writer_close(w); + check(!err); + reftable_writer_free(w); + strbuf_release(&buf); +} + +static void t_log_overflow(void) +{ + struct strbuf buf = STRBUF_INIT; + char msg[256] = { 0 }; + struct reftable_write_options opts = { + .block_size = ARRAY_SIZE(msg), + }; + int err; + struct reftable_log_record log = { + .refname = (char *) "refs/heads/master", + .update_index = 0xa, + .value_type = REFTABLE_LOG_UPDATE, + .value = { + .update = { + .old_hash = { 1 }, + .new_hash = { 2 }, + .name = (char *) "Han-Wen Nienhuys", + .email = (char *) "hanwen@google.com", + .tz_offset = 100, + .time = 0x5e430672, + .message = msg, + }, + }, + }; + struct reftable_writer *w = + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); + + memset(msg, 'x', sizeof(msg) - 1); + reftable_writer_set_limits(w, update_index, update_index); + err = reftable_writer_add_log(w, &log); + check_int(err, ==, REFTABLE_ENTRY_TOO_BIG_ERROR); + reftable_writer_free(w); + strbuf_release(&buf); +} + +static void t_log_write_read(void) +{ + int N = 2; + char **names = reftable_calloc(N + 1, sizeof(*names)); + int err; + struct reftable_write_options opts = { + .block_size = 256, + }; + struct reftable_ref_record ref = { 0 }; + int i = 0; + struct reftable_log_record log = { 0 }; + int n; + struct reftable_iterator it = { 0 }; + struct reftable_reader rd = { 0 }; + struct reftable_block_source source = { 0 }; + struct strbuf buf = STRBUF_INIT; + struct reftable_writer *w = + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); + const struct reftable_stats *stats = NULL; + reftable_writer_set_limits(w, 0, N); + for (i = 0; i < N; i++) { + char name[256]; + struct reftable_ref_record ref = { 0 }; + snprintf(name, sizeof(name), "b%02d%0*d", i, 130, 7); + names[i] = xstrdup(name); + ref.refname = name; + ref.update_index = i; + + err = reftable_writer_add_ref(w, &ref); + check(!err); + } + for (i = 0; i < N; i++) { + struct reftable_log_record log = { 0 }; + + log.refname = names[i]; + log.update_index = i; + log.value_type = REFTABLE_LOG_UPDATE; + set_test_hash(log.value.update.old_hash, i); + set_test_hash(log.value.update.new_hash, i + 1); + + err = reftable_writer_add_log(w, &log); + check(!err); + } + + n = reftable_writer_close(w); + check_int(n, ==, 0); + + stats = reftable_writer_stats(w); + check_int(stats->log_stats.blocks, >, 0); + reftable_writer_free(w); + w = NULL; + + block_source_from_strbuf(&source, &buf); + + err = init_reader(&rd, &source, "file.log"); + check(!err); + + reftable_reader_init_ref_iterator(&rd, &it); + + err = reftable_iterator_seek_ref(&it, names[N - 1]); + check(!err); + + err = reftable_iterator_next_ref(&it, &ref); + check(!err); + + /* end of iteration. */ + err = reftable_iterator_next_ref(&it, &ref); + check_int(err, >, 0); + + reftable_iterator_destroy(&it); + reftable_ref_record_release(&ref); + + reftable_reader_init_log_iterator(&rd, &it); + + err = reftable_iterator_seek_log(&it, ""); + check(!err); + + for (i = 0; ; i++) { + int err = reftable_iterator_next_log(&it, &log); + if (err > 0) + break; + check(!err); + check_str(names[i], log.refname); + check_int(i, ==, log.update_index); + reftable_log_record_release(&log); + } + + check_int(i, ==, N); + reftable_iterator_destroy(&it); + + /* cleanup. */ + strbuf_release(&buf); + free_names(names); + reader_close(&rd); +} + +static void t_log_zlib_corruption(void) +{ + struct reftable_write_options opts = { + .block_size = 256, + }; + struct reftable_iterator it = { 0 }; + struct reftable_reader rd = { 0 }; + struct reftable_block_source source = { 0 }; + struct strbuf buf = STRBUF_INIT; + struct reftable_writer *w = + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); + const struct reftable_stats *stats = NULL; + char message[100] = { 0 }; + int err, i, n; + struct reftable_log_record log = { + .refname = (char *) "refname", + .value_type = REFTABLE_LOG_UPDATE, + .value = { + .update = { + .new_hash = { 1 }, + .old_hash = { 2 }, + .name = (char *) "My Name", + .email = (char *) "myname@invalid", + .message = message, + }, + }, + }; + + for (i = 0; i < sizeof(message) - 1; i++) + message[i] = (uint8_t)(git_rand() % 64 + ' '); + + reftable_writer_set_limits(w, 1, 1); + + err = reftable_writer_add_log(w, &log); + check(!err); + + n = reftable_writer_close(w); + check_int(n, ==, 0); + + stats = reftable_writer_stats(w); + check_int(stats->log_stats.blocks, >, 0); + reftable_writer_free(w); + w = NULL; + + /* corrupt the data. */ + buf.buf[50] ^= 0x99; + + block_source_from_strbuf(&source, &buf); + + err = init_reader(&rd, &source, "file.log"); + check(!err); + + reftable_reader_init_log_iterator(&rd, &it); + err = reftable_iterator_seek_log(&it, "refname"); + check_int(err, ==, REFTABLE_ZLIB_ERROR); + + reftable_iterator_destroy(&it); + + /* cleanup. */ + strbuf_release(&buf); + reader_close(&rd); +} + +static void t_table_read_write_sequential(void) +{ + char **names; + struct strbuf buf = STRBUF_INIT; + int N = 50; + struct reftable_iterator it = { 0 }; + struct reftable_block_source source = { 0 }; + struct reftable_reader rd = { 0 }; + int err = 0; + int j = 0; + + write_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID); + + block_source_from_strbuf(&source, &buf); + + err = init_reader(&rd, &source, "file.ref"); + check(!err); + + reftable_reader_init_ref_iterator(&rd, &it); + err = reftable_iterator_seek_ref(&it, ""); + check(!err); + + for (j = 0; ; j++) { + struct reftable_ref_record ref = { 0 }; + int r = reftable_iterator_next_ref(&it, &ref); + check_int(r, >=, 0); + if (r > 0) + break; + check_str(names[j], ref.refname); + check_int(update_index, ==, ref.update_index); + reftable_ref_record_release(&ref); + } + check_int(j, ==, N); + reftable_iterator_destroy(&it); + strbuf_release(&buf); + free_names(names); + + reader_close(&rd); +} + +static void t_table_write_small_table(void) +{ + char **names; + struct strbuf buf = STRBUF_INIT; + int N = 1; + write_table(&names, &buf, N, 4096, GIT_SHA1_FORMAT_ID); + check_int(buf.len, <, 200); + strbuf_release(&buf); + free_names(names); +} + +static void t_table_read_api(void) +{ + char **names; + struct strbuf buf = STRBUF_INIT; + int N = 50; + struct reftable_reader rd = { 0 }; + struct reftable_block_source source = { 0 }; + int err; + struct reftable_log_record log = { 0 }; + struct reftable_iterator it = { 0 }; + + write_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID); + + block_source_from_strbuf(&source, &buf); + + err = init_reader(&rd, &source, "file.ref"); + check(!err); + + reftable_reader_init_ref_iterator(&rd, &it); + err = reftable_iterator_seek_ref(&it, names[0]); + check(!err); + + err = reftable_iterator_next_log(&it, &log); + check_int(err, ==, REFTABLE_API_ERROR); + + strbuf_release(&buf); + free_names(names); + reftable_iterator_destroy(&it); + reader_close(&rd); + strbuf_release(&buf); +} + +static void t_table_read_write_seek(int index, int hash_id) +{ + char **names; + struct strbuf buf = STRBUF_INIT; + int N = 50; + struct reftable_reader rd = { 0 }; + struct reftable_block_source source = { 0 }; + int err; + int i = 0; + + struct reftable_iterator it = { 0 }; + struct strbuf pastLast = STRBUF_INIT; + struct reftable_ref_record ref = { 0 }; + + write_table(&names, &buf, N, 256, hash_id); + + block_source_from_strbuf(&source, &buf); + + err = init_reader(&rd, &source, "file.ref"); + check(!err); + check_int(hash_id, ==, reftable_reader_hash_id(&rd)); + + if (!index) + rd.ref_offsets.index_offset = 0; + else + check_int(rd.ref_offsets.index_offset, >, 0); + + for (i = 1; i < N; i++) { + reftable_reader_init_ref_iterator(&rd, &it); + err = reftable_iterator_seek_ref(&it, names[i]); + check(!err); + err = reftable_iterator_next_ref(&it, &ref); + check(!err); + check_str(names[i], ref.refname); + check_int(REFTABLE_REF_VAL1, ==, ref.value_type); + check_int(i, ==, ref.value.val1[0]); + + reftable_ref_record_release(&ref); + reftable_iterator_destroy(&it); + } + + strbuf_addstr(&pastLast, names[N - 1]); + strbuf_addstr(&pastLast, "/"); + + reftable_reader_init_ref_iterator(&rd, &it); + err = reftable_iterator_seek_ref(&it, pastLast.buf); + if (err == 0) { + struct reftable_ref_record ref = { 0 }; + int err = reftable_iterator_next_ref(&it, &ref); + check_int(err, >, 0); + } else { + check_int(err, >, 0); + } + + strbuf_release(&pastLast); + reftable_iterator_destroy(&it); + + strbuf_release(&buf); + free_names(names); + reader_close(&rd); +} + +static void t_table_read_write_seek_linear(void) +{ + t_table_read_write_seek(0, GIT_SHA1_FORMAT_ID); +} + +static void t_table_read_write_seek_linear_sha256(void) +{ + t_table_read_write_seek(0, GIT_SHA256_FORMAT_ID); +} + +static void t_table_read_write_seek_index(void) +{ + t_table_read_write_seek(1, GIT_SHA1_FORMAT_ID); +} + +static void t_table_refs_for(int indexed) +{ + int N = 50; + char **want_names = reftable_calloc(N + 1, sizeof(*want_names)); + int want_names_len = 0; + uint8_t want_hash[GIT_SHA1_RAWSZ]; + + struct reftable_write_options opts = { + .block_size = 256, + }; + struct reftable_ref_record ref = { 0 }; + int i = 0; + int n; + int err; + struct reftable_reader rd; + struct reftable_block_source source = { 0 }; + + struct strbuf buf = STRBUF_INIT; + struct reftable_writer *w = + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); + + struct reftable_iterator it = { 0 }; + int j; + + set_test_hash(want_hash, 4); + + for (i = 0; i < N; i++) { + uint8_t hash[GIT_SHA1_RAWSZ]; + char fill[51] = { 0 }; + char name[100]; + struct reftable_ref_record ref = { 0 }; + + memset(hash, i, sizeof(hash)); + memset(fill, 'x', 50); + /* Put the variable part in the start */ + snprintf(name, sizeof(name), "br%02d%s", i, fill); + name[40] = 0; + ref.refname = name; + + ref.value_type = REFTABLE_REF_VAL2; + set_test_hash(ref.value.val2.value, i / 4); + set_test_hash(ref.value.val2.target_value, 3 + i / 4); + + /* 80 bytes / entry, so 3 entries per block. Yields 17 + */ + /* blocks. */ + n = reftable_writer_add_ref(w, &ref); + check_int(n, ==, 0); + + if (!memcmp(ref.value.val2.value, want_hash, GIT_SHA1_RAWSZ) || + !memcmp(ref.value.val2.target_value, want_hash, GIT_SHA1_RAWSZ)) + want_names[want_names_len++] = xstrdup(name); + } + + n = reftable_writer_close(w); + check_int(n, ==, 0); + + reftable_writer_free(w); + w = NULL; + + block_source_from_strbuf(&source, &buf); + + err = init_reader(&rd, &source, "file.ref"); + check(!err); + if (!indexed) + rd.obj_offsets.is_present = 0; + + reftable_reader_init_ref_iterator(&rd, &it); + err = reftable_iterator_seek_ref(&it, ""); + check(!err); + reftable_iterator_destroy(&it); + + err = reftable_reader_refs_for(&rd, &it, want_hash); + check(!err); + + for (j = 0; ; j++) { + int err = reftable_iterator_next_ref(&it, &ref); + check_int(err, >=, 0); + if (err > 0) + break; + check_int(j, <, want_names_len); + check_str(ref.refname, want_names[j]); + reftable_ref_record_release(&ref); + } + check_int(j, ==, want_names_len); + + strbuf_release(&buf); + free_names(want_names); + reftable_iterator_destroy(&it); + reader_close(&rd); +} + +static void t_table_refs_for_no_index(void) +{ + t_table_refs_for(0); +} + +static void t_table_refs_for_obj_index(void) +{ + t_table_refs_for(1); +} + +static void t_write_empty_table(void) +{ + struct reftable_write_options opts = { 0 }; + struct strbuf buf = STRBUF_INIT; + struct reftable_writer *w = + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); + struct reftable_block_source source = { 0 }; + struct reftable_reader *rd = NULL; + struct reftable_ref_record rec = { 0 }; + struct reftable_iterator it = { 0 }; + int err; + + reftable_writer_set_limits(w, 1, 1); + + err = reftable_writer_close(w); + check_int(err, ==, REFTABLE_EMPTY_TABLE_ERROR); + reftable_writer_free(w); + + check_int(buf.len, ==, header_size(1) + footer_size(1)); + + block_source_from_strbuf(&source, &buf); + + err = reftable_new_reader(&rd, &source, "filename"); + check(!err); + + reftable_reader_init_ref_iterator(rd, &it); + err = reftable_iterator_seek_ref(&it, ""); + check(!err); + + err = reftable_iterator_next_ref(&it, &rec); + check_int(err, >, 0); + + reftable_iterator_destroy(&it); + reftable_reader_free(rd); + strbuf_release(&buf); +} + +static void t_write_object_id_min_length(void) +{ + struct reftable_write_options opts = { + .block_size = 75, + }; + struct strbuf buf = STRBUF_INIT; + struct reftable_writer *w = + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); + struct reftable_ref_record ref = { + .update_index = 1, + .value_type = REFTABLE_REF_VAL1, + .value.val1 = {42}, + }; + int err; + int i; + + reftable_writer_set_limits(w, 1, 1); + + /* Write the same hash in many refs. If there is only 1 hash, the + * disambiguating prefix is length 0 */ + for (i = 0; i < 256; i++) { + char name[256]; + snprintf(name, sizeof(name), "ref%05d", i); + ref.refname = name; + err = reftable_writer_add_ref(w, &ref); + check(!err); + } + + err = reftable_writer_close(w); + check(!err); + check_int(reftable_writer_stats(w)->object_id_len, ==, 2); + reftable_writer_free(w); + strbuf_release(&buf); +} + +static void t_write_object_id_length(void) +{ + struct reftable_write_options opts = { + .block_size = 75, + }; + struct strbuf buf = STRBUF_INIT; + struct reftable_writer *w = + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); + struct reftable_ref_record ref = { + .update_index = 1, + .value_type = REFTABLE_REF_VAL1, + .value.val1 = {42}, + }; + int err; + int i; + + reftable_writer_set_limits(w, 1, 1); + + /* Write the same hash in many refs. If there is only 1 hash, the + * disambiguating prefix is length 0 */ + for (i = 0; i < 256; i++) { + char name[256]; + snprintf(name, sizeof(name), "ref%05d", i); + ref.refname = name; + ref.value.val1[15] = i; + err = reftable_writer_add_ref(w, &ref); + check(!err); + } + + err = reftable_writer_close(w); + check(!err); + check_int(reftable_writer_stats(w)->object_id_len, ==, 16); + reftable_writer_free(w); + strbuf_release(&buf); +} + +static void t_write_empty_key(void) +{ + struct reftable_write_options opts = { 0 }; + struct strbuf buf = STRBUF_INIT; + struct reftable_writer *w = + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); + struct reftable_ref_record ref = { + .refname = (char *) "", + .update_index = 1, + .value_type = REFTABLE_REF_DELETION, + }; + int err; + + reftable_writer_set_limits(w, 1, 1); + err = reftable_writer_add_ref(w, &ref); + check_int(err, ==, REFTABLE_API_ERROR); + + err = reftable_writer_close(w); + check_int(err, ==, REFTABLE_EMPTY_TABLE_ERROR); + reftable_writer_free(w); + strbuf_release(&buf); +} + +static void t_write_key_order(void) +{ + struct reftable_write_options opts = { 0 }; + struct strbuf buf = STRBUF_INIT; + struct reftable_writer *w = + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); + struct reftable_ref_record refs[2] = { + { + .refname = (char *) "b", + .update_index = 1, + .value_type = REFTABLE_REF_SYMREF, + .value = { + .symref = (char *) "target", + }, + }, { + .refname = (char *) "a", + .update_index = 1, + .value_type = REFTABLE_REF_SYMREF, + .value = { + .symref = (char *) "target", + }, + } + }; + int err; + + reftable_writer_set_limits(w, 1, 1); + err = reftable_writer_add_ref(w, &refs[0]); + check(!err); + err = reftable_writer_add_ref(w, &refs[1]); + check_int(err, ==, REFTABLE_API_ERROR); + + refs[0].update_index = 2; + err = reftable_writer_add_ref(w, &refs[0]); + check_int(err, ==, REFTABLE_API_ERROR); + + reftable_writer_close(w); + reftable_writer_free(w); + strbuf_release(&buf); +} + +static void t_write_multiple_indices(void) +{ + struct reftable_write_options opts = { + .block_size = 100, + }; + struct strbuf writer_buf = STRBUF_INIT, buf = STRBUF_INIT; + struct reftable_block_source source = { 0 }; + struct reftable_iterator it = { 0 }; + const struct reftable_stats *stats; + struct reftable_writer *writer; + struct reftable_reader *reader; + int err, i; + + writer = reftable_new_writer(&strbuf_add_void, &noop_flush, &writer_buf, &opts); + reftable_writer_set_limits(writer, 1, 1); + for (i = 0; i < 100; i++) { + struct reftable_ref_record ref = { + .update_index = 1, + .value_type = REFTABLE_REF_VAL1, + .value.val1 = {i}, + }; + + strbuf_reset(&buf); + strbuf_addf(&buf, "refs/heads/%04d", i); + ref.refname = buf.buf, + + err = reftable_writer_add_ref(writer, &ref); + check(!err); + } + + for (i = 0; i < 100; i++) { + struct reftable_log_record log = { + .update_index = 1, + .value_type = REFTABLE_LOG_UPDATE, + .value.update = { + .old_hash = { i }, + .new_hash = { i }, + }, + }; + + strbuf_reset(&buf); + strbuf_addf(&buf, "refs/heads/%04d", i); + log.refname = buf.buf, + + err = reftable_writer_add_log(writer, &log); + check(!err); + } + + reftable_writer_close(writer); + + /* + * The written data should be sufficiently large to result in indices + * for each of the block types. + */ + stats = reftable_writer_stats(writer); + check_int(stats->ref_stats.index_offset, >, 0); + check_int(stats->obj_stats.index_offset, >, 0); + check_int(stats->log_stats.index_offset, >, 0); + + block_source_from_strbuf(&source, &writer_buf); + err = reftable_new_reader(&reader, &source, "filename"); + check(!err); + + /* + * Seeking the log uses the log index now. In case there is any + * confusion regarding indices we would notice here. + */ + reftable_reader_init_log_iterator(reader, &it); + err = reftable_iterator_seek_log(&it, ""); + check(!err); + + reftable_iterator_destroy(&it); + reftable_writer_free(writer); + reftable_reader_free(reader); + strbuf_release(&writer_buf); + strbuf_release(&buf); +} + +static void t_write_multi_level_index(void) +{ + struct reftable_write_options opts = { + .block_size = 100, + }; + struct strbuf writer_buf = STRBUF_INIT, buf = STRBUF_INIT; + struct reftable_block_source source = { 0 }; + struct reftable_iterator it = { 0 }; + const struct reftable_stats *stats; + struct reftable_writer *writer; + struct reftable_reader *reader; + int err; + + writer = reftable_new_writer(&strbuf_add_void, &noop_flush, &writer_buf, &opts); + reftable_writer_set_limits(writer, 1, 1); + for (size_t i = 0; i < 200; i++) { + struct reftable_ref_record ref = { + .update_index = 1, + .value_type = REFTABLE_REF_VAL1, + .value.val1 = {i}, + }; + + strbuf_reset(&buf); + strbuf_addf(&buf, "refs/heads/%03" PRIuMAX, (uintmax_t)i); + ref.refname = buf.buf, + + err = reftable_writer_add_ref(writer, &ref); + check(!err); + } + reftable_writer_close(writer); + + /* + * The written refs should be sufficiently large to result in a + * multi-level index. + */ + stats = reftable_writer_stats(writer); + check_int(stats->ref_stats.max_index_level, ==, 2); + + block_source_from_strbuf(&source, &writer_buf); + err = reftable_new_reader(&reader, &source, "filename"); + check(!err); + + /* + * Seeking the last ref should work as expected. + */ + reftable_reader_init_ref_iterator(reader, &it); + err = reftable_iterator_seek_ref(&it, "refs/heads/199"); + check(!err); + + reftable_iterator_destroy(&it); + reftable_writer_free(writer); + reftable_reader_free(reader); + strbuf_release(&writer_buf); + strbuf_release(&buf); +} + +static void t_corrupt_table_empty(void) +{ + struct strbuf buf = STRBUF_INIT; + struct reftable_block_source source = { 0 }; + struct reftable_reader rd = { 0 }; + int err; + + block_source_from_strbuf(&source, &buf); + err = init_reader(&rd, &source, "file.log"); + check_int(err, ==, REFTABLE_FORMAT_ERROR); +} + +static void t_corrupt_table(void) +{ + uint8_t zeros[1024] = { 0 }; + struct strbuf buf = STRBUF_INIT; + struct reftable_block_source source = { 0 }; + struct reftable_reader rd = { 0 }; + int err; + strbuf_add(&buf, zeros, sizeof(zeros)); + + block_source_from_strbuf(&source, &buf); + err = init_reader(&rd, &source, "file.log"); + check_int(err, ==, REFTABLE_FORMAT_ERROR); + strbuf_release(&buf); +} + +int cmd_main(int argc UNUSED, const char *argv[] UNUSED) +{ + TEST(t_buffer(), "strbuf works as blocksource"); + TEST(t_corrupt_table(), "read-write on corrupted table"); + TEST(t_corrupt_table_empty(), "read-write on an empty table"); + TEST(t_log_buffer_size(), "buffer extension for log compression"); + TEST(t_log_overflow(), "log overflow returns expected error"); + TEST(t_log_write_read(), "read-write on log records"); + TEST(t_log_zlib_corruption(), "reading corrupted log record returns expected error"); + TEST(t_table_read_api(), "read on a table"); + TEST(t_table_read_write_seek_index(), "read-write on a table with index"); + TEST(t_table_read_write_seek_linear(), "read-write on a table without index (SHA1)"); + TEST(t_table_read_write_seek_linear_sha256(), "read-write on a table without index (SHA256)"); + TEST(t_table_read_write_sequential(), "sequential read-write on a table"); + TEST(t_table_refs_for_no_index(), "refs-only table with no index"); + TEST(t_table_refs_for_obj_index(), "refs-only table with index"); + TEST(t_table_write_small_table(), "write_table works"); + TEST(t_write_empty_key(), "write on refs with empty keys"); + TEST(t_write_empty_table(), "read-write on empty tables"); + TEST(t_write_key_order(), "refs must be written in increasing order"); + TEST(t_write_multi_level_index(), "table with multi-level index"); + TEST(t_write_multiple_indices(), "table with indices for multiple block types"); + TEST(t_write_object_id_length(), "prefix compression on writing refs"); + TEST(t_write_object_id_min_length(), "prefix compression on writing refs"); + + return test_done(); +} diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c index c3b610dda7..5c844cf0c9 100644 --- a/t/unit-tests/t-strvec.c +++ b/t/unit-tests/t-strvec.c @@ -19,237 +19,193 @@ } \ } while (0) -static void t_static_init(void) -{ - struct strvec vec = STRVEC_INIT; - check_pointer_eq(vec.v, empty_strvec); - check_uint(vec.nr, ==, 0); - check_uint(vec.alloc, ==, 0); -} - -static void t_dynamic_init(void) -{ - struct strvec vec; - strvec_init(&vec); - check_pointer_eq(vec.v, empty_strvec); - check_uint(vec.nr, ==, 0); - check_uint(vec.alloc, ==, 0); -} - -static void t_clear(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_push(&vec, "foo"); - strvec_clear(&vec); - check_pointer_eq(vec.v, empty_strvec); - check_uint(vec.nr, ==, 0); - check_uint(vec.alloc, ==, 0); -} - -static void t_push(void) -{ - struct strvec vec = STRVEC_INIT; - - strvec_push(&vec, "foo"); - check_strvec(&vec, "foo", NULL); - - strvec_push(&vec, "bar"); - check_strvec(&vec, "foo", "bar", NULL); - - strvec_clear(&vec); -} - -static void t_pushf(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_pushf(&vec, "foo: %d", 1); - check_strvec(&vec, "foo: 1", NULL); - strvec_clear(&vec); -} - -static void t_pushl(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); - check_strvec(&vec, "foo", "bar", "baz", NULL); - strvec_clear(&vec); -} - -static void t_pushv(void) -{ - const char *strings[] = { - "foo", "bar", "baz", NULL, - }; - struct strvec vec = STRVEC_INIT; - - strvec_pushv(&vec, strings); - check_strvec(&vec, "foo", "bar", "baz", NULL); - - strvec_clear(&vec); -} - -static void t_replace_at_head(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); - strvec_replace(&vec, 0, "replaced"); - check_strvec(&vec, "replaced", "bar", "baz", NULL); - strvec_clear(&vec); -} - -static void t_replace_at_tail(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); - strvec_replace(&vec, 2, "replaced"); - check_strvec(&vec, "foo", "bar", "replaced", NULL); - strvec_clear(&vec); -} - -static void t_replace_in_between(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); - strvec_replace(&vec, 1, "replaced"); - check_strvec(&vec, "foo", "replaced", "baz", NULL); - strvec_clear(&vec); -} - -static void t_replace_with_substring(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", NULL); - strvec_replace(&vec, 0, vec.v[0] + 1); - check_strvec(&vec, "oo", NULL); - strvec_clear(&vec); -} - -static void t_remove_at_head(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); - strvec_remove(&vec, 0); - check_strvec(&vec, "bar", "baz", NULL); - strvec_clear(&vec); -} - -static void t_remove_at_tail(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); - strvec_remove(&vec, 2); - check_strvec(&vec, "foo", "bar", NULL); - strvec_clear(&vec); -} - -static void t_remove_in_between(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); - strvec_remove(&vec, 1); - check_strvec(&vec, "foo", "baz", NULL); - strvec_clear(&vec); -} - -static void t_pop_empty_array(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_pop(&vec); - check_strvec(&vec, NULL); - strvec_clear(&vec); -} - -static void t_pop_non_empty_array(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); - strvec_pop(&vec); - check_strvec(&vec, "foo", "bar", NULL); - strvec_clear(&vec); -} - -static void t_split_empty_string(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_split(&vec, ""); - check_strvec(&vec, NULL); - strvec_clear(&vec); -} - -static void t_split_single_item(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_split(&vec, "foo"); - check_strvec(&vec, "foo", NULL); - strvec_clear(&vec); -} - -static void t_split_multiple_items(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_split(&vec, "foo bar baz"); - check_strvec(&vec, "foo", "bar", "baz", NULL); - strvec_clear(&vec); -} - -static void t_split_whitespace_only(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_split(&vec, " \t\n"); - check_strvec(&vec, NULL); - strvec_clear(&vec); -} - -static void t_split_multiple_consecutive_whitespaces(void) -{ - struct strvec vec = STRVEC_INIT; - strvec_split(&vec, "foo\n\t bar"); - check_strvec(&vec, "foo", "bar", NULL); - strvec_clear(&vec); -} - -static void t_detach(void) -{ - struct strvec vec = STRVEC_INIT; - const char **detached; - - strvec_push(&vec, "foo"); - - detached = strvec_detach(&vec); - check_str(detached[0], "foo"); - check_pointer_eq(detached[1], NULL); - - check_pointer_eq(vec.v, empty_strvec); - check_uint(vec.nr, ==, 0); - check_uint(vec.alloc, ==, 0); - - free((char *) detached[0]); - free(detached); -} - int cmd_main(int argc UNUSED, const char **argv UNUSED) { - TEST(t_static_init(), "static initialization"); - TEST(t_dynamic_init(), "dynamic initialization"); - TEST(t_clear(), "clear"); - TEST(t_push(), "push"); - TEST(t_pushf(), "pushf"); - TEST(t_pushl(), "pushl"); - TEST(t_pushv(), "pushv"); - TEST(t_replace_at_head(), "replace at head"); - TEST(t_replace_in_between(), "replace in between"); - TEST(t_replace_at_tail(), "replace at tail"); - TEST(t_replace_with_substring(), "replace with substring"); - TEST(t_remove_at_head(), "remove at head"); - TEST(t_remove_in_between(), "remove in between"); - TEST(t_remove_at_tail(), "remove at tail"); - TEST(t_pop_empty_array(), "pop with empty array"); - TEST(t_pop_non_empty_array(), "pop with non-empty array"); - TEST(t_split_empty_string(), "split empty string"); - TEST(t_split_single_item(), "split single item"); - TEST(t_split_multiple_items(), "split multiple items"); - TEST(t_split_whitespace_only(), "split whitespace only"); - TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces"); - TEST(t_detach(), "detach"); + if_test ("static initialization") { + struct strvec vec = STRVEC_INIT; + check_pointer_eq(vec.v, empty_strvec); + check_uint(vec.nr, ==, 0); + check_uint(vec.alloc, ==, 0); + } + + if_test ("dynamic initialization") { + struct strvec vec; + strvec_init(&vec); + check_pointer_eq(vec.v, empty_strvec); + check_uint(vec.nr, ==, 0); + check_uint(vec.alloc, ==, 0); + } + + if_test ("clear") { + struct strvec vec = STRVEC_INIT; + strvec_push(&vec, "foo"); + strvec_clear(&vec); + check_pointer_eq(vec.v, empty_strvec); + check_uint(vec.nr, ==, 0); + check_uint(vec.alloc, ==, 0); + } + + if_test ("push") { + struct strvec vec = STRVEC_INIT; + + strvec_push(&vec, "foo"); + check_strvec(&vec, "foo", NULL); + + strvec_push(&vec, "bar"); + check_strvec(&vec, "foo", "bar", NULL); + + strvec_clear(&vec); + } + + if_test ("pushf") { + struct strvec vec = STRVEC_INIT; + strvec_pushf(&vec, "foo: %d", 1); + check_strvec(&vec, "foo: 1", NULL); + strvec_clear(&vec); + } + + if_test ("pushl") { + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + check_strvec(&vec, "foo", "bar", "baz", NULL); + strvec_clear(&vec); + } + + if_test ("pushv") { + const char *strings[] = { + "foo", "bar", "baz", NULL, + }; + struct strvec vec = STRVEC_INIT; + + strvec_pushv(&vec, strings); + check_strvec(&vec, "foo", "bar", "baz", NULL); + + strvec_clear(&vec); + } + + if_test ("replace at head") { + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_replace(&vec, 0, "replaced"); + check_strvec(&vec, "replaced", "bar", "baz", NULL); + strvec_clear(&vec); + } + + if_test ("replace at tail") { + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_replace(&vec, 2, "replaced"); + check_strvec(&vec, "foo", "bar", "replaced", NULL); + strvec_clear(&vec); + } + + if_test ("replace in between") { + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_replace(&vec, 1, "replaced"); + check_strvec(&vec, "foo", "replaced", "baz", NULL); + strvec_clear(&vec); + } + + if_test ("replace with substring") { + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", NULL); + strvec_replace(&vec, 0, vec.v[0] + 1); + check_strvec(&vec, "oo", NULL); + strvec_clear(&vec); + } + + if_test ("remove at head") { + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_remove(&vec, 0); + check_strvec(&vec, "bar", "baz", NULL); + strvec_clear(&vec); + } + + if_test ("remove at tail") { + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_remove(&vec, 2); + check_strvec(&vec, "foo", "bar", NULL); + strvec_clear(&vec); + } + + if_test ("remove in between") { + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_remove(&vec, 1); + check_strvec(&vec, "foo", "baz", NULL); + strvec_clear(&vec); + } + + if_test ("pop with empty array") { + struct strvec vec = STRVEC_INIT; + strvec_pop(&vec); + check_strvec(&vec, NULL); + strvec_clear(&vec); + } + + if_test ("pop with non-empty array") { + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_pop(&vec); + check_strvec(&vec, "foo", "bar", NULL); + strvec_clear(&vec); + } + + if_test ("split empty string") { + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, ""); + check_strvec(&vec, NULL); + strvec_clear(&vec); + } + + if_test ("split single item") { + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, "foo"); + check_strvec(&vec, "foo", NULL); + strvec_clear(&vec); + } + + if_test ("split multiple items") { + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, "foo bar baz"); + check_strvec(&vec, "foo", "bar", "baz", NULL); + strvec_clear(&vec); + } + + if_test ("split whitespace only") { + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, " \t\n"); + check_strvec(&vec, NULL); + strvec_clear(&vec); + } + + if_test ("split multiple consecutive whitespaces") { + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, "foo\n\t bar"); + check_strvec(&vec, "foo", "bar", NULL); + strvec_clear(&vec); + } + + if_test ("detach") { + struct strvec vec = STRVEC_INIT; + const char **detached; + + strvec_push(&vec, "foo"); + + detached = strvec_detach(&vec); + check_str(detached[0], "foo"); + check_pointer_eq(detached[1], NULL); + + check_pointer_eq(vec.v, empty_strvec); + check_uint(vec.nr, ==, 0); + check_uint(vec.alloc, ==, 0); + + free((char *) detached[0]); + free(detached); + } + return test_done(); } diff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c index 3c513ce59a..fa1f95965c 100644 --- a/t/unit-tests/test-lib.c +++ b/t/unit-tests/test-lib.c @@ -16,6 +16,8 @@ static struct { unsigned running :1; unsigned skip_all :1; unsigned todo :1; + char location[100]; + char description[100]; } ctx = { .lazy_plan = 1, .result = RESULT_NONE, @@ -125,6 +127,8 @@ void test_plan(int count) int test_done(void) { + if (ctx.running && ctx.location[0] && ctx.description[0]) + test__run_end(1, ctx.location, "%s", ctx.description); assert(!ctx.running); if (ctx.lazy_plan) @@ -167,13 +171,38 @@ void test_skip_all(const char *format, ...) va_end(ap); } +void test__run_describe(const char *location, const char *format, ...) +{ + va_list ap; + int len; + + assert(ctx.running); + assert(!ctx.location[0]); + assert(!ctx.description[0]); + + xsnprintf(ctx.location, sizeof(ctx.location), "%s", + make_relative(location)); + + va_start(ap, format); + len = vsnprintf(ctx.description, sizeof(ctx.description), format, ap); + va_end(ap); + if (len < 0) + die("unable to format message: %s", format); + if (len >= sizeof(ctx.description)) + BUG("ctx.description too small to format %s", format); +} + int test__run_begin(void) { + if (ctx.running && ctx.location[0] && ctx.description[0]) + test__run_end(1, ctx.location, "%s", ctx.description); assert(!ctx.running); ctx.count++; ctx.result = RESULT_NONE; ctx.running = 1; + ctx.location[0] = '\0'; + ctx.description[0] = '\0'; return ctx.skip_all; } @@ -264,7 +293,12 @@ static void test_todo(void) int test_assert(const char *location, const char *check, int ok) { - assert(ctx.running); + if (!ctx.running) { + test_msg("BUG: check outside of test at %s", + make_relative(location)); + ctx.failed = 1; + return 0; + } if (ctx.result == RESULT_SKIP) { test_msg("skipping check '%s' at %s", check, diff --git a/t/unit-tests/test-lib.h b/t/unit-tests/test-lib.h index c59f646fd9..e4b234697f 100644 --- a/t/unit-tests/test-lib.h +++ b/t/unit-tests/test-lib.h @@ -15,6 +15,23 @@ TEST_LOCATION(), __VA_ARGS__) /* + * Run a test unless test_skip_all() has been called. Acts like a + * conditional; the test body is expected as a statement or block after + * the closing parenthesis. The description for each test should be + * unique. E.g.: + * + * if_test ("something else %d %d", arg1, arg2) { + * prepare(); + * test_something_else(arg1, arg2); + * cleanup(); + * } + */ +#define if_test(...) \ + if (test__run_begin() ? \ + (test__run_end(0, TEST_LOCATION(), __VA_ARGS__), 0) : \ + (test__run_describe(TEST_LOCATION(), __VA_ARGS__), 1)) + +/* * Print a test plan, should be called before any tests. If the number * of tests is not known in advance test_done() will automatically * print a plan at the end of the test program. @@ -154,6 +171,9 @@ union test__tmp { extern union test__tmp test__tmp[2]; +__attribute__((format (printf, 2, 3))) +void test__run_describe(const char *, const char *, ...); + int test__run_begin(void); __attribute__((format (printf, 3, 4))) int test__run_end(int, const char *, const char *, ...); |
