You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1082 lines
31 KiB

1 year ago
/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include <yaml.h>
#include "zbxmocktest.h"
#include "zbxmockdata.h"
#include "zbxstr.h"
#include "zbxnum.h"
#include "zbxalgo.h"
FILE *__real_fopen(const char *path, const char *mode);
int __real_fclose(FILE *stream);
static zbx_vector_ptr_t handle_pool; /* a place to store handles provided to mock data user */
static zbx_vector_str_t string_pool; /* a place to store strings provided to mock data user */
static yaml_document_t test_case; /* parsed YAML document with test case data */
static const yaml_node_t *root; /* the root document node */
static const yaml_node_t *in = NULL; /* pointer to "in" section of test case document */
static const yaml_node_t *out = NULL; /* pointer to "out" section of test case document */
static const yaml_node_t *db_data = NULL; /* pointer to "db data" section of test case document */
static const yaml_node_t *files = NULL; /* pointer to "files" section of test case document */
static const yaml_node_t *exit_code = NULL; /* pointer to "exit code" section of test case document */
typedef struct
{
const yaml_node_t *node; /* node of test_case document handle is associated with */
const yaml_node_item_t *item; /* current iterator position for vector handle */
}
zbx_mock_pool_handle_t;
typedef enum
{
ZBX_MOCK_IN, /* parameter from "in" section of test case data */
ZBX_MOCK_OUT, /* parameter from "out" section of test case data */
ZBX_MOCK_DB_DATA, /* data source from "db data" section of test case data */
ZBX_MOCK_FILES /* file contents from "files" section of test case data */
}
zbx_mock_parameter_t;
static const char *zbx_yaml_error_string(yaml_error_type_t error)
{
switch (error)
{
case YAML_NO_ERROR:
return "No error is produced.";
case YAML_MEMORY_ERROR:
return "Cannot allocate or reallocate a block of memory.";
case YAML_READER_ERROR:
return "Cannot read or decode the input stream.";
case YAML_SCANNER_ERROR:
return "Cannot scan the input stream.";
case YAML_PARSER_ERROR:
return "Cannot parse the input stream.";
case YAML_COMPOSER_ERROR:
return "Cannot compose a YAML document.";
case YAML_WRITER_ERROR:
return "Cannot write to the output stream.";
case YAML_EMITTER_ERROR:
return "Cannot emit a YAML stream.";
default:
return "Unknown error.";
}
}
static int zbx_yaml_scalar_cmp(const char *str, const yaml_node_t *node)
{
size_t len;
if (YAML_SCALAR_NODE != node->type)
fail_msg("Internal error: scalar comparison of nonscalar node.");
len = strlen(str);
ZBX_RETURN_IF_NOT_EQUAL(len, node->data.scalar.length);
return memcmp(str, node->data.scalar.value, len);
}
static int zbx_yaml_scalar_ncmp(const char *str, size_t len, const yaml_node_t *node)
{
if (YAML_SCALAR_NODE != node->type)
fail_msg("Internal error: scalar comparison of nonscalar node.");
if (len != node->data.scalar.length)
return -1;
return strncmp(str, (const char *)node->data.scalar.value, node->data.scalar.length);
}
static int zbx_yaml_add_node(yaml_document_t *dst_doc, yaml_document_t *src_doc, yaml_node_t *src)
{
int new_node, key, value;
yaml_node_pair_t *pair;
yaml_node_item_t *item;
switch (src->type)
{
case YAML_SCALAR_NODE:
new_node = yaml_document_add_scalar(dst_doc, src->tag, src->data.scalar.value,
src->data.scalar.length, src->data.scalar.style);
break;
case YAML_MAPPING_NODE:
new_node = yaml_document_add_mapping(dst_doc, src->tag, src->data.mapping.style);
for (pair = src->data.mapping.pairs.start; pair < src->data.mapping.pairs.top; pair++)
{
key = zbx_yaml_add_node(dst_doc, src_doc, yaml_document_get_node(src_doc, pair->key));
value = zbx_yaml_add_node(dst_doc, src_doc, yaml_document_get_node(src_doc, pair->value));
yaml_document_append_mapping_pair(dst_doc, new_node, key, value);
}
break;
case YAML_SEQUENCE_NODE:
new_node = yaml_document_add_sequence(dst_doc, src->tag, src->data.sequence.style);
for (item = src->data.sequence.items.start; item < src->data.sequence.items.top; item++)
{
value = zbx_yaml_add_node(dst_doc, src_doc, yaml_document_get_node(src_doc, *item));
yaml_document_append_sequence_item(dst_doc, new_node, value);
}
break;
case YAML_NO_NODE:
return -1;
}
return new_node;
}
static int zbx_yaml_include_file(yaml_document_t *dst_doc, const char *filename)
{
yaml_parser_t parser;
yaml_document_t doc;
yaml_node_t *src_root;
FILE *fp;
int index = -1;
if (NULL == (fp = __real_fopen(filename, "r")))
{
printf("Cannot open include file '%s': %s\n", filename, strerror(errno));
goto out;
}
yaml_parser_initialize(&parser);
yaml_parser_set_input_file(&parser, fp);
if (0 != yaml_parser_load(&parser, &doc) && NULL != (src_root = yaml_document_get_root_node(&doc)))
{
index = zbx_yaml_add_node(dst_doc, &doc, src_root);
}
else
printf("Cannot parse include file '%s'\n", filename);
__real_fclose(fp);
yaml_document_delete(&doc);
yaml_parser_delete(&parser);
out:
return index;
}
static void zbx_yaml_replace_node_rec(yaml_document_t *doc, yaml_node_t *parent, const char *old_value,
int new_index)
{
yaml_node_t *value_node;
if (YAML_MAPPING_NODE == parent->type)
{
yaml_node_pair_t *pair;
for (pair = parent->data.mapping.pairs.start; pair < parent->data.mapping.pairs.top; pair++)
{
value_node = yaml_document_get_node(doc, pair->value);
if (YAML_SCALAR_NODE == value_node->type && 0 == zbx_yaml_scalar_cmp(old_value, value_node))
pair->value = new_index;
else
zbx_yaml_replace_node_rec(doc, value_node, old_value, new_index);
}
}
else if (YAML_SEQUENCE_NODE == parent->type)
{
yaml_node_item_t *item;
for (item = parent->data.sequence.items.start; item < parent->data.sequence.items.top; item++)
{
value_node = yaml_document_get_node(doc, *item);
if (YAML_SCALAR_NODE == value_node->type && 0 == zbx_yaml_scalar_cmp(old_value, value_node))
*item = new_index;
else
zbx_yaml_replace_node_rec(doc, value_node, old_value, new_index);
}
}
}
/******************************************************************************
* *
* Purpose: replaces node occurrences in mappings and sequences with the new *
* node index *
* *
* Comments: When the test input data is converted by perl it loses anchor *
* information. As workaround we try to find the occurrences not by *
* old node index, but by old node value. With including the *
* possibility of other nodes having 'filename.inc.yaml' value is *
* practically non-existent. *
* *
******************************************************************************/
static void zbx_yaml_replace_node(yaml_document_t *doc, int old_index, int new_index)
{
char *value;
yaml_node_t *node, *parent;
if (NULL == (node = yaml_document_get_node(doc, old_index)))
return;
if (NULL == (parent = yaml_document_get_root_node(doc)))
return;
value = zbx_malloc(NULL, node->data.scalar.length + 1);
memcpy(value, node->data.scalar.value, node->data.scalar.length);
value[node->data.scalar.length] = '\0';
zbx_yaml_replace_node_rec(doc, parent, value, new_index);
zbx_free(value);
}
/******************************************************************************
* *
* Purpose: include file and add to include map *
* *
******************************************************************************/
static int zbx_yaml_add_include(yaml_document_t *doc, zbx_vector_uint64_pair_t *include_map, int index)
{
const yaml_node_t *node;
char filename[MAX_STRING_LEN];
int new_index;
zbx_uint64_pair_t pair;
node = yaml_document_get_node(doc, index);
if (YAML_SCALAR_NODE != node->type)
return -1;
memcpy(filename, node->data.scalar.value, node->data.scalar.length);
filename[node->data.scalar.length] = '\0';
if (-1 == (new_index = zbx_yaml_include_file(doc, filename)))
return -1;
pair.first = (zbx_uint64_t)index;
pair.second = (zbx_uint64_t)new_index;
zbx_vector_uint64_pair_append(include_map, pair);
return 0;
}
/******************************************************************************
* *
* Purpose: recursively include yaml documents from first level 'include' *
* mapping scalar value or sequence *
* *
******************************************************************************/
static int zbx_yaml_include(yaml_document_t *doc, int *index)
{
const yaml_node_t *node;
int i, ret = -1;
zbx_vector_uint64_pair_t include_map;
zbx_vector_uint64_pair_create(&include_map);
node = yaml_document_get_node(doc, *index);
if (YAML_SEQUENCE_NODE == node->type)
{
yaml_node_item_t *item;
zbx_vector_uint64_t indexes;
zbx_vector_uint64_create(&indexes);
for (item = node->data.sequence.items.start; item < node->data.sequence.items.top; item++)
zbx_vector_uint64_append(&indexes, (zbx_uint64_t)*item);
for (i = 0; i < indexes.values_num; i++)
{
if (-1 == zbx_yaml_add_include(doc, &include_map, (int)indexes.values[i]))
goto out;
}
zbx_vector_uint64_destroy(&indexes);
}
else
{
if (-1 == zbx_yaml_add_include(doc, &include_map, *index))
goto out;
}
for (i = 0; i < include_map.values_num; i++)
zbx_yaml_replace_node(doc, (int)include_map.values[i].first, (int)include_map.values[i].second);
/* re-acquire root node - after changes to the document */
/* the previously acquired node pointers are not valid */
root = yaml_document_get_root_node(&test_case);
ret = 0;
out:
zbx_vector_uint64_pair_destroy(&include_map);
return ret;
}
/******************************************************************************
* *
* Purpose: includes another yaml document if include tag is set *
* *
* Comments: The document is included by recursively copying its contents *
* under include tag, replacing its original value (file name). *
* The file is included from the working directory. *
* After modifying document (so after include) the previously *
* acquired yaml nodes are not guaranteed to be valid and must be *
* reinitialized. *
* *
******************************************************************************/
static int zbx_yaml_check_include(yaml_document_t *doc)
{
yaml_node_pair_t *pair;
root = yaml_document_get_root_node(doc);
if (YAML_MAPPING_NODE != root->type)
return -1;
for (pair = root->data.mapping.pairs.start; pair < root->data.mapping.pairs.top; pair++)
{
if (0 == zbx_yaml_scalar_cmp("include", yaml_document_get_node(doc, pair->key)))
return zbx_yaml_include(doc, &pair->value);
}
return 0;
}
static int zbx_mock_data_load_test_case(void)
{
const yaml_node_pair_t *pair;
if (-1 == zbx_yaml_check_include(&test_case))
return -1;
for (pair = root->data.mapping.pairs.start; pair < root->data.mapping.pairs.top; pair++)
{
const yaml_node_t *key;
key = yaml_document_get_node(&test_case, pair->key);
if (YAML_SCALAR_NODE == key->type)
{
if (0 == zbx_yaml_scalar_cmp("in", key))
{
in = yaml_document_get_node(&test_case, pair->value);
if (YAML_MAPPING_NODE != in->type)
{
printf("\"in\" is not a mapping.\n");
break;
}
}
else if (0 == zbx_yaml_scalar_cmp("out", key))
{
out = yaml_document_get_node(&test_case, pair->value);
if (YAML_MAPPING_NODE != out->type)
{
printf("\"out\" is not a mapping.\n");
break;
}
}
else if (0 == zbx_yaml_scalar_cmp("db data", key))
{
db_data = yaml_document_get_node(&test_case, pair->value);
if (YAML_MAPPING_NODE != db_data->type)
{
printf("\"db data\" is not a mapping.\n");
break;
}
}
else if (0 == zbx_yaml_scalar_cmp("files", key))
{
files = yaml_document_get_node(&test_case, pair->value);
if (YAML_MAPPING_NODE != files->type)
{
printf("\"files\" is not a mapping.\n");
break;
}
}
else if (0 == zbx_yaml_scalar_cmp("exit code", key))
{
exit_code = yaml_document_get_node(&test_case, pair->value);
if (YAML_SCALAR_NODE != exit_code->type)
{
printf("\"exit code\" is not a scalar.\n");
break;
}
if (0 != zbx_yaml_scalar_cmp("success", exit_code) &&
0 != zbx_yaml_scalar_cmp("failure", exit_code))
{
printf("Invalid value \"%.*s\" of"
" \"exit code\".\n",
(int)exit_code->data.scalar.length,
exit_code->data.scalar.value);
break;
}
}
else if (0 != zbx_yaml_scalar_cmp("test case", key) &&
0 != zbx_yaml_scalar_cmp("include", key))
{
printf("Unexpected key \"%.*s\" in mapping.\n",
(int)key->data.scalar.length,
key->data.scalar.value);
break;
}
continue;
}
else
printf("Non-scalar key in mapping.\n");
break;
}
if (pair < root->data.mapping.pairs.top)
return -1;
zbx_vector_ptr_create(&handle_pool);
zbx_vector_str_create(&string_pool);
return 0;
}
static int zbx_mock_data_load(yaml_parser_t *parser)
{
yaml_document_t tmp;
int ret = -1;
if (NULL == (root = yaml_document_get_root_node(&test_case)))
{
printf("Stream contains no documents.\n");
return -1;
}
if (YAML_MAPPING_NODE != root->type)
{
printf("Document is not a mapping.\n");
return -1;
}
if (0 == yaml_parser_load(parser, &tmp))
{
printf("Cannot parse input: %s\n", zbx_yaml_error_string(parser->error));
return -1;
}
if (NULL == yaml_document_get_root_node(&tmp))
ret = zbx_mock_data_load_test_case();
else
printf("Stream contains multiple documents.\n");
yaml_document_delete(&tmp);
return ret;
}
/* TODO: validate that keys in "in", "out", "db data" are scalars; validate "db data" */
int zbx_mock_data_init(void **state)
{
yaml_parser_t parser;
int ret;
ZBX_UNUSED(state);
yaml_parser_initialize(&parser);
yaml_parser_set_input_file(&parser, stdin);
if (0 == yaml_parser_load(&parser, &test_case))
{
printf("Cannot parse input: %s\n", zbx_yaml_error_string(parser.error));
return -1;
}
ret = zbx_mock_data_load(&parser);
yaml_parser_delete(&parser);
return ret;
}
static zbx_mock_handle_t zbx_mock_handle_alloc(const yaml_node_t *node)
{
zbx_mock_handle_t handleid;
zbx_mock_pool_handle_t *handle = NULL;
handleid = (zbx_mock_handle_t)handle_pool.values_num;
handle = zbx_malloc(handle, sizeof(zbx_mock_pool_handle_t));
handle->node = node;
handle->item = (YAML_SEQUENCE_NODE == node->type ? node->data.sequence.items.start : NULL);
zbx_vector_ptr_append(&handle_pool, handle);
return handleid;
}
int zbx_mock_data_free(void **state)
{
ZBX_UNUSED(state);
zbx_vector_str_clear_ext(&string_pool, zbx_str_free);
zbx_vector_ptr_clear_ext(&handle_pool, zbx_ptr_free);
zbx_vector_str_destroy(&string_pool);
zbx_vector_ptr_destroy(&handle_pool);
yaml_document_delete(&test_case);
return 0;
}
const char *zbx_mock_error_string(zbx_mock_error_t error)
{
switch (error)
{
case ZBX_MOCK_SUCCESS:
return "No error, actually.";
case ZBX_MOCK_INVALID_HANDLE:
return "Provided handle wasn't created properly or its lifetime has expired.";
case ZBX_MOCK_NO_PARAMETER:
return "No parameter with a given name available in test case data.";
case ZBX_MOCK_NO_EXIT_CODE:
return "No exit code provided in test case data.";
case ZBX_MOCK_NOT_AN_OBJECT:
return "Provided handle is not an object handle.";
case ZBX_MOCK_NO_SUCH_MEMBER:
return "Object has no member associated with provided key.";
case ZBX_MOCK_NOT_A_VECTOR:
return "Provided handle is not a vector handle.";
case ZBX_MOCK_END_OF_VECTOR:
return "Vector iteration reached its end.";
case ZBX_MOCK_NOT_A_STRING:
return "Provided handle is not a string handle.";
case ZBX_MOCK_INTERNAL_ERROR:
return "Internal error, please report to maintainers.";
case ZBX_MOCK_INVALID_YAML_PATH:
return "Invalid YAML path syntax.";
case ZBX_MOCK_NOT_A_BINARY:
return "Provided handle is not a binary string.";
case ZBX_MOCK_NOT_AN_UINT64:
return "Provided handle is not an unsigned 64 bit integer handle.";
case ZBX_MOCK_NOT_A_FLOAT:
return "Provided handle is not a floating point number handle.";
case ZBX_MOCK_NOT_A_TIMESTAMP:
return "Invalid timestamp format.";
case ZBX_MOCK_NOT_ENOUGH_MEMORY:
return "Not enough space in output buffer.";
case ZBX_MOCK_NOT_AN_INT:
return "Provided handle is not a integer handle.";
default:
return "Unknown error.";
}
}
static zbx_mock_error_t zbx_mock_builtin_parameter(zbx_mock_parameter_t type, const char *name, zbx_mock_handle_t *parameter)
{
const yaml_node_t *source;
const yaml_node_pair_t *pair;
switch (type)
{
case ZBX_MOCK_IN:
source = in;
break;
case ZBX_MOCK_OUT:
source = out;
break;
case ZBX_MOCK_DB_DATA:
source = db_data;
break;
case ZBX_MOCK_FILES:
source = files;
break;
default:
return ZBX_MOCK_INTERNAL_ERROR;
}
if (NULL == source)
return ZBX_MOCK_NO_PARAMETER;
if (YAML_MAPPING_NODE != source->type)
return ZBX_MOCK_INTERNAL_ERROR;
for (pair = source->data.mapping.pairs.start; pair < source->data.mapping.pairs.top; pair++)
{
const yaml_node_t *key;
key = yaml_document_get_node(&test_case, pair->key);
if (YAML_SCALAR_NODE != key->type)
return ZBX_MOCK_INTERNAL_ERROR;
if (0 == zbx_yaml_scalar_cmp(name, key))
{
*parameter = zbx_mock_handle_alloc(yaml_document_get_node(&test_case, pair->value));
return ZBX_MOCK_SUCCESS;
}
}
return ZBX_MOCK_NO_PARAMETER;
}
zbx_mock_error_t zbx_mock_in_parameter(const char *name, zbx_mock_handle_t *parameter)
{
return zbx_mock_builtin_parameter(ZBX_MOCK_IN, name, parameter);
}
zbx_mock_error_t zbx_mock_out_parameter(const char *name, zbx_mock_handle_t *parameter)
{
return zbx_mock_builtin_parameter(ZBX_MOCK_OUT, name, parameter);
}
zbx_mock_error_t zbx_mock_db_rows(const char *data_source, zbx_mock_handle_t *rows)
{
return zbx_mock_builtin_parameter(ZBX_MOCK_DB_DATA, data_source, rows);
}
zbx_mock_error_t zbx_mock_file(const char *path, zbx_mock_handle_t *file)
{
return zbx_mock_builtin_parameter(ZBX_MOCK_FILES, path, file);
}
zbx_mock_error_t zbx_mock_exit_code(int *status)
{
if (NULL == exit_code)
return ZBX_MOCK_NO_EXIT_CODE;
if (0 == zbx_yaml_scalar_cmp("success", exit_code))
*status = EXIT_SUCCESS;
else if (0 == zbx_yaml_scalar_cmp("failure", exit_code))
*status = EXIT_FAILURE;
else
return ZBX_MOCK_INTERNAL_ERROR;
return ZBX_MOCK_SUCCESS;
}
zbx_mock_error_t zbx_mock_object_member(zbx_mock_handle_t object, const char *name, zbx_mock_handle_t *member)
{
const zbx_mock_pool_handle_t *handle;
const yaml_node_pair_t *pair;
if (0 > object || object >= (zbx_mock_handle_t)handle_pool.values_num)
return ZBX_MOCK_INVALID_HANDLE;
handle = handle_pool.values[object];
if (YAML_MAPPING_NODE != handle->node->type)
return ZBX_MOCK_NOT_AN_OBJECT;
for (pair = handle->node->data.mapping.pairs.start; pair < handle->node->data.mapping.pairs.top; pair++)
{
const yaml_node_t *key;
key = yaml_document_get_node(&test_case, pair->key);
if (YAML_SCALAR_NODE != key->type) /* deep validation that every key of every mapping in test */
continue; /* case document is scalar would be an overkill, just skip */
if (0 == zbx_yaml_scalar_cmp(name, key))
{
*member = zbx_mock_handle_alloc(yaml_document_get_node(&test_case, pair->value));
return ZBX_MOCK_SUCCESS;
}
}
return ZBX_MOCK_NO_SUCH_MEMBER;
}
zbx_mock_error_t zbx_mock_vector_element(zbx_mock_handle_t vector, zbx_mock_handle_t *element)
{
zbx_mock_pool_handle_t *handle;
if (0 > vector || vector >= handle_pool.values_num)
return ZBX_MOCK_INVALID_HANDLE;
handle = handle_pool.values[vector];
if (YAML_SEQUENCE_NODE != handle->node->type)
return ZBX_MOCK_NOT_A_VECTOR;
if (handle->item >= handle->node->data.sequence.items.top)
return ZBX_MOCK_END_OF_VECTOR;
*element = zbx_mock_handle_alloc(yaml_document_get_node(&test_case, *handle->item++));
return ZBX_MOCK_SUCCESS;
}
zbx_mock_error_t zbx_mock_string(zbx_mock_handle_t string, const char **value)
{
const zbx_mock_pool_handle_t *handle;
char *tmp = NULL;
if (0 > string || string >= handle_pool.values_num)
return ZBX_MOCK_INVALID_HANDLE;
handle = handle_pool.values[string];
if (YAML_SCALAR_NODE != handle->node->type ||
NULL != memchr(handle->node->data.scalar.value, '\0', handle->node->data.scalar.length))
{
return ZBX_MOCK_NOT_A_STRING;
}
tmp = zbx_malloc(tmp, handle->node->data.scalar.length + 1);
memcpy(tmp, handle->node->data.scalar.value, handle->node->data.scalar.length);
tmp[handle->node->data.scalar.length] = '\0';
zbx_vector_str_append(&string_pool, tmp);
*value = tmp;
return ZBX_MOCK_SUCCESS;
}
zbx_mock_error_t zbx_mock_binary(zbx_mock_handle_t binary, const char **value, size_t *length)
{
const zbx_mock_pool_handle_t *handle;
char *tmp, *dst;
const char *src;
size_t i;
if (0 > binary || binary >= handle_pool.values_num)
return ZBX_MOCK_INVALID_HANDLE;
handle = handle_pool.values[binary];
if (YAML_SCALAR_NODE != handle->node->type)
return ZBX_MOCK_NOT_A_BINARY;
src = (char*)handle->node->data.scalar.value;
dst = tmp = zbx_malloc(NULL, handle->node->data.scalar.length);
for (i = 0; i < handle->node->data.scalar.length; i++)
{
if ('\\' == src[i])
{
if (i + 3 >= handle->node->data.scalar.length || 'x' != src[i + 1] ||
SUCCEED != zbx_is_hex_n_range(&src[i + 2], 2, dst, sizeof(char), 0, 0xff))
{
zbx_free(tmp);
return ZBX_MOCK_NOT_A_BINARY;
}
dst++;
i += 3;
}
else
*dst++ = src[i];
}
zbx_vector_str_append(&string_pool, tmp);
*value = tmp;
*length = dst - tmp;
return ZBX_MOCK_SUCCESS;
}
static zbx_mock_error_t zbx_yaml_path_next(const char **pnext, const char **key, int *key_len, int *index)
{
const char *next = *pnext;
size_t pos;
char quotes;
while ('.' == *next)
next++;
/* process dot notation component */
if ('[' != *next)
{
*key = next;
while (0 != isalnum(*next) || '_' == *next)
next++;
if (*key == next)
return ZBX_MOCK_INVALID_YAML_PATH;
*key_len = next - *key;
if ('\0' != *next && '.' != *next && '[' != *next)
return ZBX_MOCK_INVALID_YAML_PATH;
*pnext = next;
*index = 0;
return ZBX_MOCK_SUCCESS;
}
while (*(++next) == ' ')
;
/* process array index component */
if (0 != isdigit(*next))
{
for (pos = 1; 0 != isdigit(next[pos]); pos++)
;
*key = next;
*key_len = pos;
next += pos;
while (*next == ' ')
next++;
if (']' != *next++)
return ZBX_MOCK_INVALID_YAML_PATH;
*pnext = next;
*index = 1;
return ZBX_MOCK_SUCCESS;
}
/* process bracket notation component */
if ('\'' != *next && '"' != *next)
return ZBX_MOCK_INVALID_YAML_PATH;
*key = next + 1;
for (quotes = *next++; quotes != *next; next++)
{
if ('\0' == *next)
return ZBX_MOCK_INVALID_YAML_PATH;
}
if (*key == next)
return ZBX_MOCK_INVALID_YAML_PATH;
*key_len = next - *key;
while (*(++next) == ' ')
;
if (']' != *next++)
return ZBX_MOCK_INVALID_YAML_PATH;
*pnext = next;
*index = 0;
return ZBX_MOCK_SUCCESS;
}
static zbx_mock_error_t zbx_mock_parameter_rec(const yaml_node_t *node, const char *path, zbx_mock_handle_t *parameter)
{
const yaml_node_pair_t *pair;
const char *pnext = path, *key_name;
int err, key_len, index;
/* end of the path, return whatever has been found */
if ('\0' == *pnext)
{
if (NULL != parameter)
*parameter = zbx_mock_handle_alloc(node);
return ZBX_MOCK_SUCCESS;
}
if (ZBX_MOCK_SUCCESS != (err = zbx_yaml_path_next(&pnext, &key_name, &key_len, &index)))
return err;
/* the path component is array index, attempt to extract sequence element */
if (0 != index)
{
const yaml_node_t *element;
if (YAML_SEQUENCE_NODE != node->type)
return ZBX_MOCK_NOT_A_VECTOR;
index = atoi(key_name);
if (0 > index || index >= (node->data.sequence.items.top - node->data.sequence.items.start))
return ZBX_MOCK_END_OF_VECTOR;
element = yaml_document_get_node(&test_case, node->data.sequence.items.start[index]);
return zbx_mock_parameter_rec(element, pnext, parameter);
}
/* the patch component is object key, attempt to extract object member */
if (YAML_MAPPING_NODE != node->type)
return ZBX_MOCK_NOT_AN_OBJECT;
for (pair = node->data.mapping.pairs.start; pair < node->data.mapping.pairs.top; pair++)
{
const yaml_node_t *key, *value;
key = yaml_document_get_node(&test_case, pair->key);
if (0 == zbx_yaml_scalar_ncmp(key_name, key_len, key))
{
value = yaml_document_get_node(&test_case, pair->value);
return zbx_mock_parameter_rec(value, pnext, parameter);
}
}
return ZBX_MOCK_NO_SUCH_MEMBER;
}
zbx_mock_error_t zbx_mock_parameter(const char *path, zbx_mock_handle_t *parameter)
{
return zbx_mock_parameter_rec(root, path, parameter);
}
zbx_mock_error_t zbx_mock_parameter_exists(const char *path)
{
return zbx_mock_parameter_rec(root, path, NULL);
}
zbx_mock_error_t zbx_mock_uint64(zbx_mock_handle_t object, zbx_uint64_t *value)
{
const zbx_mock_pool_handle_t *handle;
if (0 > object || object >= handle_pool.values_num)
return ZBX_MOCK_INVALID_HANDLE;
handle = handle_pool.values[object];
if (YAML_SCALAR_NODE != handle->node->type || ZBX_MAX_UINT64_LEN < handle->node->data.scalar.length)
return ZBX_MOCK_NOT_AN_UINT64;
if (SUCCEED != zbx_is_uint64_n((const char *)handle->node->data.scalar.value, handle->node->data.scalar.length,
value))
{
return ZBX_MOCK_NOT_AN_UINT64;
}
return ZBX_MOCK_SUCCESS;
}
zbx_mock_error_t zbx_mock_float(zbx_mock_handle_t object, double *value)
{
const zbx_mock_pool_handle_t *handle;
char *tmp = NULL;
zbx_mock_error_t res = ZBX_MOCK_SUCCESS;
if (0 > object || object >= handle_pool.values_num)
return ZBX_MOCK_INVALID_HANDLE;
handle = handle_pool.values[object];
if (YAML_SCALAR_NODE != handle->node->type)
return ZBX_MOCK_NOT_A_FLOAT;
tmp = zbx_malloc(tmp, handle->node->data.scalar.length + 1);
memcpy(tmp, handle->node->data.scalar.value, handle->node->data.scalar.length);
tmp[handle->node->data.scalar.length] = '\0';
if (SUCCEED != zbx_is_double(tmp, value))
res = ZBX_MOCK_NOT_A_FLOAT;
zbx_free(tmp);
return res;
}
zbx_mock_error_t zbx_mock_int(zbx_mock_handle_t object, int *value)
{
const zbx_mock_pool_handle_t *handle;
char *tmp = NULL, *ptr;
zbx_mock_error_t res = ZBX_MOCK_SUCCESS;
if (0 > object || object >= handle_pool.values_num)
return ZBX_MOCK_INVALID_HANDLE;
handle = handle_pool.values[object];
if (YAML_SCALAR_NODE != handle->node->type)
return ZBX_MOCK_NOT_AN_INT;
tmp = zbx_malloc(tmp, handle->node->data.scalar.length + 1);
memcpy(tmp, handle->node->data.scalar.value, handle->node->data.scalar.length);
tmp[handle->node->data.scalar.length] = '\0';
ptr = tmp;
if ('-' == *ptr)
ptr++;
if (SUCCEED != zbx_is_uint31(ptr, value))
res = ZBX_MOCK_NOT_AN_INT;
if (ptr != tmp)
*value = -(*value);
zbx_free(tmp);
return res;
}
/******************************************************************************
* *
* Purpose: return string object contents *
* *
* Comments: The object can be either scalar value or a mapping. In the first *
* case the scalar value is returned. In the other case the string *
* is assembled from the object properties: *
* <header> + <page> * <pages> + <footer> *
* (<header>, <page>, <pages>, <footer> properties are optional and *
* by default are empty except pages, which is 1 by default). *
* *
******************************************************************************/
zbx_mock_error_t zbx_mock_string_ex(zbx_mock_handle_t hobject, const char **value)
{
zbx_mock_error_t err;
zbx_mock_handle_t handle;
char *tmp = NULL;
size_t tmp_alloc = 0, tmp_offset = 0;
/* if the object is string - return the value */
if (ZBX_MOCK_SUCCESS == (err = zbx_mock_string(hobject, value)))
return ZBX_MOCK_SUCCESS;
if (ZBX_MOCK_SUCCESS == zbx_mock_object_member(hobject, "header", &handle))
{
const char *header;
if (ZBX_MOCK_SUCCESS != (err = zbx_mock_string(handle, &header)))
fail_msg("Cannot read header property: %s", zbx_mock_error_string(err));
zbx_strcpy_alloc(&tmp, &tmp_alloc, &tmp_offset, header);
}
if (ZBX_MOCK_SUCCESS == zbx_mock_object_member(hobject, "page", &handle))
{
const char *page;
int i, pages_num = 1;
if (ZBX_MOCK_SUCCESS != (err = zbx_mock_string(handle, &page)))
fail_msg("Cannot read page property: %s", zbx_mock_error_string(err));
if (ZBX_MOCK_SUCCESS == zbx_mock_object_member(hobject, "pages", &handle))
{
const char *pages;
if (ZBX_MOCK_SUCCESS != (err = zbx_mock_string(handle, &pages)))
fail_msg("Cannot read pages property: %s", zbx_mock_error_string(err));
pages_num = atoi(pages);
}
for (i = 0; i < pages_num; i++)
zbx_strcpy_alloc(&tmp, &tmp_alloc, &tmp_offset, page);
}
if (ZBX_MOCK_SUCCESS == zbx_mock_object_member(hobject, "footer", &handle))
{
const char *footer;
if (ZBX_MOCK_SUCCESS != (err = zbx_mock_string(handle, &footer)))
fail_msg("Cannot read footer property: %s", zbx_mock_error_string(err));
zbx_strcpy_alloc(&tmp, &tmp_alloc, &tmp_offset, footer);
}
zbx_vector_str_append(&string_pool, tmp);
*value = tmp;
return ZBX_MOCK_SUCCESS;
}