Adding logger support, and other things.

This commit is contained in:
Peter Goodman
2017-10-29 18:54:41 -04:00
parent 7c9710cd05
commit e0f104aaef
9 changed files with 494 additions and 60 deletions
+93 -2
View File
@@ -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
+2 -2
View File
@@ -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()));
}
}
+115
View File
@@ -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
View File
@@ -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