From: Kirill Isakov Date: Wed, 23 Mar 2022 11:41:31 +0000 (+0600) Subject: Add unit tests suite using cmocka library X-Git-Url: https://www.tinc-vpn.org/git/browse?p=tinc;a=commitdiff_plain;h=6565c9690d7325537eb0d3f7c298037ca1843737 Add unit tests suite using cmocka library --- diff --git a/src/include/meson.build b/src/include/meson.build index 55da167b..f6f4e043 100644 --- a/src/include/meson.build +++ b/src/include/meson.build @@ -1,6 +1,6 @@ configure_file(output: 'config.h', configuration: cdata) -src_lib_tinc += vcs_tag( +src_lib_common += vcs_tag( command: './git_tag.sh', fallback: 'unknown', input: '../version_git.h.in', diff --git a/src/meson.build b/src/meson.build index c9f5d594..3eb4bc45 100644 --- a/src/meson.build +++ b/src/meson.build @@ -79,7 +79,7 @@ check_types = [ subdir('ed25519') subdir('chacha-poly1305') -src_lib_tinc = [ +src_lib_common = [ 'conf.c', 'dropin.c', 'keys.c', @@ -101,7 +101,6 @@ src_tinc = [ 'ifconfig.c', 'info.c', 'invitation.c', - 'tincctl.c', 'top.c', ] @@ -135,7 +134,6 @@ src_tincd = [ 'raw_socket_device.c', 'route.c', 'subnet.c', - 'tincd.c', ] cc_flags_tincd = cc_flags @@ -198,7 +196,7 @@ foreach type : check_types endforeach if not cdata.has('HAVE_GETOPT_H') or not cc.has_function('getopt_long', prefix: have_prefix, args: cc_defs) - src_lib_tinc += ['getopt.c', 'getopt1.c'] + src_lib_common += ['getopt.c', 'getopt1.c'] endif if not opt_miniupnpc.disabled() @@ -326,13 +324,36 @@ lib_crypto = static_library( build_by_default: false, ) -deps_lib_tinc = [deps_common, dep_crypto] +deps_lib_common = [deps_common, dep_crypto] +deps_tinc += deps_lib_common +deps_tincd += deps_lib_common + +lib_common = static_library( + 'common', + sources: src_lib_common, + dependencies: deps_lib_common, + link_with: [lib_ed25519, lib_chacha_poly, lib_crypto], + implicit_include_directories: false, + include_directories: inc_conf, + build_by_default: false, +) lib_tinc = static_library( 'tinc', - sources: src_lib_tinc, - dependencies: deps_lib_tinc, - link_with: [lib_ed25519, lib_chacha_poly, lib_crypto], + sources: src_tinc, + dependencies: deps_tinc, + link_with: lib_common, + implicit_include_directories: false, + include_directories: inc_conf, + build_by_default: false, +) + +lib_tincd = static_library( + 'tincd', + sources: src_tincd, + dependencies: deps_tincd, + link_with: lib_common, + c_args: cc_flags_tincd, implicit_include_directories: false, include_directories: inc_conf, build_by_default: false, @@ -340,8 +361,8 @@ lib_tinc = static_library( exe_tinc = executable( 'tinc', - sources: src_tinc, - dependencies: [deps_lib_tinc, deps_tinc], + sources: 'tincctl.c', + dependencies: deps_tinc, link_with: lib_tinc, implicit_include_directories: false, include_directories: inc_conf, @@ -351,9 +372,9 @@ exe_tinc = executable( exe_tincd = executable( 'tincd', - sources: src_tincd, - dependencies: [deps_lib_tinc, deps_tincd], - link_with: lib_tinc, + sources: 'tincd.c', + dependencies: deps_tincd, + link_with: lib_tincd, c_args: cc_flags_tincd, implicit_include_directories: false, include_directories: inc_conf, @@ -364,8 +385,8 @@ exe_tincd = executable( exe_sptps_test = executable( 'sptps_test', sources: 'sptps_test.c', - dependencies: deps_lib_tinc, - link_with: lib_tinc, + dependencies: deps_lib_common, + link_with: lib_common, implicit_include_directories: false, include_directories: inc_conf, build_by_default: false, @@ -374,8 +395,8 @@ exe_sptps_test = executable( exe_sptps_keypair = executable( 'sptps_keypair', sources: 'sptps_keypair.c', - dependencies: deps_lib_tinc, - link_with: lib_tinc, + dependencies: deps_lib_common, + link_with: lib_common, implicit_include_directories: false, include_directories: inc_conf, build_by_default: false, @@ -387,8 +408,8 @@ if os_name == 'linux' exe_sptps_speed = executable( 'sptps_speed', sources: 'sptps_speed.c', - dependencies: [deps_lib_tinc, dep_rt], - link_with: lib_tinc, + dependencies: [deps_lib_common, dep_rt], + link_with: lib_common, implicit_include_directories: false, include_directories: inc_conf, build_by_default: false, diff --git a/test/meson.build b/test/meson.build index db719a2f..48e66898 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,2 +1,3 @@ subdir('integration') +subdir('unit') diff --git a/test/unit/meson.build b/test/unit/meson.build new file mode 100644 index 00000000..120d527a --- /dev/null +++ b/test/unit/meson.build @@ -0,0 +1,66 @@ +dep_cmocka = dependency('cmocka', required: opt_tests) +if not dep_cmocka.found() + subdir_done() +endif + +can_wrap = cc.has_link_argument('-Wl,--wrap=func') +if not can_wrap + message('linker has no support for function wrapping, mocked tests will not run') +endif + +link_tinc = { 'lib': lib_tinc, 'dep': deps_tinc } +link_tincd = { 'lib': lib_tincd, 'dep': deps_tincd } + +# Test definition format: +# +# 'free-form test name': { +# 'code': 'test1.c', // or ['test1.c', 'test1_util.c'] +# 'mock': ['foo', 'bar'], // list of functions to mock (default: empty) +# 'link': link_tinc, // which binary to link with (default: tincd) +# } + +tests = { + 'net': { + 'code': 'test_net.c', + 'mock': ['execute_script', 'environment_init', 'environment_exit'], + }, + 'subnet': { + 'code': 'test_subnet.c', + }, + 'splay_tree': { + 'code': 'test_splay_tree.c', + 'link': link_tinc, + }, +} + +env = ['CMOCKA_MESSAGE_OUTPUT=TAP'] + +foreach test, data : tests + args = ld_flags + + if can_wrap + mocks = data.get('mock', []) + if mocks.length() > 0 + args += ',--wrap='.join(['-Wl'] + mocks) + endif + endif + + libs = data.get('link', link_tincd) + + exe = executable(test, + sources: data['code'], + link_args: args, + dependencies: [libs['dep'], dep_cmocka], + link_with: libs['lib'], + implicit_include_directories: false, + include_directories: inc_conf, + build_by_default: false) + + test(test, + exe, + suite: 'unit', + timeout: 60, + protocol: 'tap', + env: env) +endforeach + diff --git a/test/unit/test_net.c b/test/unit/test_net.c new file mode 100644 index 00000000..5901fc71 --- /dev/null +++ b/test/unit/test_net.c @@ -0,0 +1,58 @@ +#include "unittest.h" +#include "../../src/net.h" +#include "../../src/script.h" + +static environment_t *device_env = NULL; + +// silence -Wmissing-prototypes +void __wrap_environment_init(environment_t *env); +void __wrap_environment_exit(environment_t *env); +bool __wrap_execute_script(const char *name, environment_t *env); + +void __wrap_environment_init(environment_t *env) { + assert_non_null(env); + assert_null(device_env); + device_env = env; +} + +void __wrap_environment_exit(environment_t *env) { + assert_ptr_equal(device_env, env); + device_env = NULL; +} + +bool __wrap_execute_script(const char *name, environment_t *env) { + (void)env; + + check_expected_ptr(name); + + // Used instead of mock_type(bool) to silence clang warning + return mock() ? true : false; +} + +static void run_device_enable_disable(void (*device_func)(void), + const char *script) { + expect_string(__wrap_execute_script, name, script); + will_return(__wrap_execute_script, true); + + device_func(); +} + +static void test_device_enable_calls_tinc_up(void **state) { + (void)state; + + run_device_enable_disable(&device_enable, "tinc-up"); +} + +static void test_device_disable_calls_tinc_down(void **state) { + (void)state; + + run_device_enable_disable(&device_disable, "tinc-down"); +} + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_device_enable_calls_tinc_up), + cmocka_unit_test(test_device_disable_calls_tinc_down), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/test/unit/test_splay_tree.c b/test/unit/test_splay_tree.c new file mode 100644 index 00000000..dfeaaba6 --- /dev/null +++ b/test/unit/test_splay_tree.c @@ -0,0 +1,270 @@ +#include "unittest.h" +#include "../../src/splay_tree.h" + +typedef struct node_t { + int id; +} node_t; + +// We cannot use test_malloc / test_free here, because the library seems to be +// checking for leaks right after running each test, before doing teardown, +// which results in a bunch of spurious test failures. We rely on teardown to +// clean up after us. Valgrind and ASAN show no leaks. +static node_t *create_node(int id) { + node_t *node = malloc(sizeof(node_t)); + node->id = id; + return node; +} + +static void free_node(node_t *node) { + free(node); +} + +static int node_compare(const node_t *lhs, const node_t *rhs) { + return lhs->id - rhs->id; +} + +static int test_setup(void **state) { + splay_tree_t *tree = splay_alloc_tree((splay_compare_t) node_compare, (splay_action_t) free_node); + + if(!tree) { + return -1; + } + + *state = tree; + return 0; +} + +static int test_teardown(void **state) { + splay_delete_tree(*state); + return 0; +} + +static void test_tree_allocation_deletion(void **state) { + (void)state; + + splay_tree_t *tree = splay_alloc_tree((splay_compare_t) node_compare, + (splay_action_t) free_node); + assert_non_null(tree); + + node_t *one = create_node(1); + assert_non_null(splay_insert(tree, one)); + + node_t *two = create_node(2); + assert_non_null(splay_insert(tree, two)); + + // AddressSanitizer will notify us if there's a leak + splay_delete_tree(tree); +} + +static int multiply_tree_node_calls = 0; + +static void increment_id_tree_node(node_t *node) { + ++node->id; + ++multiply_tree_node_calls; +} + +static int multiply_splay_node_calls = 0; + +static void multiply_id_splay_node(splay_node_t *node) { + node_t *t = node->data; + t->id *= 2; + ++multiply_splay_node_calls; +} + +static void test_splay_foreach(void **state) { + splay_tree_t *tree = *state; + + node_t *one = create_node(1); + splay_node_t *node_one = splay_insert(tree, one); + assert_ptr_equal(one, node_one->data); + + node_t *two = create_node(5); + splay_node_t *node_two = splay_insert(tree, two); + assert_ptr_equal(two, node_two->data); + + splay_foreach(tree, (splay_action_t) increment_id_tree_node); + assert_int_equal(2, one->id); + assert_int_equal(6, two->id); + + splay_foreach_node(tree, (splay_action_t) multiply_id_splay_node); + assert_int_equal(4, one->id); + assert_int_equal(12, two->id); + + assert_int_equal(2, multiply_tree_node_calls); + assert_int_equal(2, multiply_splay_node_calls); +} + +static void test_splay_each(void **state) { + splay_tree_t *tree = *state; + + node_t *one = create_node(1); + node_t *two = create_node(2); + + splay_insert(tree, one); + splay_insert(tree, two); + + // splay_each should iterate over all nodes + for splay_each(node_t, n, tree) { + n->id = -n->id; + } + + assert_int_equal(-1, one->id); + assert_int_equal(-2, two->id); + + // splay_each should allow removal of the current node + for splay_each(node_t, n, tree) { + splay_delete(tree, n); + } +} + +static void test_splay_basic_ops(void **state) { + splay_tree_t *tree = *state; + node_t *node = create_node(1); + + // Should not find anything if the tree is empty + node_t *found_one = splay_search(tree, node); + assert_null(found_one); + + // Insertion should return a non-NULL node with `data` pointing to our `tree_node` + splay_node_t *node_one = splay_insert(tree, node); + assert_ptr_equal(node, node_one->data); + + // Should find after insertion + found_one = splay_search(tree, node); + assert_ptr_equal(node, found_one); +} + +static void test_splay_insert_before_after(void **state) { + splay_tree_t *tree = *state; + + node_t *one = create_node(1); + splay_node_t *node_one = splay_insert(tree, one); + assert_non_null(node_one); + + // splay_insert_before should set up `prev` and `next` pointers + splay_node_t *node_two = splay_alloc_node(); + assert_non_null(node_two); + node_two->data = create_node(2); + + splay_insert_after(tree, node_one, node_two); + assert_null(node_one->prev); + assert_ptr_equal(node_one->next, node_two); + assert_ptr_equal(node_two->prev, node_one); + assert_null(node_two->next); + + splay_node_t *node_thr = splay_alloc_node(); + assert_non_null(node_thr); + node_thr->data = create_node(3); + + splay_insert_after(tree, node_two, node_thr); + assert_null(node_one->prev); + assert_ptr_equal(node_one->next, node_two); + assert_ptr_equal(node_two->prev, node_one); + assert_ptr_equal(node_two->next, node_thr); + assert_ptr_equal(node_thr->prev, node_two); + assert_null(node_thr->next); +} + +static void test_search_node(void **state) { + splay_tree_t *tree = *state; + + node_t *one = create_node(1); + node_t *two = create_node(2); + + splay_node_t *one_node = splay_search_node(tree, one); + assert_null(one_node); + + one_node = splay_insert(tree, one); + assert_ptr_equal(one_node, splay_search_node(tree, one)); + + splay_node_t *two_node = splay_search_node(tree, two); + assert_null(two_node); + + two_node = splay_insert(tree, two); + assert_ptr_equal(one_node, splay_search_node(tree, one)); + assert_ptr_equal(two_node, splay_search_node(tree, two)); + + node_t *copy_one = create_node(1); + node_t *copy_two = create_node(2); + + splay_delete(tree, one); + assert_null(splay_search_node(tree, copy_one)); + assert_ptr_equal(two_node, splay_search_node(tree, two)); + + splay_delete(tree, two); + assert_null(splay_search_node(tree, copy_one)); + assert_null(splay_search_node(tree, copy_two)); + + free_node(copy_one); + free_node(copy_two); +} + +static void test_unlink(void **state) { + splay_tree_t *tree = *state; + node_t *one = create_node(1); + + splay_node_t *node_one = splay_insert(tree, one); + + // Unlink should return the unlinked node + splay_node_t *unlinked_one = splay_unlink(tree, one); + assert_ptr_equal(one, unlinked_one->data); + + // Unlinking the same node should return NULL + assert_null(splay_unlink(tree, one)); + + // Inserting it back should return the same node + unlinked_one = splay_insert_node(tree, unlinked_one); + assert_ptr_equal(node_one, unlinked_one); +} + +static void test_unlink_node(void **state) { + splay_tree_t *tree = *state; + node_t *one = create_node(1); + + splay_node_t *node_one = splay_insert(tree, one); + assert_ptr_equal(one, node_one->data); + assert_ptr_equal(one, splay_search(tree, one)); + assert_ptr_equal(node_one, splay_search_node(tree, one)); + + splay_unlink_node(tree, node_one); + assert_null(splay_search(tree, one)); + assert_null(splay_search_node(tree, one)); + + splay_free_node(tree, node_one); +} + +static void test_delete_node(void **state) { + splay_tree_t *tree = *state; + node_t *one = create_node(1); + + splay_node_t *node_one = splay_insert(tree, one); + assert_ptr_equal(one, node_one->data); + assert_ptr_equal(one, splay_search(tree, one)); + assert_ptr_equal(node_one, splay_search_node(tree, one)); + + node_t *copy = create_node(1); + assert_ptr_equal(one, splay_search(tree, copy)); + + splay_delete_node(tree, node_one); + assert_null(splay_search(tree, copy)); + + free_node(copy); +} + +#define test_with_state(test_func) \ + cmocka_unit_test_setup_teardown((test_func), test_setup, test_teardown) + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_tree_allocation_deletion), + test_with_state(test_splay_basic_ops), + test_with_state(test_splay_insert_before_after), + test_with_state(test_splay_foreach), + test_with_state(test_splay_each), + test_with_state(test_search_node), + test_with_state(test_unlink), + test_with_state(test_unlink_node), + test_with_state(test_delete_node), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/test/unit/test_subnet.c b/test/unit/test_subnet.c new file mode 100644 index 00000000..a3621647 --- /dev/null +++ b/test/unit/test_subnet.c @@ -0,0 +1,551 @@ +#include "unittest.h" +#include "../../src/subnet.h" + +typedef struct net_str_testcase { + const char *text; + subnet_t data; +} net_str_testcase; + +static void test_subnet_compare_different_types(void **state) { + (void)state; + + const subnet_t ipv4 = {.type = SUBNET_IPV4}; + const subnet_t ipv6 = {.type = SUBNET_IPV6}; + const subnet_t mac = {.type = SUBNET_MAC}; + + assert_int_not_equal(0, subnet_compare(&ipv4, &ipv6)); + assert_int_not_equal(0, subnet_compare(&ipv4, &mac)); + assert_int_not_equal(0, subnet_compare(&ipv6, &mac)); +} + +static const mac_t mac1 = {{0x00, 0x01, 0x02, 0x03, 0x04, 0x05}}; +static const mac_t mac2 = {{0x42, 0x01, 0x02, 0x03, 0x04, 0x05}}; + +static const subnet_ipv4_t ipv4_1 = {.address = {{0x01, 0x02, 0x03, 0x04}}, .prefixlength = 24}; +static const subnet_ipv4_t ipv4_1_pref = {.address = {{0x01, 0x02, 0x03, 0x04}}, .prefixlength = 16}; +static const subnet_ipv4_t ipv4_2 = {.address = {{0x11, 0x22, 0x33, 0x44}}, .prefixlength = 16}; + +static const subnet_ipv6_t ipv6_1 = { + .address = {{0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04}}, + .prefixlength = 24 +}; + +static const subnet_ipv6_t ipv6_1_pref = { + .address = {{0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04}}, + .prefixlength = 16 +}; + +static const subnet_ipv6_t ipv6_2 = { + .address = {{0x11, 0x22, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04}}, + .prefixlength = 24 +}; + +static void test_maskcmp(void **state) { + (void)state; + + const ipv4_t a = {{1, 2, 3, 4}}; + const ipv4_t b = {{1, 2, 3, 0xff}}; + + for(int mask = 0; mask <= 24; ++mask) { + assert_int_equal(0, maskcmp(&a, &b, mask)); + } + + for(int mask = 25; mask <= 32; ++mask) { + assert_true(maskcmp(&a, &b, mask) != 0); + } +} + +static void test_mask(void **state) { + (void)state; + + ipv4_t dst = {{0xff, 0xff, 0xff, 0xff}}; + mask(&dst, 23, sizeof(dst)); + + const ipv4_t ref = {{0xff, 0xff, 0xfe, 0x00}}; + assert_memory_equal(&ref, &dst, sizeof(dst)); +} + +static void test_maskcpy(void **state) { + (void)state; + + const ipv4_t src = {{0xff, 0xff, 0xff, 0xff}}; + const ipv4_t ref = {{0xff, 0xff, 0xfe, 0x00}}; + ipv4_t dst; + + maskcpy(&dst, &src, 23, sizeof(src)); + + assert_memory_equal(&ref, &dst, sizeof(dst)); +} + +static void test_subnet_compare_mac_eq(void **state) { + (void)state; + + node_t owner = {.name = strdup("foobar")}; + const subnet_t a = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 42, .owner = &owner}; + const subnet_t b = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 42, .owner = &owner}; + + assert_int_equal(0, subnet_compare(&a, &a)); + assert_int_equal(0, subnet_compare(&a, &b)); + assert_int_equal(0, subnet_compare(&b, &a)); + + free(owner.name); +} + +static void test_subnet_compare_mac_neq_address(void **state) { + (void)state; + + node_t owner = {.name = strdup("foobar")}; + const subnet_t a = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 10, .owner = &owner}; + const subnet_t b = {.type = SUBNET_MAC, .net.mac.address = mac2, .weight = 10, .owner = &owner}; + + assert_true(subnet_compare(&a, &b) < 0); + assert_true(subnet_compare(&b, &a) > 0); + + free(owner.name); +} + +static void test_subnet_compare_mac_weight(void **state) { + (void)state; + + node_t owner = {.name = strdup("foobar")}; + const subnet_t a = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 42, .owner = &owner}; + const subnet_t b = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 42, .owner = &owner}; + const subnet_t c = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 10, .owner = &owner}; + + assert_int_equal(0, subnet_compare(&a, &a)); + assert_int_equal(0, subnet_compare(&a, &b)); + assert_int_equal(0, subnet_compare(&b, &a)); + + assert_true(subnet_compare(&a, &c) > 0); + assert_true(subnet_compare(&c, &a) < 0); + + free(owner.name); +} + +static void test_subnet_compare_mac_owners(void **state) { + (void)state; + + node_t foo = {.name = strdup("foo")}; + node_t bar = {.name = strdup("bar")}; + + const subnet_t a = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 42, .owner = &foo}; + const subnet_t b = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 42, .owner = &bar}; + + assert_int_equal(0, subnet_compare(&a, &a)); + assert_int_equal(0, subnet_compare(&b, &b)); + + assert_true(subnet_compare(&a, &b) > 0); + assert_true(subnet_compare(&b, &a) < 0); + + free(foo.name); + free(bar.name); +} + + +static void test_subnet_compare_ipv4_eq(void **state) { + (void)state; + + const subnet_t a = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1}; + const subnet_t b = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1}; + + assert_int_equal(0, subnet_compare(&a, &b)); + assert_int_equal(0, subnet_compare(&b, &a)); +} + +static void test_subnet_compare_ipv4_neq(void **state) { + (void)state; + + const subnet_t a = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1}; + const subnet_t b = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1_pref}; + const subnet_t c = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_2}; + + assert_true(subnet_compare(&a, &b) < 0); + assert_true(subnet_compare(&b, &a) > 0); + + assert_true(subnet_compare(&a, &c) < 0); + assert_true(subnet_compare(&b, &c) < 0); +} + +static void test_subnet_compare_ipv4_weight(void **state) { + (void)state; + + const subnet_t a = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1, .weight = 1}; + const subnet_t b = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1, .weight = 2}; + + assert_true(subnet_compare(&a, &b) < 0); +} + +static void test_subnet_compare_ipv4_owners(void **state) { + (void)state; + + node_t foo = {.name = strdup("foo")}; + node_t bar = {.name = strdup("bar")}; + + const subnet_t a = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1, .owner = &foo}; + const subnet_t b = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1, .owner = &foo}; + const subnet_t c = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1, .owner = &bar}; + + assert_int_equal(0, subnet_compare(&a, &b)); + assert_true(subnet_compare(&a, &c) > 0); + + free(foo.name); + free(bar.name); +} + +static void test_subnet_compare_ipv6_eq(void **state) { + (void)state; + + const subnet_t a = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1}; + const subnet_t b = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1}; + + assert_int_equal(0, subnet_compare(&a, &b)); + assert_int_equal(0, subnet_compare(&b, &a)); +} + +static void test_subnet_compare_ipv6_neq(void **state) { + (void)state; + + const subnet_t a = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1}; + const subnet_t b = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1_pref}; + const subnet_t c = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_2}; + + assert_true(subnet_compare(&a, &b) < 0); + assert_true(subnet_compare(&b, &a) > 0); + + assert_true(subnet_compare(&a, &c) < 0); + assert_true(subnet_compare(&b, &c) > 0); +} + +static void test_subnet_compare_ipv6_weight(void **state) { + (void)state; + + const subnet_t a = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1, .weight = 1}; + const subnet_t b = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1, .weight = 2}; + + assert_true(subnet_compare(&a, &b) < 0); +} + +static void test_subnet_compare_ipv6_owners(void **state) { + (void)state; + + node_t foo = {.name = strdup("foo")}; + node_t bar = {.name = strdup("bar")}; + + const subnet_t a = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1, .owner = &foo}; + const subnet_t b = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1, .owner = &foo}; + const subnet_t c = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1, .owner = &bar}; + + assert_int_equal(0, subnet_compare(&a, &b)); + assert_true(subnet_compare(&a, &c) > 0); + + free(foo.name); + free(bar.name); +} + +static void test_str2net_valid(void **state) { + (void)state; + + const net_str_testcase testcases[] = { + { + .text = "1.2.3.0/24#42", + .data = { + .type = SUBNET_IPV4, + .weight = 42, + .net = { + .ipv4 = { + .address = { + .x = {1, 2, 3, 0} + }, + .prefixlength = 24, + }, + }, + }, + }, + { + .text = "04fb:7deb:78db:1950:2d21:258d:40b6:f0d7/128#999", + .data = { + .type = SUBNET_IPV6, + .weight = 999, + .net = { + .ipv6 = { + .address = { + .x = { + htons(0x04fb), htons(0x7deb), htons(0x78db), htons(0x1950), + htons(0x2d21), htons(0x258d), htons(0x40b6), htons(0xf0d7), + } + }, + .prefixlength = 128, + }, + }, + }, + }, + { + .text = "fe80::16dd:a9ff:fe7e:b4c2/64", + .data = { + .type = SUBNET_IPV6, + .weight = 10, + .net = { + .ipv6 = { + .address = { + .x = { + htons(0xfe80), htons(0x0000), htons(0x0000), htons(0x0000), + htons(0x16dd), htons(0xa9ff), htons(0xfe7e), htons(0xb4c2), + } + }, + .prefixlength = 64, + }, + }, + }, + }, + { + .text = "57:04:13:01:f9:26#60", + .data = { + .type = SUBNET_MAC, + .weight = 60, + .net = { + .mac = { + .address = { + .x = {0x57, 0x04, 0x13, 0x01, 0xf9, 0x26}, + }, + }, + }, + }, + }, + }; + + for(size_t i = 0; i < sizeof(testcases) / sizeof(*testcases); ++i) { + const char *text = testcases[i].text; + const subnet_t *ref = &testcases[i].data; + + subnet_t sub = {0}; + bool ok = str2net(&sub, text); + + // Split into separate assertions for more clear failures + assert_true(ok); + assert_int_equal(ref->type, sub.type); + assert_int_equal(ref->weight, sub.weight); + + switch(ref->type) { + case SUBNET_MAC: + assert_memory_equal(&ref->net.mac.address, &sub.net.mac.address, sizeof(mac_t)); + break; + + case SUBNET_IPV4: + assert_int_equal(ref->net.ipv4.prefixlength, sub.net.ipv4.prefixlength); + assert_memory_equal(&ref->net.ipv4.address, &sub.net.ipv4.address, sizeof(ipv4_t)); + break; + + case SUBNET_IPV6: + assert_int_equal(ref->net.ipv6.prefixlength, sub.net.ipv6.prefixlength); + assert_memory_equal(&ref->net.ipv6.address, &sub.net.ipv6.address, sizeof(ipv6_t)); + break; + + default: + fail_msg("unknown subnet type %d", ref->type); + } + } +} + +static void test_str2net_invalid(void **state) { + (void)state; + + subnet_t sub = {0}; + + const char *test_cases[] = { + // Overflow + "1.2.256.0", + + // Invalid mask + "1.2.3.0/", + "1.2.3.0/42", + "1.2.3.0/MASK", + "fe80::/129", + "fe80::/MASK", + "cb:0c:1b:60:ed:7a/1", + + // Invalid weight + "1.2.3.4#WEIGHT", + "1.2.0.0/16#WEIGHT", + "1.2.0.0/16#", + "feff::/16#", + "feff::/16#w", + + NULL, + }; + + for(const char **str = test_cases; *str; ++str) { + bool ok = str2net(&sub, *str); + assert_false(ok); + } +} + +static void test_net2str_valid(void **state) { + (void)state; + + const net_str_testcase testcases[] = { + { + .text = "12:fe:ff:3a:28:90#42", + .data = { + .type = SUBNET_MAC, + .weight = 42, + .net = { + .mac = { + .address = { + .x = {0x12, 0xfe, 0xff, 0x3a, 0x28, 0x90} + }, + }, + }, + }, + }, + { + .text = "1.2.3.4", + .data = { + .type = SUBNET_IPV4, + .weight = 10, + .net = { + .ipv4 = { + .address = { + .x = {1, 2, 3, 4} + }, + .prefixlength = 32, + }, + }, + }, + }, + { + .text = "181.35.16.0/27#1", + .data = { + .type = SUBNET_IPV4, + .weight = 1, + .net = { + .ipv4 = { + .address = { + .x = {181, 35, 16, 0} + }, + .prefixlength = 27, + }, + }, + }, + }, + { + .text = "5fbf:5cfe:0:fdd2:fd76::/96#900", + .data = { + .type = SUBNET_IPV6, + .weight = 900, + .net = { + .ipv6 = { + .address = { + .x = { + htons(0x5fbf), htons(0x5cfe), htons(0x0000), htons(0xfdd2), + htons(0xfd76), htons(0x0000), htons(0x0000), htons(0x0000), + }, + }, + .prefixlength = 96, + }, + }, + }, + }, + }; + + for(size_t i = 0; i < sizeof(testcases) / sizeof(*testcases); ++i) { + const char *text = testcases[i].text; + const subnet_t *ref = &testcases[i].data; + + char buf[256]; + bool ok = net2str(buf, sizeof(buf), ref); + + assert_true(ok); + assert_string_equal(text, buf); + } +} + +static void test_net2str_invalid(void **state) { + (void)state; + + const subnet_t sub = {0}; + char buf[256]; + assert_false(net2str(NULL, sizeof(buf), &sub)); + assert_false(net2str(buf, sizeof(buf), NULL)); +} + +static void test_maskcheck_valid_ipv4(void **state) { + (void)state; + + const ipv4_t a = {{10, 0, 0, 0}}; + const ipv4_t b = {{192, 168, 0, 0}}; + const ipv4_t c = {{192, 168, 24, 0}}; + + assert_true(maskcheck(&a, 8, sizeof(a))); + assert_true(maskcheck(&b, 16, sizeof(b))); + assert_true(maskcheck(&c, 24, sizeof(c))); +} + +static void test_maskcheck_valid_ipv6(void **state) { + (void)state; + + const ipv6_t a = {{10, 0, 0, 0, 0, 0, 0, 0}}; + assert_true(maskcheck(&a, 8, sizeof(a))); + + const ipv6_t b = {{10, 20, 0, 0, 0, 0, 0, 0}}; + assert_true(maskcheck(&b, 32, sizeof(b))); + + const ipv6_t c = {{192, 168, 24, 0, 0, 0, 0, 0}}; + assert_true(maskcheck(&c, 48, sizeof(c))); +} + +static void test_maskcheck_invalid_ipv4(void **state) { + (void)state; + + const ipv4_t a = {{10, 20, 0, 0}}; + const ipv4_t b = {{10, 20, 30, 0}}; + + assert_false(maskcheck(&a, 8, sizeof(a))); + assert_false(maskcheck(&b, 16, sizeof(b))); +} + +static void test_maskcheck_invalid_ipv6(void **state) { + (void)state; + + const ipv6_t a = {{1, 2, 3, 4, 5, 6, 7, 0xAABB}}; + + for(int mask = 0; mask < 128; mask += 8) { + assert_false(maskcheck(&a, mask, sizeof(a))); + } +} + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_maskcmp), + cmocka_unit_test(test_mask), + cmocka_unit_test(test_maskcpy), + + cmocka_unit_test(test_subnet_compare_different_types), + + cmocka_unit_test(test_subnet_compare_mac_eq), + cmocka_unit_test(test_subnet_compare_mac_neq_address), + cmocka_unit_test(test_subnet_compare_mac_weight), + cmocka_unit_test(test_subnet_compare_mac_owners), + + cmocka_unit_test(test_subnet_compare_ipv4_eq), + cmocka_unit_test(test_subnet_compare_ipv4_neq), + cmocka_unit_test(test_subnet_compare_ipv4_weight), + cmocka_unit_test(test_subnet_compare_ipv4_owners), + + cmocka_unit_test(test_subnet_compare_ipv6_eq), + cmocka_unit_test(test_subnet_compare_ipv6_neq), + cmocka_unit_test(test_subnet_compare_ipv6_weight), + cmocka_unit_test(test_subnet_compare_ipv6_owners), + + cmocka_unit_test(test_str2net_valid), + cmocka_unit_test(test_str2net_invalid), + + cmocka_unit_test(test_net2str_valid), + cmocka_unit_test(test_net2str_invalid), + + cmocka_unit_test(test_maskcheck_valid_ipv4), + cmocka_unit_test(test_maskcheck_valid_ipv6), + cmocka_unit_test(test_maskcheck_invalid_ipv4), + cmocka_unit_test(test_maskcheck_invalid_ipv6), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/test/unit/unittest.h b/test/unit/unittest.h new file mode 100644 index 00000000..e96cb049 --- /dev/null +++ b/test/unit/unittest.h @@ -0,0 +1,11 @@ +#ifndef TINC_UNITTEST_H +#define TINC_UNITTEST_H + +#include +#include +#include +#include +#include +#include "../../src/system.h" + +#endif // TINC_UNITTEST_H