blob: 43b63c1b3b867d31cba7a61aab234a489ee08fb7 [file] [log] [blame]
/*
* Copyright (c) 2018 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Test log core
*
*/
#include <tc_util.h>
#include <stdbool.h>
#include <zephyr.h>
#include <ztest.h>
#include <logging/log_backend.h>
#include <logging/log_ctrl.h>
#include <logging/log.h>
#include "test_module.h"
#define LOG_MODULE_NAME test
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
typedef void (*custom_put_callback_t)(struct log_backend const *const backend,
struct log_msg *msg, size_t counter);
static bool in_panic;
struct backend_cb {
size_t counter;
bool panic;
bool keep_msgs;
bool check_id;
u32_t exp_id[100];
bool check_timestamp;
u32_t exp_timestamps[100];
bool check_args;
u32_t exp_nargs[100];
bool check_strdup;
bool exp_strdup[100];
custom_put_callback_t callback;
u32_t total_drops;
};
static void put(struct log_backend const *const backend,
struct log_msg *msg)
{
log_msg_get(msg);
u32_t nargs = log_msg_nargs_get(msg);
struct backend_cb *cb = (struct backend_cb *)backend->cb->ctx;
if (cb->check_id) {
u32_t exp_id = cb->exp_id[cb->counter];
zassert_equal(log_msg_source_id_get(msg),
exp_id,
"Unexpected source_id");
}
if (cb->check_timestamp) {
u32_t exp_timestamp = cb->exp_timestamps[cb->counter];
zassert_equal(log_msg_timestamp_get(msg),
exp_timestamp,
"Unexpected message index");
}
/* Arguments in the test are fixed, 1,2,3,4,5,... */
if (cb->check_args && log_msg_is_std(msg) && nargs > 0) {
for (int i = 0; i < nargs; i++) {
u32_t arg = log_msg_arg_get(msg, i);
zassert_equal(i+1, arg,
"Unexpected argument in the message");
}
}
if (cb->check_strdup) {
zassert_false(cb->exp_strdup[cb->counter]
^ log_is_strdup((void *)log_msg_arg_get(msg, 0)),
NULL);
}
if (cb->callback) {
cb->callback(backend, msg, cb->counter);
}
cb->counter++;
if (!cb->keep_msgs) {
log_msg_put(msg);
}
}
static void panic(struct log_backend const *const backend)
{
struct backend_cb *cb = (struct backend_cb *)backend->cb->ctx;
cb->panic = true;
}
static void dropped(struct log_backend const *const backend, u32_t cnt)
{
struct backend_cb *cb = (struct backend_cb *)backend->cb->ctx;
cb->total_drops += cnt;
}
const struct log_backend_api log_backend_test_api = {
.put = put,
.panic = panic,
.dropped = dropped,
};
LOG_BACKEND_DEFINE(backend1, log_backend_test_api, false);
struct backend_cb backend1_cb;
LOG_BACKEND_DEFINE(backend2, log_backend_test_api, false);
struct backend_cb backend2_cb;
static u32_t stamp;
static u32_t test_source_id;
static u32_t timestamp_get(void)
{
return stamp++;
}
/**
* @brief Function for finding source ID based on source name.
*
* @param name Source name
*
* @return Source ID.
*/
static int log_source_id_get(const char *name)
{
for (int i = 0; i < log_src_cnt_get(CONFIG_LOG_DOMAIN_ID); i++) {
if (strcmp(log_source_name_get(CONFIG_LOG_DOMAIN_ID, i), name)
== 0) {
return i;
}
}
return -1;
}
static void log_setup(bool backend2_enable)
{
stamp = 0U;
zassert_false(in_panic, "Logger in panic state.");
log_init();
zassert_equal(0, log_set_timestamp_func(timestamp_get, 0),
"Expects successful timestamp function setting.");
memset(&backend1_cb, 0, sizeof(backend1_cb));
log_backend_enable(&backend1, &backend1_cb, LOG_LEVEL_DBG);
if (backend2_enable) {
memset(&backend2_cb, 0, sizeof(backend2_cb));
log_backend_enable(&backend2, &backend2_cb, LOG_LEVEL_DBG);
} else {
log_backend_disable(&backend2);
}
test_source_id = log_source_id_get(STRINGIFY(LOG_MODULE_NAME));
}
/*
* Test is using 2 backends and runtime filtering is enabled. After first call
* filtering for backend2 is reduced to warning. It is expected that next INFO
* level log message will be passed only to backend1.
*/
static void test_log_backend_runtime_filtering(void)
{
log_setup(true);
backend1_cb.check_timestamp = true;
backend2_cb.check_timestamp = true;
backend1_cb.exp_timestamps[0] = 0U;
backend1_cb.exp_timestamps[1] = 1U;
backend1_cb.exp_timestamps[2] = 2U;
/* Expect one less log message */
backend2_cb.exp_timestamps[0] = 0U;
backend2_cb.exp_timestamps[1] = 2U;
LOG_INF("test");
while (log_process(false)) {
}
log_filter_set(&backend2,
CONFIG_LOG_DOMAIN_ID,
test_source_id,
LOG_LEVEL_WRN);
LOG_INF("test");
LOG_WRN("test");
while (log_process(false)) {
}
zassert_equal(3,
backend1_cb.counter,
"Unexpected amount of messages received by the backend.");
zassert_equal(2,
backend2_cb.counter,
"Unexpected amount of messages received by the backend.");
}
/*
* When LOG_MOVE_OVERFLOW is enabled, logger should discard oldest messages when
* there is no room. However, if after discarding all messages there is still no
* room then current log is discarded.
*/
u8_t data[CONFIG_LOG_BUFFER_SIZE];
static void test_log_overflow(void)
{
u32_t msgs_in_buf = CONFIG_LOG_BUFFER_SIZE/sizeof(union log_msg_chunk);
u32_t max_hexdump_len = LOG_MSG_HEXDUMP_BYTES_HEAD_CHUNK +
HEXDUMP_BYTES_CONT_MSG * (msgs_in_buf - 1);
u32_t hexdump_len = max_hexdump_len - HEXDUMP_BYTES_CONT_MSG;
zassert_true(IS_ENABLED(CONFIG_LOG_MODE_OVERFLOW),
"Test requires that overflow mode is enabled");
log_setup(false);
backend1_cb.check_timestamp = true;
backend2_cb.check_timestamp = true;
/* expect first message to be dropped */
backend1_cb.exp_timestamps[0] = 1U;
backend1_cb.exp_timestamps[1] = 2U;
LOG_INF("test");
LOG_INF("test");
LOG_HEXDUMP_INF(data, hexdump_len, "test");
while (log_process(false)) {
}
/* Expect big message to be dropped because it does not fit in.
* First message is also dropped in the process of finding free space.
*/
backend1_cb.exp_timestamps[2] = 3U;
LOG_INF("test");
LOG_HEXDUMP_INF(data, max_hexdump_len+1, "test");
while (log_process(false)) {
}
zassert_equal(2,
backend1_cb.counter,
"Unexpected amount of messages received by the backend.");
}
/*
* Test checks if arguments are correctly processed by the logger.
*
* Log messages with supported number of messages are called. Test backend
* validates number of arguments and values.
*/
static void test_log_arguments(void)
{
log_setup(false);
backend1_cb.check_args = true;
backend1_cb.exp_nargs[0] = 10U;
backend1_cb.exp_nargs[1] = 1U;
backend1_cb.exp_nargs[2] = 2U;
backend1_cb.exp_nargs[3] = 3U;
backend1_cb.exp_nargs[4] = 4U;
backend1_cb.exp_nargs[5] = 5U;
backend1_cb.exp_nargs[6] = 6U;
backend1_cb.exp_nargs[7] = 10U;
LOG_INF("test");
LOG_INF("test %d", 1);
LOG_INF("test %d %d", 1, 2);
LOG_INF("test %d %d %d", 1, 2, 3);
LOG_INF("test %d %d %d %d", 1, 2, 3, 4);
LOG_INF("test %d %d %d %d %d", 1, 2, 3, 4, 5);
LOG_INF("test %d %d %d %d %d %d", 1, 2, 3, 4, 5, 6);
LOG_INF("test %d %d %d %d %d %d %d %d %d %d",
1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
while (log_process(false)) {
}
zassert_equal(8,
backend1_cb.counter,
"Unexpected amount of messages received by the backend.");
}
/* Function comes from the file which is part of test module. It is
* expected that logs coming from it will have same source_id as current
* module (this file).
*/
static void test_log_from_declared_module(void)
{
log_setup(false);
/* Setup log backend to validate source_id of the message. */
backend1_cb.check_id = true;
backend1_cb.exp_id[0] = LOG_CURRENT_MODULE_ID();
backend1_cb.exp_id[1] = LOG_CURRENT_MODULE_ID();
test_func();
test_inline_func();
while (log_process(false)) {
}
zassert_equal(2, backend1_cb.counter,
"Unexpected amount of messages received by the backend.");
}
static void test_log_strdup_gc(void)
{
char test_str[] = "test string";
char *dstr;
log_setup(false);
BUILD_ASSERT(CONFIG_LOG_STRDUP_BUF_COUNT == 1,
"Test assumes certain configuration");
backend1_cb.check_strdup = true;
backend1_cb.exp_strdup[0] = true;
backend1_cb.exp_strdup[1] = false;
dstr = log_strdup(test_str);
/* test if message freeing is not fooled by using value within strdup
* buffer pool but with different format specifier.
*/
LOG_INF("%s %p", dstr, dstr + 1);
LOG_INF("%s", log_strdup(test_str));
while (log_process(false)) {
}
zassert_equal(2, backend1_cb.counter,
"Unexpected amount of messages received by the backend.");
/* Processing should free strdup buffer. */
backend1_cb.exp_strdup[2] = true;
LOG_INF("%s", log_strdup(test_str));
while (log_process(false)) {
}
zassert_equal(3, backend1_cb.counter,
"Unexpected amount of messages received by the backend.");
}
#define DETECT_STRDUP_MISSED(str, do_strdup, ...) \
{\
char tmp[] = "tmp";\
u32_t exp_cnt = backend1_cb.counter + 1 + (do_strdup ? 0 : 1); \
LOG_ERR(str, ##__VA_ARGS__, do_strdup ? log_strdup(tmp) : tmp); \
\
while (log_process(false)) { \
} \
\
zassert_equal(exp_cnt, backend1_cb.counter,\
"Unexpected amount of messages received by the backend (%d).", \
backend1_cb.counter); \
}
static void test_log_strdup_detect_miss(void)
{
if (IS_ENABLED(CONFIG_LOG_DETECT_MISSED_STRDUP)) {
return;
}
log_setup(false);
DETECT_STRDUP_MISSED("%s", true);
DETECT_STRDUP_MISSED("%s", false);
DETECT_STRDUP_MISSED("%-20s", true);
DETECT_STRDUP_MISSED("%-20s", false);
DETECT_STRDUP_MISSED("%20s", true);
DETECT_STRDUP_MISSED("%20s", false);
DETECT_STRDUP_MISSED("%20.4s", true);
DETECT_STRDUP_MISSED("%20.4s", false);
DETECT_STRDUP_MISSED("%% %s %%", true);
DETECT_STRDUP_MISSED("%% %s %%", false);
DETECT_STRDUP_MISSED("%% %08X %s", true, 4);
DETECT_STRDUP_MISSED("%% %08X %s", false, 4);
}
static void strdup_trim_callback(struct log_backend const *const backend,
struct log_msg *msg, size_t counter)
{
char *str = (char *)log_msg_arg_get(msg, 0);
size_t len = strlen(str);
zassert_equal(len, CONFIG_LOG_STRDUP_MAX_STRING,
"Expected trimmed string");
}
static void test_strdup_trimming(void)
{
char test_str[] = "123456789";
BUILD_ASSERT(CONFIG_LOG_STRDUP_MAX_STRING == 8,
"Test assumes certain configuration");
log_setup(false);
backend1_cb.callback = strdup_trim_callback;
LOG_INF("%s", log_strdup(test_str));
while (log_process(false)) {
}
zassert_equal(1, backend1_cb.counter,
"Unexpected amount of messages received by the backend.");
}
static void log_n_messages(u32_t n_msg, u32_t exp_dropped)
{
int i;
for (i = 0; i < n_msg; i++) {
LOG_INF("dummy");
}
while (log_process(false)) {
}
zassert_equal(backend1_cb.total_drops, exp_dropped,
"Unexpected log msg dropped %d (expected %d)",
backend1_cb.total_drops, exp_dropped);
}
/*
* Test checks if backend receives notification about dropped messages. It
* first blocks threads to ensure full control of log processing time and
* then logs certain log messages, expecting dropped notification.
*/
static void test_log_msg_dropped_notification(void)
{
__ASSERT_NO_MSG(CONFIG_LOG_MODE_OVERFLOW);
u32_t capacity = CONFIG_LOG_BUFFER_SIZE/sizeof(struct log_msg);
log_setup(false);
/* Ensure that log messages aren't processed */
k_sched_lock();
log_n_messages(capacity, 0);
/* Expect messages dropped when logger more than buffer capacity. */
log_n_messages(capacity + 1, 1);
log_n_messages(capacity + 2, 3);
k_sched_unlock();
}
static void test_single_z_log_get_s_mask(const char *str, u32_t nargs,
u32_t exp_mask)
{
u32_t mask = z_log_get_s_mask(str, nargs);
zassert_equal(mask, exp_mask, "Unexpected mask %x (expected %x)",
mask, exp_mask);
}
static void test_z_log_get_s_mask(void)
{
test_single_z_log_get_s_mask("%d%%%-10s%p%x", 4, 0x2);
test_single_z_log_get_s_mask("%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d"
"%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%s",
32, 0x80000000);
}
/*
* Test checks if panic is correctly executed. On panic logger should flush all
* messages and process logs in place (not in deferred way).
*
* NOTE: this test must be the last in the suite because after this test log
* is in panic mode.
*/
static void test_log_panic(void)
{
log_setup(false);
LOG_INF("test");
LOG_INF("test");
/* logs should be flushed in panic */
log_panic();
in_panic = true;
zassert_true(backend1_cb.panic,
"Expecting backend to receive panic notification.");
zassert_equal(2,
backend1_cb.counter,
"Unexpected amount of messages received by the backend.");
/* messages processed where called */
LOG_INF("test");
zassert_equal(3,
backend1_cb.counter,
"Unexpected amount of messages received by the backend.");
}
/*test case main entry*/
void test_main(void)
{
ztest_test_suite(test_log_list,
ztest_unit_test(test_log_backend_runtime_filtering),
ztest_unit_test(test_log_overflow),
ztest_unit_test(test_log_arguments),
ztest_unit_test(test_log_from_declared_module),
ztest_unit_test(test_log_strdup_gc),
ztest_unit_test(test_log_strdup_detect_miss),
ztest_unit_test(test_strdup_trimming),
ztest_unit_test(test_log_msg_dropped_notification),
ztest_unit_test(test_z_log_get_s_mask),
ztest_unit_test(test_log_panic));
ztest_run_test_suite(test_log_list);
}