/* Copyright (c) 2015-2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#define LOG_PRIVATE
#include "lib/log/torlog.h"
#include "test/log_test_helpers.h"
/**
* \file log_test_helpers.c
* \brief Code to check for expected log messages during testing.
*/
static void mock_saving_logv(int severity, log_domain_mask_t domain,
const char *funcname, const char *suffix,
const char *format, va_list ap)
CHECK_PRINTF(5, 0);
/**
* Smartlist of all the logs we've received since we last set up
* log capture.
*/
static smartlist_t *saved_logs = NULL;
/** Boolean: should we also send messages to the test-runner? */
static int echo_to_real_logs = 1;
/** Record logs at this level or more severe */
static int record_logs_at_level = LOG_ERR;
static int saved_log_level = 0;
/**
* As setup_capture_of_logs, but do not relay log messages into the main
* logging system.
*
* Avoid using this function; use setup_capture_of_logs() instead if you
* can. If you must use this function, then make sure you detect any
* unexpected log messages, and treat them as test failures. */
void
setup_full_capture_of_logs(int new_level)
{
setup_capture_of_logs(new_level);
echo_to_real_logs = 0;
}
/**
* Temporarily capture all the messages logged at severity new_level or
* higher.
*
* This function does not prevent messages from being sent to the main
* logging system.
*/
void
setup_capture_of_logs(int new_level)
{
if (saved_log_level == 0) {
saved_log_level = log_global_min_severity_;
} else {
tor_assert(0);
}
/* Only change the log_global_min_severity_ if we're making things _more_
* verbose. Otherwise we could prevent real log messages that the test-
* runner wanted.
*/
if (log_global_min_severity_ < new_level)
log_global_min_severity_ = new_level;
record_logs_at_level = new_level;
mock_clean_saved_logs();
saved_logs = smartlist_new();
MOCK(logv, mock_saving_logv);
echo_to_real_logs = 1;
}
/**
* Undo setup_capture_of_logs().
*
* This function is safe to call more than once.
*/
void
teardown_capture_of_logs(void)
{
UNMOCK(logv);
if (saved_log_level)
log_global_min_severity_ = saved_log_level;
saved_log_level = 0;
mock_clean_saved_logs();
}
/**
* Clear all messages in mock_saved_logs()
*/
void
mock_clean_saved_logs(void)
{
if (!saved_logs)
return;
SMARTLIST_FOREACH(saved_logs, mock_saved_log_entry_t *, m,
{ tor_free(m->generated_msg); tor_free(m); });
smartlist_free(saved_logs);
saved_logs = NULL;
}
/**
* Return a list of all the messages captured since the last
* setup_[full_]capture_of_logs() call. Each log call is recorded as a
* mock_saved_log_entry_t.
*/
const smartlist_t *
mock_saved_logs(void)
{
return saved_logs;
}
int
mock_saved_log_n_entries(void)
{
return saved_logs ? smartlist_len(saved_logs) : 0;
}
/**
* Return true iff there is a message recorded by log capture
* that is exactly equal to msg
*/
int
mock_saved_log_has_message(const char *msg)
{
if (saved_logs) {
SMARTLIST_FOREACH(saved_logs, mock_saved_log_entry_t *, m,
{
if (msg && m->generated_msg &&
!strcmp(msg, m->generated_msg)) {
return 1;
}
});
}
return 0;
}
/**
* Return true iff there is a message recorded by log capture
* that contains msg as a substring.
*/
int
mock_saved_log_has_message_containing(const char *msg)
{
if (saved_logs) {
SMARTLIST_FOREACH(saved_logs, mock_saved_log_entry_t *, m,
{
if (msg && m->generated_msg &&
strstr(m->generated_msg, msg)) {
return 1;
}
});
}
return 0;
}
/** Return true iff the saved logs have any messages with severity */
int
mock_saved_log_has_severity(int severity)
{
int has_sev = 0;
if (saved_logs) {
SMARTLIST_FOREACH(saved_logs, mock_saved_log_entry_t *, m,
{
if (m->severity == severity) {
has_sev = 1;
}
});
}
return has_sev;
}
/** Return true iff the the saved logs have at lease one message */
int
mock_saved_log_has_entry(void)
{
if (saved_logs) {
return smartlist_len(saved_logs) > 0;
}
return 0;
}
/* Replacement for logv: record the log message, and (maybe) send it
* into the logging system again.
*/
static void
mock_saving_logv(int severity, log_domain_mask_t domain,
const char *funcname, const char *suffix,
const char *format, va_list ap)
{
char *buf = tor_malloc_zero(10240);
int n;
n = tor_vsnprintf(buf,10240,format,ap);
tor_assert(n < 10240-1);
buf[n]='\n';
buf[n+1]='\0';
if (echo_to_real_logs) {
tor_log(severity, domain|LD_NO_MOCK, "%s", buf);
}
if (severity > record_logs_at_level) {
tor_free(buf);
return;
}
if (!saved_logs)
saved_logs = smartlist_new();
mock_saved_log_entry_t *e = tor_malloc_zero(sizeof(mock_saved_log_entry_t));
e->severity = severity;
e->funcname = funcname;
e->suffix = suffix;
e->format = format;
e->generated_msg = tor_strdup(buf);
tor_free(buf);
smartlist_add(saved_logs, e);
}
void
mock_dump_saved_logs(void)
{
if (saved_logs == NULL) {
puts(" Captured logs: NULL");
return;
}
puts(" Captured logs:");
SMARTLIST_FOREACH_BEGIN(saved_logs, const mock_saved_log_entry_t *, m) {
printf("% 5d. %s: %s\n", m_sl_idx + 1,
log_level_to_string(m->severity),
escaped(m->generated_msg));
} SMARTLIST_FOREACH_END(m);
}