Add test framework

master
nirenjan 2021-07-14 11:00:44 -07:00
parent 490a2e3faa
commit 10941defb2
5 changed files with 272 additions and 27 deletions

View File

@ -18,3 +18,25 @@ libpinelog_la_SOURCES = pinelog.c
libpinelog_la_CFLAGS = @PINELOG_CFLAGS@ $(WARN_CFLAGS) libpinelog_la_CFLAGS = @PINELOG_CFLAGS@ $(WARN_CFLAGS)
libpinelog_la_LDFLAGS = $(WARN_LDFLAGS) libpinelog_la_LDFLAGS = $(WARN_LDFLAGS)
test_SRCFILES = test_pinelog.c $(libpinelog_la_SOURCES)
test_CFLAGS = \
-DPINELOG_FATAL_STR='"F"' \
-DPINELOG_ERROR_STR='"E"' \
-DPINELOG_WARNING_STR='"W"' \
-DPINELOG_INFO_STR='"I"' \
-DPINELOG_DEBUG_STR='"D"' \
-DPINELOG_TRACE_STR='"T"' \
-DPINELOG_DEFAULT_LEVEL=PINELOG_LVL_TRACE \
-DPINELOG_DEFAULT_STREAM=stderr \
-DPINELOG_TEST
LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tap-driver.sh
TESTS = \
test_ts_lvl_tr
check_PROGRAMS = $(TESTS)
test_ts_lvl_tr_SOURCES = $(test_SRCFILES)
test_ts_lvl_tr_CFLAGS = $(WARN_CFLAGS) $(test_CFLAGS) \
-DPINELOG_SHOW_DATE=1 -DPINELOG_SHOW_LEVEL=1 -DPINELOG_SHOW_BACKTRACE=1
test_ts_lvl_tr_LDFLAGS = $(WARN_LDFLAGS)

View File

@ -10,6 +10,7 @@ AM_INIT_AUTOMAKE([-Wall foreign subdir-objects])
AC_REQUIRE_AUX_FILE([tap-driver.sh]) AC_REQUIRE_AUX_FILE([tap-driver.sh])
AC_PROG_CC AC_PROG_CC
AC_PROG_CC_STDC AC_PROG_CC_STDC
AC_PROG_AWK
AM_PROG_AR AM_PROG_AR
LT_INIT LT_INIT
PKG_PROG_PKG_CONFIG PKG_PROG_PKG_CONFIG

View File

@ -43,28 +43,28 @@
/********************************************************************** /**********************************************************************
* Configure level strings * Configure level strings
*********************************************************************/ *********************************************************************/
#ifndef PINELOG_FATAL #ifndef PINELOG_FATAL_STR
#define PINELOG_FATAL "FATAL" #define PINELOG_FATAL_STR "FATAL"
#endif #endif
#ifndef PINELOG_ERROR #ifndef PINELOG_ERROR_STR
#define PINELOG_ERROR "ERROR" #define PINELOG_ERROR_STR "ERROR"
#endif #endif
#ifndef PINELOG_WARNING #ifndef PINELOG_WARNING_STR
#define PINELOG_WARNING "WARNING" #define PINELOG_WARNING_STR "WARNING"
#endif #endif
#ifndef PINELOG_INFO #ifndef PINELOG_INFO_STR
#define PINELOG_INFO "INFO" #define PINELOG_INFO_STR "INFO"
#endif #endif
#ifndef PINELOG_DEBUG #ifndef PINELOG_DEBUG_STR
#define PINELOG_DEBUG "DEBUG" #define PINELOG_DEBUG_STR "DEBUG"
#endif #endif
#ifndef PINELOG_TRACE #ifndef PINELOG_TRACE_STR
#define PINELOG_TRACE "TRACE" #define PINELOG_TRACE_STR "TRACE"
#endif #endif
/********************************************************************** /**********************************************************************
@ -103,6 +103,13 @@ int pinelog_set_output_stream(FILE *stream)
return 0; return 0;
} }
#ifdef PINELOG_TEST
FILE * pinelog_get_output_stream(void)
{
return output_stream;
}
#endif
int pinelog_set_output_file(const char *file) int pinelog_set_output_file(const char *file)
{ {
FILE *stream; FILE *stream;
@ -151,41 +158,46 @@ void pinelog_log_message(int level, const char *file, int line, const char *fmt,
level = PINELOG_LVL_TRACE; level = PINELOG_LVL_TRACE;
} }
/* Validate and set output stream */ #if !HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR
/*
* Validate and set output stream. Only necessary if the compiler doesn't
* support the constructor attribute
*/
if (output_stream == NULL) { if (output_stream == NULL) {
output_stream = stdout; output_stream = PINELOG_DEFAULT_STREAM;
} }
#endif
#if PINELOG_SHOW_DATE #if PINELOG_SHOW_DATE
do { do {
time_t t; time_t t;
struct tm *tmp; struct tm *tmp;
char date_string[20]; char date_string[30];
t = time(NULL); t = time(NULL);
tmp = localtime(&t); tmp = localtime(&t);
strftime(date_string, sizeof(date_string), "%F %T ", tmp); strftime(date_string, sizeof(date_string), "%F %T ", tmp);
fputs(date_string, out_stream); fputs(date_string, output_stream);
} while (0); } while (0);
#endif #endif
#if PINELOG_SHOW_LEVEL #if PINELOG_SHOW_LEVEL
do { do {
static const char *level_strings[] = { static const char *level_strings[] = {
PINELOG_FATAL, PINELOG_FATAL_STR,
PINELOG_ERROR, PINELOG_ERROR_STR,
PINELOG_WARNING, PINELOG_WARNING_STR,
PINELOG_INFO, PINELOG_INFO_STR,
PINELOG_DEBUG, PINELOG_DEBUG_STR,
PINELOG_TRACE, PINELOG_TRACE_STR,
}; };
fputs(level_strings[level], out_stream); fputs(level_strings[level], output_stream);
fputs(": ", out_stream); fputs(": ", output_stream);
} while (0) } while (0);
#endif #endif
#if PINELOG_SHOW_BACKTRACE #if PINELOG_SHOW_BACKTRACE
fprintf(out_stream, "%s:%d ", file, line); fprintf(output_stream, "%s:%d ", file, line);
#endif #endif
va_start(ap, fmt); va_start(ap, fmt);

