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