blob: 505ae3e785fa963e2288b4a42c3dd6f3e9c09ae5 [file] [log] [blame]
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include "debug.h"
#include "exception.h"
#include <kj/compat/gtest.h>
#include <string>
#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <exception>
#include <stdlib.h>
#include "miniposix.h"
#if !_WIN32
#include <sys/wait.h>
#endif
#if _MSC_VER && !defined(__clang__)
#pragma warning(disable: 4996)
// Warns that sprintf() is buffer-overrunny. Yeah, I know, it's cool.
#endif
namespace kj {
namespace _ { // private
namespace {
class MockException {};
class MockExceptionCallback: public ExceptionCallback {
public:
~MockExceptionCallback() {}
std::string text;
int outputPipe = -1;
bool forkForDeathTest() {
// This is called when exceptions are disabled. We fork the process instead and then expect
// the child to die.
#if _WIN32
// Windows doesn't support fork() or anything like it. Just skip the test.
return false;
#else
int pipeFds[2];
KJ_SYSCALL(pipe(pipeFds));
pid_t child = fork();
if (child == 0) {
// This is the child!
close(pipeFds[0]);
outputPipe = pipeFds[1];
text.clear();
return true;
} else {
close(pipeFds[1]);
// Read child error messages into our local buffer.
char buf[1024];
for (;;) {
ssize_t n = read(pipeFds[0], buf, sizeof(buf));
if (n < 0) {
if (errno == EINTR) {
continue;
} else {
break;
}
} else if (n == 0) {
break;
} else {
text.append(buf, n);
}
}
close(pipeFds[0]);
// Get exit status.
int status;
KJ_SYSCALL(waitpid(child, &status, 0));
EXPECT_TRUE(WIFEXITED(status));
EXPECT_EQ(74, WEXITSTATUS(status));
return false;
}
#endif // _WIN32, else
}
void flush() {
if (outputPipe != -1) {
const char* pos = &*text.begin();
const char* end = pos + text.size();
while (pos < end) {
miniposix::ssize_t n = miniposix::write(outputPipe, pos, end - pos);
if (n < 0) {
if (errno == EINTR) {
continue;
} else {
break; // Give up on error.
}
}
pos += n;
}
text.clear();
}
}
void onRecoverableException(Exception&& exception) override {
text += "recoverable exception: ";
auto what = str(exception);
// Discard the stack trace.
const char* end = strstr(what.cStr(), "\nstack: ");
if (end == nullptr) {
text += what.cStr();
} else {
text.append(what.cStr(), end);
}
text += '\n';
flush();
}
void onFatalException(Exception&& exception) override {
text += "fatal exception: ";
auto what = str(exception);
// Discard the stack trace.
const char* end = strstr(what.cStr(), "\nstack: ");
if (end == nullptr) {
text += what.cStr();
} else {
text.append(what.cStr(), end);
}
text += '\n';
flush();
#if KJ_NO_EXCEPTIONS
if (outputPipe >= 0) {
// This is a child process. We got what we want, now exit quickly without writing any
// additional messages, with a status code that the parent will interpret as "exited in the
// way we expected".
_exit(74);
}
#else
throw MockException();
#endif
}
void logMessage(LogSeverity severity, const char* file, int line, int contextDepth,
String&& text) override {
this->text += "log message: ";
text = str(file, ":", line, ":+", contextDepth, ": ", severity, ": ", mv(text));
this->text.append(text.begin(), text.end());
this->text.append("\n");
}
};
#define EXPECT_LOG_EQ(f, expText) do { \
std::string text; \
{ \
MockExceptionCallback mockCallback; \
f(); \
text = kj::mv(mockCallback.text); \
} \
EXPECT_EQ(expText, text); \
} while(0)
#if KJ_NO_EXCEPTIONS
#define EXPECT_FATAL(code) if (mockCallback.forkForDeathTest()) { code; abort(); }
#else
#define EXPECT_FATAL(code) \
try { code; KJ_FAIL_EXPECT("expected exception"); } \
catch (MockException e) {} \
catch (...) { KJ_FAIL_EXPECT("wrong exception"); }
#endif
std::string fileLine(std::string file, int line) {
file = trimSourceFilename(file.c_str()).cStr();
file += ':';
char buffer[32];
sprintf(buffer, "%d", line);
file += buffer;
return file;
}
TEST(Debug, Log) {
int line;
EXPECT_LOG_EQ([&](){
KJ_LOG(WARNING, "Hello world!"); line = __LINE__;
}, "log message: " + fileLine(__FILE__, line) + ":+0: warning: Hello world!\n");
int i = 123;
const char* str = "foo";
EXPECT_LOG_EQ([&](){
KJ_LOG(ERROR, i, str); line = __LINE__;
}, "log message: " + fileLine(__FILE__, line) + ":+0: error: i = 123; str = foo\n");
// kj::str() expressions are included literally.
EXPECT_LOG_EQ([&](){
KJ_LOG(ERROR, kj::str(i, str), "x"); line = __LINE__;
}, "log message: " + fileLine(__FILE__, line) + ":+0: error: 123foo; x\n");
EXPECT_LOG_EQ([&](){
KJ_DBG("Some debug text."); line = __LINE__;
}, "log message: " + fileLine(__FILE__, line) + ":+0: debug: Some debug text.\n");
// INFO logging is disabled by default.
EXPECT_LOG_EQ([&](){
KJ_LOG(INFO, "Info."); line = __LINE__;
}, "");
// Enable it.
Debug::setLogLevel(Debug::Severity::INFO);
EXPECT_LOG_EQ([&](){
KJ_LOG(INFO, "Some text."); line = __LINE__;
}, "log message: " + fileLine(__FILE__, line) + ":+0: info: Some text.\n");
// Back to default.
Debug::setLogLevel(Debug::Severity::WARNING);
EXPECT_LOG_EQ([&](){
KJ_ASSERT(1 == 1);
}, "");
EXPECT_LOG_EQ([&](){
EXPECT_FATAL(KJ_ASSERT(1 == 2)); line = __LINE__;
}, "fatal exception: " + fileLine(__FILE__, line) + ": failed: expected 1 == 2 [1 == 2]\n");
KJ_ASSERT(1 == 1) {
ADD_FAILURE() << "Shouldn't call recovery code when check passes.";
break;
};
bool recovered = false;
EXPECT_LOG_EQ([&](){
KJ_ASSERT(1 == 2, "1 is not 2") { recovered = true; break; } line = __LINE__;
}, (
"recoverable exception: " + fileLine(__FILE__, line) + ": "
"failed: expected 1 == 2 [1 == 2]; 1 is not 2\n"
));
EXPECT_TRUE(recovered);
EXPECT_LOG_EQ([&](){
EXPECT_FATAL(KJ_ASSERT(1 == 2, i, "hi", str)); line = __LINE__;
}, (
"fatal exception: " + fileLine(__FILE__, line) + ": "
"failed: expected 1 == 2 [1 == 2]; i = 123; hi; str = foo\n"
));
EXPECT_LOG_EQ([&](){
EXPECT_FATAL(KJ_REQUIRE(1 == 2, i, "hi", str)); line = __LINE__;
}, (
"fatal exception: " + fileLine(__FILE__, line) + ": "
"failed: expected 1 == 2 [1 == 2]; i = 123; hi; str = foo\n"
));
EXPECT_LOG_EQ([&](){
EXPECT_FATAL(KJ_FAIL_ASSERT("foo")); line = __LINE__;
}, "fatal exception: " + fileLine(__FILE__, line) + ": failed: foo\n");
}
TEST(Debug, Exception) {
int i = 123;
int line = __LINE__; Exception exception = KJ_EXCEPTION(DISCONNECTED, "foo", i);
EXPECT_EQ(Exception::Type::DISCONNECTED, exception.getType());
EXPECT_TRUE(kj::StringPtr(__FILE__).endsWith(exception.getFile()));
EXPECT_EQ(line, exception.getLine());
EXPECT_EQ("foo; i = 123", exception.getDescription());
}
TEST(Debug, Catch) {
int line;
{
// Catch recoverable as kj::Exception.
Maybe<Exception> exception = kj::runCatchingExceptions([&](){
line = __LINE__; KJ_FAIL_ASSERT("foo") { break; }
});
KJ_IF_MAYBE(e, exception) {
String what = str(*e);
KJ_IF_MAYBE(eol, what.findFirst('\n')) {
what = kj::str(what.slice(0, *eol));
}
std::string text(what.cStr());
EXPECT_EQ(fileLine(__FILE__, line) + ": failed: foo", text);
} else {
ADD_FAILURE() << "Expected exception.";
}
}
#if !KJ_NO_EXCEPTIONS
{
// Catch fatal as kj::Exception.
Maybe<Exception> exception = kj::runCatchingExceptions([&](){
line = __LINE__; KJ_FAIL_ASSERT("foo");
});
KJ_IF_MAYBE(e, exception) {
String what = str(*e);
KJ_IF_MAYBE(eol, what.findFirst('\n')) {
what = kj::str(what.slice(0, *eol));
}
std::string text(what.cStr());
EXPECT_EQ(fileLine(__FILE__, line) + ": failed: foo", text);
} else {
ADD_FAILURE() << "Expected exception.";
}
}
{
// Catch as std::exception.
try {
line = __LINE__; KJ_FAIL_ASSERT("foo");
KJ_KNOWN_UNREACHABLE(ADD_FAILURE() << "Expected exception.");
} catch (const std::exception& e) {
kj::StringPtr what = e.what();
std::string text;
KJ_IF_MAYBE(eol, what.findFirst('\n')) {
text.assign(what.cStr(), *eol);
} else {
text.assign(what.cStr());
}
EXPECT_EQ(fileLine(__FILE__, line) + ": failed: foo", text);
}
}
#endif
}
int mockSyscall(int i, int error = 0) {
errno = error;
return i;
}
TEST(Debug, Syscall) {
int line;
int i = 123;
const char* str = "foo";
EXPECT_LOG_EQ([&](){
KJ_SYSCALL(mockSyscall(0));
KJ_SYSCALL(mockSyscall(1));
}, "");
EXPECT_LOG_EQ([&](){
EXPECT_FATAL(KJ_SYSCALL(mockSyscall(-1, EBADF), i, "bar", str)); line = __LINE__;
}, (
"fatal exception: " + fileLine(__FILE__, line) +
": failed: mockSyscall(-1, EBADF): " + strerror(EBADF) +
"; i = 123; bar; str = foo\n"
));
EXPECT_LOG_EQ([&](){
EXPECT_FATAL(KJ_SYSCALL(mockSyscall(-1, ECONNRESET), i, "bar", str)); line = __LINE__;
}, (
"fatal exception: " + fileLine(__FILE__, line) +
": disconnected: mockSyscall(-1, ECONNRESET): " + strerror(ECONNRESET) +
"; i = 123; bar; str = foo\n"
));
EXPECT_LOG_EQ([&](){
EXPECT_FATAL(KJ_SYSCALL(mockSyscall(-1, ENOMEM), i, "bar", str)); line = __LINE__;
}, (
"fatal exception: " + fileLine(__FILE__, line) +
": overloaded: mockSyscall(-1, ENOMEM): " + strerror(ENOMEM) +
"; i = 123; bar; str = foo\n"
));
EXPECT_LOG_EQ([&](){
EXPECT_FATAL(KJ_SYSCALL(mockSyscall(-1, ENOSYS), i, "bar", str)); line = __LINE__;
}, (
"fatal exception: " + fileLine(__FILE__, line) +
": unimplemented: mockSyscall(-1, ENOSYS): " + strerror(ENOSYS) +
"; i = 123; bar; str = foo\n"
));
int result = 0;
bool recovered = false;
EXPECT_LOG_EQ([&](){
KJ_SYSCALL(result = mockSyscall(-2, EBADF), i, "bar", str) { recovered = true; break; } line = __LINE__;
}, (
"recoverable exception: " + fileLine(__FILE__, line) +
": failed: mockSyscall(-2, EBADF): " + strerror(EBADF) +
"; i = 123; bar; str = foo\n"
));
EXPECT_EQ(-2, result);
EXPECT_TRUE(recovered);
}
TEST(Debug, Context) {
int line;
int line2;
int cline;
int cline2;
EXPECT_LOG_EQ([&](){
KJ_CONTEXT("foo"); cline = __LINE__;
KJ_LOG(WARNING, "blah"); line = __LINE__;
EXPECT_FATAL(KJ_FAIL_ASSERT("bar")); line2 = __LINE__;
}, (
"log message: " + fileLine(__FILE__, cline) + ":+0: info: context: foo\n\n"
"log message: " + fileLine(__FILE__, line) + ":+1: warning: blah\n"
"fatal exception: " + fileLine(__FILE__, cline) + ": context: foo\n"
+ fileLine(__FILE__, line2) + ": failed: bar\n"
));
EXPECT_LOG_EQ([&](){
KJ_CONTEXT("foo"); cline = __LINE__;
{
int i = 123;
const char* str = "qux";
KJ_CONTEXT("baz", i, "corge", str); cline2 = __LINE__;
EXPECT_FATAL(KJ_FAIL_ASSERT("bar")); line = __LINE__;
}
}, (
"fatal exception: " + fileLine(__FILE__, cline) + ": context: foo\n"
+ fileLine(__FILE__, cline2) + ": context: baz; i = 123; corge; str = qux\n"
+ fileLine(__FILE__, line) + ": failed: bar\n"
));
EXPECT_LOG_EQ([&](){
KJ_CONTEXT("foo"); cline = __LINE__;
{
int i = 123;
const char* str = "qux";
KJ_CONTEXT("baz", i, "corge", str); cline2 = __LINE__;
}
{
KJ_CONTEXT("grault"); cline2 = __LINE__;
EXPECT_FATAL(KJ_FAIL_ASSERT("bar")); line = __LINE__;
}
}, (
"fatal exception: " + fileLine(__FILE__, cline) + ": context: foo\n"
+ fileLine(__FILE__, cline2) + ": context: grault\n"
+ fileLine(__FILE__, line) + ": failed: bar\n"
));
}
KJ_TEST("magic assert stringification") {
{
auto exception = KJ_ASSERT_NONNULL(kj::runCatchingExceptions([&]() {
int foo = 123;
int bar = 456;
KJ_ASSERT(foo == bar) { break; }
}));
KJ_EXPECT(exception.getDescription() == "expected foo == bar [123 == 456]");
}
{
auto exception = KJ_ASSERT_NONNULL(kj::runCatchingExceptions([&]() {
auto foo = kj::str("hello");
auto bar = kj::str("world!");
KJ_ASSERT(foo == bar, foo.size(), bar.size()) { break; }
}));
KJ_EXPECT(exception.getDescription() ==
"expected foo == bar [hello == world!]; foo.size() = 5; bar.size() = 6");
}
{
auto exception = KJ_ASSERT_NONNULL(kj::runCatchingExceptions([&]() {
KJ_ASSERT(kj::str("hello") == kj::str("world!")) { break; }
}));
KJ_EXPECT(exception.getDescription() ==
"expected kj::str(\"hello\") == kj::str(\"world!\") [hello == world!]");
}
{
auto exception = KJ_ASSERT_NONNULL(kj::runCatchingExceptions([&]() {
int foo = 123;
int bar = 456;
KJ_ASSERT((foo == bar)) { break; }
}));
KJ_EXPECT(exception.getDescription() == "expected (foo == bar)");
}
// Test use of << on left side, which could create confusion.
{
auto exception = KJ_ASSERT_NONNULL(kj::runCatchingExceptions([&]() {
int foo = 123;
int bar = 456;
KJ_ASSERT(foo << 2 == bar) { break; }
}));
KJ_EXPECT(exception.getDescription() == "expected foo << 2 == bar [492 == 456]");
}
// Test use of & on left side.
{
int foo = 4;
KJ_ASSERT(foo & 4);
auto exception = KJ_ASSERT_NONNULL(kj::runCatchingExceptions([&]() {
KJ_ASSERT(foo & 2) { break; }
}));
KJ_EXPECT(exception.getDescription() == "expected foo & 2");
}
}
} // namespace
} // namespace _ (private)
} // namespace kj