View File

@ -20,7 +20,9 @@
#include "config.h" #include "config.h"
#include <stdio.h> #include <stdio.h>
#ifndef PINELOG_TEST
#include <stdlib.h> #include <stdlib.h>
#endif
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -60,6 +62,15 @@ enum {
*/ */
void pinelog_set_defaults(void); void pinelog_set_defaults(void);
#ifdef PINELOG_TEST
/**
* @brief Get the pointer to the output stream. Only used in test harness.
*
* @returns FILE pointer to output stream
*/
FILE * pinelog_get_output_stream(void);
#endif
/** /**
* @brief Set the output stream. Must be a FILE pointer. * @brief Set the output stream. Must be a FILE pointer.
* *
@ -113,11 +124,16 @@ __attribute__((format(printf, 4, 5)))
void pinelog_log_message(int level, const char *file, int line, const char *fmt, ...); void pinelog_log_message(int level, const char *file, int line, const char *fmt, ...);
// Test harness will redefine pinelog_exit
#ifndef PINELOG_TEST
#define pinelog_exit exit
#endif
#define PINELOG_FATAL(fmt, ...) do { \ #define PINELOG_FATAL(fmt, ...) do { \
if (PINELOG_LVL_FATAL <= pinelog_get_level()) { \ if (PINELOG_LVL_FATAL <= pinelog_get_level()) { \
pinelog_log_message(PINELOG_LVL_FATAL, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ pinelog_log_message(PINELOG_LVL_FATAL, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
} \ } \
exit(1); \ pinelog_exit(1); \
} while (0) } while (0)
#define PINELOG_ERROR(fmt, ...) do { \ #define PINELOG_ERROR(fmt, ...) do { \

194
test_pinelog.c 100644
View File

@ -0,0 +1,194 @@
/*
* Pinelog lightweight logging library - test harness
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: MIT
*/
#include "pinelog.h"
#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
/**********************************************************************
* Global variables
*********************************************************************/
// Test ID (of current test case)
static unsigned int test_id;
// Observed output stream
static FILE *observed_stream_w;
static FILE *observed_stream_r;
// Temporary pipe for observed data
static char observed_fifo[NAME_MAX];
// Buffer for expected output
static char expected_output[1024];
static size_t expected_len;
static void test_case(const char *desc, bool test)
{
test_id++;
if (test) {
printf("ok %u %s\n", test_id, desc);
} else {
printf("not ok %u %s\n", test_id, desc);
}
}
static void pinelog_exit(int status)
{
fprintf(observed_stream_w, "EXIT(%d)\n", status);
expected_len += snprintf(&expected_output[expected_len],
sizeof(expected_output) - expected_len,
"EXIT(%d)\n", status);
}
static void dump_data(const char *type, size_t len, char *data)
{
char *line;
printf("# %s (%lu bytes):\n", type, len);
line = strtok(data, "\n");
while (line != NULL) {
printf("#\t%s\n", line);
line = strtok(NULL, "\n");
}
printf("\n");
}
static void test_setup(int level, int filter, const char *file, int line, const char *fmt, ...)
{
va_list ap;
expected_len = 0;
memset(expected_output, 0, sizeof(expected_output));
if (level <= filter) {
if (PINELOG_SHOW_DATE) {
time_t t;
struct tm *tmp;
t = time(NULL);
tmp = localtime(&t);
expected_len += strftime(&expected_output[expected_len],
sizeof(expected_output) - expected_len,
"%F %T ", tmp);
}
if (PINELOG_SHOW_LEVEL) {
const char * level_string[] = {
PINELOG_FATAL_STR,
PINELOG_ERROR_STR,
PINELOG_WARNING_STR,
PINELOG_INFO_STR,
PINELOG_DEBUG_STR,
PINELOG_TRACE_STR,
};
expected_len += snprintf(&expected_output[expected_len],
sizeof(expected_output) - expected_len,
"%s: ", level_string[level]);
}
if (PINELOG_SHOW_BACKTRACE) {
expected_len += snprintf(&expected_output[expected_len],
sizeof(expected_output) - expected_len,
"%s:%d ", file, line);
}
va_start(ap, fmt);
expected_len += vsnprintf(&expected_output[expected_len],
sizeof(expected_output) - expected_len,
fmt, ap);
va_end(ap);
expected_output[expected_len] = '\n';
expected_len++;
}
}
static void test_teardown(const char *desc)
{
// Compare the output
static char observed[1024];
size_t observed_len;
int result;
observed_len = fread(observed, 1, sizeof(observed), observed_stream_r);
result = ((expected_len == observed_len) &&
(memcmp(expected_output, observed, expected_len) == 0));
test_case(desc, result);
if (!result) {
dump_data("expected", expected_len, expected_output);
dump_data("observed", observed_len, observed);
}
}
static void verify_defaults(void)
{
test_case("Get default output stream",
pinelog_get_output_stream() == PINELOG_DEFAULT_STREAM);
test_case("Get default logging level",
pinelog_get_level() == PINELOG_DEFAULT_LEVEL);
}
#define PINELOG_WARNING PINELOG_WARN
#define TEST_LOG(lvl, filter, fmt, ...) do { \
test_setup(PINELOG_LVL_ ## lvl, PINELOG_LVL_ ## filter, \
__FILE__, __LINE__, fmt, ##__VA_ARGS__); \
PINELOG_ ## lvl (fmt, ##__VA_ARGS__); \
test_teardown("Log " #lvl " filter " #filter); \
} while(0)
#define TEST(filter, fmt, ...) do { \
pinelog_set_level(PINELOG_LVL_ ## filter); \
TEST_LOG(TRACE, filter, fmt, ##__VA_ARGS__); \
TEST_LOG(DEBUG, filter, fmt, ##__VA_ARGS__); \
TEST_LOG(INFO, filter, fmt, ##__VA_ARGS__); \
TEST_LOG(WARNING, filter, fmt, ##__VA_ARGS__); \
TEST_LOG(ERROR, filter, fmt, ##__VA_ARGS__); \
TEST_LOG(FATAL, filter, fmt, ##__VA_ARGS__); \
} while (0)
int main(int argc, char **argv)
{
int fifo_fd_r, fifo_fd_w;
snprintf(observed_fifo, sizeof(observed_fifo), "%s.fifo", argv[0]);
mkfifo(observed_fifo, 0777);
fifo_fd_r = open(observed_fifo, O_RDONLY | O_NONBLOCK);
fifo_fd_w = open(observed_fifo, O_WRONLY | O_NONBLOCK);
observed_stream_r = fdopen(fifo_fd_r, "r");
observed_stream_w = fdopen(fifo_fd_w, "w");
verify_defaults();
pinelog_set_output_stream(observed_stream_w);
TEST(TRACE, "testing %s... %d, %f, %u", "testing", -1, 0.0, 1);
TEST(DEBUG, "testing %s... %d, %f, %u", "testing", -1, 0.0, 1);
TEST(INFO, "testing %s... %d, %f, %u", "testing", -1, 0.0, 1);
TEST(WARNING, "testing %s... %d, %f, %u", "testing", -1, 0.0, 1);
TEST(ERROR, "testing %s... %d, %f, %u", "testing", -1, 0.0, 1);
TEST(FATAL, "testing %s... %d, %f, %u", "testing", -1, 0.0, 1);
TEST(NONE, "testing %s... %d, %f, %u", "testing", -1, 0.0, 1);
printf("1..%u\n", test_id);
fclose(observed_stream_w);
fclose(observed_stream_r);
close(fifo_fd_w);
close(fifo_fd_r);
unlink(observed_fifo);
return 0;
}