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