diff --git a/configure.ac b/configure.ac index d0b6472c382..643b75241e2 100644 --- a/configure.ac +++ b/configure.ac @@ -109,6 +109,26 @@ AS_IF([${LN_S} --relative symlinktest 2>/dev/null], # AC_DEFINE_UNQUOTED([UCX_CONFIGURE_FLAGS], ["$config_flags"], [UCX configure flags]) +# +# File path for ucx conf +# ./configure is /etc/ucx.conf +# ./configure --prefix=/usr is /etc/ucx.conf +# ./configure --prefix=/usr/local is /usr/local/etc/ucx.conf +# ./configure --prefix=/usr/local --sysconfdir=/etc is /etc/ucx.conf +# +case "$prefix" in + NONE | /usr) + case "$sysconfdir" in + '${prefix}/etc') + sysconfdir=/etc + ;; + esac + ;; +esac + +SYSCONFDIR_TMP="`eval echo $sysconfdir`" +AC_SUBST([ucx_conf_file], [${SYSCONFDIR_TMP}/ucx.conf]) +AC_DEFINE_UNQUOTED([UCX_CONF_FILE], ["$ucx_conf_file"], [ucx conf file]) # # Provide the functionality of AS_VAR_APPEND if Autoconf does not have it. @@ -391,6 +411,7 @@ AC_MSG_NOTICE([Building documents only]) [ AC_MSG_NOTICE([UCX build configuration:]) AC_MSG_NOTICE([ Build prefix: ${prefix}]) +AC_MSG_NOTICE([ config file: ${ucx_conf_file}]) AC_MSG_NOTICE([Preprocessor flags: ${BASE_CPPFLAGS}]) AC_MSG_NOTICE([ C compiler: ${CC} ${BASE_CFLAGS}]) AC_MSG_NOTICE([ C++ compiler: ${CXX} ${BASE_CXXFLAGS}]) diff --git a/src/ucs/Makefile.am b/src/ucs/Makefile.am index 6386d089cb9..275a9cba727 100644 --- a/src/ucs/Makefile.am +++ b/src/ucs/Makefile.am @@ -20,6 +20,7 @@ nobase_dist_libucs_la_HEADERS = \ algorithm/qsort_r.h \ async/async_fwd.h \ config/global_opts.h \ + config/ini.h \ config/parser.h \ config/types.h \ datastruct/callbackq.h \ @@ -122,6 +123,7 @@ libucs_la_SOURCES = \ async/thread.c \ config/global_opts.c \ config/ucm_opts.c \ + config/ini.c \ config/parser.c \ datastruct/arbiter.c \ datastruct/callbackq.c \ diff --git a/src/ucs/config/ini.c b/src/ucs/config/ini.c new file mode 100644 index 00000000000..ab0b87bf210 --- /dev/null +++ b/src/ucs/config/ini.c @@ -0,0 +1,344 @@ +/* inih -- simple .INI file parser + +SPDX-License-Identifier: BSD-3-Clause + +Copyright (C) 2009-2020, Ben Hoyt + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +/* +An example without section +test.ini +########## +# Test config file for ini example +version = 0.0.1 # verison + name = jack # name +########## + +#include +#include +#include +#include +#include "ini.h" +typedef struct { + char* version; + char* name; +} config; +static int handler(void* user, const char* section, const char* name, + const char* value) { + config* conf = (config*)user; + #define MATCH(n) (strcmp(section, "") == 0 && strcmp(name, n) == 0) + if (MATCH("version")) { + conf->version = strdup(value); + } else if (MATCH("name")) { + conf->name = strdup(value); + } else { + return 0; + } + return 1; +} +int main() { + config conf; + if (ini_parse("test.ini", handler, &conf) < 0) { + printf("Can't load test.ini %s\n", strerror(errno)); + return 1; + } + printf("Config loaded from test.int: version=%s, name=%s\n", + conf.name, conf.version); + free(conf.version); + free(conf.name); + return 0; +} + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#if INI_CUSTOM_ALLOCATOR +#include +void* ini_malloc(size_t size); +void ini_free(void* ptr); +void* ini_realloc(void* ptr, size_t size); +#else +#include +#define ini_malloc malloc +#define ini_free free +#define ini_realloc realloc +#endif +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Used by ini_parse_string() to keep track of string parsing state. */ +typedef struct { + const char* ptr; + size_t num_left; +} ini_parse_string_ctx; + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to NUL at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Similar to strncpy, but ensures dest (size bytes) is + NUL-terminated, and doesn't pad with NULs. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ + size_t i; + for (i = 0; i < size - 1 && src[i]; i++) + dest[i] = src[i]; + dest[i] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; + int max_line = INI_MAX_LINE; +#else + char* line; + size_t max_line = INI_INITIAL_ALLOC; +#endif +#if INI_ALLOW_REALLOC && !INI_USE_STACK + char* new_line; + size_t offset; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)ini_malloc(INI_INITIAL_ALLOC); + if (!line) { + return -2; + } +#endif + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, (int)max_line, stream) != NULL) { +#if INI_ALLOW_REALLOC && !INI_USE_STACK + offset = strlen(line); + while (offset == max_line - 1 && line[offset - 1] != '\n') { + max_line *= 2; + if (max_line > INI_MAX_LINE) + max_line = INI_MAX_LINE; + new_line = ini_realloc(line, max_line); + if (!new_line) { + ini_free(line); + return -2; + } + line = new_line; + if (reader(line + offset, (int)(max_line - offset), stream) == NULL) + break; + if (max_line >= INI_MAX_LINE) + break; + offset += strlen(line + offset); + } +#endif + + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (strchr(INI_START_COMMENT_PREFIXES, *start)) { + /* Start-of-line comment */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!HANDLER(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; +#if INI_CALL_HANDLER_ON_NEW_SECTION + if (!HANDLER(user, section, NULL, NULL) && !error) + error = lineno; +#endif + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = end + 1; +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + value = lskip(value); + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ +#if INI_ALLOW_NO_VALUE + *end = '\0'; + name = rstrip(start); + if (!HANDLER(user, section, name, NULL) && !error) + error = lineno; +#else + error = lineno; +#endif + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + ini_free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} + +/* An ini_reader function to read the next line from a string buffer. This + is the fgets() equivalent used by ini_parse_string(). */ +static char* ini_reader_string(char* str, int num, void* stream) { + ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; + const char* ctx_ptr = ctx->ptr; + size_t ctx_num_left = ctx->num_left; + char* strp = str; + char c; + + if (ctx_num_left == 0 || num < 2) + return NULL; + + while (num > 1 && ctx_num_left != 0) { + c = *ctx_ptr++; + ctx_num_left--; + *strp++ = c; + if (c == '\n') + break; + num--; + } + + *strp = '\0'; + ctx->ptr = ctx_ptr; + ctx->num_left = ctx_num_left; + return str; +} + +/* See documentation in header file. */ +int ini_parse_string(const char* string, ini_handler handler, void* user) { + ini_parse_string_ctx ctx; + + ctx.ptr = string; + ctx.num_left = strlen(string); + return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, + user); +} diff --git a/src/ucs/config/ini.h b/src/ucs/config/ini.h new file mode 100644 index 00000000000..514af719941 --- /dev/null +++ b/src/ucs/config/ini.h @@ -0,0 +1,157 @@ +/* inih -- simple .INI file parser + +SPDX-License-Identifier: BSD-3-Clause + +Copyright (C) 2009-2020, Ben Hoyt + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef UCS_CONFIG_INI_H +#define UCS_CONFIG_INI_H + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Nonzero if ini_handler callback should accept lineno parameter. */ +#ifndef INI_HANDLER_LINENO +#define INI_HANDLER_LINENO 0 +#endif + +/* Typedef for prototype of handler function. */ +#if INI_HANDLER_LINENO +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value, + int lineno); +#else +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); +#endif + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O (see also + ini_parse_string). */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Same as ini_parse(), but takes a zero-terminated string with the INI data +instead of a file. Useful for parsing INI data from a network socket or +already in memory. */ +int ini_parse_string(const char* string, ini_handler handler, void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See https://github.com/benhoyt/inih/issues/21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Chars that begin a start-of-line comment. Per Python configparser, allow + both ; and # comments at the start of a line by default. */ +#ifndef INI_START_COMMENT_PREFIXES +#define INI_START_COMMENT_PREFIXES ";#" +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";#" +#endif + +/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Maximum line length for any line in INI file (stack or heap). Note that + this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +/* Nonzero to allow heap line buffer to grow via realloc(), zero for a + fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is + zero. */ +#ifndef INI_ALLOW_REALLOC +#define INI_ALLOW_REALLOC 0 +#endif + +/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK + is zero. */ +#ifndef INI_INITIAL_ALLOC +#define INI_INITIAL_ALLOC 200 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Nonzero to call the handler at the start of each new section (with + name and value NULL). Default is to only call the handler on + each name=value pair. */ +#ifndef INI_CALL_HANDLER_ON_NEW_SECTION +#define INI_CALL_HANDLER_ON_NEW_SECTION 0 +#endif + +/* Nonzero to allow a name without a value (no '=' or ':' on the line) and + call the handler with value NULL in this case. Default is to treat + no-value lines as an error. */ +#ifndef INI_ALLOW_NO_VALUE +#define INI_ALLOW_NO_VALUE 0 +#endif + +/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory + allocation functions (INI_USE_STACK must also be 0). These functions must + have the same signatures as malloc/free/realloc and behave in a similar + way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ +#ifndef INI_CUSTOM_ALLOCATOR +#define INI_CUSTOM_ALLOCATOR 0 +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* UCS_CONFIG_INI_H */ diff --git a/src/ucs/config/parser.c b/src/ucs/config/parser.c index c77638be119..7a9e31a9fa0 100644 --- a/src/ucs/config/parser.c +++ b/src/ucs/config/parser.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,8 @@ typedef UCS_CONFIG_ARRAY_FIELD(void, data) ucs_config_array_field_t; KHASH_SET_INIT_STR(ucs_config_env_vars) +KHASH_MAP_INIT_STR(ucs_config_file, char*) + /* Process environment variables */ extern char **environ; @@ -47,6 +50,7 @@ extern char **environ; UCS_LIST_HEAD(ucs_config_global_list); static khash_t(ucs_config_env_vars) ucs_config_parser_env_vars = {0}; static pthread_mutex_t ucs_config_parser_env_vars_hash_lock = PTHREAD_MUTEX_INITIALIZER; +static khash_t(ucs_config_file) ucs_config_parser_file_vars; const char *ucs_async_mode_names[] = { @@ -1090,6 +1094,47 @@ static void ucs_config_parser_mark_env_var_used(const char *name, int *added) pthread_mutex_unlock(&ucs_config_parser_env_vars_hash_lock); } +static int ucs_config_file_read_handler(void *user, const char *section, const char *name, const char *value) +{ + ucs_assert(strcmp(section, "") == 0); + int ret; + khiter_t khiter = kh_put(ucs_config_file, &ucs_config_parser_file_vars, ucs_strdup(name, "config_parser_file_key"), &ret); + if (ucs_unlikely(ret == -1)) { + ucs_error("ucs config parser: kh_put failed for %s=%s", name, value); + return 0; + } else { + /* if a config is repeated, the latter should override the previous. */ + kh_val(&ucs_config_parser_file_vars, khiter) = ucs_strdup(value, "config_parser_file_var"); + } + return 1; +} + +void ucs_config_file_parse(const char *conf_file, int override) +{ + static int visit = 0; + if (visit ==0 || override) { + pthread_mutex_lock(&ucs_config_parser_env_vars_hash_lock); + if (visit == 0 || override) { + /* if one config parse error, should not stop parser. */ + if (ini_parse(conf_file, ucs_config_file_read_handler, NULL) < 0) { + ucs_warn("ini_parse parser file failed: %s", conf_file); + } + visit = 1; + } + pthread_mutex_unlock(&ucs_config_parser_env_vars_hash_lock); + } +} + +static const char* ucx_config_file_get_value(const char *name) +{ + pthread_mutex_lock(&ucs_config_parser_env_vars_hash_lock); + khiter_t khiter = kh_get(ucs_config_file, &ucs_config_parser_file_vars, name); + const char *value = (khiter == kh_end(&ucs_config_parser_file_vars)) ? NULL : + kh_val(&ucs_config_parser_file_vars, khiter); + pthread_mutex_unlock(&ucs_config_parser_env_vars_hash_lock); + return value; +} + static ucs_status_t ucs_config_apply_env_vars(void *opts, ucs_config_field_t *fields, const char *prefix, const char *table_prefix, int recurse, int ignore_errors) @@ -1134,9 +1179,13 @@ static ucs_status_t ucs_config_apply_env_vars(void *opts, ucs_config_field_t *fi } else { /* Read and parse environment variable */ strncpy(buf + prefix_len, field->name, sizeof(buf) - prefix_len - 1); + /* environmental variavle prior to file config */ env_value = getenv(buf); if (env_value == NULL) { - continue; + env_value = ucx_config_file_get_value(buf); + if (env_value == NULL) { + continue; + } } ucs_config_parser_mark_env_var_used(buf, &added); @@ -1214,6 +1263,9 @@ ucs_status_t ucs_config_parser_fill_opts(void *opts, ucs_config_field_t *fields, goto err; } + /* file config should do before apply environment variables */ + ucs_config_file_parse(UCX_CONF_FILE, 0); + /* Apply environment variables */ if (sub_prefix != NULL) { status = ucs_config_apply_env_vars(opts, fields, sub_prefix, table_prefix, diff --git a/src/ucs/config/parser.h b/src/ucs/config/parser.h index 4135e3d7915..2112a3031e7 100644 --- a/src/ucs/config/parser.h +++ b/src/ucs/config/parser.h @@ -452,6 +452,15 @@ size_t ucs_config_memunits_get(size_t config_size, size_t auto_size, int ucs_config_names_search(ucs_config_names_array_t config_names, const char *str); +/** + * parse the value of ucx config file + * + * @param conf_file the ucx config filename + * @param override is override the forward config file or not, often 0 + * 1 only for gtest + */ +void ucs_config_file_parse(const char *conf_file, int override); + END_C_DECLS #endif diff --git a/test/gtest/ucs/test_config.cc b/test/gtest/ucs/test_config.cc index 8801cf0f2b8..2befad7946d 100644 --- a/test/gtest/ucs/test_config.cc +++ b/test/gtest/ucs/test_config.cc @@ -10,6 +10,8 @@ extern "C" { #include #include +#undef UCX_CONF_FILE +#define UCX_CONF_FILE "ucs/test_ucx.conf" } @@ -69,6 +71,10 @@ typedef struct { const char *model; color_t color; unsigned long vin; + const char *version; + unsigned length; + unsigned size; + double pi; double bw_bytes; double bw_kbytes; @@ -160,6 +166,18 @@ ucs_config_field_t car_opts_table[] = { {"VIN", "auto", "Vehicle identification number", ucs_offsetof(car_opts_t, vin), UCS_CONFIG_TYPE_ULUNITS}, + {"VERSION", "1.0.0", "test string file config TEST_FILE_VERSION", + ucs_offsetof(car_opts_t, version), UCS_CONFIG_TYPE_STRING}, + + {"LENGTH", "10", "test uint file config TEST_FILE_LENGTH", + ucs_offsetof(car_opts_t, length), UCS_CONFIG_TYPE_UINT}, + + {"SIZE", "20", "test uint file config TEST_FILE_SIZE", + ucs_offsetof(car_opts_t, size), UCS_CONFIG_TYPE_UINT}, + + {"PI", "3.14", "test double file config TEST_FILE_PI", + ucs_offsetof(car_opts_t, pi), UCS_CONFIG_TYPE_DOUBLE}, + {"BW_BYTES", "1024Bs", "Bandwidth in bytes", ucs_offsetof(car_opts_t, bw_bytes), UCS_CONFIG_TYPE_BW}, @@ -286,6 +304,7 @@ class test_config : public ucs::test { static car_opts_t parse(const char *env_prefix, const char *table_prefix) { car_opts_t tmp; + ucs_config_file_parse(UCX_CONF_FILE, 1); ucs_status_t status = ucs_config_parser_fill_opts(&tmp, car_opts_table, env_prefix, @@ -466,6 +485,22 @@ UCS_TEST_F(test_config, set_get_with_env_prefix) { std::string(opts.get("COLOR"))); } +UCS_TEST_F(test_config, set_get_with_env_and_file) { + /* default file env result + * TEST_FILE_VERSION "1.0.0" "1.2.3" x "1.2.3" + * TEST_FILE_LENGTH 10 100 1000 1000 + * TEST_FILE_SIZE 20 x x 20 + * TEST_FILE_PI 3.14 3.14159 x 3.14159 + * */ + ucs::scoped_setenv env1("TEST_FILE_LENGTH", "1000"); + + car_opts opts("TEST_FILE_", NULL); + EXPECT_EQ(std::string("1.2.3"), std::string(opts->version)); + EXPECT_EQ(1000, opts->length); + EXPECT_EQ(20, opts->size); + EXPECT_EQ(3.14159, opts->pi); +} + UCS_TEST_F(test_config, performance) { /* Add stuff to env to presumably make getenv() slower */ diff --git a/test/gtest/ucs/test_ucx.conf b/test/gtest/ucs/test_ucx.conf new file mode 100644 index 00000000000..25fcacc2386 --- /dev/null +++ b/test/gtest/ucs/test_ucx.conf @@ -0,0 +1,4 @@ +# Test config file for gtest +TEST_FILE_VERSION = 1.2.3 # char* +TEST_FILE_LENGTH = 100 # int +TEST_FILE_PI = 3.14159 # double