diff --git a/lib/cfl/.github/workflows/build.yaml b/lib/cfl/.github/workflows/build.yaml index 8f3ad73910e..ff9d15095d1 100644 --- a/lib/cfl/.github/workflows/build.yaml +++ b/lib/cfl/.github/workflows/build.yaml @@ -7,6 +7,7 @@ on: branches: - master types: [opened, reopened, synchronize] + jobs: build-windows: name: Build sources on amd64 for ${{ matrix.os }} @@ -57,13 +58,17 @@ jobs: name: CentOS 7 build to confirm no issues once used downstream runs-on: ubuntu-latest container: centos:7 + env: + # workaround required for checkout@v3, https://github.com/actions/checkout/issues/1590 + ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true steps: - name: Set up base image dependencies run: | + sed -i -e "s/^mirrorlist=http:\/\/mirrorlist.centos.org/#mirrorlist=http:\/\/mirrorlist.centos.org/g" /etc/yum.repos.d/CentOS-Base.repo + sed -i -e "s/^#baseurl=http:\/\/mirror.centos.org/baseurl=http:\/\/vault.centos.org/g" /etc/yum.repos.d/CentOS-Base.repo yum -y update yum install -y ca-certificates cmake gcc gcc-c++ make wget - wget http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm - rpm -ivh epel-release-latest-7.noarch.rpm + yum install -y epel-release yum install -y cmake3 shell: bash @@ -103,7 +108,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Build on ${{ matrix.os }} with ${{ matrix.compiler }} - uses: uraimo/run-on-arch-action@v2.3.0 + uses: uraimo/run-on-arch-action@v2.5.0 with: arch: aarch64 distro: ubuntu20.04 diff --git a/lib/cfl/.github/workflows/packages.yaml b/lib/cfl/.github/workflows/packages.yaml index 2c5bedbee2b..34967c491d6 100644 --- a/lib/cfl/.github/workflows/packages.yaml +++ b/lib/cfl/.github/workflows/packages.yaml @@ -19,7 +19,7 @@ jobs: format: [ rpm, deb ] steps: - uses: actions/checkout@v3 - - uses: uraimo/run-on-arch-action@v2.3.0 + - uses: uraimo/run-on-arch-action@v2.5.0 name: Build the ${{matrix.format}} packages with: arch: aarch64 diff --git a/lib/cfl/CMakeLists.txt b/lib/cfl/CMakeLists.txt index f6a355679a4..2193cb29ce3 100644 --- a/lib/cfl/CMakeLists.txt +++ b/lib/cfl/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # C Floppy Version set(CFL_VERSION_MAJOR 0) set(CFL_VERSION_MINOR 5) -set(CFL_VERSION_PATCH 0) +set(CFL_VERSION_PATCH 1) set(CFL_VERSION_STR "${CFL_VERSION_MAJOR}.${CFL_VERSION_MINOR}.${CFL_VERSION_PATCH}") # Configuration options diff --git a/lib/cfl/include/cfl/cfl.h b/lib/cfl/include/cfl/cfl.h index 8e4efa763d4..921767e8091 100644 --- a/lib/cfl/include/cfl/cfl.h +++ b/lib/cfl/include/cfl/cfl.h @@ -37,7 +37,9 @@ #include #include #include +#include int cfl_init(); +char *cfl_version(); #endif diff --git a/lib/cfl/include/cfl/cfl_array.h b/lib/cfl/include/cfl/cfl_array.h index 3977df75e6b..331a4cbc6b6 100644 --- a/lib/cfl/include/cfl/cfl_array.h +++ b/lib/cfl/include/cfl/cfl_array.h @@ -43,6 +43,11 @@ static inline struct cfl_variant *cfl_array_fetch_by_index(struct cfl_array *arr return array->entries[position]; } +static inline size_t cfl_array_size(struct cfl_array *array) +{ + return array->entry_count; +} + int cfl_array_append(struct cfl_array *array, struct cfl_variant *value); int cfl_array_append_string(struct cfl_array *array, char *value); int cfl_array_append_string_s(struct cfl_array *array, char *str, size_t str_len, int referenced); diff --git a/lib/cfl/include/cfl/cfl_compat.h b/lib/cfl/include/cfl/cfl_compat.h index bc1de9a0673..4972f907e49 100644 --- a/lib/cfl/include/cfl/cfl_compat.h +++ b/lib/cfl/include/cfl/cfl_compat.h @@ -45,4 +45,3 @@ #endif #endif - diff --git a/lib/cfl/include/cfl/cfl_utils.h b/lib/cfl/include/cfl/cfl_utils.h new file mode 100644 index 00000000000..db9d873ee1c --- /dev/null +++ b/lib/cfl/include/cfl/cfl_utils.h @@ -0,0 +1,20 @@ +#ifndef CFL_UTILS_H +#define CFL_UTILS_H + +#include /* off_t */ +#include +#include + +struct cfl_split_entry { + char *value; + int len; + off_t last_pos; + struct cfl_list _head; +}; + +struct cfl_list *cfl_utils_split_quoted(const char *line, int separator, int max_split); +struct cfl_list *cfl_utils_split(const char *line, int separator, int max_split); +void cfl_utils_split_free_entry(struct cfl_split_entry *entry); +void cfl_utils_split_free(struct cfl_list *list); + +#endif diff --git a/lib/cfl/src/CMakeLists.txt b/lib/cfl/src/CMakeLists.txt index 05383f3fef3..f09a5c3d8b1 100644 --- a/lib/cfl/src/CMakeLists.txt +++ b/lib/cfl/src/CMakeLists.txt @@ -9,6 +9,7 @@ set(src cfl_array.c cfl_variant.c cfl_checksum.c + cfl_utils.c ) # Static Library diff --git a/lib/cfl/src/cfl.c b/lib/cfl/src/cfl.c index 7afb1109aa5..426319a2747 100644 --- a/lib/cfl/src/cfl.c +++ b/lib/cfl/src/cfl.c @@ -17,7 +17,15 @@ * limitations under the License. */ +#include + int cfl_init() { return 0; } + +char *cfl_version() +{ + return CFL_VERSION_STR; +} + diff --git a/lib/cfl/src/cfl_utils.c b/lib/cfl/src/cfl_utils.c new file mode 100644 index 00000000000..5342134e7e5 --- /dev/null +++ b/lib/cfl/src/cfl_utils.c @@ -0,0 +1,285 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* CFL + * === + * Copyright (C) 2022 The CFL Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +/* Lookup char into string, return position + * Based on monkey/monkey's mk_string_char_search. + */ +static int cfl_string_char_search(const char *string, int c, int len) +{ + char *p; + + if (len < 0) { + len = strlen(string); + } + + p = memchr(string, c, len); + if (p) { + return (p - string); + } + + return -1; +} + +/* Return a buffer with a new string from string. + * Based on monkey/monkey's mk_string_copy_substr. + */ +static char *cfl_string_copy_substr(const char *string, int pos_init, int pos_end) +{ + unsigned int size, bytes; + char *buffer = 0; + + if (pos_init > pos_end) { + return NULL; + } + + size = (unsigned int) (pos_end - pos_init) + 1; + if (size <= 2) { + size = 4; + } + + buffer = calloc(1, size); + + if (!buffer) { + return NULL; + } + + bytes = pos_end - pos_init; + memcpy(buffer, string + pos_init, bytes); + buffer[bytes] = '\0'; + + return (char *) buffer; +} + +/* + * quoted_string_len returns the length of a quoted string, not including the quotes. + */ +static int quoted_string_len(const char *str) +{ + int len = 0; + char quote = *str++; /* Consume the quote character. */ + + while (quote != 0) { + char c = *str++; + switch (c) { + case '\0': + /* Error: string ends before end-quote was seen. */ + return -1; + case '\\': + /* Skip escaped quote or \\. */ + if (*str == quote || *str == '\\') { + str++; + } + break; + case '\'': + case '"': + /* End-quote seen: stop iterating. */ + if (c == quote) { + quote = 0; + } + break; + default: + break; + } + len++; + } + + /* Go back one character to ignore end-quote */ + len--; + + return len; +} + +/* + * next_token returns the next token in the string 'str' delimited by 'separator'. + * 'out' is set to the beginning of the token. + * 'out_len' is set to the length of the token. + * 'parse_quotes' is set to CFL_TRUE when quotes shall be considered when tokenizing the 'str'. + * The function returns offset to next token in the string. + */ +static int next_token(const char *str, int separator, char **out, int *out_len, int parse_quotes) { + const char *token_in = str; + char *token_out; + int next_separator = 0; + int quote = 0; /* Parser state: 0 not inside quoted string, or '"' or '\'' when inside quoted string. */ + int len = 0; + int i; + + /* Skip leading separators. */ + while (*token_in == separator) { + token_in++; + } + + /* Should quotes be parsed? Or is token quoted? If not, copy until separator or the end of string. */ + if (parse_quotes == CFL_FALSE || (*token_in != '"' && *token_in != '\'')) { + len = (int)strlen(token_in); + next_separator = cfl_string_char_search(token_in, separator, len); + if (next_separator > 0) { + len = next_separator; + } + *out_len = len; + *out = cfl_string_copy_substr(token_in, 0, len); + if (*out == NULL) { + return -1; + } + + return (int)(token_in - str) + len; + } + + /* Token is quoted. */ + + len = quoted_string_len(token_in); + if (len < 0) { + return -1; + } + + /* Consume the quote character. */ + quote = *token_in++; + + token_out = calloc(1, len + 1); + if (!token_out) { + return -1; + } + + /* Copy the token */ + for (i = 0; i < len; i++) { + /* Handle escapes when inside quoted token: + * \" -> " + * \' -> ' + * \\ -> \ + */ + if (*token_in == '\\' && (token_in[1] == quote || token_in[1] == '\\')) { + token_in++; + } + token_out[i] = *token_in++; + } + token_out[i] = '\0'; + + *out = token_out; + *out_len = len; + + return (int)(token_in - str); +} + + +static struct cfl_list *split(const char *line, int separator, int max_split, int quoted) +{ + int i = 0; + int count = 0; + int val_len; + int len; + int end; + char *val; + struct cfl_list *list; + struct cfl_split_entry *new; + + if (!line) { + return NULL; + } + + list = calloc(1, sizeof(struct cfl_list)); + if (!list) { + cfl_errno(); + return NULL; + } + cfl_list_init(list); + + len = strlen(line); + while (i < len) { + end = next_token(line + i, separator, &val, &val_len, quoted); + if (end == -1) { + cfl_report_runtime_error(); + cfl_utils_split_free(list); + return NULL; + } + + /* Update last position */ + i += end; + + /* Create new entry */ + new = calloc(1, sizeof(struct cfl_split_entry)); + if (!new) { + cfl_errno(); + free(val); + cfl_utils_split_free(list); + return NULL; + } + new->value = val; + new->len = val_len; + new->last_pos = i; + cfl_list_add(&new->_head, list); + count++; + + /* Update index for next loop */ + i++; + + /* + * If the counter exceeded the maximum specified and there + * are still remaining bytes, append those bytes in a new + * and last entry. + */ + if (count >= max_split && max_split > 0 && i < len) { + new = calloc(1, sizeof(struct cfl_split_entry)); + if (!new) { + cfl_errno(); + cfl_utils_split_free(list); + return NULL; + } + new->value = cfl_string_copy_substr(line, i, len); + new->len = len - i; + cfl_list_add(&new->_head, list); + break; + } + } + + return list; +} + +struct cfl_list *cfl_utils_split_quoted(const char *line, int separator, int max_split) +{ + return split(line, separator, max_split, CFL_TRUE); +} + +struct cfl_list *cfl_utils_split(const char *line, int separator, int max_split) +{ + return split(line, separator, max_split, CFL_FALSE); +} + + +void cfl_utils_split_free_entry(struct cfl_split_entry *entry) +{ + cfl_list_del(&entry->_head); + free(entry->value); + free(entry); +} + +void cfl_utils_split_free(struct cfl_list *list) +{ + struct cfl_list *tmp; + struct cfl_list *head; + struct cfl_split_entry *entry; + + cfl_list_foreach_safe(head, tmp, list) { + entry = cfl_list_entry(head, struct cfl_split_entry, _head); + cfl_utils_split_free_entry(entry); + } + + free(list); +} diff --git a/lib/cfl/tests/CMakeLists.txt b/lib/cfl/tests/CMakeLists.txt index 13d113c5ba3..4dba27058fb 100644 --- a/lib/cfl/tests/CMakeLists.txt +++ b/lib/cfl/tests/CMakeLists.txt @@ -9,6 +9,8 @@ set(UNIT_TESTS_FILES list.c variant.c object.c + version.c + utils.c ) configure_file( diff --git a/lib/cfl/tests/array.c b/lib/cfl/tests/array.c index 2ec72e1d459..90332bbfa22 100644 --- a/lib/cfl/tests/array.c +++ b/lib/cfl/tests/array.c @@ -18,7 +18,6 @@ */ #include - #include #include diff --git a/lib/cfl/tests/list.c b/lib/cfl/tests/list.c index 0c12f888fd7..9717be41678 100644 --- a/lib/cfl/tests/list.c +++ b/lib/cfl/tests/list.c @@ -2,7 +2,7 @@ /* CFL * === - * Copyright (C) 2022 The CFL Authors + * Copyright (C) 2022-2024 The CFL Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ */ #include - #include "cfl_tests_internal.h" struct test { @@ -27,24 +26,74 @@ struct test { static void checks() { - struct test *t; - struct cfl_list list; + struct test *t; + struct cfl_list list; + + cfl_list_init(&list); + TEST_CHECK(cfl_list_is_empty(&list)); + + t = malloc(sizeof(struct test)); + cfl_list_add(&t->_head, &list); + TEST_CHECK(!cfl_list_is_empty(&list)); + + cfl_list_del(&t->_head); + TEST_CHECK(cfl_list_is_empty(&list)); + + free(t); +} + +static void add() +{ + int i; + int count = 0; + struct cfl_list list; + struct cfl_list *head; + struct cfl_list *tmp; + + struct node { + int value; + struct cfl_list _head; + }; + + struct node **nodes; + struct node *node; + + nodes = malloc(sizeof(struct node *) * 3); + for (i = 0; i < 3; i++) { + nodes[i] = malloc(sizeof(struct node)); + nodes[i]->value = i; + } + + cfl_list_init(&list); + cfl_list_add(&nodes[0]->_head, &list); + cfl_list_add(&nodes[2]->_head, &list); + - cfl_list_init(&list); - TEST_CHECK(cfl_list_is_empty(&list)); + node = nodes[2]; + cfl_list_add_before(&nodes[1]->_head, &node->_head, &list); - t = malloc(sizeof(struct test)); - cfl_list_add(&t->_head, &list); - TEST_CHECK(!cfl_list_is_empty(&list)); + /* print all nodes */ + printf("\n"); + cfl_list_foreach(head, &list) { + node = cfl_list_entry(head, struct node, _head); + printf("node value: %d\n", node->value); + count++; + } + TEST_CHECK(count == 3); - cfl_list_del(&t->_head); - TEST_CHECK(cfl_list_is_empty(&list)); + cfl_list_foreach_safe(head, tmp, &list) { + node = cfl_list_entry(head, struct node, _head); + cfl_list_del(&node->_head); + free(node); + count++; + } - free(t); + free(nodes); } TEST_LIST = { {"checks", checks}, + {"add", add}, { 0 } }; diff --git a/lib/cfl/tests/utils.c b/lib/cfl/tests/utils.c new file mode 100644 index 00000000000..a24e0df4b7a --- /dev/null +++ b/lib/cfl/tests/utils.c @@ -0,0 +1,162 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* CFL + * === + * Copyright (C) 2022-2024 The CFL Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include "cfl_tests_internal.h" + +static int compare_split_entry(const char* input, int separator, int max_split, int quoted, ...) +{ + va_list ap; + int count = 1; + char *expect; + struct cfl_list *split = NULL; + struct cfl_list *tmp_list = NULL; + struct cfl_list *head = NULL; + struct cfl_split_entry *entry = NULL; + + if (quoted) { + split = cfl_utils_split_quoted(input, separator, max_split); + } + else { + split = cfl_utils_split(input, separator, max_split); + } + + if (!TEST_CHECK(split != NULL)) { + TEST_MSG("flb_utils_split failed. input=%s", input); + return -1; + } + if (!TEST_CHECK(cfl_list_is_empty(split) != 1)) { + TEST_MSG("list is empty. input=%s", input); + return -1; + } + + va_start(ap, quoted); + cfl_list_foreach_safe(head, tmp_list, split) { + if (max_split > 0 && !TEST_CHECK(count <= max_split) ) { + TEST_MSG("count error. got=%d expect=%d input=%s", count, max_split, input); + } + + expect = va_arg(ap, char*); + entry = cfl_list_entry(head, struct cfl_split_entry, _head); + if (!TEST_CHECK(entry != NULL)) { + TEST_MSG("entry is NULL. input=%s", input); + goto comp_end; + } + /* + printf("%d:%s\n", count, entry->value); + */ + if (!TEST_CHECK(strcmp(expect, entry->value) == 0)) { + TEST_MSG("mismatch. got=%s expect=%s. input=%s", entry->value, expect, input); + goto comp_end; + } + count++; + } + comp_end: + if (split != NULL) { + cfl_utils_split_free(split); + } + va_end(ap); + return 0; +} + +void test_cfl_utils_split() +{ + compare_split_entry("aa,bb", ',', 2, CFL_FALSE, "aa","bb" ); + compare_split_entry("localhost:12345", ':', 2, CFL_FALSE, "localhost","12345" ); + compare_split_entry("https://fluentbit.io/announcements/", '/', -1, CFL_FALSE, "https:", "fluentbit.io","announcements" ); + + /* /proc/net/dev example */ + compare_split_entry("enp0s3: 1955136 1768 0 0 0 0 0 0 89362 931 0 0 0 0 0 0", + ' ', 256, CFL_FALSE, + "enp0s3:", "1955136", "1768", "0", "0", "0", "0", "0", "0", "89362", "931", "0", "0", "0", "0", "0", "0", "0"); + + /* filter_grep configuration */ + compare_split_entry("Regex test *a*", ' ', 3, CFL_FALSE, "Regex", "test", "*a*"); + + /* filter_modify configuration */ + compare_split_entry("Condition Key_Value_Does_Not_Equal cpustats KNOWN", ' ', 4, + CFL_FALSE, "Condition", "Key_Value_Does_Not_Equal", "cpustats", "KNOWN"); + + /* nginx_exporter_metrics example */ + compare_split_entry("Active connections: 1\nserver accepts handled requests\n 10 10 10\nReading: 0 Writing: 1 Waiting: 0", '\n', 4, + CFL_FALSE, "Active connections: 1", "server accepts handled requests", " 10 10 10","Reading: 0 Writing: 1 Waiting: 0"); + + /* out_cloudwatch_logs example */ + compare_split_entry("dimension_1,dimension_2;dimension_3", ';', 256, + CFL_FALSE, "dimension_1,dimension_2", "dimension_3"); + /* separator is not contained */ + compare_split_entry("aa,bb", '/', 2, CFL_FALSE, "aa,bb"); + + /* do not parse quotes when tokenizing */ + compare_split_entry("aa \"bb cc\" dd", ' ', 256, CFL_FALSE, "aa", "\"bb", "cc\"", "dd"); +} + +void test_cfl_utils_split_quoted() +{ + /* Tokens quoted with "..." */ + compare_split_entry("aa \"double quote\" bb", ' ', 256, CFL_TRUE, "aa", "double quote", "bb"); + compare_split_entry("\"begin with double quote\" aa", ' ', 256, CFL_TRUE, "begin with double quote", "aa"); + compare_split_entry("aa \"end with double quote\"", ' ', 256, CFL_TRUE, "aa", "end with double quote"); + + /* Tokens quoted with '...' */ + compare_split_entry("aa bb 'single quote' cc", ' ', 256, CFL_TRUE, "aa", "bb", "single quote", "cc"); + compare_split_entry("'begin with single quote' aa", ' ', 256, CFL_TRUE, "begin with single quote", "aa"); + compare_split_entry("aa 'end with single quote'", ' ', 256, CFL_TRUE, "aa", "end with single quote"); + + /* Tokens surrounded by more than one separator character */ + compare_split_entry(" aa \" spaces bb \" cc ' spaces dd ' ff", ' ', 256, CFL_TRUE, + "aa", " spaces bb ", "cc", " spaces dd ", "ff"); + + /* Escapes within quoted token */ + compare_split_entry("aa \"escaped \\\" quote\" bb", ' ', 256, CFL_TRUE, "aa", "escaped \" quote", "bb"); + compare_split_entry("aa 'escaped \\' quote\' bb", ' ', 256, CFL_TRUE, "aa", "escaped \' quote", "bb"); + compare_split_entry("aa \"\\\"escaped balanced quotes\\\"\" bb", ' ', 256, CFL_TRUE, + "aa", "\"escaped balanced quotes\"", "bb"); + compare_split_entry("aa '\\'escaped balanced quotes\\'\' bb", ' ', 256, CFL_TRUE, + "aa", "'escaped balanced quotes'", "bb"); + compare_split_entry("aa 'escaped \\\\ escape\' bb", ' ', 256, CFL_TRUE, "aa", "escaped \\ escape", "bb"); + + /* Escapes that are not processed */ + compare_split_entry("\\\"aa bb", ' ', 256, CFL_TRUE, "\\\"aa", "bb"); + compare_split_entry("\\'aa bb", ' ', 256, CFL_TRUE, "\\'aa", "bb"); + compare_split_entry("\\\\aa bb", ' ', 256, CFL_TRUE, "\\\\aa", "bb"); + compare_split_entry("aa\\ bb", ' ', 256, CFL_TRUE, "aa\\", "bb"); +} + +void test_cfl_utils_split_quoted_errors() +{ + struct cfl_list *split = NULL; + + split = cfl_utils_split_quoted("aa \"unbalanced quotes should fail", ' ', 256); + TEST_CHECK(split == NULL); + split = cfl_utils_split_quoted("aa 'unbalanced quotes should fail", ' ', 256); + TEST_CHECK(split == NULL); +} + + +TEST_LIST = { + { "test_flb_utils_split", test_cfl_utils_split }, + { "test_flb_utils_split_quoted", test_cfl_utils_split_quoted}, + { "test_flb_utils_split_quoted_errors", test_cfl_utils_split_quoted_errors}, + { 0 } +}; diff --git a/lib/cfl/tests/version.c b/lib/cfl/tests/version.c new file mode 100644 index 00000000000..4d1510b82ad --- /dev/null +++ b/lib/cfl/tests/version.c @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* CFL + * === + * Copyright (C) 2022 The CFL Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "cfl_tests_internal.h" + +static void test_version() +{ + char *v; + + v = cfl_version(); + TEST_CHECK(v != NULL); + TEST_CHECK(strlen(v) >= 5); + + printf("CFL VERSION => '%s'\n", v); +} + +TEST_LIST = { + { "version" , test_version }, + { 0 } +};