Adding logger support, and other things.
This commit is contained in:
@@ -18,9 +18,12 @@
|
||||
#define INCLUDE_MCTEST_MCTEST_H_
|
||||
|
||||
#include <assert.h>
|
||||
#include <setjmp.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <mctest/Compiler.h>
|
||||
|
||||
@@ -43,6 +46,11 @@ extern int16_t McTest_Short(void);
|
||||
extern uint8_t McTest_UChar(void);
|
||||
extern int8_t McTest_Char(void);
|
||||
|
||||
/* Returns `1` if `expr` is true, and `0` otherwise. This is kind of an indirect
|
||||
* way to take a symbolic value, introduce a fork, and on each size, replace its
|
||||
* value with a concrete value. */
|
||||
extern int McTest_IsTrue(int expr);
|
||||
|
||||
/* Symbolize the data in the range `[begin, end)`. */
|
||||
extern void McTest_SymbolizeData(void *begin, void *end);
|
||||
|
||||
@@ -91,16 +99,40 @@ extern void _McTest_Assume(int expr);
|
||||
MCTEST_NORETURN
|
||||
extern void McTest_Fail(void);
|
||||
|
||||
/* Mark this test as failing, but don't hard exit. */
|
||||
extern void McTest_SoftFail(void);
|
||||
|
||||
MCTEST_NORETURN
|
||||
extern void McTest_Pass(void);
|
||||
|
||||
/* Asserts that `expr` must hold. */
|
||||
/* Asserts that `expr` must hold. If it does not, then the test fails and
|
||||
* immediately stops. */
|
||||
MCTEST_INLINE static void McTest_Assert(int expr) {
|
||||
if (!expr) {
|
||||
McTest_Fail();
|
||||
}
|
||||
}
|
||||
|
||||
/* Asserts that `expr` must hold. If it does not, then the test fails, but
|
||||
* nonetheless continues on. */
|
||||
MCTEST_INLINE static void McTest_Check(int expr) {
|
||||
if (!expr) {
|
||||
McTest_SoftFail();
|
||||
}
|
||||
}
|
||||
|
||||
enum McTest_LogLevel {
|
||||
McTest_LogDebug = 0,
|
||||
McTest_LogInfo = 1,
|
||||
McTest_LogWarning = 2,
|
||||
McTest_LogError = 3,
|
||||
McTest_LogFatal = 4,
|
||||
};
|
||||
|
||||
/* Outputs information to a log, using a specific log level. */
|
||||
extern void McTest_Log(enum McTest_LogLevel level, const char *begin,
|
||||
const char *end);
|
||||
|
||||
/* Return a symbolic value in a the range `[low_inc, high_inc]`. */
|
||||
#define MCTEST_MAKE_SYMBOLIC_RANGE(Tname, tname) \
|
||||
MCTEST_INLINE static tname McTest_ ## Tname ## InRange( \
|
||||
@@ -211,9 +243,68 @@ extern struct McTest_TestInfo *McTest_LastTestInfo;
|
||||
} \
|
||||
void McTest_Test_ ## test_name(void)
|
||||
|
||||
/* Set up McTest. */
|
||||
extern void McTest_Setup(void);
|
||||
|
||||
/* Return the first test case to run. */
|
||||
extern struct McTest_TestInfo *McTest_FirstTest(void);
|
||||
|
||||
/* Returns 1 if a failure was caught, otherwise 0. */
|
||||
extern int McTest_CatchFail(void);
|
||||
|
||||
/* Jump buffer for returning to `McTest_Run`. */
|
||||
extern jmp_buf McTest_ReturnToRun;
|
||||
|
||||
/* Start McTest and run the tests. Returns the number of failed tests. */
|
||||
extern int McTest_Run(void);
|
||||
static int McTest_Run(void) {
|
||||
int num_failed_tests = 0;
|
||||
struct McTest_TestInfo *test = NULL;
|
||||
char buff[1024];
|
||||
int num_buff_bytes_used = 0;
|
||||
|
||||
McTest_Setup();
|
||||
|
||||
for (test = McTest_FirstTest(); test != NULL; test = test->prev) {
|
||||
|
||||
/* Print the test that we're going to run. */
|
||||
num_buff_bytes_used = sprintf(buff, "Running: %s from %s:%u",
|
||||
test->test_name, test->file_name,
|
||||
test->line_number);
|
||||
McTest_Log(McTest_LogInfo, buff, &(buff[num_buff_bytes_used]));
|
||||
|
||||
/* Run the test. */
|
||||
if (!setjmp(McTest_ReturnToRun)) {
|
||||
|
||||
/* Convert uncaught C++ exceptions into a test failure. */
|
||||
#if defined(__cplusplus) && defined(__cpp_exceptions)
|
||||
try {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
test->test_func(); /* Run the test function. */
|
||||
McTest_Pass();
|
||||
|
||||
#if defined(__cplusplus) && defined(__cpp_exceptions)
|
||||
} catch(...) {
|
||||
McTest_Fail();
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/* We caught a failure when running the test. */
|
||||
} else if (McTest_CatchFail()) {
|
||||
++num_failed_tests;
|
||||
|
||||
num_buff_bytes_used = sprintf(buff, "Failed: %s", test->test_name);
|
||||
McTest_Log(McTest_LogInfo, buff, &(buff[num_buff_bytes_used]));
|
||||
|
||||
/* The test passed. */
|
||||
} else {
|
||||
num_buff_bytes_used = sprintf(buff, "Passed: %s", test->test_name);
|
||||
McTest_Log(McTest_LogInfo, buff, &(buff[num_buff_bytes_used]));
|
||||
}
|
||||
}
|
||||
|
||||
return num_failed_tests;
|
||||
}
|
||||
|
||||
MCTEST_END_EXTERN_C
|
||||
|
||||
|
||||
@@ -137,8 +137,8 @@ class SymbolicLinearContainer {
|
||||
public:
|
||||
MCTEST_INLINE explicit SymbolicLinearContainer(size_t len)
|
||||
: value(len) {
|
||||
if (len) {
|
||||
McTest_SymbolizeData(&(value.begin()), &(value.end()));
|
||||
if (!value.empty()) {
|
||||
McTest_SymbolizeData(&(value.front()), &(value.back()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Trail of Bits, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDE_MCTEST_MCUNIT_HPP_
|
||||
#define INCLUDE_MCTEST_MCUNIT_HPP_
|
||||
|
||||
#include <mctest/McTest.hpp>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#define TEST(category, name) \
|
||||
McTest_EntryPoint(category ## _ ## name)
|
||||
|
||||
namespace mctest {
|
||||
|
||||
/* Base logger */
|
||||
class Logger {
|
||||
public:
|
||||
MCTEST_INLINE Logger(McTest_LogLevel level_, bool expr_,
|
||||
const char *file_, unsigned line_)
|
||||
: level(level_),
|
||||
expr(!!McTest_IsTrue(expr_)),
|
||||
file(file_),
|
||||
line(line_) {}
|
||||
|
||||
MCTEST_INLINE ~Logger(void) {
|
||||
if (!expr) {
|
||||
std::stringstream report_ss;
|
||||
report_ss << file << "(" << line << "): " << ss.str();
|
||||
auto report_str = report_ss.str();
|
||||
auto report_c_str = report_str.c_str();
|
||||
McTest_Log(level, report_c_str, report_c_str + report_str.size());
|
||||
}
|
||||
}
|
||||
|
||||
MCTEST_INLINE std::stringstream &stream(void) {
|
||||
return ss;
|
||||
}
|
||||
|
||||
private:
|
||||
Logger(void) = delete;
|
||||
Logger(const Logger &) = delete;
|
||||
Logger &operator=(const Logger &) = delete;
|
||||
|
||||
const McTest_LogLevel level;
|
||||
const bool expr;
|
||||
const char * const file;
|
||||
const unsigned line;
|
||||
std::stringstream ss;
|
||||
};
|
||||
|
||||
} // namespace mctest
|
||||
|
||||
#define MCTEST_LOG_BINOP(a, b, op, level) \
|
||||
::mctest::Logger( \
|
||||
level, ((a) op (b)), __FILE__, __LINE__).stream()
|
||||
|
||||
#define ASSERT_EQ(a, b) MCTEST_LOG_BINOP(a, b, ==, McTest_LogFatal)
|
||||
#define ASSERT_NE(a, b) MCTEST_LOG_BINOP(a, b, !=, McTest_LogFatal)
|
||||
#define ASSERT_LT(a, b) MCTEST_LOG_BINOP(a, b, <, McTest_LogFatal)
|
||||
#define ASSERT_LE(a, b) MCTEST_LOG_BINOP(a, b, <=, McTest_LogFatal)
|
||||
#define ASSERT_GT(a, b) MCTEST_LOG_BINOP(a, b, >, McTest_LogFatal)
|
||||
#define ASSERT_GE(a, b) MCTEST_LOG_BINOP(a, b, >=, McTest_LogFatal)
|
||||
|
||||
#define CHECK_EQ(a, b) MCTEST_LOG_BINOP(a, b, ==, McTest_LogError)
|
||||
#define CHECK_NE(a, b) MCTEST_LOG_BINOP(a, b, !=, McTest_LogError)
|
||||
#define CHECK_LT(a, b) MCTEST_LOG_BINOP(a, b, <, McTest_LogError)
|
||||
#define CHECK_LE(a, b) MCTEST_LOG_BINOP(a, b, <=, McTest_LogError)
|
||||
#define CHECK_GT(a, b) MCTEST_LOG_BINOP(a, b, >, McTest_LogError)
|
||||
#define CHECK_GE(a, b) MCTEST_LOG_BINOP(a, b, >=, McTest_LogError)
|
||||
|
||||
#define ASSERT(expr) \
|
||||
::mctest::Logger( \
|
||||
McTest_LogFatal, !!(expr), __FILE__, __LINE__).stream()
|
||||
|
||||
#define ASSERT_TRUE ASSERT
|
||||
#define ASSERT_FALSE(expr) ASSERT(!(expr))
|
||||
|
||||
#define CHECK(expr) \
|
||||
::mctest::Logger( \
|
||||
McTest_LogError, !!(expr), __FILE__, __LINE__).stream()
|
||||
|
||||
#define CHECK_TRUE CHECK
|
||||
#define CHECK_FALSE(expr) CHECK(!(expr))
|
||||
|
||||
#define ASSUME(expr) \
|
||||
McTest_Assume(expr), ::mctest::Logger( \
|
||||
McTest_LogInfo, false, __FILE__, __LINE__).stream()
|
||||
|
||||
|
||||
#define MCTEST_ASSUME_BINOP(a, b, op) \
|
||||
McTest_Assume(((a) op (b))), ::mctest::Logger( \
|
||||
McTest_LogInfo, false, __FILE__, __LINE__).stream()
|
||||
|
||||
#define ASSUME_EQ(a, b) MCTEST_ASSUME_BINOP(a, b, ==)
|
||||
#define ASSUME_NE(a, b) MCTEST_ASSUME_BINOP(a, b, !=)
|
||||
#define ASSUME_LT(a, b) MCTEST_ASSUME_BINOP(a, b, <)
|
||||
#define ASSUME_LE(a, b) MCTEST_ASSUME_BINOP(a, b, <=)
|
||||
#define ASSUME_GT(a, b) MCTEST_ASSUME_BINOP(a, b, >)
|
||||
#define ASSUME_GE(a, b) MCTEST_ASSUME_BINOP(a, b, >=)
|
||||
|
||||
#endif // INCLUDE_MCTEST_MCUNIT_HPP_
|
||||
+81
-28
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(unix) || defined(__unix) || defined(__unix__)
|
||||
# define _GNU_SOURCE
|
||||
@@ -41,23 +42,26 @@ static volatile uint8_t McTest_Input[McTest_InputLength];
|
||||
* been consumed. */
|
||||
static uint32_t McTest_InputIndex = 0;
|
||||
|
||||
/* Jump buffer for returning to `McTest_Main`. */
|
||||
static jmp_buf McTest_ReturnToMain;
|
||||
/* Jump buffer for returning to `McTest_Run`. */
|
||||
jmp_buf McTest_ReturnToRun = {};
|
||||
|
||||
static int McTest_TestPassed = 0;
|
||||
static int McTest_TestFailed = 0;
|
||||
|
||||
/* Mark this test as failing. */
|
||||
MCTEST_NORETURN
|
||||
extern void McTest_Fail(void) {
|
||||
McTest_TestPassed = 0;
|
||||
longjmp(McTest_ReturnToMain, 1);
|
||||
void McTest_Fail(void) {
|
||||
McTest_TestFailed = 1;
|
||||
longjmp(McTest_ReturnToRun, 1);
|
||||
}
|
||||
|
||||
/* Mark this test as passing. */
|
||||
MCTEST_NORETURN
|
||||
extern void McTest_Pass(void) {
|
||||
McTest_TestPassed = 1;
|
||||
longjmp(McTest_ReturnToMain, 0);
|
||||
void McTest_Pass(void) {
|
||||
longjmp(McTest_ReturnToRun, 0);
|
||||
}
|
||||
|
||||
void McTest_SoftFail(void) {
|
||||
McTest_TestFailed = 1;
|
||||
}
|
||||
|
||||
void McTest_SymbolizeData(void *begin, void *end) {
|
||||
@@ -76,6 +80,25 @@ void McTest_SymbolizeData(void *begin, void *end) {
|
||||
}
|
||||
}
|
||||
|
||||
MCTEST_NOINLINE int McTest_One(void) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
MCTEST_NOINLINE int McTest_Zero(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Returns `1` if `expr` is true, and `0` otherwise. This is kind of an indirect
|
||||
* way to take a symbolic value, introduce a fork, and on each size, replace its
|
||||
* value with a concrete value. */
|
||||
int McTest_IsTrue(int expr) {
|
||||
if (expr == McTest_Zero()) {
|
||||
return McTest_Zero();
|
||||
} else {
|
||||
return McTest_One();
|
||||
}
|
||||
}
|
||||
|
||||
/* Return a symbolic value of a given type. */
|
||||
int McTest_Bool(void) {
|
||||
return McTest_Input[McTest_InputIndex++] & 1;
|
||||
@@ -91,6 +114,9 @@ int McTest_Bool(void) {
|
||||
return val; \
|
||||
}
|
||||
|
||||
|
||||
MAKE_SYMBOL_FUNC(Size, size_t)
|
||||
|
||||
MAKE_SYMBOL_FUNC(UInt64, uint64_t)
|
||||
int64_t McTest_Int64(void) {
|
||||
return (int64_t) McTest_UInt64();
|
||||
@@ -122,6 +148,39 @@ int McTest_IsSymbolicUInt(uint32_t x) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Returns a printable string version of the log level. */
|
||||
static const char *McTest_LogLevelStr(enum McTest_LogLevel level) {
|
||||
switch (level) {
|
||||
case McTest_LogDebug:
|
||||
return "DEBUG";
|
||||
case McTest_LogInfo:
|
||||
return "INFO";
|
||||
case McTest_LogWarning:
|
||||
return "WARNING";
|
||||
case McTest_LogError:
|
||||
return "ERROR";
|
||||
case McTest_LogFatal:
|
||||
return "FATAL";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
/* Outputs information to a log, using a specific log level. */
|
||||
void McTest_Log(enum McTest_LogLevel level, const char *begin,
|
||||
const char *end) {
|
||||
int str_len = (int) (end - begin);
|
||||
fprintf(stderr, "%s: %.*s\n", McTest_LogLevelStr(level),
|
||||
str_len, begin);
|
||||
|
||||
if (McTest_LogError == level) {
|
||||
McTest_SoftFail();
|
||||
|
||||
} else if (McTest_LogFatal == level) {
|
||||
McTest_Fail();
|
||||
}
|
||||
}
|
||||
|
||||
/* A McTest-specific symbol that is needed for hooking. */
|
||||
struct McTest_IndexEntry {
|
||||
const char * const name;
|
||||
@@ -133,6 +192,8 @@ struct McTest_IndexEntry {
|
||||
const struct McTest_IndexEntry McTest_API[] = {
|
||||
{"Pass", (void *) McTest_Pass},
|
||||
{"Fail", (void *) McTest_Fail},
|
||||
{"SoftFail", (void *) McTest_SoftFail},
|
||||
{"Log", (void *) McTest_Log},
|
||||
{"Assume", (void *) _McTest_Assume},
|
||||
{"IsSymbolicUInt", (void *) McTest_IsSymbolicUInt},
|
||||
{"InputBegin", (void *) &(McTest_Input[0])},
|
||||
@@ -142,8 +203,8 @@ const struct McTest_IndexEntry McTest_API[] = {
|
||||
{NULL, NULL},
|
||||
};
|
||||
|
||||
int McTest_Run(void) {
|
||||
|
||||
/* Set up McTest. */
|
||||
void McTest_Setup(void) {
|
||||
/* Manticore entrypoint. Manticore doesn't (yet?) support symbol lookups, so
|
||||
* we instead interpose on this fake system call, and discover the API table
|
||||
* via the first argument to the system call. */
|
||||
@@ -153,25 +214,17 @@ int McTest_Run(void) {
|
||||
syscall(0x41414141, &McTest_API);
|
||||
#endif
|
||||
|
||||
int num_failed_tests = 0;
|
||||
for (struct McTest_TestInfo *info = McTest_LastTestInfo;
|
||||
info != NULL;
|
||||
info = info->prev) {
|
||||
/* TODO(pag): Sort the test cases by file name and line number. */
|
||||
}
|
||||
|
||||
McTest_TestPassed = 0;
|
||||
if (!setjmp(McTest_ReturnToMain)) {
|
||||
printf("Running %s from %s:%u\n", info->test_name, info->file_name,
|
||||
info->line_number);
|
||||
info->test_func();
|
||||
/* Return the first test case to run. */
|
||||
struct McTest_TestInfo *McTest_FirstTest(void) {
|
||||
return McTest_LastTestInfo;
|
||||
}
|
||||
|
||||
} else if (McTest_TestPassed) {
|
||||
printf(" %s Passed\n", info->test_name);
|
||||
} else {
|
||||
printf(" %s Failed\n", info->test_name);
|
||||
num_failed_tests += 1;
|
||||
}
|
||||
}
|
||||
return num_failed_tests;
|
||||
/* Returns 1 if a failure was caught, otherwise 0. */
|
||||
int McTest_CatchFail(void) {
|
||||
return McTest_TestFailed;
|
||||
}
|
||||
|
||||
MCTEST_END_EXTERN_C
|
||||
|
||||
Reference in New Issue
Block a user