diff options
Diffstat (limited to 'exporting/tests')
-rw-r--r-- | exporting/tests/Makefile.am | 4 | ||||
-rw-r--r-- | exporting/tests/exporting_doubles.c | 395 | ||||
-rw-r--r-- | exporting/tests/exporting_fixtures.c | 163 | ||||
-rw-r--r-- | exporting/tests/netdata_doubles.c | 247 | ||||
-rw-r--r-- | exporting/tests/system_doubles.c | 61 | ||||
-rw-r--r-- | exporting/tests/test_exporting_engine.c | 1939 | ||||
-rw-r--r-- | exporting/tests/test_exporting_engine.h | 203 |
7 files changed, 3012 insertions, 0 deletions
diff --git a/exporting/tests/Makefile.am b/exporting/tests/Makefile.am new file mode 100644 index 00000000..babdcf0d --- /dev/null +++ b/exporting/tests/Makefile.am @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in diff --git a/exporting/tests/exporting_doubles.c b/exporting/tests/exporting_doubles.c new file mode 100644 index 00000000..3c73e032 --- /dev/null +++ b/exporting/tests/exporting_doubles.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "test_exporting_engine.h" + +struct engine *__real_read_exporting_config(); +struct engine *__wrap_read_exporting_config() +{ + function_called(); + return mock_ptr_type(struct engine *); +} + +struct engine *__mock_read_exporting_config() +{ + struct engine *engine = calloc(1, sizeof(struct engine)); + engine->config.hostname = strdupz("test_engine_host"); + engine->config.update_every = 3; + + + engine->instance_root = calloc(1, sizeof(struct instance)); + struct instance *instance = engine->instance_root; + instance->engine = engine; + instance->config.type = EXPORTING_CONNECTOR_TYPE_GRAPHITE; + instance->config.name = strdupz("instance_name"); + instance->config.destination = strdupz("localhost"); + instance->config.prefix = strdupz("netdata"); + instance->config.hostname = strdupz("test-host"); + instance->config.update_every = 1; + instance->config.buffer_on_failures = 10; + instance->config.timeoutms = 10000; + instance->config.charts_pattern = simple_pattern_create("*", NULL, SIMPLE_PATTERN_EXACT); + instance->config.hosts_pattern = simple_pattern_create("*", NULL, SIMPLE_PATTERN_EXACT); + instance->config.options = EXPORTING_SOURCE_DATA_AS_COLLECTED | EXPORTING_OPTION_SEND_NAMES; + + return engine; +} + +int __real_init_connectors(struct engine *engine); +int __wrap_init_connectors(struct engine *engine) +{ + function_called(); + check_expected_ptr(engine); + return mock_type(int); +} + +int __real_mark_scheduled_instances(struct engine *engine); +int __wrap_mark_scheduled_instances(struct engine *engine) +{ + function_called(); + check_expected_ptr(engine); + return mock_type(int); +} + +calculated_number __real_exporting_calculate_value_from_stored_data( + struct instance *instance, + RRDDIM *rd, + time_t *last_timestamp); +calculated_number __wrap_exporting_calculate_value_from_stored_data( + struct instance *instance, + RRDDIM *rd, + time_t *last_timestamp) +{ + (void)instance; + (void)rd; + + *last_timestamp = 15052; + + function_called(); + return mock_type(calculated_number); +} + +int __real_prepare_buffers(struct engine *engine); +int __wrap_prepare_buffers(struct engine *engine) +{ + function_called(); + check_expected_ptr(engine); + return mock_type(int); +} + +void __wrap_create_main_rusage_chart(RRDSET **st_rusage, RRDDIM **rd_user, RRDDIM **rd_system) +{ + function_called(); + check_expected_ptr(st_rusage); + check_expected_ptr(rd_user); + check_expected_ptr(rd_system); +} + +void __wrap_send_main_rusage(RRDSET *st_rusage, RRDDIM *rd_user, RRDDIM *rd_system) +{ + function_called(); + check_expected_ptr(st_rusage); + check_expected_ptr(rd_user); + check_expected_ptr(rd_system); +} + +int __wrap_send_internal_metrics(struct instance *instance) +{ + function_called(); + check_expected_ptr(instance); + return mock_type(int); +} + +int __wrap_rrdhost_is_exportable(struct instance *instance, RRDHOST *host) +{ + function_called(); + check_expected_ptr(instance); + check_expected_ptr(host); + return mock_type(int); +} + +int __wrap_rrdset_is_exportable(struct instance *instance, RRDSET *st) +{ + function_called(); + check_expected_ptr(instance); + check_expected_ptr(st); + return mock_type(int); +} + +int __mock_start_batch_formatting(struct instance *instance) +{ + function_called(); + check_expected_ptr(instance); + return mock_type(int); +} + +int __mock_start_host_formatting(struct instance *instance, RRDHOST *host) +{ + function_called(); + check_expected_ptr(instance); + check_expected_ptr(host); + return mock_type(int); +} + +int __mock_start_chart_formatting(struct instance *instance, RRDSET *st) +{ + function_called(); + check_expected_ptr(instance); + check_expected_ptr(st); + return mock_type(int); +} + +int __mock_metric_formatting(struct instance *instance, RRDDIM *rd) +{ + function_called(); + check_expected_ptr(instance); + check_expected_ptr(rd); + return mock_type(int); +} + +int __mock_end_chart_formatting(struct instance *instance, RRDSET *st) +{ + function_called(); + check_expected_ptr(instance); + check_expected_ptr(st); + return mock_type(int); +} + +int __mock_end_host_formatting(struct instance *instance, RRDHOST *host) +{ + function_called(); + check_expected_ptr(instance); + check_expected_ptr(host); + return mock_type(int); +} + +int __mock_end_batch_formatting(struct instance *instance) +{ + function_called(); + check_expected_ptr(instance); + return mock_type(int); +} + +int __wrap_simple_connector_end_batch(struct instance *instance) +{ + function_called(); + check_expected_ptr(instance); + return mock_type(int); +} + +#if ENABLE_PROMETHEUS_REMOTE_WRITE +void *__wrap_init_write_request() +{ + function_called(); + return mock_ptr_type(void *); +} + +void __wrap_add_host_info( + void *write_request_p, + const char *name, const char *instance, const char *application, const char *version, const int64_t timestamp) +{ + function_called(); + check_expected_ptr(write_request_p); + check_expected_ptr(name); + check_expected_ptr(instance); + check_expected_ptr(application); + check_expected_ptr(version); + check_expected(timestamp); +} + +void __wrap_add_label(void *write_request_p, char *key, char *value) +{ + function_called(); + check_expected_ptr(write_request_p); + check_expected_ptr(key); + check_expected_ptr(value); +} + +void __wrap_add_metric( + void *write_request_p, + const char *name, const char *chart, const char *family, const char *dimension, + const char *instance, const double value, const int64_t timestamp) +{ + function_called(); + check_expected_ptr(write_request_p); + check_expected_ptr(name); + check_expected_ptr(chart); + check_expected_ptr(family); + check_expected_ptr(dimension); + check_expected_ptr(instance); + check_expected(value); + check_expected(timestamp); +} +#endif // ENABLE_PROMETHEUS_REMOTE_WRITE + +#if HAVE_KINESIS +void __wrap_aws_sdk_init() +{ + function_called(); +} + +void __wrap_kinesis_init( + void *kinesis_specific_data_p, const char *region, const char *access_key_id, const char *secret_key, + const long timeout) +{ + function_called(); + check_expected_ptr(kinesis_specific_data_p); + check_expected_ptr(region); + check_expected_ptr(access_key_id); + check_expected_ptr(secret_key); + check_expected(timeout); +} + +void __wrap_kinesis_put_record( + void *kinesis_specific_data_p, const char *stream_name, const char *partition_key, const char *data, + size_t data_len) +{ + function_called(); + check_expected_ptr(kinesis_specific_data_p); + check_expected_ptr(stream_name); + check_expected_ptr(partition_key); + check_expected_ptr(data); + check_expected_ptr(data); + check_expected(data_len); +} + +int __wrap_kinesis_get_result(void *request_outcomes_p, char *error_message, size_t *sent_bytes, size_t *lost_bytes) +{ + function_called(); + check_expected_ptr(request_outcomes_p); + check_expected_ptr(error_message); + check_expected_ptr(sent_bytes); + check_expected_ptr(lost_bytes); + return mock_type(int); +} +#endif // HAVE_KINESIS + +#if ENABLE_EXPORTING_PUBSUB +int __wrap_pubsub_init( + void *pubsub_specific_data_p, char *error_message, const char *destination, const char *credentials_file, + const char *project_id, const char *topic_id) +{ + function_called(); + check_expected_ptr(pubsub_specific_data_p); + check_expected_ptr(error_message); + check_expected_ptr(destination); + check_expected_ptr(credentials_file); + check_expected_ptr(project_id); + check_expected_ptr(topic_id); + return mock_type(int); +} + +int __wrap_pubsub_add_message(void *pubsub_specific_data_p, char *data) +{ + function_called(); + check_expected_ptr(pubsub_specific_data_p); + check_expected_ptr(data); + return mock_type(int); +} + +int __wrap_pubsub_publish( + void *pubsub_specific_data_p, char *error_message, size_t buffered_metrics, size_t buffered_bytes) +{ + function_called(); + check_expected_ptr(pubsub_specific_data_p); + check_expected_ptr(error_message); + check_expected(buffered_metrics); + check_expected(buffered_bytes); + return mock_type(int); +} + +int __wrap_pubsub_get_result( + void *pubsub_specific_data_p, char *error_message, + size_t *sent_metrics, size_t *sent_bytes, size_t *lost_metrics, size_t *lost_bytes) +{ + function_called(); + check_expected_ptr(pubsub_specific_data_p); + check_expected_ptr(error_message); + check_expected_ptr(sent_metrics); + check_expected_ptr(sent_bytes); + check_expected_ptr(lost_metrics); + check_expected_ptr(lost_bytes); + return mock_type(int); +} +#endif // ENABLE_EXPORTING_PUBSUB + +#if HAVE_MONGOC +void __wrap_mongoc_init() +{ + function_called(); +} + +mongoc_uri_t * __wrap_mongoc_uri_new_with_error (const char *uri_string, bson_error_t *error) +{ + function_called(); + check_expected_ptr(uri_string); + check_expected_ptr(error); + return mock_ptr_type(mongoc_uri_t *); +} + +int32_t __wrap_mongoc_uri_get_option_as_int32(const mongoc_uri_t *uri, const char *option, int32_t fallback) +{ + function_called(); + check_expected_ptr(uri); + check_expected_ptr(option); + check_expected(fallback); + return mock_type(int32_t); +} + +bool __wrap_mongoc_uri_set_option_as_int32 (const mongoc_uri_t *uri, const char *option, int32_t value) +{ + function_called(); + check_expected_ptr(uri); + check_expected_ptr(option); + check_expected(value); + return mock_type(bool); +} + +mongoc_client_t * __wrap_mongoc_client_new_from_uri (const mongoc_uri_t *uri) +{ + function_called(); + check_expected_ptr(uri); + return mock_ptr_type(mongoc_client_t *); +} + +bool __wrap_mongoc_client_set_appname (mongoc_client_t *client, const char *appname) +{ + function_called(); + check_expected_ptr(client); + check_expected_ptr(appname); + return mock_type(bool); +} + +mongoc_collection_t * +__wrap_mongoc_client_get_collection(mongoc_client_t *client, const char *db, const char *collection) +{ + function_called(); + check_expected_ptr(client); + check_expected_ptr(db); + check_expected_ptr(collection); + return mock_ptr_type(mongoc_collection_t *); +} + +void __wrap_mongoc_uri_destroy (mongoc_uri_t *uri) +{ + function_called(); + check_expected_ptr(uri); +} + +bool __wrap_mongoc_collection_insert_many( + mongoc_collection_t *collection, + const bson_t **documents, + size_t n_documents, + const bson_t *opts, + bson_t *reply, + bson_error_t *error) +{ + function_called(); + check_expected_ptr(collection); + check_expected_ptr(documents); + check_expected(n_documents); + check_expected_ptr(opts); + check_expected_ptr(reply); + check_expected_ptr(error); + return mock_type(bool); +} +#endif // HAVE_MONGOC diff --git a/exporting/tests/exporting_fixtures.c b/exporting/tests/exporting_fixtures.c new file mode 100644 index 00000000..00bb0ed0 --- /dev/null +++ b/exporting/tests/exporting_fixtures.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "test_exporting_engine.h" + +int setup_configured_engine(void **state) +{ + struct engine *engine = __mock_read_exporting_config(); + engine->instance_root->data_is_ready = 1; + + *state = engine; + + return 0; +} + +int teardown_configured_engine(void **state) +{ + struct engine *engine = *state; + + struct instance *instance = engine->instance_root; + free((void *)instance->config.destination); + free((void *)instance->config.name); + free((void *)instance->config.prefix); + free((void *)instance->config.hostname); + simple_pattern_free(instance->config.charts_pattern); + simple_pattern_free(instance->config.hosts_pattern); + free(instance); + + free((void *)engine->config.hostname); + free(engine); + + return 0; +} + +int setup_rrdhost() +{ + localhost = calloc(1, sizeof(RRDHOST)); + + localhost->rrd_update_every = 1; + + localhost->tags = strdupz("TAG1=VALUE1 TAG2=VALUE2"); + + struct label *label = calloc(1, sizeof(struct label)); + label->key = strdupz("key1"); + label->value = strdupz("value1"); + label->label_source = LABEL_SOURCE_NETDATA_CONF; + localhost->labels.head = label; + + label = calloc(1, sizeof(struct label)); + label->key = strdupz("key2"); + label->value = strdupz("value2"); + label->label_source = LABEL_SOURCE_AUTO; + localhost->labels.head->next = label; + + localhost->rrdset_root = calloc(1, sizeof(RRDSET)); + RRDSET *st = localhost->rrdset_root; + st->rrdhost = localhost; + strcpy(st->id, "chart_id"); + st->name = strdupz("chart_name"); + st->flags |= RRDSET_FLAG_ENABLED; + st->rrd_memory_mode |= RRD_MEMORY_MODE_SAVE; + st->update_every = 1; + + localhost->rrdset_root->dimensions = calloc(1, sizeof(RRDDIM)); + RRDDIM *rd = localhost->rrdset_root->dimensions; + rd->rrdset = st; + rd->id = strdupz("dimension_id"); + rd->name = strdupz("dimension_name"); + rd->last_collected_value = 123000321; + rd->last_collected_time.tv_sec = 15051; + rd->collections_counter++; + rd->next = NULL; + + rd->state = calloc(1, sizeof(*rd->state)); + rd->state->query_ops.oldest_time = __mock_rrddim_query_oldest_time; + rd->state->query_ops.latest_time = __mock_rrddim_query_latest_time; + rd->state->query_ops.init = __mock_rrddim_query_init; + rd->state->query_ops.is_finished = __mock_rrddim_query_is_finished; + rd->state->query_ops.next_metric = __mock_rrddim_query_next_metric; + rd->state->query_ops.finalize = __mock_rrddim_query_finalize; + + return 0; +} + +int teardown_rrdhost() +{ + RRDDIM *rd = localhost->rrdset_root->dimensions; + free((void *)rd->name); + free((void *)rd->id); + free(rd->state); + free(rd); + + RRDSET *st = localhost->rrdset_root; + free((void *)st->name); + free(st); + + free(localhost->labels.head->next->key); + free(localhost->labels.head->next->value); + free(localhost->labels.head->next); + free(localhost->labels.head->key); + free(localhost->labels.head->value); + free(localhost->labels.head); + + free((void *)localhost->tags); + free(localhost); + + return 0; +} + +int setup_initialized_engine(void **state) +{ + setup_configured_engine(state); + + struct engine *engine = *state; + init_connectors_in_tests(engine); + + setup_rrdhost(); + + return 0; +} + +int teardown_initialized_engine(void **state) +{ + struct engine *engine = *state; + + teardown_rrdhost(); + buffer_free(engine->instance_root->labels); + buffer_free(engine->instance_root->buffer); + teardown_configured_engine(state); + + return 0; +} + +int setup_prometheus(void **state) +{ + (void)state; + + prometheus_exporter_instance = calloc(1, sizeof(struct instance)); + + setup_rrdhost(); + + prometheus_exporter_instance->config.update_every = 10; + + prometheus_exporter_instance->config.options |= + EXPORTING_OPTION_SEND_NAMES | EXPORTING_OPTION_SEND_CONFIGURED_LABELS | EXPORTING_OPTION_SEND_AUTOMATIC_LABELS; + + prometheus_exporter_instance->config.charts_pattern = simple_pattern_create("*", NULL, SIMPLE_PATTERN_EXACT); + prometheus_exporter_instance->config.hosts_pattern = simple_pattern_create("*", NULL, SIMPLE_PATTERN_EXACT); + + return 0; +} + +int teardown_prometheus(void **state) +{ + (void)state; + + teardown_rrdhost(); + + simple_pattern_free(prometheus_exporter_instance->config.charts_pattern); + simple_pattern_free(prometheus_exporter_instance->config.hosts_pattern); + free(prometheus_exporter_instance); + + return 0; +} diff --git a/exporting/tests/netdata_doubles.c b/exporting/tests/netdata_doubles.c new file mode 100644 index 00000000..f4da7769 --- /dev/null +++ b/exporting/tests/netdata_doubles.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "test_exporting_engine.h" + +// Use memomy allocation functions guarded by CMocka in strdupz +const char *__wrap_strdupz(const char *s) +{ + char *duplicate = malloc(sizeof(char) * (strlen(s) + 1)); + strcpy(duplicate, s); + + return duplicate; +} + +time_t __wrap_now_realtime_sec(void) +{ + function_called(); + return mock_type(time_t); +} + +void __wrap_uv_thread_set_name_np(uv_thread_t ut, const char* name) +{ + (void)ut; + (void)name; + + function_called(); +} + +void __wrap_info_int(const char *file, const char *function, const unsigned long line, const char *fmt, ...) +{ + (void)file; + (void)function; + (void)line; + + function_called(); + + va_list args; + + va_start(args, fmt); + vsnprintf(log_line, MAX_LOG_LINE, fmt, args); + va_end(args); +} + +int __wrap_connect_to_one_of( + const char *destination, + int default_port, + struct timeval *timeout, + size_t *reconnects_counter, + char *connected_to, + size_t connected_to_size) +{ + (void)timeout; + + function_called(); + + check_expected(destination); + check_expected_ptr(default_port); + // TODO: check_expected_ptr(timeout); + check_expected(reconnects_counter); + check_expected(connected_to); + check_expected(connected_to_size); + + return mock_type(int); +} + +void __rrdhost_check_rdlock(RRDHOST *host, const char *file, const char *function, const unsigned long line) +{ + (void)host; + (void)file; + (void)function; + (void)line; +} + +void __rrdset_check_rdlock(RRDSET *st, const char *file, const char *function, const unsigned long line) +{ + (void)st; + (void)file; + (void)function; + (void)line; +} + +void __rrd_check_rdlock(const char *file, const char *function, const unsigned long line) +{ + (void)file; + (void)function; + (void)line; +} + +RRDSET *rrdset_create_custom( + RRDHOST *host, + const char *type, + const char *id, + const char *name, + const char *family, + const char *context, + const char *title, + const char *units, + const char *plugin, + const char *module, + long priority, + int update_every, + RRDSET_TYPE chart_type, + RRD_MEMORY_MODE memory_mode, + long history_entries) +{ + check_expected_ptr(host); + check_expected_ptr(type); + check_expected_ptr(id); + check_expected_ptr(name); + check_expected_ptr(family); + check_expected_ptr(context); + UNUSED(title); + check_expected_ptr(units); + check_expected_ptr(plugin); + check_expected_ptr(module); + check_expected(priority); + check_expected(update_every); + check_expected(chart_type); + UNUSED(memory_mode); + UNUSED(history_entries); + + function_called(); + + return mock_ptr_type(RRDSET *); +} + +void rrdset_next_usec(RRDSET *st, usec_t microseconds) +{ + check_expected_ptr(st); + UNUSED(microseconds); + + function_called(); +} + +void rrdset_done(RRDSET *st) +{ + check_expected_ptr(st); + + function_called(); +} + +RRDDIM *rrddim_add_custom( + RRDSET *st, + const char *id, + const char *name, + collected_number multiplier, + collected_number divisor, + RRD_ALGORITHM algorithm, + RRD_MEMORY_MODE memory_mode) +{ + check_expected_ptr(st); + UNUSED(id); + check_expected_ptr(name); + check_expected(multiplier); + check_expected(divisor); + check_expected(algorithm); + UNUSED(memory_mode); + + function_called(); + + return NULL; +} + +collected_number rrddim_set_by_pointer(RRDSET *st, RRDDIM *rd, collected_number value) +{ + check_expected_ptr(st); + UNUSED(rd); + UNUSED(value); + + function_called(); + + return 0; +} + +const char *rrd_memory_mode_name(RRD_MEMORY_MODE id) +{ + (void)id; + return RRD_MEMORY_MODE_NONE_NAME; +} + +calculated_number rrdvar2number(RRDVAR *rv) +{ + (void)rv; + return 0; +} + +int foreach_host_variable_callback(RRDHOST *host, int (*callback)(RRDVAR *rv, void *data), void *data) +{ + (void)host; + (void)callback; + (void)data; + return 0; +} + +void rrdset_update_heterogeneous_flag(RRDSET *st) +{ + (void)st; +} + +time_t __mock_rrddim_query_oldest_time(RRDDIM *rd) +{ + (void)rd; + + function_called(); + return mock_type(time_t); +} + +time_t __mock_rrddim_query_latest_time(RRDDIM *rd) +{ + (void)rd; + + function_called(); + return mock_type(time_t); +} + +void __mock_rrddim_query_init(RRDDIM *rd, struct rrddim_query_handle *handle, time_t start_time, time_t end_time) +{ + (void)rd; + (void)handle; + + function_called(); + check_expected(start_time); + check_expected(end_time); +} + +int __mock_rrddim_query_is_finished(struct rrddim_query_handle *handle) +{ + (void)handle; + + function_called(); + return mock_type(int); +} + +storage_number __mock_rrddim_query_next_metric(struct rrddim_query_handle *handle, time_t *current_time) +{ + (void)handle; + (void)current_time; + + function_called(); + return mock_type(storage_number); +} + +void __mock_rrddim_query_finalize(struct rrddim_query_handle *handle) +{ + (void)handle; + + function_called(); +} diff --git a/exporting/tests/system_doubles.c b/exporting/tests/system_doubles.c new file mode 100644 index 00000000..ca85800c --- /dev/null +++ b/exporting/tests/system_doubles.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "test_exporting_engine.h" + +void __wrap_uv_thread_create(uv_thread_t thread, void (*worker)(void *arg), void *arg) +{ + function_called(); + + check_expected_ptr(thread); + check_expected_ptr(worker); + check_expected_ptr(arg); +} + +void __wrap_uv_mutex_lock(uv_mutex_t *mutex) +{ + (void)mutex; +} + +void __wrap_uv_mutex_unlock(uv_mutex_t *mutex) +{ + (void)mutex; +} + +void __wrap_uv_cond_signal(uv_cond_t *cond_var) +{ + (void)cond_var; +} + +void __wrap_uv_cond_wait(uv_cond_t *cond_var, uv_mutex_t *mutex) +{ + (void)cond_var; + (void)mutex; +} + +ssize_t __wrap_recv(int sockfd, void *buf, size_t len, int flags) +{ + function_called(); + + check_expected(sockfd); + check_expected_ptr(buf); + check_expected(len); + check_expected(flags); + + char *mock_string = "Test recv"; + strcpy(buf, mock_string); + + return strlen(mock_string); +} + +ssize_t __wrap_send(int sockfd, const void *buf, size_t len, int flags) +{ + function_called(); + + check_expected(sockfd); + check_expected_ptr(buf); + check_expected_ptr(buf); + check_expected(len); + check_expected(flags); + + return strlen(buf); +} diff --git a/exporting/tests/test_exporting_engine.c b/exporting/tests/test_exporting_engine.c new file mode 100644 index 00000000..774d1a26 --- /dev/null +++ b/exporting/tests/test_exporting_engine.c @@ -0,0 +1,1939 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "test_exporting_engine.h" +#include "libnetdata/required_dummies.h" + +RRDHOST *localhost; +netdata_rwlock_t rrd_rwlock; + +// global variables needed by read_exporting_config() +struct config netdata_config; +char *netdata_configured_user_config_dir = "."; +char *netdata_configured_stock_config_dir = "."; +char *netdata_configured_hostname = "test_global_host"; + +char log_line[MAX_LOG_LINE + 1]; + +BACKEND_OPTIONS global_backend_options = 0; +const char *global_backend_source = "average"; +const char *global_backend_prefix = "netdata"; + +void init_connectors_in_tests(struct engine *engine) +{ + expect_function_call(__wrap_now_realtime_sec); + will_return(__wrap_now_realtime_sec, 2); + + expect_function_call(__wrap_uv_thread_create); + + expect_value(__wrap_uv_thread_create, thread, &engine->instance_root->thread); + expect_value(__wrap_uv_thread_create, worker, simple_connector_worker); + expect_value(__wrap_uv_thread_create, arg, engine->instance_root); + + expect_function_call(__wrap_uv_thread_set_name_np); + + assert_int_equal(__real_init_connectors(engine), 0); + + assert_int_equal(engine->now, 2); + assert_int_equal(engine->instance_root->after, 2); +} + +static void test_exporting_engine(void **state) +{ + struct engine *engine = *state; + + expect_function_call(__wrap_read_exporting_config); + will_return(__wrap_read_exporting_config, engine); + + expect_function_call(__wrap_init_connectors); + expect_memory(__wrap_init_connectors, engine, engine, sizeof(struct engine)); + will_return(__wrap_init_connectors, 0); + + expect_function_call(__wrap_create_main_rusage_chart); + expect_not_value(__wrap_create_main_rusage_chart, st_rusage, NULL); + expect_not_value(__wrap_create_main_rusage_chart, rd_user, NULL); + expect_not_value(__wrap_create_main_rusage_chart, rd_system, NULL); + + expect_function_call(__wrap_now_realtime_sec); + will_return(__wrap_now_realtime_sec, 2); + + expect_function_call(__wrap_mark_scheduled_instances); + expect_memory(__wrap_mark_scheduled_instances, engine, engine, sizeof(struct engine)); + will_return(__wrap_mark_scheduled_instances, 1); + + expect_function_call(__wrap_prepare_buffers); + expect_memory(__wrap_prepare_buffers, engine, engine, sizeof(struct engine)); + will_return(__wrap_prepare_buffers, 0); + + expect_function_call(__wrap_send_main_rusage); + expect_value(__wrap_send_main_rusage, st_rusage, NULL); + expect_value(__wrap_send_main_rusage, rd_user, NULL); + expect_value(__wrap_send_main_rusage, rd_system, NULL); + + void *ptr = malloc(sizeof(struct netdata_static_thread)); + assert_ptr_equal(exporting_main(ptr), NULL); + assert_int_equal(engine->now, 2); + free(ptr); +} + +static void test_read_exporting_config(void **state) +{ + struct engine *engine = __mock_read_exporting_config(); // TODO: use real read_exporting_config() function + *state = engine; + + assert_ptr_not_equal(engine, NULL); + assert_string_equal(engine->config.hostname, "test_engine_host"); + assert_int_equal(engine->config.update_every, 3); + assert_int_equal(engine->instance_num, 0); + + + struct instance *instance = engine->instance_root; + assert_ptr_not_equal(instance, NULL); + assert_ptr_equal(instance->next, NULL); + assert_ptr_equal(instance->engine, engine); + assert_int_equal(instance->config.type, EXPORTING_CONNECTOR_TYPE_GRAPHITE); + assert_string_equal(instance->config.destination, "localhost"); + assert_string_equal(instance->config.prefix, "netdata"); + assert_int_equal(instance->config.update_every, 1); + assert_int_equal(instance->config.buffer_on_failures, 10); + assert_int_equal(instance->config.timeoutms, 10000); + assert_true(simple_pattern_matches(instance->config.charts_pattern, "any_chart")); + assert_true(simple_pattern_matches(instance->config.hosts_pattern, "anyt_host")); + assert_int_equal(instance->config.options, EXPORTING_SOURCE_DATA_AS_COLLECTED | EXPORTING_OPTION_SEND_NAMES); + + teardown_configured_engine(state); +} + +static void test_init_connectors(void **state) +{ + struct engine *engine = *state; + + init_connectors_in_tests(engine); + + assert_int_equal(engine->instance_num, 1); + + struct instance *instance = engine->instance_root; + + assert_ptr_equal(instance->next, NULL); + assert_int_equal(instance->index, 0); + + struct simple_connector_config *connector_specific_config = instance->config.connector_specific_config; + assert_int_equal(connector_specific_config->default_port, 2003); + + assert_ptr_equal(instance->worker, simple_connector_worker); + assert_ptr_equal(instance->start_batch_formatting, NULL); + assert_ptr_equal(instance->start_host_formatting, format_host_labels_graphite_plaintext); + assert_ptr_equal(instance->start_chart_formatting, NULL); + assert_ptr_equal(instance->metric_formatting, format_dimension_collected_graphite_plaintext); + assert_ptr_equal(instance->end_chart_formatting, NULL); + assert_ptr_equal(instance->end_host_formatting, flush_host_labels); + + BUFFER *buffer = instance->buffer; + assert_ptr_not_equal(buffer, NULL); + buffer_sprintf(buffer, "%s", "graphite test"); + assert_string_equal(buffer_tostring(buffer), "graphite test"); +} + +static void test_init_graphite_instance(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->config.options = EXPORTING_SOURCE_DATA_AS_COLLECTED | EXPORTING_OPTION_SEND_NAMES; + assert_int_equal(init_graphite_instance(instance), 0); + assert_int_equal( + ((struct simple_connector_config *)(instance->config.connector_specific_config))->default_port, 2003); + freez(instance->config.connector_specific_config); + assert_ptr_equal(instance->metric_formatting, format_dimension_collected_graphite_plaintext); + assert_ptr_not_equal(instance->buffer, NULL); + buffer_free(instance->buffer); + + instance->config.options = EXPORTING_SOURCE_DATA_AVERAGE | EXPORTING_OPTION_SEND_NAMES; + assert_int_equal(init_graphite_instance(instance), 0); + assert_ptr_equal(instance->metric_formatting, format_dimension_stored_graphite_plaintext); +} + +static void test_init_json_instance(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->config.options = EXPORTING_SOURCE_DATA_AS_COLLECTED | EXPORTING_OPTION_SEND_NAMES; + assert_int_equal(init_json_instance(instance), 0); + assert_int_equal( + ((struct simple_connector_config *)(instance->config.connector_specific_config))->default_port, 5448); + freez(instance->config.connector_specific_config); + assert_ptr_equal(instance->metric_formatting, format_dimension_collected_json_plaintext); + assert_ptr_not_equal(instance->buffer, NULL); + buffer_free(instance->buffer); + + instance->config.options = EXPORTING_SOURCE_DATA_AVERAGE | EXPORTING_OPTION_SEND_NAMES; + assert_int_equal(init_json_instance(instance), 0); + assert_ptr_equal(instance->metric_formatting, format_dimension_stored_json_plaintext); +} + +static void test_init_opentsdb_telnet_instance(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->config.options = EXPORTING_SOURCE_DATA_AS_COLLECTED | EXPORTING_OPTION_SEND_NAMES; + assert_int_equal(init_opentsdb_telnet_instance(instance), 0); + assert_int_equal( + ((struct simple_connector_config *)(instance->config.connector_specific_config))->default_port, 4242); + freez(instance->config.connector_specific_config); + assert_ptr_equal(instance->metric_formatting, format_dimension_collected_opentsdb_telnet); + assert_ptr_not_equal(instance->buffer, NULL); + buffer_free(instance->buffer); + + instance->config.options = EXPORTING_SOURCE_DATA_AVERAGE | EXPORTING_OPTION_SEND_NAMES; + assert_int_equal(init_opentsdb_telnet_instance(instance), 0); + assert_ptr_equal(instance->metric_formatting, format_dimension_stored_opentsdb_telnet); +} + +static void test_init_opentsdb_http_instance(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->config.options = EXPORTING_SOURCE_DATA_AS_COLLECTED | EXPORTING_OPTION_SEND_NAMES; + assert_int_equal(init_opentsdb_http_instance(instance), 0); + assert_int_equal( + ((struct simple_connector_config *)(instance->config.connector_specific_config))->default_port, 4242); + freez(instance->config.connector_specific_config); + assert_ptr_equal(instance->metric_formatting, format_dimension_collected_opentsdb_http); + assert_ptr_not_equal(instance->buffer, NULL); + buffer_free(instance->buffer); + + instance->config.options = EXPORTING_SOURCE_DATA_AVERAGE | EXPORTING_OPTION_SEND_NAMES; + assert_int_equal(init_opentsdb_http_instance(instance), 0); + assert_ptr_equal(instance->metric_formatting, format_dimension_stored_opentsdb_http); +} + +static void test_mark_scheduled_instances(void **state) +{ + struct engine *engine = *state; + + assert_int_equal(__real_mark_scheduled_instances(engine), 1); + + struct instance *instance = engine->instance_root; + assert_int_equal(instance->scheduled, 1); + assert_int_equal(instance->before, 2); +} + +static void test_rrdhost_is_exportable(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + expect_function_call(__wrap_info_int); + + assert_ptr_equal(localhost->exporting_flags, NULL); + + assert_int_equal(__real_rrdhost_is_exportable(instance, localhost), 1); + + assert_string_equal(log_line, "enabled exporting of host 'localhost' for instance 'instance_name'"); + + assert_ptr_not_equal(localhost->exporting_flags, NULL); + assert_int_equal(localhost->exporting_flags[0], RRDHOST_FLAG_BACKEND_SEND); +} + +static void test_false_rrdhost_is_exportable(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + simple_pattern_free(instance->config.hosts_pattern); + instance->config.hosts_pattern = simple_pattern_create("!*", NULL, SIMPLE_PATTERN_EXACT); + + expect_function_call(__wrap_info_int); + + assert_ptr_equal(localhost->exporting_flags, NULL); + + assert_int_equal(__real_rrdhost_is_exportable(instance, localhost), 0); + + assert_string_equal(log_line, "disabled exporting of host 'localhost' for instance 'instance_name'"); + + assert_ptr_not_equal(localhost->exporting_flags, NULL); + assert_int_equal(localhost->exporting_flags[0], RRDHOST_FLAG_BACKEND_DONT_SEND); +} + +static void test_rrdset_is_exportable(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + RRDSET *st = localhost->rrdset_root; + + assert_ptr_equal(st->exporting_flags, NULL); + + assert_int_equal(__real_rrdset_is_exportable(instance, st), 1); + + assert_ptr_not_equal(st->exporting_flags, NULL); + assert_int_equal(st->exporting_flags[0], RRDSET_FLAG_BACKEND_SEND); +} + +static void test_false_rrdset_is_exportable(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + RRDSET *st = localhost->rrdset_root; + + simple_pattern_free(instance->config.charts_pattern); + instance->config.charts_pattern = simple_pattern_create("!*", NULL, SIMPLE_PATTERN_EXACT); + + assert_ptr_equal(st->exporting_flags, NULL); + + assert_int_equal(__real_rrdset_is_exportable(instance, st), 0); + + assert_ptr_not_equal(st->exporting_flags, NULL); + assert_int_equal(st->exporting_flags[0], RRDSET_FLAG_BACKEND_IGNORE); +} + +static void test_exporting_calculate_value_from_stored_data(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + RRDDIM *rd = localhost->rrdset_root->dimensions; + time_t timestamp; + + instance->after = 3; + instance->before = 10; + + expect_function_call(__mock_rrddim_query_oldest_time); + will_return(__mock_rrddim_query_oldest_time, 1); + + expect_function_call(__mock_rrddim_query_latest_time); + will_return(__mock_rrddim_query_latest_time, 2); + + expect_function_call(__mock_rrddim_query_init); + expect_value(__mock_rrddim_query_init, start_time, 1); + expect_value(__mock_rrddim_query_init, end_time, 2); + + expect_function_call(__mock_rrddim_query_is_finished); + will_return(__mock_rrddim_query_is_finished, 0); + expect_function_call(__mock_rrddim_query_next_metric); + will_return(__mock_rrddim_query_next_metric, pack_storage_number(27, SN_EXISTS)); + + expect_function_call(__mock_rrddim_query_is_finished); + will_return(__mock_rrddim_query_is_finished, 0); + expect_function_call(__mock_rrddim_query_next_metric); + will_return(__mock_rrddim_query_next_metric, pack_storage_number(45, SN_EXISTS)); + + expect_function_call(__mock_rrddim_query_is_finished); + will_return(__mock_rrddim_query_is_finished, 1); + + expect_function_call(__mock_rrddim_query_finalize); + + assert_int_equal(__real_exporting_calculate_value_from_stored_data(instance, rd, ×tamp), 36); +} + +static void test_prepare_buffers(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->start_batch_formatting = __mock_start_batch_formatting; + instance->start_host_formatting = __mock_start_host_formatting; + instance->start_chart_formatting = __mock_start_chart_formatting; + instance->metric_formatting = __mock_metric_formatting; + instance->end_chart_formatting = __mock_end_chart_formatting; + instance->end_host_formatting = __mock_end_host_formatting; + instance->end_batch_formatting = __mock_end_batch_formatting; + __real_mark_scheduled_instances(engine); + + expect_function_call(__mock_start_batch_formatting); + expect_value(__mock_start_batch_formatting, instance, instance); + will_return(__mock_start_batch_formatting, 0); + + expect_function_call(__wrap_rrdhost_is_exportable); + expect_value(__wrap_rrdhost_is_exportable, instance, instance); + expect_value(__wrap_rrdhost_is_exportable, host, localhost); + will_return(__wrap_rrdhost_is_exportable, 1); + + expect_function_call(__mock_start_host_formatting); + expect_value(__mock_start_host_formatting, instance, instance); + expect_value(__mock_start_host_formatting, host, localhost); + will_return(__mock_start_host_formatting, 0); + + RRDSET *st = localhost->rrdset_root; + expect_function_call(__wrap_rrdset_is_exportable); + expect_value(__wrap_rrdset_is_exportable, instance, instance); + expect_value(__wrap_rrdset_is_exportable, st, st); + will_return(__wrap_rrdset_is_exportable, 1); + + expect_function_call(__mock_start_chart_formatting); + expect_value(__mock_start_chart_formatting, instance, instance); + expect_value(__mock_start_chart_formatting, st, st); + will_return(__mock_start_chart_formatting, 0); + + RRDDIM *rd = localhost->rrdset_root->dimensions; + expect_function_call(__mock_metric_formatting); + expect_value(__mock_metric_formatting, instance, instance); + expect_value(__mock_metric_formatting, rd, rd); + will_return(__mock_metric_formatting, 0); + + expect_function_call(__mock_end_chart_formatting); + expect_value(__mock_end_chart_formatting, instance, instance); + expect_value(__mock_end_chart_formatting, st, st); + will_return(__mock_end_chart_formatting, 0); + + expect_function_call(__mock_end_host_formatting); + expect_value(__mock_end_host_formatting, instance, instance); + expect_value(__mock_end_host_formatting, host, localhost); + will_return(__mock_end_host_formatting, 0); + + expect_function_call(__mock_end_batch_formatting); + expect_value(__mock_end_batch_formatting, instance, instance); + will_return(__mock_end_batch_formatting, 0); + + assert_int_equal(__real_prepare_buffers(engine), 0); + + assert_int_equal(instance->stats.buffered_metrics, 1); + + // check with NULL functions + instance->start_batch_formatting = NULL; + instance->start_host_formatting = NULL; + instance->start_chart_formatting = NULL; + instance->metric_formatting = NULL; + instance->end_chart_formatting = NULL; + instance->end_host_formatting = NULL; + instance->end_batch_formatting = NULL; + assert_int_equal(__real_prepare_buffers(engine), 0); + + assert_int_equal(instance->scheduled, 0); + assert_int_equal(instance->after, 2); +} + +static void test_exporting_name_copy(void **state) +{ + (void)state; + + char *source_name = "test.name-with/special#characters_"; + char destination_name[RRD_ID_LENGTH_MAX + 1]; + + assert_int_equal(exporting_name_copy(destination_name, source_name, RRD_ID_LENGTH_MAX), 34); + assert_string_equal(destination_name, "test.name_with_special_characters_"); +} + +static void test_format_dimension_collected_graphite_plaintext(void **state) +{ + struct engine *engine = *state; + + RRDDIM *rd = localhost->rrdset_root->dimensions; + assert_int_equal(format_dimension_collected_graphite_plaintext(engine->instance_root, rd), 0); + assert_string_equal( + buffer_tostring(engine->instance_root->buffer), + "netdata.test-host.chart_name.dimension_name;TAG1=VALUE1 TAG2=VALUE2 123000321 15051\n"); +} + +static void test_format_dimension_stored_graphite_plaintext(void **state) +{ + struct engine *engine = *state; + + expect_function_call(__wrap_exporting_calculate_value_from_stored_data); + will_return(__wrap_exporting_calculate_value_from_stored_data, pack_storage_number(27, SN_EXISTS)); + + RRDDIM *rd = localhost->rrdset_root->dimensions; + assert_int_equal(format_dimension_stored_graphite_plaintext(engine->instance_root, rd), 0); + assert_string_equal( + buffer_tostring(engine->instance_root->buffer), + "netdata.test-host.chart_name.dimension_name;TAG1=VALUE1 TAG2=VALUE2 690565856.0000000 15052\n"); +} + +static void test_format_dimension_collected_json_plaintext(void **state) +{ + struct engine *engine = *state; + + RRDDIM *rd = localhost->rrdset_root->dimensions; + assert_int_equal(format_dimension_collected_json_plaintext(engine->instance_root, rd), 0); + assert_string_equal( + buffer_tostring(engine->instance_root->buffer), + "{\"prefix\":\"netdata\",\"hostname\":\"test-host\",\"host_tags\":\"TAG1=VALUE1 TAG2=VALUE2\"," + "\"chart_id\":\"chart_id\",\"chart_name\":\"chart_name\",\"chart_family\":\"(null)\"," + "\"chart_context\":\"(null)\",\"chart_type\":\"(null)\",\"units\":\"(null)\",\"id\":\"dimension_id\"," + "\"name\":\"dimension_name\",\"value\":123000321,\"timestamp\":15051}\n"); +} + +static void test_format_dimension_stored_json_plaintext(void **state) +{ + struct engine *engine = *state; + + expect_function_call(__wrap_exporting_calculate_value_from_stored_data); + will_return(__wrap_exporting_calculate_value_from_stored_data, pack_storage_number(27, SN_EXISTS)); + + RRDDIM *rd = localhost->rrdset_root->dimensions; + assert_int_equal(format_dimension_stored_json_plaintext(engine->instance_root, rd), 0); + assert_string_equal( + buffer_tostring(engine->instance_root->buffer), + "{\"prefix\":\"netdata\",\"hostname\":\"test-host\",\"host_tags\":\"TAG1=VALUE1 TAG2=VALUE2\"," + "\"chart_id\":\"chart_id\",\"chart_name\":\"chart_name\",\"chart_family\":\"(null)\"," \ + "\"chart_context\": \"(null)\",\"chart_type\":\"(null)\",\"units\": \"(null)\",\"id\":\"dimension_id\"," + "\"name\":\"dimension_name\",\"value\":690565856.0000000,\"timestamp\": 15052}\n"); +} + +static void test_format_dimension_collected_opentsdb_telnet(void **state) +{ + struct engine *engine = *state; + + RRDDIM *rd = localhost->rrdset_root->dimensions; + assert_int_equal(format_dimension_collected_opentsdb_telnet(engine->instance_root, rd), 0); + assert_string_equal( + buffer_tostring(engine->instance_root->buffer), + "put netdata.chart_name.dimension_name 15051 123000321 host=test-host TAG1=VALUE1 TAG2=VALUE2\n"); +} + +static void test_format_dimension_stored_opentsdb_telnet(void **state) +{ + struct engine *engine = *state; + + expect_function_call(__wrap_exporting_calculate_value_from_stored_data); + will_return(__wrap_exporting_calculate_value_from_stored_data, pack_storage_number(27, SN_EXISTS)); + + RRDDIM *rd = localhost->rrdset_root->dimensions; + assert_int_equal(format_dimension_stored_opentsdb_telnet(engine->instance_root, rd), 0); + assert_string_equal( + buffer_tostring(engine->instance_root->buffer), + "put netdata.chart_name.dimension_name 15052 690565856.0000000 host=test-host TAG1=VALUE1 TAG2=VALUE2\n"); +} + +static void test_format_dimension_collected_opentsdb_http(void **state) +{ + struct engine *engine = *state; + + RRDDIM *rd = localhost->rrdset_root->dimensions; + assert_int_equal(format_dimension_collected_opentsdb_http(engine->instance_root, rd), 0); + assert_string_equal( + buffer_tostring(engine->instance_root->buffer), + "{\"metric\":\"netdata.chart_name.dimension_name\"," + "\"timestamp\":15051," + "\"value\":123000321," + "\"tags\":{\"host\":\"test-host TAG1=VALUE1 TAG2=VALUE2\"}}"); +} + +static void test_format_dimension_stored_opentsdb_http(void **state) +{ + struct engine *engine = *state; + + expect_function_call(__wrap_exporting_calculate_value_from_stored_data); + will_return(__wrap_exporting_calculate_value_from_stored_data, pack_storage_number(27, SN_EXISTS)); + + RRDDIM *rd = localhost->rrdset_root->dimensions; + assert_int_equal(format_dimension_stored_opentsdb_http(engine->instance_root, rd), 0); + assert_string_equal( + buffer_tostring(engine->instance_root->buffer), + "{\"metric\":\"netdata.chart_name.dimension_name\"," + "\"timestamp\":15052," + "\"value\":690565856.0000000," + "\"tags\":{\"host\":\"test-host TAG1=VALUE1 TAG2=VALUE2\"}}"); +} + +static void test_exporting_discard_response(void **state) +{ + struct engine *engine = *state; + + BUFFER *response = buffer_create(0); + buffer_sprintf(response, "Test response"); + + assert_int_equal(exporting_discard_response(response, engine->instance_root), 0); + assert_int_equal(buffer_strlen(response), 0); + + buffer_free(response); +} + +static void test_simple_connector_receive_response(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + struct stats *stats = &instance->stats; + + int sock = 1; + + expect_function_call(__wrap_recv); + expect_value(__wrap_recv, sockfd, 1); + expect_not_value(__wrap_recv, buf, 0); + expect_value(__wrap_recv, len, 4096); + expect_value(__wrap_recv, flags, MSG_DONTWAIT); + + simple_connector_receive_response(&sock, instance); + + assert_int_equal(stats->received_bytes, 9); + assert_int_equal(stats->receptions, 1); + assert_int_equal(sock, 1); +} + +static void test_simple_connector_send_buffer(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + struct stats *stats = &instance->stats; + + int sock = 1; + int failures = 3; + size_t buffered_metrics = 1; + BUFFER *header = buffer_create(0); + BUFFER *buffer = buffer_create(0); + buffer_strcat(header, "test header\n"); + buffer_strcat(buffer, "test buffer\n"); + + expect_function_call(__wrap_send); + expect_value(__wrap_send, sockfd, 1); + expect_value(__wrap_send, buf, buffer_tostring(header)); + expect_string(__wrap_send, buf, "test header\n"); + expect_value(__wrap_send, len, 12); + expect_value(__wrap_send, flags, MSG_NOSIGNAL); + + expect_function_call(__wrap_send); + expect_value(__wrap_send, sockfd, 1); + expect_value(__wrap_send, buf, buffer_tostring(buffer)); + expect_string(__wrap_send, buf, "test buffer\n"); + expect_value(__wrap_send, len, 12); + expect_value(__wrap_send, flags, MSG_NOSIGNAL); + + simple_connector_send_buffer(&sock, &failures, instance, header, buffer, buffered_metrics); + + assert_int_equal(failures, 0); + assert_int_equal(stats->transmission_successes, 1); + assert_int_equal(stats->sent_bytes, 12); + assert_int_equal(stats->sent_metrics, 1); + assert_int_equal(stats->transmission_failures, 0); + + assert_int_equal(buffer_strlen(buffer), 0); + + assert_int_equal(sock, 1); +} + +static void test_simple_connector_worker(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + struct stats *stats = &instance->stats; + + __real_mark_scheduled_instances(engine); + + struct simple_connector_data *simple_connector_data = callocz(1, sizeof(struct simple_connector_data)); + instance->connector_specific_data = simple_connector_data; + simple_connector_data->last_buffer = callocz(1, sizeof(struct simple_connector_buffer)); + simple_connector_data->first_buffer = simple_connector_data->last_buffer; + simple_connector_data->header = buffer_create(0); + simple_connector_data->buffer = buffer_create(0); + simple_connector_data->last_buffer->header = buffer_create(0); + simple_connector_data->last_buffer->buffer = buffer_create(0); + + buffer_sprintf(simple_connector_data->last_buffer->header, "test header"); + buffer_sprintf(simple_connector_data->last_buffer->buffer, "test buffer"); + + expect_function_call(__wrap_connect_to_one_of); + expect_string(__wrap_connect_to_one_of, destination, "localhost"); + expect_value(__wrap_connect_to_one_of, default_port, 2003); + expect_not_value(__wrap_connect_to_one_of, reconnects_counter, 0); + expect_value(__wrap_connect_to_one_of, connected_to, 0); + expect_value(__wrap_connect_to_one_of, connected_to_size, 0); + will_return(__wrap_connect_to_one_of, 2); + + expect_function_call(__wrap_send); + expect_value(__wrap_send, sockfd, 2); + expect_not_value(__wrap_send, buf, buffer_tostring(simple_connector_data->last_buffer->buffer)); + expect_string(__wrap_send, buf, "test header"); + expect_value(__wrap_send, len, 11); + expect_value(__wrap_send, flags, MSG_NOSIGNAL); + + expect_function_call(__wrap_send); + expect_value(__wrap_send, sockfd, 2); + expect_value(__wrap_send, buf, buffer_tostring(simple_connector_data->last_buffer->buffer)); + expect_string(__wrap_send, buf, "test buffer"); + expect_value(__wrap_send, len, 11); + expect_value(__wrap_send, flags, MSG_NOSIGNAL); + + expect_function_call(__wrap_send_internal_metrics); + expect_value(__wrap_send_internal_metrics, instance, instance); + will_return(__wrap_send_internal_metrics, 0); + + simple_connector_worker(instance); + + assert_int_equal(stats->buffered_metrics, 0); + assert_int_equal(stats->buffered_bytes, 0); + assert_int_equal(stats->received_bytes, 0); + assert_int_equal(stats->sent_bytes, 0); + assert_int_equal(stats->sent_metrics, 0); + assert_int_equal(stats->lost_metrics, 0); + assert_int_equal(stats->receptions, 0); + assert_int_equal(stats->transmission_successes, 0); + assert_int_equal(stats->transmission_failures, 0); + assert_int_equal(stats->data_lost_events, 0); + assert_int_equal(stats->lost_bytes, 0); + assert_int_equal(stats->reconnects, 0); +} + +static void test_sanitize_json_string(void **state) +{ + (void)state; + + char *src = "check \t\\\" string"; + char dst[19 + 1]; + + sanitize_json_string(dst, src, 19); + + assert_string_equal(dst, "check _\\\\\\\" string"); +} + +static void test_sanitize_graphite_label_value(void **state) +{ + (void)state; + + char *src = "check ;~ string"; + char dst[15 + 1]; + + sanitize_graphite_label_value(dst, src, 15); + + assert_string_equal(dst, "check____string"); +} + +static void test_sanitize_opentsdb_label_value(void **state) +{ + (void)state; + + char *src = "check \t\\\" #&$? -_./ string"; + char dst[26 + 1]; + + sanitize_opentsdb_label_value(dst, src, 26); + + assert_string_equal(dst, "check__________-_./_string"); +} + +static void test_format_host_labels_json_plaintext(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->config.options |= EXPORTING_OPTION_SEND_CONFIGURED_LABELS; + instance->config.options |= EXPORTING_OPTION_SEND_AUTOMATIC_LABELS; + + assert_int_equal(format_host_labels_json_plaintext(instance, localhost), 0); + assert_string_equal(buffer_tostring(instance->labels), "\"labels\":{\"key1\":\"value1\",\"key2\":\"value2\"},"); +} + +static void test_format_host_labels_graphite_plaintext(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->config.options |= EXPORTING_OPTION_SEND_CONFIGURED_LABELS; + instance->config.options |= EXPORTING_OPTION_SEND_AUTOMATIC_LABELS; + + assert_int_equal(format_host_labels_graphite_plaintext(instance, localhost), 0); + assert_string_equal(buffer_tostring(instance->labels), ";key1=value1;key2=value2"); +} + +static void test_format_host_labels_opentsdb_telnet(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->config.options |= EXPORTING_OPTION_SEND_CONFIGURED_LABELS; + instance->config.options |= EXPORTING_OPTION_SEND_AUTOMATIC_LABELS; + + assert_int_equal(format_host_labels_opentsdb_telnet(instance, localhost), 0); + assert_string_equal(buffer_tostring(instance->labels), " key1=value1 key2=value2"); +} + +static void test_format_host_labels_opentsdb_http(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->config.options |= EXPORTING_OPTION_SEND_CONFIGURED_LABELS; + instance->config.options |= EXPORTING_OPTION_SEND_AUTOMATIC_LABELS; + + assert_int_equal(format_host_labels_opentsdb_http(instance, localhost), 0); + assert_string_equal(buffer_tostring(instance->labels), ",\"key1\":\"value1\",\"key2\":\"value2\""); +} + +static void test_flush_host_labels(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->labels = buffer_create(12); + buffer_strcat(instance->labels, "check string"); + assert_int_equal(buffer_strlen(instance->labels), 12); + + assert_int_equal(flush_host_labels(instance, localhost), 0); + assert_int_equal(buffer_strlen(instance->labels), 0); +} + +static void test_create_main_rusage_chart(void **state) +{ + UNUSED(state); + + RRDSET *st_rusage = calloc(1, sizeof(RRDSET)); + RRDDIM *rd_user = NULL; + RRDDIM *rd_system = NULL; + + expect_function_call(rrdset_create_custom); + expect_value(rrdset_create_custom, host, localhost); + expect_string(rrdset_create_custom, type, "netdata"); + expect_string(rrdset_create_custom, id, "exporting_main_thread_cpu"); + expect_value(rrdset_create_custom, name, NULL); + expect_string(rrdset_create_custom, family, "exporting"); + expect_string(rrdset_create_custom, context, "exporting_cpu_usage"); + expect_string(rrdset_create_custom, units, "milliseconds/s"); + expect_string(rrdset_create_custom, plugin, "exporting"); + expect_value(rrdset_create_custom, module, NULL); + expect_value(rrdset_create_custom, priority, 130600); + expect_value(rrdset_create_custom, update_every, localhost->rrd_update_every); + expect_value(rrdset_create_custom, chart_type, RRDSET_TYPE_STACKED); + will_return(rrdset_create_custom, st_rusage); + + expect_function_calls(rrddim_add_custom, 2); + expect_value_count(rrddim_add_custom, st, st_rusage, 2); + expect_value_count(rrddim_add_custom, name, NULL, 2); + expect_value_count(rrddim_add_custom, multiplier, 1, 2); + expect_value_count(rrddim_add_custom, divisor, 1000, 2); + expect_value_count(rrddim_add_custom, algorithm, RRD_ALGORITHM_INCREMENTAL, 2); + + __real_create_main_rusage_chart(&st_rusage, &rd_user, &rd_system); + + free(st_rusage); +} + +static void test_send_main_rusage(void **state) +{ + UNUSED(state); + + RRDSET *st_rusage = calloc(1, sizeof(RRDSET)); + st_rusage->counter_done = 1; + + expect_function_call(rrdset_next_usec); + expect_value(rrdset_next_usec, st, st_rusage); + + expect_function_calls(rrddim_set_by_pointer, 2); + expect_value_count(rrddim_set_by_pointer, st, st_rusage, 2); + + expect_function_call(rrdset_done); + expect_value(rrdset_done, st, st_rusage); + + __real_send_main_rusage(st_rusage, NULL, NULL); + + free(st_rusage); +} + +static void test_send_internal_metrics(void **state) +{ + UNUSED(state); + + struct instance *instance = calloc(1, sizeof(struct instance)); + instance->config.name = (const char *)strdupz("test_instance"); + instance->config.update_every = 2; + + struct stats *stats = &instance->stats; + + stats->st_metrics = calloc(1, sizeof(RRDSET)); + stats->st_metrics->counter_done = 1; + stats->st_bytes = calloc(1, sizeof(RRDSET)); + stats->st_bytes->counter_done = 1; + stats->st_ops = calloc(1, sizeof(RRDSET)); + stats->st_ops->counter_done = 1; + stats->st_rusage = calloc(1, sizeof(RRDSET)); + stats->st_rusage->counter_done = 1; + + // ------------------------------------------------------------------------ + + expect_function_call(rrdset_create_custom); + expect_value(rrdset_create_custom, host, localhost); + expect_string(rrdset_create_custom, type, "netdata"); + expect_string(rrdset_create_custom, id, "exporting_test_instance_metrics"); + expect_value(rrdset_create_custom, name, NULL); + expect_string(rrdset_create_custom, family, "exporting_test_instance"); + expect_string(rrdset_create_custom, context, "exporting_buffer"); + expect_string(rrdset_create_custom, units, "metrics"); + expect_string(rrdset_create_custom, plugin, "exporting"); + expect_value(rrdset_create_custom, module, NULL); + expect_value(rrdset_create_custom, priority, 130610); + expect_value(rrdset_create_custom, update_every, 2); + expect_value(rrdset_create_custom, chart_type, RRDSET_TYPE_LINE); + will_return(rrdset_create_custom, stats->st_metrics); + + expect_function_calls(rrddim_add_custom, 3); + expect_value_count(rrddim_add_custom, st, stats->st_metrics, 3); + expect_value_count(rrddim_add_custom, name, NULL, 3); + expect_value_count(rrddim_add_custom, multiplier, 1, 3); + expect_value_count(rrddim_add_custom, divisor, 1, 3); + expect_value_count(rrddim_add_custom, algorithm, RRD_ALGORITHM_ABSOLUTE, 3); + + // ------------------------------------------------------------------------ + + expect_function_call(rrdset_create_custom); + expect_value(rrdset_create_custom, host, localhost); + expect_string(rrdset_create_custom, type, "netdata"); + expect_string(rrdset_create_custom, id, "exporting_test_instance_bytes"); + expect_value(rrdset_create_custom, name, NULL); + expect_string(rrdset_create_custom, family, "exporting_test_instance"); + expect_string(rrdset_create_custom, context, "exporting_data_size"); + expect_string(rrdset_create_custom, units, "KiB"); + expect_string(rrdset_create_custom, plugin, "exporting"); + expect_value(rrdset_create_custom, module, NULL); + expect_value(rrdset_create_custom, priority, 130620); + expect_value(rrdset_create_custom, update_every, 2); + expect_value(rrdset_create_custom, chart_type, RRDSET_TYPE_AREA); + will_return(rrdset_create_custom, stats->st_bytes); + + expect_function_calls(rrddim_add_custom, 4); + expect_value_count(rrddim_add_custom, st, stats->st_bytes, 4); + expect_value_count(rrddim_add_custom, name, NULL, 4); + expect_value_count(rrddim_add_custom, multiplier, 1, 4); + expect_value_count(rrddim_add_custom, divisor, 1024, 4); + expect_value_count(rrddim_add_custom, algorithm, RRD_ALGORITHM_ABSOLUTE, 4); + + // ------------------------------------------------------------------------ + + expect_function_call(rrdset_create_custom); + expect_value(rrdset_create_custom, host, localhost); + expect_string(rrdset_create_custom, type, "netdata"); + expect_string(rrdset_create_custom, id, "exporting_test_instance_ops"); + expect_value(rrdset_create_custom, name, NULL); + expect_string(rrdset_create_custom, family, "exporting_test_instance"); + expect_string(rrdset_create_custom, context, "exporting_operations"); + expect_string(rrdset_create_custom, units, "operations"); + expect_string(rrdset_create_custom, plugin, "exporting"); + expect_value(rrdset_create_custom, module, NULL); + expect_value(rrdset_create_custom, priority, 130630); + expect_value(rrdset_create_custom, update_every, 2); + expect_value(rrdset_create_custom, chart_type, RRDSET_TYPE_LINE); + will_return(rrdset_create_custom, stats->st_ops); + + expect_function_calls(rrddim_add_custom, 5); + expect_value_count(rrddim_add_custom, st, stats->st_ops, 5); + expect_value_count(rrddim_add_custom, name, NULL, 5); + expect_value_count(rrddim_add_custom, multiplier, 1, 5); + expect_value_count(rrddim_add_custom, divisor, 1, 5); + expect_value_count(rrddim_add_custom, algorithm, RRD_ALGORITHM_ABSOLUTE, 5); + + // ------------------------------------------------------------------------ + + expect_function_call(rrdset_create_custom); + expect_value(rrdset_create_custom, host, localhost); + expect_string(rrdset_create_custom, type, "netdata"); + expect_string(rrdset_create_custom, id, "exporting_test_instance_thread_cpu"); + expect_value(rrdset_create_custom, name, NULL); + expect_string(rrdset_create_custom, family, "exporting_test_instance"); + expect_string(rrdset_create_custom, context, "exporting_instance"); + expect_string(rrdset_create_custom, units, "milliseconds/s"); + expect_string(rrdset_create_custom, plugin, "exporting"); + expect_value(rrdset_create_custom, module, NULL); + expect_value(rrdset_create_custom, priority, 130640); + expect_value(rrdset_create_custom, update_every, 2); + expect_value(rrdset_create_custom, chart_type, RRDSET_TYPE_STACKED); + will_return(rrdset_create_custom, stats->st_rusage); + + expect_function_calls(rrddim_add_custom, 2); + expect_value_count(rrddim_add_custom, st, stats->st_rusage, 2); + expect_value_count(rrddim_add_custom, name, NULL, 2); + expect_value_count(rrddim_add_custom, multiplier, 1, 2); + expect_value_count(rrddim_add_custom, divisor, 1000, 2); + expect_value_count(rrddim_add_custom, algorithm, RRD_ALGORITHM_INCREMENTAL, 2); + + // ------------------------------------------------------------------------ + + expect_function_call(rrdset_next_usec); + expect_value(rrdset_next_usec, st, stats->st_metrics); + + expect_function_calls(rrddim_set_by_pointer, 3); + expect_value_count(rrddim_set_by_pointer, st, stats->st_metrics, 3); + + expect_function_call(rrdset_done); + expect_value(rrdset_done, st, stats->st_metrics); + + // ------------------------------------------------------------------------ + + expect_function_call(rrdset_next_usec); + expect_value(rrdset_next_usec, st, stats->st_bytes); + + expect_function_calls(rrddim_set_by_pointer, 4); + expect_value_count(rrddim_set_by_pointer, st, stats->st_bytes, 4); + + expect_function_call(rrdset_done); + expect_value(rrdset_done, st, stats->st_bytes); + + // ------------------------------------------------------------------------ + + expect_function_call(rrdset_next_usec); + expect_value(rrdset_next_usec, st, stats->st_ops); + + expect_function_calls(rrddim_set_by_pointer, 5); + expect_value_count(rrddim_set_by_pointer, st, stats->st_ops, 5); + + expect_function_call(rrdset_done); + expect_value(rrdset_done, st, stats->st_ops); + + // ------------------------------------------------------------------------ + + expect_function_call(rrdset_next_usec); + expect_value(rrdset_next_usec, st, stats->st_rusage); + + expect_function_calls(rrddim_set_by_pointer, 2); + expect_value_count(rrddim_set_by_pointer, st, stats->st_rusage, 2); + + expect_function_call(rrdset_done); + expect_value(rrdset_done, st, stats->st_rusage); + + // ------------------------------------------------------------------------ + + __real_send_internal_metrics(instance); + + free(stats->st_metrics); + free(stats->st_bytes); + free(stats->st_ops); + free(stats->st_rusage); + free((void *)instance->config.name); + free(instance); +} + +static void test_can_send_rrdset(void **state) +{ + (void)*state; + + assert_int_equal(can_send_rrdset(prometheus_exporter_instance, localhost->rrdset_root), 1); + + rrdset_flag_set(localhost->rrdset_root, RRDSET_FLAG_BACKEND_IGNORE); + assert_int_equal(can_send_rrdset(prometheus_exporter_instance, localhost->rrdset_root), 0); + rrdset_flag_clear(localhost->rrdset_root, RRDSET_FLAG_BACKEND_IGNORE); + + // TODO: test with a denying simple pattern + + rrdset_flag_set(localhost->rrdset_root, RRDSET_FLAG_OBSOLETE); + assert_int_equal(can_send_rrdset(prometheus_exporter_instance, localhost->rrdset_root), 0); + rrdset_flag_clear(localhost->rrdset_root, RRDSET_FLAG_OBSOLETE); + + localhost->rrdset_root->rrd_memory_mode = RRD_MEMORY_MODE_NONE; + prometheus_exporter_instance->config.options |= EXPORTING_SOURCE_DATA_AVERAGE; + assert_int_equal(can_send_rrdset(prometheus_exporter_instance, localhost->rrdset_root), 0); +} + +static void test_prometheus_name_copy(void **state) +{ + (void)*state; + + char destination_name[PROMETHEUS_ELEMENT_MAX + 1]; + assert_int_equal(prometheus_name_copy(destination_name, "test-name", PROMETHEUS_ELEMENT_MAX), 9); + + assert_string_equal(destination_name, "test_name"); +} + +static void test_prometheus_label_copy(void **state) +{ + (void)*state; + + char destination_name[PROMETHEUS_ELEMENT_MAX + 1]; + assert_int_equal(prometheus_label_copy(destination_name, "test\"\\\nlabel", PROMETHEUS_ELEMENT_MAX), 15); + + assert_string_equal(destination_name, "test\\\"\\\\\\\nlabel"); +} + +static void test_prometheus_units_copy(void **state) +{ + (void)*state; + + char destination_name[PROMETHEUS_ELEMENT_MAX + 1]; + assert_string_equal(prometheus_units_copy(destination_name, "test-units", PROMETHEUS_ELEMENT_MAX, 0), "_test_units"); + assert_string_equal(destination_name, "_test_units"); + + assert_string_equal(prometheus_units_copy(destination_name, "%", PROMETHEUS_ELEMENT_MAX, 0), "_percent"); + assert_string_equal(prometheus_units_copy(destination_name, "test-units/s", PROMETHEUS_ELEMENT_MAX, 0), "_test_units_persec"); + + assert_string_equal(prometheus_units_copy(destination_name, "KiB", PROMETHEUS_ELEMENT_MAX, 1), "_KB"); +} + +static void test_format_host_labels_prometheus(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->config.options |= EXPORTING_OPTION_SEND_CONFIGURED_LABELS; + instance->config.options |= EXPORTING_OPTION_SEND_AUTOMATIC_LABELS; + + format_host_labels_prometheus(instance, localhost); + assert_string_equal(buffer_tostring(instance->labels), "key1=\"netdata\",key2=\"value2\""); +} + +static void rrd_stats_api_v1_charts_allmetrics_prometheus(void **state) +{ + (void)state; + + BUFFER *buffer = buffer_create(0); + + localhost->hostname = strdupz("test_hostname"); + localhost->rrdset_root->family = strdupz("test_family"); + localhost->rrdset_root->context = strdupz("test_context"); + + expect_function_call(__wrap_now_realtime_sec); + will_return(__wrap_now_realtime_sec, 2); + + expect_function_call(__wrap_exporting_calculate_value_from_stored_data); + will_return(__wrap_exporting_calculate_value_from_stored_data, pack_storage_number(27, SN_EXISTS)); + + rrd_stats_api_v1_charts_allmetrics_prometheus_single_host(localhost, buffer, "test_server", "test_prefix", 0, 0); + + assert_string_equal( + buffer_tostring(buffer), + "netdata_info{instance=\"test_hostname\",application=\"(null)\",version=\"(null)\"} 1\n" + "netdata_host_tags_info{key1=\"value1\",key2=\"value2\"} 1\n" + "netdata_host_tags{key1=\"value1\",key2=\"value2\"} 1\n" + "test_prefix_test_context{chart=\"chart_id\",family=\"test_family\",dimension=\"dimension_id\"} 690565856.0000000\n"); + + buffer_flush(buffer); + + expect_function_call(__wrap_now_realtime_sec); + will_return(__wrap_now_realtime_sec, 2); + + expect_function_call(__wrap_exporting_calculate_value_from_stored_data); + will_return(__wrap_exporting_calculate_value_from_stored_data, pack_storage_number(27, SN_EXISTS)); + + rrd_stats_api_v1_charts_allmetrics_prometheus_single_host( + localhost, buffer, "test_server", "test_prefix", 0, PROMETHEUS_OUTPUT_NAMES | PROMETHEUS_OUTPUT_TYPES); + + assert_string_equal( + buffer_tostring(buffer), + "netdata_info{instance=\"test_hostname\",application=\"(null)\",version=\"(null)\"} 1\n" + "netdata_host_tags_info{key1=\"value1\",key2=\"value2\"} 1\n" + "netdata_host_tags{key1=\"value1\",key2=\"value2\"} 1\n" + "# TYPE test_prefix_test_context gauge\n" + "test_prefix_test_context{chart=\"chart_name\",family=\"test_family\",dimension=\"dimension_name\"} 690565856.0000000\n"); + + buffer_flush(buffer); + + expect_function_call(__wrap_now_realtime_sec); + will_return(__wrap_now_realtime_sec, 2); + + expect_function_call(__wrap_exporting_calculate_value_from_stored_data); + will_return(__wrap_exporting_calculate_value_from_stored_data, pack_storage_number(27, SN_EXISTS)); + + rrd_stats_api_v1_charts_allmetrics_prometheus_all_hosts(localhost, buffer, "test_server", "test_prefix", 0, 0); + + assert_string_equal( + buffer_tostring(buffer), + "netdata_info{instance=\"test_hostname\",application=\"(null)\",version=\"(null)\"} 1\n" + "netdata_host_tags_info{instance=\"test_hostname\",key1=\"value1\",key2=\"value2\"} 1\n" + "netdata_host_tags{instance=\"test_hostname\",key1=\"value1\",key2=\"value2\"} 1\n" + "test_prefix_test_context{chart=\"chart_id\",family=\"test_family\",dimension=\"dimension_id\",instance=\"test_hostname\"} 690565856.0000000\n"); + + free(localhost->rrdset_root->context); + free(localhost->rrdset_root->family); + free(localhost->hostname); + buffer_free(buffer); +} + +#if ENABLE_PROMETHEUS_REMOTE_WRITE +static void test_init_prometheus_remote_write_instance(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + expect_function_call(__wrap_init_write_request); + will_return(__wrap_init_write_request, 0xff); + + assert_int_equal(init_prometheus_remote_write_instance(instance), 0); + + assert_ptr_equal(instance->worker, simple_connector_worker); + assert_ptr_equal(instance->start_batch_formatting, NULL); + assert_ptr_equal(instance->start_host_formatting, format_host_prometheus_remote_write); + assert_ptr_equal(instance->start_chart_formatting, format_chart_prometheus_remote_write); + assert_ptr_equal(instance->metric_formatting, format_dimension_prometheus_remote_write); + assert_ptr_equal(instance->end_chart_formatting, NULL); + assert_ptr_equal(instance->end_host_formatting, NULL); + assert_ptr_equal(instance->end_batch_formatting, format_batch_prometheus_remote_write); + assert_ptr_equal(instance->prepare_header, prometheus_remote_write_prepare_header); + assert_ptr_equal(instance->check_response, process_prometheus_remote_write_response); + + assert_ptr_not_equal(instance->buffer, NULL); + buffer_free(instance->buffer); + + struct prometheus_remote_write_specific_data *connector_specific_data = + (struct prometheus_remote_write_specific_data *)instance->connector_specific_data; + + assert_ptr_not_equal(instance->connector_specific_data, NULL); + assert_ptr_not_equal(connector_specific_data->write_request, NULL); + freez(instance->connector_specific_data); +} + +static void test_prometheus_remote_write_prepare_header(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + struct prometheus_remote_write_specific_config *connector_specific_config = + callocz(1, sizeof(struct prometheus_remote_write_specific_config)); + instance->config.connector_specific_config = connector_specific_config; + connector_specific_config->remote_write_path = strdupz("/receive"); + + struct simple_connector_data *simple_connector_data = callocz(1, sizeof(struct simple_connector_data)); + instance->connector_specific_data = simple_connector_data; + simple_connector_data->last_buffer = callocz(1, sizeof(struct simple_connector_buffer)); + simple_connector_data->last_buffer->header = buffer_create(0); + simple_connector_data->last_buffer->buffer = buffer_create(0); + + buffer_sprintf(simple_connector_data->last_buffer->buffer, "test buffer"); + + prometheus_remote_write_prepare_header(instance); + + assert_string_equal( + buffer_tostring(simple_connector_data->last_buffer->header), + "POST /receive HTTP/1.1\r\n" + "Host: localhost\r\n" + "Accept: */*\r\n" + "Content-Encoding: snappy\r\n" + "Content-Type: application/x-protobuf\r\n" + "X-Prometheus-Remote-Write-Version: 0.1.0\r\n" + "Content-Length: 11\r\n" + "\r\n"); + + free(connector_specific_config->remote_write_path); + + buffer_free(simple_connector_data->last_buffer->header); + buffer_free(simple_connector_data->last_buffer->buffer); +} + +static void test_process_prometheus_remote_write_response(void **state) +{ + (void)state; + BUFFER *buffer = buffer_create(0); + + buffer_sprintf(buffer, "HTTP/1.1 200 OK\r\n"); + assert_int_equal(process_prometheus_remote_write_response(buffer, NULL), 0); + + buffer_free(buffer); +} + +static void test_format_host_prometheus_remote_write(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->config.options |= EXPORTING_OPTION_SEND_CONFIGURED_LABELS; + instance->config.options |= EXPORTING_OPTION_SEND_AUTOMATIC_LABELS; + + struct simple_connector_data *simple_connector_data = mallocz(sizeof(struct simple_connector_data *)); + instance->connector_specific_data = simple_connector_data; + struct prometheus_remote_write_specific_data *connector_specific_data = + mallocz(sizeof(struct prometheus_remote_write_specific_data *)); + simple_connector_data->connector_specific_data = (void *)connector_specific_data; + connector_specific_data->write_request = (void *)0xff; + + localhost->program_name = strdupz("test_program"); + localhost->program_version = strdupz("test_version"); + + expect_function_call(__wrap_add_host_info); + expect_value(__wrap_add_host_info, write_request_p, 0xff); + expect_string(__wrap_add_host_info, name, "netdata_info"); + expect_string(__wrap_add_host_info, instance, "test-host"); + expect_string(__wrap_add_host_info, application, "test_program"); + expect_string(__wrap_add_host_info, version, "test_version"); + expect_in_range( + __wrap_add_host_info, timestamp, now_realtime_usec() / USEC_PER_MS - 1000, now_realtime_usec() / USEC_PER_MS); + + expect_function_call(__wrap_add_label); + expect_value(__wrap_add_label, write_request_p, 0xff); + expect_string(__wrap_add_label, key, "key1"); + expect_string(__wrap_add_label, value, "value1"); + + expect_function_call(__wrap_add_label); + expect_value(__wrap_add_label, write_request_p, 0xff); + expect_string(__wrap_add_label, key, "key2"); + expect_string(__wrap_add_label, value, "value2"); + + assert_int_equal(format_host_prometheus_remote_write(instance, localhost), 0); + + freez(connector_specific_data); + freez(simple_connector_data); + free(localhost->program_name); + free(localhost->program_version); +} + +static void test_format_dimension_prometheus_remote_write(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + struct simple_connector_data *simple_connector_data = mallocz(sizeof(struct simple_connector_data *)); + instance->connector_specific_data = simple_connector_data; + struct prometheus_remote_write_specific_data *connector_specific_data = + mallocz(sizeof(struct prometheus_remote_write_specific_data *)); + simple_connector_data->connector_specific_data = (void *)connector_specific_data; + connector_specific_data->write_request = (void *)0xff; + + RRDDIM *rd = localhost->rrdset_root->dimensions; + + expect_function_call(__wrap_exporting_calculate_value_from_stored_data); + will_return(__wrap_exporting_calculate_value_from_stored_data, pack_storage_number(27, SN_EXISTS)); + + expect_function_call(__wrap_add_metric); + expect_value(__wrap_add_metric, write_request_p, 0xff); + expect_string(__wrap_add_metric, name, "netdata_"); + expect_string(__wrap_add_metric, chart, ""); + expect_string(__wrap_add_metric, family, ""); + expect_string(__wrap_add_metric, dimension, "dimension_name"); + expect_string(__wrap_add_metric, instance, "test-host"); + expect_value(__wrap_add_metric, value, 0x292932e0); + expect_value(__wrap_add_metric, timestamp, 15052 * MSEC_PER_SEC); + + assert_int_equal(format_dimension_prometheus_remote_write(instance, rd), 0); +} + +static void test_format_batch_prometheus_remote_write(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + struct simple_connector_data *simple_connector_data = mallocz(sizeof(struct simple_connector_data *)); + instance->connector_specific_data = simple_connector_data; + struct prometheus_remote_write_specific_data *connector_specific_data = + mallocz(sizeof(struct prometheus_remote_write_specific_data *)); + simple_connector_data->connector_specific_data = (void *)connector_specific_data; + connector_specific_data->write_request = __real_init_write_request(); + + expect_function_call(__wrap_simple_connector_end_batch); + expect_value(__wrap_simple_connector_end_batch, instance, instance); + will_return(__wrap_simple_connector_end_batch, 0); + __real_add_host_info( + connector_specific_data->write_request, + "test_name", "test_instance", "test_application", "test_version", 15051); + + __real_add_label(connector_specific_data->write_request, "test_key", "test_value"); + + __real_add_metric( + connector_specific_data->write_request, + "test_name", "test chart", "test_family", "test_dimension", "test_instance", + 123000321, 15052); + + assert_int_equal(format_batch_prometheus_remote_write(instance), 0); + + BUFFER *buffer = instance->buffer; + assert_int_equal(buffer_strlen(buffer), 192); + + BUFFER *escaped_buffer = buffer_create(850); + size_t len = buffer_strlen(buffer); + char *ch = (char *)buffer_tostring(buffer); + for (; len > 0; ch++, len--) + buffer_sprintf(escaped_buffer, "\\%03o", (unsigned int)*ch); + assert_string_equal( + buffer_tostring(escaped_buffer), + "\\37777777641\\002\\120\\012\\37777777622\\001\\012\\025\\012\\010\\137\\137\\156\\141\\155\\145\\137\\137" + "\\022\\011\\164\\145\\163\\164\\005\\015\\064\\012\\031\\012\\010\\151\\156\\163\\164\\141\\156\\143\\145\\022" + "\\015\\005\\027\\021\\017\\100\\012\\037\\012\\013\\141\\160\\160\\154\\151\\143\\141\\164\\151\\157\\156\\022" + "\\020\\005\\036\\035\\022\\034\\012\\027\\012\\007\\166\\145\\162\\163\\001\\035\\000\\014\\005\\035\\015\\016" + "\\014\\012\\026\\012\\010\\005\\020\\020\\153\\145\\171\\022\\012\\005\\012\\040\\166\\141\\154\\165\\145\\022" + "\\014\\011\\000\\005\\001\\030\\37777777760\\077\\020\\37777777713\\165\\012\\37777777611\\142\\37777777625" + "\\000\\034\\023\\012\\005\\143\\150\\141\\162\\164\\011\\075\\000\\040\\005\\014\\054\\012\\025\\012\\006\\146" + "\\141\\155\\151\\154\\171\\022\\013\\005\\123\\011\\015\\040\\012\\033\\012\\011\\144\\151\\155\\145\\156\\005" + "\\37777777607\\000\\016\\005\\032\\025\\020\\000\\012\\146\\37777777736\\000\\064\\022\\014\\011\\000\\000\\000" + "\\004\\130\\123\\37777777635\\101\\020\\37777777714\\165"); + + buffer_free(escaped_buffer); + protocol_buffers_shutdown(); +} +#endif // ENABLE_PROMETHEUS_REMOTE_WRITE + +#if HAVE_KINESIS +static void test_init_aws_kinesis_instance(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->config.options = EXPORTING_SOURCE_DATA_AS_COLLECTED | EXPORTING_OPTION_SEND_NAMES; + + struct aws_kinesis_specific_config *connector_specific_config = + callocz(1, sizeof(struct aws_kinesis_specific_config)); + instance->config.connector_specific_config = connector_specific_config; + connector_specific_config->stream_name = strdupz("test_stream"); + connector_specific_config->auth_key_id = strdupz("test_auth_key_id"); + connector_specific_config->secure_key = strdupz("test_secure_key"); + + expect_function_call(__wrap_aws_sdk_init); + expect_function_call(__wrap_kinesis_init); + expect_not_value(__wrap_kinesis_init, kinesis_specific_data_p, NULL); + expect_string(__wrap_kinesis_init, region, "localhost"); + expect_string(__wrap_kinesis_init, access_key_id, "test_auth_key_id"); + expect_string(__wrap_kinesis_init, secret_key, "test_secure_key"); + expect_value(__wrap_kinesis_init, timeout, 10000); + + assert_int_equal(init_aws_kinesis_instance(instance), 0); + + assert_ptr_equal(instance->worker, aws_kinesis_connector_worker); + assert_ptr_equal(instance->start_batch_formatting, NULL); + assert_ptr_equal(instance->start_host_formatting, format_host_labels_json_plaintext); + assert_ptr_equal(instance->start_chart_formatting, NULL); + assert_ptr_equal(instance->metric_formatting, format_dimension_collected_json_plaintext); + assert_ptr_equal(instance->end_chart_formatting, NULL); + assert_ptr_equal(instance->end_host_formatting, flush_host_labels); + assert_ptr_equal(instance->end_batch_formatting, NULL); + assert_ptr_not_equal(instance->buffer, NULL); + buffer_free(instance->buffer); + assert_ptr_not_equal(instance->connector_specific_data, NULL); + freez(instance->connector_specific_data); + + instance->config.options = EXPORTING_SOURCE_DATA_AVERAGE | EXPORTING_OPTION_SEND_NAMES; + + expect_function_call(__wrap_kinesis_init); + expect_not_value(__wrap_kinesis_init, kinesis_specific_data_p, NULL); + expect_string(__wrap_kinesis_init, region, "localhost"); + expect_string(__wrap_kinesis_init, access_key_id, "test_auth_key_id"); + expect_string(__wrap_kinesis_init, secret_key, "test_secure_key"); + expect_value(__wrap_kinesis_init, timeout, 10000); + + assert_int_equal(init_aws_kinesis_instance(instance), 0); + assert_ptr_equal(instance->metric_formatting, format_dimension_stored_json_plaintext); + + free(connector_specific_config->stream_name); + free(connector_specific_config->auth_key_id); + free(connector_specific_config->secure_key); +} + +static void test_aws_kinesis_connector_worker(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + struct stats *stats = &instance->stats; + BUFFER *buffer = instance->buffer; + + __real_mark_scheduled_instances(engine); + + expect_function_call(__wrap_rrdhost_is_exportable); + expect_value(__wrap_rrdhost_is_exportable, instance, instance); + expect_value(__wrap_rrdhost_is_exportable, host, localhost); + will_return(__wrap_rrdhost_is_exportable, 1); + + RRDSET *st = localhost->rrdset_root; + expect_function_call(__wrap_rrdset_is_exportable); + expect_value(__wrap_rrdset_is_exportable, instance, instance); + expect_value(__wrap_rrdset_is_exportable, st, st); + will_return(__wrap_rrdset_is_exportable, 1); + + expect_function_call(__wrap_simple_connector_end_batch); + expect_value(__wrap_simple_connector_end_batch, instance, instance); + will_return(__wrap_simple_connector_end_batch, 0); + __real_prepare_buffers(engine); + + struct aws_kinesis_specific_config *connector_specific_config = + callocz(1, sizeof(struct aws_kinesis_specific_config)); + instance->config.connector_specific_config = connector_specific_config; + connector_specific_config->stream_name = strdupz("test_stream"); + connector_specific_config->auth_key_id = strdupz("test_auth_key_id"); + connector_specific_config->secure_key = strdupz("test_secure_key"); + + struct aws_kinesis_specific_data *connector_specific_data = callocz(1, sizeof(struct aws_kinesis_specific_data)); + instance->connector_specific_data = (void *)connector_specific_data; + + expect_function_call(__wrap_kinesis_put_record); + expect_not_value(__wrap_kinesis_put_record, kinesis_specific_data_p, NULL); + expect_string(__wrap_kinesis_put_record, stream_name, "test_stream"); + expect_string(__wrap_kinesis_put_record, partition_key, "netdata_0"); + expect_value(__wrap_kinesis_put_record, data, buffer_tostring(buffer)); + // The buffer is prepared by Graphite exporting connector + expect_string( + __wrap_kinesis_put_record, data, + "netdata.test-host.chart_name.dimension_name;TAG1=VALUE1 TAG2=VALUE2 123000321 15051\n"); + expect_value(__wrap_kinesis_put_record, data_len, 84); + + expect_function_call(__wrap_kinesis_get_result); + expect_value(__wrap_kinesis_get_result, request_outcomes_p, NULL); + expect_not_value(__wrap_kinesis_get_result, error_message, NULL); + expect_not_value(__wrap_kinesis_get_result, sent_bytes, NULL); + expect_not_value(__wrap_kinesis_get_result, lost_bytes, NULL); + will_return(__wrap_kinesis_get_result, 0); + + expect_function_call(__wrap_send_internal_metrics); + expect_value(__wrap_send_internal_metrics, instance, instance); + will_return(__wrap_send_internal_metrics, 0); + + aws_kinesis_connector_worker(instance); + + assert_int_equal(stats->buffered_metrics, 0); + assert_int_equal(stats->buffered_bytes, 84); + assert_int_equal(stats->received_bytes, 0); + assert_int_equal(stats->sent_bytes, 84); + assert_int_equal(stats->sent_metrics, 1); + assert_int_equal(stats->lost_metrics, 0); + assert_int_equal(stats->receptions, 1); + assert_int_equal(stats->transmission_successes, 1); + assert_int_equal(stats->transmission_failures, 0); + assert_int_equal(stats->data_lost_events, 0); + assert_int_equal(stats->lost_bytes, 0); + assert_int_equal(stats->reconnects, 0); + + free(connector_specific_config->stream_name); + free(connector_specific_config->auth_key_id); + free(connector_specific_config->secure_key); +} +#endif // HAVE_KINESIS + +#if ENABLE_EXPORTING_PUBSUB +static void test_init_pubsub_instance(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->config.options = EXPORTING_SOURCE_DATA_AS_COLLECTED | EXPORTING_OPTION_SEND_NAMES; + + struct pubsub_specific_config *connector_specific_config = + callocz(1, sizeof(struct pubsub_specific_config)); + instance->config.connector_specific_config = connector_specific_config; + connector_specific_config->credentials_file = strdupz("/test/credentials/file"); + connector_specific_config->project_id = strdupz("test_project_id"); + connector_specific_config->topic_id = strdupz("test_topic_id"); + + expect_function_call(__wrap_pubsub_init); + expect_not_value(__wrap_pubsub_init, pubsub_specific_data_p, NULL); + expect_string(__wrap_pubsub_init, destination, "localhost"); + expect_string(__wrap_pubsub_init, error_message, ""); + expect_string(__wrap_pubsub_init, credentials_file, "/test/credentials/file"); + expect_string(__wrap_pubsub_init, project_id, "test_project_id"); + expect_string(__wrap_pubsub_init, topic_id, "test_topic_id"); + will_return(__wrap_pubsub_init, 0); + + assert_int_equal(init_pubsub_instance(instance), 0); + + assert_ptr_equal(instance->worker, pubsub_connector_worker); + assert_ptr_equal(instance->start_batch_formatting, NULL); + assert_ptr_equal(instance->start_host_formatting, format_host_labels_json_plaintext); + assert_ptr_equal(instance->start_chart_formatting, NULL); + assert_ptr_equal(instance->metric_formatting, format_dimension_collected_json_plaintext); + assert_ptr_equal(instance->end_chart_formatting, NULL); + assert_ptr_equal(instance->end_host_formatting, flush_host_labels); + assert_ptr_equal(instance->end_batch_formatting, NULL); + assert_ptr_not_equal(instance->buffer, NULL); + buffer_free(instance->buffer); + assert_ptr_not_equal(instance->connector_specific_data, NULL); + freez(instance->connector_specific_data); + + instance->config.options = EXPORTING_SOURCE_DATA_AVERAGE | EXPORTING_OPTION_SEND_NAMES; + + expect_function_call(__wrap_pubsub_init); + expect_not_value(__wrap_pubsub_init, pubsub_specific_data_p, NULL); + expect_string(__wrap_pubsub_init, destination, "localhost"); + expect_string(__wrap_pubsub_init, error_message, ""); + expect_string(__wrap_pubsub_init, credentials_file, "/test/credentials/file"); + expect_string(__wrap_pubsub_init, project_id, "test_project_id"); + expect_string(__wrap_pubsub_init, topic_id, "test_topic_id"); + will_return(__wrap_pubsub_init, 0); + + assert_int_equal(init_pubsub_instance(instance), 0); + assert_ptr_equal(instance->metric_formatting, format_dimension_stored_json_plaintext); + + free(connector_specific_config->credentials_file); + free(connector_specific_config->project_id); + free(connector_specific_config->topic_id); +} + +static void test_pubsub_connector_worker(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + struct stats *stats = &instance->stats; + + __real_mark_scheduled_instances(engine); + + expect_function_call(__wrap_rrdhost_is_exportable); + expect_value(__wrap_rrdhost_is_exportable, instance, instance); + expect_value(__wrap_rrdhost_is_exportable, host, localhost); + will_return(__wrap_rrdhost_is_exportable, 1); + + RRDSET *st = localhost->rrdset_root; + expect_function_call(__wrap_rrdset_is_exportable); + expect_value(__wrap_rrdset_is_exportable, instance, instance); + expect_value(__wrap_rrdset_is_exportable, st, st); + will_return(__wrap_rrdset_is_exportable, 1); + + expect_function_call(__wrap_simple_connector_end_batch); + expect_value(__wrap_simple_connector_end_batch, instance, instance); + will_return(__wrap_simple_connector_end_batch, 0); + __real_prepare_buffers(engine); + + struct pubsub_specific_config *connector_specific_config = + callocz(1, sizeof(struct pubsub_specific_config)); + instance->config.connector_specific_config = connector_specific_config; + connector_specific_config->credentials_file = strdupz("/test/credentials/file"); + connector_specific_config->project_id = strdupz("test_project_id"); + connector_specific_config->topic_id = strdupz("test_topic_id"); + + struct pubsub_specific_data *connector_specific_data = callocz(1, sizeof(struct pubsub_specific_data)); + instance->connector_specific_data = (void *)connector_specific_data; + + expect_function_call(__wrap_pubsub_add_message); + expect_not_value(__wrap_pubsub_add_message, pubsub_specific_data_p, NULL); + // The buffer is prepared by Graphite exporting connector + expect_string( + __wrap_pubsub_add_message, data, + "netdata.test-host.chart_name.dimension_name;TAG1=VALUE1 TAG2=VALUE2 123000321 15051\n"); + will_return(__wrap_pubsub_add_message, 0); + + expect_function_call(__wrap_pubsub_publish); + expect_not_value(__wrap_pubsub_publish, pubsub_specific_data_p, NULL); + expect_string(__wrap_pubsub_publish, error_message, ""); + expect_value(__wrap_pubsub_publish, buffered_metrics, 1); + expect_value(__wrap_pubsub_publish, buffered_bytes, 84); + will_return(__wrap_pubsub_publish, 0); + + expect_function_call(__wrap_pubsub_get_result); + expect_not_value(__wrap_pubsub_get_result, pubsub_specific_data_p, NULL); + expect_not_value(__wrap_pubsub_get_result, error_message, NULL); + expect_not_value(__wrap_pubsub_get_result, sent_metrics, NULL); + expect_not_value(__wrap_pubsub_get_result, sent_bytes, NULL); + expect_not_value(__wrap_pubsub_get_result, lost_metrics, NULL); + expect_not_value(__wrap_pubsub_get_result, lost_bytes, NULL); + will_return(__wrap_pubsub_get_result, 0); + + expect_function_call(__wrap_send_internal_metrics); + expect_value(__wrap_send_internal_metrics, instance, instance); + will_return(__wrap_send_internal_metrics, 0); + + pubsub_connector_worker(instance); + + assert_int_equal(stats->buffered_metrics, 0); + assert_int_equal(stats->buffered_bytes, 84); + assert_int_equal(stats->received_bytes, 0); + assert_int_equal(stats->sent_bytes, 84); + assert_int_equal(stats->sent_metrics, 0); + assert_int_equal(stats->lost_metrics, 0); + assert_int_equal(stats->receptions, 1); + assert_int_equal(stats->transmission_successes, 1); + assert_int_equal(stats->transmission_failures, 0); + assert_int_equal(stats->data_lost_events, 0); + assert_int_equal(stats->lost_bytes, 0); + assert_int_equal(stats->reconnects, 0); + + free(connector_specific_config->credentials_file); + free(connector_specific_config->project_id); + free(connector_specific_config->topic_id); +} +#endif // ENABLE_EXPORTING_PUBSUB + +#if HAVE_MONGOC +static void test_init_mongodb_instance(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + instance->config.options = EXPORTING_SOURCE_DATA_AS_COLLECTED | EXPORTING_OPTION_SEND_NAMES; + + struct mongodb_specific_config *connector_specific_config = callocz(1, sizeof(struct mongodb_specific_config)); + instance->config.connector_specific_config = connector_specific_config; + connector_specific_config->database = strdupz("test_database"); + connector_specific_config->collection = strdupz("test_collection"); + instance->config.buffer_on_failures = 10; + + expect_function_call(__wrap_mongoc_init); + expect_function_call(__wrap_mongoc_uri_new_with_error); + expect_string(__wrap_mongoc_uri_new_with_error, uri_string, "localhost"); + expect_not_value(__wrap_mongoc_uri_new_with_error, error, NULL); + will_return(__wrap_mongoc_uri_new_with_error, 0xf1); + + expect_function_call(__wrap_mongoc_uri_get_option_as_int32); + expect_value(__wrap_mongoc_uri_get_option_as_int32, uri, 0xf1); + expect_string(__wrap_mongoc_uri_get_option_as_int32, option, MONGOC_URI_SOCKETTIMEOUTMS); + expect_value(__wrap_mongoc_uri_get_option_as_int32, fallback, 1000); + will_return(__wrap_mongoc_uri_get_option_as_int32, 1000); + + expect_function_call(__wrap_mongoc_uri_set_option_as_int32); + expect_value(__wrap_mongoc_uri_set_option_as_int32, uri, 0xf1); + expect_string(__wrap_mongoc_uri_set_option_as_int32, option, MONGOC_URI_SOCKETTIMEOUTMS); + expect_value(__wrap_mongoc_uri_set_option_as_int32, value, 1000); + will_return(__wrap_mongoc_uri_set_option_as_int32, true); + + expect_function_call(__wrap_mongoc_client_new_from_uri); + expect_value(__wrap_mongoc_client_new_from_uri, uri, 0xf1); + will_return(__wrap_mongoc_client_new_from_uri, 0xf2); + + expect_function_call(__wrap_mongoc_client_set_appname); + expect_value(__wrap_mongoc_client_set_appname, client, 0xf2); + expect_string(__wrap_mongoc_client_set_appname, appname, "netdata"); + will_return(__wrap_mongoc_client_set_appname, true); + + expect_function_call(__wrap_mongoc_client_get_collection); + expect_value(__wrap_mongoc_client_get_collection, client, 0xf2); + expect_string(__wrap_mongoc_client_get_collection, db, "test_database"); + expect_string(__wrap_mongoc_client_get_collection, collection, "test_collection"); + will_return(__wrap_mongoc_client_get_collection, 0xf3); + + expect_function_call(__wrap_mongoc_uri_destroy); + expect_value(__wrap_mongoc_uri_destroy, uri, 0xf1); + + assert_int_equal(init_mongodb_instance(instance), 0); + + assert_ptr_equal(instance->worker, mongodb_connector_worker); + assert_ptr_equal(instance->start_batch_formatting, NULL); + assert_ptr_equal(instance->start_host_formatting, format_host_labels_json_plaintext); + assert_ptr_equal(instance->start_chart_formatting, NULL); + assert_ptr_equal(instance->metric_formatting, format_dimension_collected_json_plaintext); + assert_ptr_equal(instance->end_chart_formatting, NULL); + assert_ptr_equal(instance->end_host_formatting, flush_host_labels); + assert_ptr_equal(instance->end_batch_formatting, format_batch_mongodb); + assert_ptr_equal(instance->prepare_header, NULL); + assert_ptr_equal(instance->check_response, NULL); + + assert_ptr_not_equal(instance->buffer, NULL); + buffer_free(instance->buffer); + + assert_ptr_not_equal(instance->connector_specific_data, NULL); + + struct mongodb_specific_data *connector_specific_data = + (struct mongodb_specific_data *)instance->connector_specific_data; + size_t number_of_buffers = 1; + struct bson_buffer *current_buffer = connector_specific_data->first_buffer; + while (current_buffer->next != connector_specific_data->first_buffer) { + current_buffer = current_buffer->next; + number_of_buffers++; + if (number_of_buffers == (size_t)(instance->config.buffer_on_failures + 1)) { + number_of_buffers = 0; + break; + } + } + assert_int_equal(number_of_buffers, 9); + + free(connector_specific_config->database); + free(connector_specific_config->collection); +} + +static void test_format_batch_mongodb(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + struct stats *stats = &instance->stats; + + struct mongodb_specific_data *connector_specific_data = mallocz(sizeof(struct mongodb_specific_data)); + instance->connector_specific_data = (void *)connector_specific_data; + + struct bson_buffer *current_buffer = callocz(1, sizeof(struct bson_buffer)); + connector_specific_data->first_buffer = current_buffer; + connector_specific_data->first_buffer->next = current_buffer; + connector_specific_data->last_buffer = current_buffer; + + BUFFER *buffer = buffer_create(0); + buffer_sprintf(buffer, "{ \"metric\": \"test_metric\" }\n"); + instance->buffer = buffer; + stats->buffered_metrics = 1; + + assert_int_equal(format_batch_mongodb(instance), 0); + + assert_int_equal(connector_specific_data->last_buffer->documents_inserted, 1); + assert_int_equal(buffer_strlen(buffer), 0); + + size_t len; + char *str = bson_as_canonical_extended_json(connector_specific_data->last_buffer->insert[0], &len); + assert_string_equal(str, "{ \"metric\" : \"test_metric\" }"); + + freez(str); + buffer_free(buffer); +} + +static void test_mongodb_connector_worker(void **state) +{ + struct engine *engine = *state; + struct instance *instance = engine->instance_root; + + struct mongodb_specific_config *connector_specific_config = callocz(1, sizeof(struct mongodb_specific_config)); + instance->config.connector_specific_config = connector_specific_config; + connector_specific_config->database = strdupz("test_database"); + + struct mongodb_specific_data *connector_specific_data = callocz(1, sizeof(struct mongodb_specific_data)); + instance->connector_specific_data = (void *)connector_specific_data; + connector_specific_config->collection = strdupz("test_collection"); + + struct bson_buffer *buffer = callocz(1, sizeof(struct bson_buffer)); + buffer->documents_inserted = 1; + connector_specific_data->first_buffer = buffer; + connector_specific_data->first_buffer->next = buffer; + + connector_specific_data->first_buffer->insert = callocz(1, sizeof(bson_t *)); + bson_error_t bson_error; + connector_specific_data->first_buffer->insert[0] = + bson_new_from_json((const uint8_t *)"{ \"test_key\" : \"test_value\" }", -1, &bson_error); + + connector_specific_data->client = mongoc_client_new("mongodb://localhost"); + connector_specific_data->collection = + __real_mongoc_client_get_collection(connector_specific_data->client, "test_database", "test_collection"); + + expect_function_call(__wrap_mongoc_collection_insert_many); + expect_value(__wrap_mongoc_collection_insert_many, collection, connector_specific_data->collection); + expect_value(__wrap_mongoc_collection_insert_many, documents, connector_specific_data->first_buffer->insert); + expect_value(__wrap_mongoc_collection_insert_many, n_documents, 1); + expect_value(__wrap_mongoc_collection_insert_many, opts, NULL); + expect_value(__wrap_mongoc_collection_insert_many, reply, NULL); + expect_not_value(__wrap_mongoc_collection_insert_many, error, NULL); + will_return(__wrap_mongoc_collection_insert_many, true); + + expect_function_call(__wrap_send_internal_metrics); + expect_value(__wrap_send_internal_metrics, instance, instance); + will_return(__wrap_send_internal_metrics, 0); + + mongodb_connector_worker(instance); + + assert_ptr_equal(connector_specific_data->first_buffer->insert, NULL); + assert_int_equal(connector_specific_data->first_buffer->documents_inserted, 0); + assert_ptr_equal(connector_specific_data->first_buffer, connector_specific_data->first_buffer->next); + + struct stats *stats = &instance->stats; + assert_int_equal(stats->buffered_metrics, 0); + assert_int_equal(stats->buffered_bytes, 0); + assert_int_equal(stats->received_bytes, 0); + assert_int_equal(stats->sent_bytes, 30); + assert_int_equal(stats->sent_metrics, 1); + assert_int_equal(stats->lost_metrics, 0); + assert_int_equal(stats->receptions, 1); + assert_int_equal(stats->transmission_successes, 1); + assert_int_equal(stats->transmission_failures, 0); + assert_int_equal(stats->data_lost_events, 0); + assert_int_equal(stats->lost_bytes, 0); + assert_int_equal(stats->reconnects, 0); + + free(connector_specific_config->database); + free(connector_specific_config->collection); +} +#endif // HAVE_MONGOC + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_exporting_engine, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test(test_read_exporting_config), + cmocka_unit_test_setup_teardown(test_init_connectors, setup_configured_engine, teardown_configured_engine), + cmocka_unit_test_setup_teardown( + test_init_graphite_instance, setup_configured_engine, teardown_configured_engine), + cmocka_unit_test_setup_teardown( + test_init_json_instance, setup_configured_engine, teardown_configured_engine), + cmocka_unit_test_setup_teardown( + test_init_opentsdb_telnet_instance, setup_configured_engine, teardown_configured_engine), + cmocka_unit_test_setup_teardown( + test_init_opentsdb_http_instance, setup_configured_engine, teardown_configured_engine), + cmocka_unit_test_setup_teardown( + test_mark_scheduled_instances, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_rrdhost_is_exportable, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_false_rrdhost_is_exportable, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_rrdset_is_exportable, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_false_rrdset_is_exportable, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_exporting_calculate_value_from_stored_data, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown(test_prepare_buffers, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test(test_exporting_name_copy), + cmocka_unit_test_setup_teardown( + test_format_dimension_collected_graphite_plaintext, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_format_dimension_stored_graphite_plaintext, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_format_dimension_collected_json_plaintext, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_format_dimension_stored_json_plaintext, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_format_dimension_collected_opentsdb_telnet, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_format_dimension_stored_opentsdb_telnet, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_format_dimension_collected_opentsdb_http, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_format_dimension_stored_opentsdb_http, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_exporting_discard_response, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_simple_connector_receive_response, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_simple_connector_send_buffer, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_simple_connector_worker, setup_initialized_engine, teardown_initialized_engine), + }; + + const struct CMUnitTest label_tests[] = { + cmocka_unit_test(test_sanitize_json_string), + cmocka_unit_test(test_sanitize_graphite_label_value), + cmocka_unit_test(test_sanitize_opentsdb_label_value), + cmocka_unit_test_setup_teardown( + test_format_host_labels_json_plaintext, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_format_host_labels_graphite_plaintext, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_format_host_labels_opentsdb_telnet, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_format_host_labels_opentsdb_http, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown(test_flush_host_labels, setup_initialized_engine, teardown_initialized_engine), + }; + + int test_res = cmocka_run_group_tests_name("exporting_engine", tests, NULL, NULL) + + cmocka_run_group_tests_name("labels_in_exporting_engine", label_tests, NULL, NULL); + + const struct CMUnitTest internal_metrics_tests[] = { + cmocka_unit_test_setup_teardown(test_create_main_rusage_chart, setup_rrdhost, teardown_rrdhost), + cmocka_unit_test(test_send_main_rusage), + cmocka_unit_test(test_send_internal_metrics), + }; + + test_res += cmocka_run_group_tests_name("internal_metrics", internal_metrics_tests, NULL, NULL); + + const struct CMUnitTest prometheus_web_api_tests[] = { + cmocka_unit_test_setup_teardown(test_can_send_rrdset, setup_prometheus, teardown_prometheus), + cmocka_unit_test_setup_teardown(test_prometheus_name_copy, setup_prometheus, teardown_prometheus), + cmocka_unit_test_setup_teardown(test_prometheus_label_copy, setup_prometheus, teardown_prometheus), + cmocka_unit_test_setup_teardown(test_prometheus_units_copy, setup_prometheus, teardown_prometheus), + cmocka_unit_test_setup_teardown( + test_format_host_labels_prometheus, setup_configured_engine, teardown_configured_engine), + cmocka_unit_test_setup_teardown( + rrd_stats_api_v1_charts_allmetrics_prometheus, setup_prometheus, teardown_prometheus), + }; + + test_res += cmocka_run_group_tests_name("prometheus_web_api", prometheus_web_api_tests, NULL, NULL); + +#if ENABLE_PROMETHEUS_REMOTE_WRITE + const struct CMUnitTest prometheus_remote_write_tests[] = { + cmocka_unit_test_setup_teardown( + test_init_prometheus_remote_write_instance, setup_configured_engine, teardown_configured_engine), + cmocka_unit_test_setup_teardown( + test_prometheus_remote_write_prepare_header, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test(test_process_prometheus_remote_write_response), + cmocka_unit_test_setup_teardown( + test_format_host_prometheus_remote_write, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_format_dimension_prometheus_remote_write, setup_initialized_engine, teardown_initialized_engine), + cmocka_unit_test_setup_teardown( + test_format_batch_prometheus_remote_write, setup_initialized_engine, teardown_initialized_engine), + }; + + test_res += cmocka_run_group_tests_name( + "prometheus_remote_write_exporting_connector", prometheus_remote_write_tests, NULL, NULL); +#endif + +#if HAVE_KINESIS + const struct CMUnitTest kinesis_tests[] = { + cmocka_unit_test_setup_teardown( + test_init_aws_kinesis_instance, setup_configured_engine, teardown_configured_engine), + cmocka_unit_test_setup_teardown( + test_aws_kinesis_connector_worker, setup_initialized_engine, teardown_initialized_engine), + }; + + test_res += cmocka_run_group_tests_name("kinesis_exporting_connector", kinesis_tests, NULL, NULL); +#endif + +#if ENABLE_EXPORTING_PUBSUB + const struct CMUnitTest pubsub_tests[] = { + cmocka_unit_test_setup_teardown( + test_init_pubsub_instance, setup_configured_engine, teardown_configured_engine), + cmocka_unit_test_setup_teardown( + test_pubsub_connector_worker, setup_initialized_engine, teardown_initialized_engine), + }; + + test_res += cmocka_run_group_tests_name("pubsub_exporting_connector", pubsub_tests, NULL, NULL); +#endif + +#if HAVE_MONGOC + const struct CMUnitTest mongodb_tests[] = { + cmocka_unit_test_setup_teardown( + test_init_mongodb_instance, setup_configured_engine, teardown_configured_engine), + cmocka_unit_test_setup_teardown( + test_format_batch_mongodb, setup_configured_engine, teardown_configured_engine), + cmocka_unit_test_setup_teardown( + test_mongodb_connector_worker, setup_configured_engine, teardown_configured_engine), + }; + + test_res += cmocka_run_group_tests_name("mongodb_exporting_connector", mongodb_tests, NULL, NULL); +#endif + + return test_res; +} diff --git a/exporting/tests/test_exporting_engine.h b/exporting/tests/test_exporting_engine.h new file mode 100644 index 00000000..800be1b9 --- /dev/null +++ b/exporting/tests/test_exporting_engine.h @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef TEST_EXPORTING_ENGINE_H +#define TEST_EXPORTING_ENGINE_H 1 + +#include "libnetdata/libnetdata.h" + +#include "exporting/exporting_engine.h" +#include "exporting/graphite/graphite.h" +#include "exporting/json/json.h" +#include "exporting/opentsdb/opentsdb.h" + +#if ENABLE_PROMETHEUS_REMOTE_WRITE +#include "exporting/prometheus/remote_write/remote_write.h" +#endif + +#if HAVE_KINESIS +#include "exporting/aws_kinesis/aws_kinesis.h" +#endif + +#if ENABLE_EXPORTING_PUBSUB +#include "exporting/pubsub/pubsub.h" +#endif + +#if HAVE_MONGOC +#include "exporting/mongodb/mongodb.h" +#endif + +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <stdint.h> +#include <cmocka.h> + +#define MAX_LOG_LINE 1024 +extern char log_line[]; + +// ----------------------------------------------------------------------- +// doubles for Netdata functions + +const char *__wrap_strdupz(const char *s); +void __wrap_info_int(const char *file, const char *function, const unsigned long line, const char *fmt, ...); +int __wrap_connect_to_one_of( + const char *destination, + int default_port, + struct timeval *timeout, + size_t *reconnects_counter, + char *connected_to, + size_t connected_to_size); +void __rrdhost_check_rdlock(RRDHOST *host, const char *file, const char *function, const unsigned long line); +void __rrdset_check_rdlock(RRDSET *st, const char *file, const char *function, const unsigned long line); +void __rrd_check_rdlock(const char *file, const char *function, const unsigned long line); +time_t __mock_rrddim_query_oldest_time(RRDDIM *rd); +time_t __mock_rrddim_query_latest_time(RRDDIM *rd); +void __mock_rrddim_query_init(RRDDIM *rd, struct rrddim_query_handle *handle, time_t start_time, time_t end_time); +int __mock_rrddim_query_is_finished(struct rrddim_query_handle *handle); +storage_number __mock_rrddim_query_next_metric(struct rrddim_query_handle *handle, time_t *current_time); +void __mock_rrddim_query_finalize(struct rrddim_query_handle *handle); + +// ----------------------------------------------------------------------- +// wraps for system functions + +void __wrap_uv_thread_create(uv_thread_t thread, void (*worker)(void *arg), void *arg); +void __wrap_uv_mutex_lock(uv_mutex_t *mutex); +void __wrap_uv_mutex_unlock(uv_mutex_t *mutex); +void __wrap_uv_cond_signal(uv_cond_t *cond_var); +void __wrap_uv_cond_wait(uv_cond_t *cond_var, uv_mutex_t *mutex); +ssize_t __wrap_recv(int sockfd, void *buf, size_t len, int flags); +ssize_t __wrap_send(int sockfd, const void *buf, size_t len, int flags); + +// ----------------------------------------------------------------------- +// doubles and originals for exporting engine functions + +struct engine *__real_read_exporting_config(); +struct engine *__wrap_read_exporting_config(); +struct engine *__mock_read_exporting_config(); + +int __real_init_connectors(struct engine *engine); +int __wrap_init_connectors(struct engine *engine); + +int __real_mark_scheduled_instances(struct engine *engine); +int __wrap_mark_scheduled_instances(struct engine *engine); + +calculated_number __real_exporting_calculate_value_from_stored_data( + struct instance *instance, + RRDDIM *rd, + time_t *last_timestamp); +calculated_number __wrap_exporting_calculate_value_from_stored_data( + struct instance *instance, + RRDDIM *rd, + time_t *last_timestamp); + +int __real_prepare_buffers(struct engine *engine); +int __wrap_prepare_buffers(struct engine *engine); + +void __real_create_main_rusage_chart(RRDSET **st_rusage, RRDDIM **rd_user, RRDDIM **rd_system); +void __wrap_create_main_rusage_chart(RRDSET **st_rusage, RRDDIM **rd_user, RRDDIM **rd_system); + +void __real_send_main_rusage(RRDSET *st_rusage, RRDDIM *rd_user, RRDDIM *rd_system); +void __wrap_send_main_rusage(RRDSET *st_rusage, RRDDIM *rd_user, RRDDIM *rd_system); + +int __real_send_internal_metrics(struct instance *instance); +int __wrap_send_internal_metrics(struct instance *instance); + +int __real_rrdhost_is_exportable(struct instance *instance, RRDHOST *host); +int __wrap_rrdhost_is_exportable(struct instance *instance, RRDHOST *host); + +int __real_rrdset_is_exportable(struct instance *instance, RRDSET *st); +int __wrap_rrdset_is_exportable(struct instance *instance, RRDSET *st); + +int __mock_start_batch_formatting(struct instance *instance); +int __mock_start_host_formatting(struct instance *instance, RRDHOST *host); +int __mock_start_chart_formatting(struct instance *instance, RRDSET *st); +int __mock_metric_formatting(struct instance *instance, RRDDIM *rd); +int __mock_end_chart_formatting(struct instance *instance, RRDSET *st); +int __mock_end_host_formatting(struct instance *instance, RRDHOST *host); +int __mock_end_batch_formatting(struct instance *instance); + +int __wrap_simple_connector_end_batch(struct instance *instance); + +#if ENABLE_PROMETHEUS_REMOTE_WRITE +void *__real_init_write_request(); +void *__wrap_init_write_request(); + +void __real_add_host_info( + void *write_request_p, + const char *name, const char *instance, const char *application, const char *version, const int64_t timestamp); +void __wrap_add_host_info( + void *write_request_p, + const char *name, const char *instance, const char *application, const char *version, const int64_t timestamp); + +void __real_add_label(void *write_request_p, char *key, char *value); +void __wrap_add_label(void *write_request_p, char *key, char *value); + +void __real_add_metric( + void *write_request_p, + const char *name, const char *chart, const char *family, const char *dimension, + const char *instance, const double value, const int64_t timestamp); +void __wrap_add_metric( + void *write_request_p, + const char *name, const char *chart, const char *family, const char *dimension, + const char *instance, const double value, const int64_t timestamp); +#endif /* ENABLE_PROMETHEUS_REMOTE_WRITE */ + +#if HAVE_KINESIS +void __wrap_aws_sdk_init(); +void __wrap_kinesis_init( + void *kinesis_specific_data_p, const char *region, const char *access_key_id, const char *secret_key, + const long timeout); +void __wrap_kinesis_put_record( + void *kinesis_specific_data_p, const char *stream_name, const char *partition_key, const char *data, + size_t data_len); +int __wrap_kinesis_get_result(void *request_outcomes_p, char *error_message, size_t *sent_bytes, size_t *lost_bytes); +#endif /* HAVE_KINESIS */ + +#if ENABLE_EXPORTING_PUBSUB +int __wrap_pubsub_init( + void *pubsub_specific_data_p, char *error_message, const char *destination, const char *credentials_file, + const char *project_id, const char *topic_id); +int __wrap_pubsub_add_message(void *pubsub_specific_data_p, char *data); +int __wrap_pubsub_publish( + void *pubsub_specific_data_p, char *error_message, size_t buffered_metrics, size_t buffered_bytes); +int __wrap_pubsub_get_result( + void *pubsub_specific_data_p, char *error_message, + size_t *sent_metrics, size_t *sent_bytes, size_t *lost_metrics, size_t *lost_bytes); +#endif /* ENABLE_EXPORTING_PUBSUB */ + +#if HAVE_MONGOC +void __wrap_mongoc_init(); +mongoc_uri_t *__wrap_mongoc_uri_new_with_error(const char *uri_string, bson_error_t *error); +int32_t __wrap_mongoc_uri_get_option_as_int32(const mongoc_uri_t *uri, const char *option, int32_t fallback); +bool __wrap_mongoc_uri_set_option_as_int32(const mongoc_uri_t *uri, const char *option, int32_t value); +mongoc_client_t *__wrap_mongoc_client_new_from_uri(const mongoc_uri_t *uri); +bool __wrap_mongoc_client_set_appname(mongoc_client_t *client, const char *appname); +mongoc_collection_t * +__wrap_mongoc_client_get_collection(mongoc_client_t *client, const char *db, const char *collection); +mongoc_collection_t * +__real_mongoc_client_get_collection(mongoc_client_t *client, const char *db, const char *collection); +void __wrap_mongoc_uri_destroy(mongoc_uri_t *uri); +bool __wrap_mongoc_collection_insert_many( + mongoc_collection_t *collection, + const bson_t **documents, + size_t n_documents, + const bson_t *opts, + bson_t *reply, + bson_error_t *error); +#endif /* HAVE_MONGOC */ + +// ----------------------------------------------------------------------- +// fixtures + +int setup_configured_engine(void **state); +int teardown_configured_engine(void **state); +int setup_rrdhost(); +int teardown_rrdhost(); +int setup_initialized_engine(void **state); +int teardown_initialized_engine(void **state); +int setup_prometheus(void **state); +int teardown_prometheus(void **state); + +void init_connectors_in_tests(struct engine *engine); + +#endif /* TEST_EXPORTING_ENGINE_H */ |