diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 449be5384..e9a1324ab 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,7 +19,8 @@ docker-image: stage: prepare # Must match the docker version on the build machine! before_script: - - git submodule update --init --recursive + - git submodule sync --recursive + - git submodule update --recursive --init - docker info script: - docker build -t $DOCKER_REGISTRY/$DOCKER_IMAGE . @@ -78,8 +79,7 @@ unit: dependencies: - build script: - - make tests - - build/release/testsuite + - make run-tests image: $DOCKER_REGISTRY/$DOCKER_IMAGE tags: - docker diff --git a/.gitmodules b/.gitmodules index a61e863eb..79fac9332 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,7 +9,7 @@ url = https://github.com/warmcat/libwebsockets [submodule "thirdparty/criterion"] path = thirdparty/criterion - url = https://github.com/stv0g/Criterion + url = https://github.com/Snaipe/Criterion [submodule "thirdparty/libnl"] path = thirdparty/libnl url = https://github.com/thom311/libnl.git diff --git a/config.h b/config.h index 2ca343bbe..087f7b3f9 100644 --- a/config.h +++ b/config.h @@ -24,9 +24,6 @@ #define DEFAULT_VALUES 64 #define DEFAULT_QUEUELEN 1024 -/** Whether or not to send / receive timestamp & sequence number as first values of payload */ -#define GTNET_SKT_HEADER 1 - /** Width of log output in characters */ #define LOG_WIDTH 132 diff --git a/etc/example.conf b/etc/example.conf index 90ce35282..7f5ce6753 100644 --- a/etc/example.conf +++ b/etc/example.conf @@ -26,16 +26,24 @@ nodes = { ### The following settings are specific to the socket node-type!! ### - layer = "udp" # Layer can be one of: - # udp Send / recv UDP packets - # ip Send / recv IP packets - # eth Send / recv raw Ethernet frames (IEEE802.3) + layer = "udp", # Layer can be one of: + # udp Send / receive UDP packets + # ip Send / receive IP packets + # eth Send / receive raw Ethernet frames (IEEE802.3) + + + header = "gtnet-skt:fake", # Header can be one of: + # default | villas Use VILLASnode protocol (see struct msg) (default) + # none | gtnet-skt Use no header, send raw data as used by RTDS GTNETv2-SKT + # fake | gtnet-skt:fake Same as 'none', but use first three data values as + # sequence, seconds & nanoseconds timestamp + # In this mode values are uint32_t not floats! local = "127.0.0.1:12001", # This node only received messages on this IP:Port pair remote = "127.0.0.1:12000" # This node sents outgoing messages to this IP:Port pair - combine = 30 # Receive and sent 30 samples per message (multiplexing). + vectorize = 30 # Receive and sent 30 samples per message (combining). }, ethernet_node = { type = "socket", # See above. @@ -141,7 +149,7 @@ paths = ( hook = "print", # Register custom hook funktion (see src/hooks.c) poolsize = 30 # The amount of samples which are kept in a circular buffer. - # This number must be larger than the 'combine' settings of all + # This number must be larger than the 'vectorize' settings of all # associated input and output nodes! }, { diff --git a/etc/gtnet_test1.conf b/etc/gtnet-skt/test1.conf similarity index 100% rename from etc/gtnet_test1.conf rename to etc/gtnet-skt/test1.conf diff --git a/etc/gtnet_test2.conf b/etc/gtnet-skt/test2.conf similarity index 100% rename from etc/gtnet_test2.conf rename to etc/gtnet-skt/test2.conf diff --git a/etc/gtnet_test3.conf b/etc/gtnet-skt/test3.conf similarity index 100% rename from etc/gtnet_test3.conf rename to etc/gtnet-skt/test3.conf diff --git a/etc/gtnet_test4.conf b/etc/gtnet-skt/test4.conf similarity index 94% rename from etc/gtnet_test4.conf rename to etc/gtnet-skt/test4.conf index b80cbd41d..4a56ee34d 100644 --- a/etc/gtnet_test4.conf +++ b/etc/gtnet-skt/test4.conf @@ -32,8 +32,8 @@ nodes = { node1 = { type = "socket", layer = "udp", - local = "192.168.88.128:12002", # Local ip:port, use '*' for random port - remote = "192.168.88.129:12001", + local = "134.130.169.31:12002", # Local ip:port, use '*' for random port + remote = "134.130.169.98:12001", header = "gtnet-skt", # 'gtnet-skt' or 'villas'. If not provided, 'villas' header will be used vectorize = 1, # Number of samples to fetch per iteration from the socket netem = { diff --git a/include/villas/hooks.h b/include/villas/hooks.h index 7c69b0125..8dbd4c09c 100644 --- a/include/villas/hooks.h +++ b/include/villas/hooks.h @@ -22,6 +22,7 @@ #define _HOOKS_H_ #include +#include #include "queue.h" #include "list.h" diff --git a/include/villas/memory.h b/include/villas/memory.h index d66955727..cf1d7acd5 100644 --- a/include/villas/memory.h +++ b/include/villas/memory.h @@ -8,10 +8,13 @@ */ #include +#include #ifndef _MEMORY_H_ #define _MEMORY_H_ +#define HUGEPAGESIZE (1 << 21) + typedef void *(*memzone_allocator_t)(size_t len); typedef int (*memzone_deallocator_t)(void *ptr, size_t len); diff --git a/include/villas/node.h b/include/villas/node.h index e01863444..c24841832 100644 --- a/include/villas/node.h +++ b/include/villas/node.h @@ -47,6 +47,8 @@ struct node int vectorize; /**< Number of messages to send / recv at once (scatter / gather) */ int affinity; /**< CPU Affinity of this node */ + + unsigned long sequence; /**< This is a counter of received samples, in case the node-type does not generate sequence numbers itself. */ enum node_state { NODE_INVALID, /**< This node object is not in a valid state. */ diff --git a/include/villas/nodes/socket.h b/include/villas/nodes/socket.h index 044e76de9..4a0a6c337 100644 --- a/include/villas/nodes/socket.h +++ b/include/villas/nodes/socket.h @@ -29,8 +29,9 @@ enum socket_layer { }; enum socket_header { - SOCKET_HEADER_DEFAULT, /**< Default header in the payload, (see msg_format.h) */ - SOCKET_HEADER_GTNET_SKT /**< No header in the payload, same as HDR_NONE*/ + SOCKET_HEADER_DEFAULT, /**> Default header in the payload, (see msg_format.h) */ + SOCKET_HEADER_NONE, /**> No header in the payload, same as HDR_NONE*/ + SOCKET_HEADER_FAKE /**> Same as SOCKET_HEADER_NONE but using the first three data values as: sequence, seconds & nano-seconds. */ }; union sockaddr_union { @@ -41,29 +42,19 @@ union sockaddr_union { }; struct socket { - /** The socket descriptor */ - int sd; - /** Socket mark for netem, routing and filtering */ - int mark; + int sd; /**> The socket descriptor */ + int mark; /**> Socket mark for netem, routing and filtering */ - /** The OSI / IP layer which should be used for this socket */ - enum socket_layer layer; + enum socket_layer layer; /**> The OSI / IP layer which should be used for this socket */ + enum socket_header header; /**> Payload header type */ - /** Payload header type */ - enum socket_header header; + union sockaddr_union local; /**> Local address of the socket */ + union sockaddr_union remote; /**> Remote address of the socket */ - /** Local address of the socket */ - union sockaddr_union local; - /** Remote address of the socket */ - union sockaddr_union remote; + struct rtnl_qdisc *tc_qdisc; /**> libnl3: Network emulator queuing discipline */ + struct rtnl_cls *tc_classifier; /**> libnl3: Firewall mark classifier */ - /** libnl3: Network emulator queuing discipline */ - struct rtnl_qdisc *tc_qdisc; - /** libnl3: Firewall mark classifier */ - struct rtnl_cls *tc_classifier; - - /* Linked list _per_interface_ */ - struct socket *next; + struct socket *next; /* Linked list _per_interface_ */ }; diff --git a/include/villas/utils.h b/include/villas/utils.h index 4207d3626..0e29fba8e 100644 --- a/include/villas/utils.h +++ b/include/villas/utils.h @@ -59,7 +59,10 @@ #define CEIL(x, y) (((x) + (y) - 1) / (y)) /** Get nearest up-rounded power of 2 */ -#define LOG2_CEIL(x) (1 << log2i((x) - 1) + 1) +#define LOG2_CEIL(x) (1 << (log2i((x) - 1) + 1)) + +/** Check if the number is a power of 2 */ +#define IS_POW2(x) (((x) != 0) && !((x) & ((x) - 1))) /** Calculate the number of elements in an array. */ #define ARRAY_LEN(a) ( sizeof (a) / sizeof (a)[0] ) @@ -184,7 +187,7 @@ struct version { }; /** Compare two versions. */ -int version_compare(struct version *a, struct version *b); +int version_cmp(struct version *a, struct version *b); /** Parse a dotted version string. */ int version_parse(const char *s, struct version *v); diff --git a/lib/kernel/kernel.c b/lib/kernel/kernel.c index 6548dbf49..b2c9b94cc 100644 --- a/lib/kernel/kernel.c +++ b/lib/kernel/kernel.c @@ -101,7 +101,7 @@ int kernel_has_version(int maj, int min) if (version_parse(uts.release, ¤t)) return -1; - return version_compare(¤t, &required) < 0; + return version_cmp(¤t, &required) < 0; } int kernel_is_rt() diff --git a/lib/memory.c b/lib/memory.c index df4eccd7e..7525a1c0c 100644 --- a/lib/memory.c +++ b/lib/memory.c @@ -65,6 +65,8 @@ static void * memory_hugepage_alloc(size_t len) static int memory_hugepage_free(void *ptr, size_t len) { + len = ALIGN(len, HUGEPAGESIZE); /* ugly see: https://lkml.org/lkml/2015/3/27/171 */ + return munmap(ptr, len); } diff --git a/lib/node.c b/lib/node.c index 7baa1a407..b4aa9dc02 100644 --- a/lib/node.c +++ b/lib/node.c @@ -113,6 +113,8 @@ int node_start(struct node *n) if (ret == 0) n->state = NODE_RUNNING; + n->sequence = 0; + return ret; } diff --git a/lib/nodes/socket.c b/lib/nodes/socket.c index f6c3e3f12..d28644836 100644 --- a/lib/nodes/socket.c +++ b/lib/nodes/socket.c @@ -103,8 +103,9 @@ char * socket_print(struct node *n) } switch (s->header) { - case SOCKET_HEADER_GTNET_SKT: header = "gtnet-skt"; break; - case SOCKET_HEADER_DEFAULT: header = "villas"; break; + case SOCKET_HEADER_NONE: header = "none"; break; + case SOCKET_HEADER_FAKE: header = "fake"; break; + case SOCKET_HEADER_DEFAULT: header = "default"; break; } char *local = socket_print_addr((struct sockaddr *) &s->local); @@ -214,34 +215,29 @@ int socket_read(struct node *n, struct sample *smps[], unsigned cnt) int samples, ret, received, length; ssize_t bytes; - if (s->header == SOCKET_HEADER_GTNET_SKT) { + if (s->header == SOCKET_HEADER_NONE || s->header == SOCKET_HEADER_FAKE) { if (cnt < 1) return 0; /* The GTNETv2-SKT protocol send every sample in a single packet. * socket_read() receives a single packet. */ + int iov_len = s->header == SOCKET_HEADER_FAKE ? 2 : 1; + struct iovec iov[iov_len]; struct sample *smp = smps[0]; - -#if defined(GTNET_SKT_HEADER) && GTNET_SKT_HEADER + uint32_t header[3]; - - struct iovec iov[] = { - { /* First three values are sequence, seconds and nano-seconds */ - .iov_base = header, - .iov_len = sizeof(header) - }, -#else - struct iovec iov[] = { -#endif - { /* Remaining values are payload */ - .iov_base = &smp->data, - .iov_len = SAMPLE_DATA_LEN(smp->capacity) - } - }; + if (s->header == SOCKET_HEADER_FAKE) { + iov[0].iov_base = header; + iov[0].iov_len = sizeof(header); + } + + /* Remaining values are payload */ + iov[iov_len-1].iov_base = &smp->data; + iov[iov_len-1].iov_len = SAMPLE_DATA_LEN(smp->capacity); struct msghdr mhdr = { .msg_iov = iov, - .msg_iovlen = ARRAY_LEN(iov), + .msg_iovlen = iov_len, .msg_name = (struct sockaddr *) &s->remote, .msg_namelen = sizeof(s->remote) }; @@ -258,28 +254,23 @@ int socket_read(struct node *n, struct sample *smps[], unsigned cnt) return -1; } -#if defined(GTNET_SKT_HEADER) && GTNET_SKT_HEADER - length = (bytes - sizeof(header)) / SAMPLE_DATA_LEN(1); -#else - length = bytes / SAMPLE_DATA_LEN(1); -#endif + length = (s->header == SOCKET_HEADER_FAKE ? bytes - sizeof(header) : bytes) / SAMPLE_DATA_LEN(1); if (length > smp->capacity) { warn("Node %s received more values than supported. Dropping %u values", node_name(n), length - smp->capacity); length = smp->capacity; } - /** @todo Should we generate sequence no here manually? - * Or maybe optinally use the first data value as a sequence? - * However this would require the RTDS model to be changed. */ -#if defined(GTNET_SKT_HEADER) && GTNET_SKT_HEADER - smp->sequence = header[0]; - smp->ts.origin.tv_sec = header[1]; - smp->ts.origin.tv_nsec = header[2]; -#else - smp->sequence = -1; - smp->ts.origin.tv_sec = -1; - smp->ts.origin.tv_nsec = -1; -#endif + if (s->header == SOCKET_HEADER_FAKE) { + smp->sequence = header[0]; + smp->ts.origin.tv_sec = header[1]; + smp->ts.origin.tv_nsec = header[2]; + } + else { + smp->sequence = n->sequence++; /* Fake sequence no generated by VILLASnode */ + smp->ts.origin.tv_sec = -1; + smp->ts.origin.tv_nsec = -1; + } + smp->ts.received.tv_sec = -1; smp->ts.received.tv_nsec = -1; @@ -376,35 +367,32 @@ int socket_write(struct node *n, struct sample *smps[], unsigned cnt) int sent = 0; /* Construct iovecs */ - if (s->header == SOCKET_HEADER_GTNET_SKT) { + if (s->header == SOCKET_HEADER_NONE || s->header == SOCKET_HEADER_FAKE) { if (cnt < 1) return 0; - for (int i = 0; i < cnt; i++) { -#if defined(GTNET_SKT_HEADER) && GTNET_SKT_HEADER - uint32_t header[] = { - smps[i]->sequence, - smps[i]->ts.origin.tv_sec, - smps[i]->ts.origin.tv_nsec - }; + for (int i = 0; i < cnt; i++) { + int iov_len = s->header == SOCKET_HEADER_FAKE ? 2 : 1; + struct iovec iov[iov_len]; - struct iovec iov[] = { - { /* First three values are sequence, seconds and nano-seconds */ - .iov_base = header, - .iov_len = sizeof(header) - }, -#else - struct iovec iov[] = { -#endif - { /* Remaining values are payload */ - .iov_base = &smps[i]->data, - .iov_len = SAMPLE_DATA_LEN(smps[i]->length) - } - }; + /* First three values are sequence, seconds and nano-seconds timestamps */ + uint32_t header[3]; + if (s->header == SOCKET_HEADER_FAKE) { + header[0] = smps[i]->sequence; + header[1] = smps[i]->ts.origin.tv_sec; + header[2] = smps[i]->ts.origin.tv_nsec; + + iov[0].iov_base = header; + iov[0].iov_len = sizeof(header); + } + + /* Remaining values are payload */ + iov[iov_len-1].iov_base = &smps[i]->data; + iov[iov_len-1].iov_len = SAMPLE_DATA_LEN(smps[i]->length); struct msghdr mhdr = { .msg_iov = iov, - .msg_iovlen = ARRAY_LEN(iov), + .msg_iovlen = iov_len, .msg_name = (struct sockaddr *) &s->remote, .msg_namelen = sizeof(s->remote) }; @@ -480,9 +468,11 @@ int socket_parse(struct node *n, config_setting_t *cfg) if (!config_setting_lookup_string(cfg, "header", &hdr)) s->header = SOCKET_HEADER_DEFAULT; else { - if (!strcmp(hdr, "gtnet-skt") || (!strcmp(hdr, "none"))) - s->header = SOCKET_HEADER_GTNET_SKT; - else if (!strcmp(hdr, "default") || !strcmp(hdr, "villas")) + if (!strcmp(hdr, "gtnet-skt") || (!strcmp(hdr, "none"))) + s->header = SOCKET_HEADER_NONE; + else if (!strcmp(hdr, "gtnet-skt:fake") || (!strcmp(hdr, "fake"))) + s->header = SOCKET_HEADER_FAKE; + else if (!strcmp(hdr, "villas") || !strcmp(hdr, "default")) s->header = SOCKET_HEADER_DEFAULT; else cerror(cfg, "Invalid application header type '%s' for node %s", hdr, node_name(n)); diff --git a/lib/utils.c b/lib/utils.c index 2c7fb04fd..d12b7394a 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -35,7 +35,7 @@ int version_parse(const char *s, struct version *v) return sscanf(s, "%u.%u", &v->major, &v->minor) != 2; } -int version_compare(struct version *a, struct version *b) { +int version_cmp(struct version *a, struct version *b) { int major = a->major - b->major; int minor = a->minor - b->minor; diff --git a/tests/Makefile.inc b/tests/Makefile.inc index facbef13f..9bac51525 100644 --- a/tests/Makefile.inc +++ b/tests/Makefile.inc @@ -7,6 +7,10 @@ TEST_LDLIBS = $(LDLIBS) -lcriterion -lvillas -pthread tests: $(BUILDDIR)/testsuite +run-tests: tests + echo 25 > /proc/sys/vm/nr_hugepages + $(BUILDDIR)/testsuite + # Compile $(BUILDDIR)/tests/%.o: tests/%.c | $$(dir $$@) $(CC) $(TEST_CFLAGS) -c $< -o $@ @@ -25,4 +29,4 @@ install-tests: clean-tests: rm -rf $(BUILDDIR)/tests $(BUILDDIR)/testsuite -.PHONY: tests install-tests clean-tests \ No newline at end of file +.PHONY: tests install-tests clean-tests run-tests \ No newline at end of file diff --git a/tests/memory.c b/tests/memory.c new file mode 100644 index 000000000..234e6defd --- /dev/null +++ b/tests/memory.c @@ -0,0 +1,39 @@ +/** Unit tests for memory management + * + * @author Steffen Vogel + * @copyright 2014-2016, Institute for Automation of Complex Power Systems, EONERC + * This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential. + * Unauthorized copying of this file, via any medium is strictly prohibited. + *********************************************************************************/ + +#include +#include + +#include + +#include "memory.h" +#include "utils.h" + +TheoryDataPoints(memory, aligned) = { +// DataPoints(size_t, 1, 32, 55, 1 << 10, 1 << 20), + DataPoints(size_t, 1<<12), + DataPoints(size_t, 1, 8, 1 << 12), + DataPoints(const struct memtype *, &memtype_heap, &memtype_hugepage) +}; + +Theory((size_t len, size_t align, const struct memtype *m), memory, aligned) { + int ret; + void *ptr; + + ptr = memory_alloc_aligned(m, len, align); + cr_assert_neq(ptr, NULL, "Failed to allocate memory"); + + //cr_assert(IS_ALIGNED(ptr, align)); + + if (m == &memtype_hugepage) { + cr_assert(IS_ALIGNED(ptr, HUGEPAGESIZE)); + } + + ret = memory_free(m, ptr, len); + cr_assert_eq(ret, 0, "Failed to release memory: ret=%d, ptr=%p, len=%zu: %s", ret, ptr, len, strerror(errno)); +} \ No newline at end of file diff --git a/tests/pool.c b/tests/pool.c new file mode 100644 index 000000000..8654ad6ea --- /dev/null +++ b/tests/pool.c @@ -0,0 +1,68 @@ +/** Unit tests for memory pool + * + * @author Steffen Vogel + * @copyright 2014-2016, Institute for Automation of Complex Power Systems, EONERC + * This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential. + * Unauthorized copying of this file, via any medium is strictly prohibited. + *********************************************************************************/ + +#include +#include + +#include + +#include "pool.h" +#include "utils.h" + +struct param { + int thread_count; + int pool_size; + size_t block_size; + const struct memtype *memtype; +}; + +ParameterizedTestParameters(pool, basic) +{ + static struct param params[] = { + { 1, 4096, 150, &memtype_heap }, + { 1, 128, 8, &memtype_hugepage }, + { 1, 4, 8192, &memtype_hugepage }, + { 1, 1 << 13, 4, &memtype_heap } + }; + + return cr_make_param_array(struct param, params, ARRAY_LEN(params)); +} + +ParameterizedTest(struct param *p, pool, basic) +{ + int ret; + struct pool pool; + + void *ptr, *ptrs[p->pool_size]; + + ret = pool_init(&pool, p->pool_size, p->block_size, p->memtype); + cr_assert_eq(ret, 0, "Failed to create pool"); + + ptr = pool_get(&pool); + cr_assert_neq(ptr, NULL); + + memset(ptr, 1, p->block_size); /* check that we dont get a seg fault */ + + int i; + for (i = 1; i < p->pool_size; i++) { + ptrs[i] = pool_get(&pool); + + if (ptrs[i] == NULL) + break; + } + + if (i < p->pool_size) + cr_assert_neq(ptrs[i], NULL); + + ptr = pool_get(&pool); + cr_assert_eq(ptr, NULL); + + ret = pool_destroy(&pool); + cr_assert_eq(ret, 0, "Failed to destroy pool"); + +} \ No newline at end of file diff --git a/tests/timing.c b/tests/timing.c index 82fb8bf6f..89866c07f 100644 --- a/tests/timing.c +++ b/tests/timing.c @@ -7,6 +7,7 @@ *********************************************************************************/ #include +#include #include #include "timing.h" @@ -86,7 +87,10 @@ Test(timing, timerfd_create_rate, .timeout = 20) cr_assert(tfd > 0); - for (int i = 0; i < 10; i++) { + timerfd_wait(tfd); + + int i; + for (i = 0; i < 10; i++) { start = time_now(); timerfd_wait(tfd); @@ -94,9 +98,13 @@ Test(timing, timerfd_create_rate, .timeout = 20) end = time_now(); waited = time_delta(&start, &end); - cr_assert_float_eq(waited, 1.0 / rate, 10e-3, "We slept for %f instead of %f secs in round %d", waited, 1.0 / rate, i); + if (fabs(waited - 1.0 / rate) > 10e-3) + break; } + if (i < 10) + cr_assert_float_eq(waited, 1.0 / rate, 10e-3, "We slept for %f instead of %f secs in round %d", waited, 1.0 / rate, i); + close(tfd); } diff --git a/tests/utils.c b/tests/utils.c new file mode 100644 index 000000000..6999cd70e --- /dev/null +++ b/tests/utils.c @@ -0,0 +1,64 @@ +/** Unit tests for utilities + * + * @author Steffen Vogel + * @copyright 2014-2016, Institute for Automation of Complex Power Systems, EONERC + * This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential. + * Unauthorized copying of this file, via any medium is strictly prohibited. + *********************************************************************************/ + +#include + +#include "utils.h" + +Test(utils, is_aligned) +{ + /* Positive */ + cr_assert(IS_ALIGNED(1, 1)); + cr_assert(IS_ALIGNED(128, 64)); + + /* Negative */ + cr_assert(!IS_ALIGNED(55, 16)); + cr_assert(!IS_ALIGNED(55, 55)); + cr_assert(!IS_ALIGNED(1128, 256)); +} + +Test(utils, ceil) +{ + cr_assert_eq(CEIL(10, 3), 4); + cr_assert_eq(CEIL(10, 5), 2); + cr_assert_eq(CEIL(4, 3), 2); +} + +Test(utils, is_pow2) +{ + /* Positive */ + cr_assert(IS_POW2(1)); + cr_assert(IS_POW2(2)); + cr_assert(IS_POW2(64)); + + /* Negative */ + cr_assert(!IS_POW2(0)); + cr_assert(!IS_POW2(3)); + cr_assert(!IS_POW2(11111)); + cr_assert(!IS_POW2(-1)); +} + +struct version_param { + const char *v1, *v2; + int result; +}; + +Test(utils, version) +{ + struct version v1, v2, v3, v4; + + version_parse("1.2", &v1); + version_parse("1.3", &v2); + version_parse("55", &v3); + version_parse("66", &v4); + + cr_assert_lt(version_cmp(&v1, &v2), 0); + cr_assert_eq(version_cmp(&v1, &v1), 0); + cr_assert_gt(version_cmp(&v2, &v1), 0); + cr_assert_lt(version_cmp(&v3, &v4), 0); +} \ No newline at end of file diff --git a/thirdparty/criterion b/thirdparty/criterion index 1b687a9f6..101d0e0f3 160000 --- a/thirdparty/criterion +++ b/thirdparty/criterion @@ -1 +1 @@ -Subproject commit 1b687a9f6a0e51c9e9b0a047e1fcd6c94de7a080 +Subproject commit 101d0e0f3a71c414c7d2a7a2a2fa465969b6b667