jpayne@69: // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors jpayne@69: // Licensed under the MIT License: jpayne@69: // jpayne@69: // Permission is hereby granted, free of charge, to any person obtaining a copy jpayne@69: // of this software and associated documentation files (the "Software"), to deal jpayne@69: // in the Software without restriction, including without limitation the rights jpayne@69: // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell jpayne@69: // copies of the Software, and to permit persons to whom the Software is jpayne@69: // furnished to do so, subject to the following conditions: jpayne@69: // jpayne@69: // The above copyright notice and this permission notice shall be included in jpayne@69: // all copies or substantial portions of the Software. jpayne@69: // jpayne@69: // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR jpayne@69: // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, jpayne@69: // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE jpayne@69: // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER jpayne@69: // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, jpayne@69: // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN jpayne@69: // THE SOFTWARE. jpayne@69: jpayne@69: #pragma once jpayne@69: jpayne@69: #include "debug.h" jpayne@69: #include "vector.h" jpayne@69: #include "function.h" jpayne@69: #include "windows-sanity.h" // work-around macro conflict with `ERROR` jpayne@69: jpayne@69: KJ_BEGIN_HEADER jpayne@69: jpayne@69: namespace kj { jpayne@69: jpayne@69: class TestRunner; jpayne@69: jpayne@69: class TestCase { jpayne@69: public: jpayne@69: TestCase(const char* file, uint line, const char* description); jpayne@69: ~TestCase(); jpayne@69: jpayne@69: virtual void run() = 0; jpayne@69: jpayne@69: protected: jpayne@69: template jpayne@69: void doBenchmark(Func&& func) { jpayne@69: // Perform a benchmark with configurable iterations. func() will be called N times, where N jpayne@69: // is set by the --benchmark CLI flag. This defaults to 1, so that when --benchmark is not jpayne@69: // specified, we only test that the benchmark works. jpayne@69: // jpayne@69: // In the future, this could adaptively choose iteration count by running a few iterations to jpayne@69: // find out how fast the benchmark is, then scaling. jpayne@69: jpayne@69: for (size_t i = iterCount(); i-- > 0;) { jpayne@69: func(); jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: private: jpayne@69: const char* file; jpayne@69: uint line; jpayne@69: const char* description; jpayne@69: TestCase* next; jpayne@69: TestCase** prev; jpayne@69: bool matchedFilter; jpayne@69: jpayne@69: static size_t iterCount(); jpayne@69: jpayne@69: friend class TestRunner; jpayne@69: }; jpayne@69: jpayne@69: #define KJ_TEST(description) \ jpayne@69: /* Make sure the linker fails if tests are not in anonymous namespaces. */ \ jpayne@69: extern int KJ_CONCAT(YouMustWrapTestsInAnonymousNamespace, __COUNTER__) KJ_UNUSED; \ jpayne@69: class KJ_UNIQUE_NAME(TestCase): public ::kj::TestCase { \ jpayne@69: public: \ jpayne@69: KJ_UNIQUE_NAME(TestCase)(): ::kj::TestCase(__FILE__, __LINE__, description) {} \ jpayne@69: void run() override; \ jpayne@69: } KJ_UNIQUE_NAME(testCase); \ jpayne@69: void KJ_UNIQUE_NAME(TestCase)::run() jpayne@69: jpayne@69: #if KJ_MSVC_TRADITIONAL_CPP jpayne@69: #define KJ_INDIRECT_EXPAND(m, vargs) m vargs jpayne@69: #define KJ_FAIL_EXPECT(...) \ jpayne@69: KJ_INDIRECT_EXPAND(KJ_LOG, (ERROR , __VA_ARGS__)); jpayne@69: #define KJ_EXPECT(cond, ...) \ jpayne@69: if (auto _kjCondition = ::kj::_::MAGIC_ASSERT << cond); \ jpayne@69: else KJ_INDIRECT_EXPAND(KJ_FAIL_EXPECT, ("failed: expected " #cond , _kjCondition, __VA_ARGS__)) jpayne@69: #else jpayne@69: #define KJ_FAIL_EXPECT(...) \ jpayne@69: KJ_LOG(ERROR, ##__VA_ARGS__); jpayne@69: #define KJ_EXPECT(cond, ...) \ jpayne@69: if (auto _kjCondition = ::kj::_::MAGIC_ASSERT << cond); \ jpayne@69: else KJ_FAIL_EXPECT("failed: expected " #cond, _kjCondition, ##__VA_ARGS__) jpayne@69: #endif jpayne@69: jpayne@69: #if _MSC_VER && !defined(__clang__) jpayne@69: #define KJ_EXPECT_THROW_RECOVERABLE(type, code, ...) \ jpayne@69: do { \ jpayne@69: KJ_IF_MAYBE(e, ::kj::runCatchingExceptions([&]() { code; })) { \ jpayne@69: KJ_INDIRECT_EXPAND(KJ_EXPECT, (e->getType() == ::kj::Exception::Type::type, \ jpayne@69: "code threw wrong exception type: " #code, *e, __VA_ARGS__)); \ jpayne@69: } else { \ jpayne@69: KJ_INDIRECT_EXPAND(KJ_FAIL_EXPECT, ("code did not throw: " #code, __VA_ARGS__)); \ jpayne@69: } \ jpayne@69: } while (false) jpayne@69: jpayne@69: #define KJ_EXPECT_THROW_RECOVERABLE_MESSAGE(message, code, ...) \ jpayne@69: do { \ jpayne@69: KJ_IF_MAYBE(e, ::kj::runCatchingExceptions([&]() { code; })) { \ jpayne@69: KJ_INDIRECT_EXPAND(KJ_EXPECT, (::kj::_::hasSubstring(e->getDescription(), message), \ jpayne@69: "exception description didn't contain expected substring", *e, __VA_ARGS__)); \ jpayne@69: } else { \ jpayne@69: KJ_INDIRECT_EXPAND(KJ_FAIL_EXPECT, ("code did not throw: " #code, __VA_ARGS__)); \ jpayne@69: } \ jpayne@69: } while (false) jpayne@69: #else jpayne@69: #define KJ_EXPECT_THROW_RECOVERABLE(type, code, ...) \ jpayne@69: do { \ jpayne@69: KJ_IF_MAYBE(e, ::kj::runCatchingExceptions([&]() { code; })) { \ jpayne@69: KJ_EXPECT(e->getType() == ::kj::Exception::Type::type, \ jpayne@69: "code threw wrong exception type: " #code, *e, ##__VA_ARGS__); \ jpayne@69: } else { \ jpayne@69: KJ_FAIL_EXPECT("code did not throw: " #code, ##__VA_ARGS__); \ jpayne@69: } \ jpayne@69: } while (false) jpayne@69: jpayne@69: #define KJ_EXPECT_THROW_RECOVERABLE_MESSAGE(message, code, ...) \ jpayne@69: do { \ jpayne@69: KJ_IF_MAYBE(e, ::kj::runCatchingExceptions([&]() { code; })) { \ jpayne@69: KJ_EXPECT(::kj::_::hasSubstring(e->getDescription(), message), \ jpayne@69: "exception description didn't contain expected substring", *e, ##__VA_ARGS__); \ jpayne@69: } else { \ jpayne@69: KJ_FAIL_EXPECT("code did not throw: " #code, ##__VA_ARGS__); \ jpayne@69: } \ jpayne@69: } while (false) jpayne@69: #endif jpayne@69: jpayne@69: #if KJ_NO_EXCEPTIONS jpayne@69: #define KJ_EXPECT_THROW(type, code, ...) \ jpayne@69: do { \ jpayne@69: KJ_EXPECT(::kj::_::expectFatalThrow(::kj::Exception::Type::type, nullptr, [&]() { code; })); \ jpayne@69: } while (false) jpayne@69: #define KJ_EXPECT_THROW_MESSAGE(message, code, ...) \ jpayne@69: do { \ jpayne@69: KJ_EXPECT(::kj::_::expectFatalThrow(nullptr, kj::StringPtr(message), [&]() { code; })); \ jpayne@69: } while (false) jpayne@69: #else jpayne@69: #define KJ_EXPECT_THROW KJ_EXPECT_THROW_RECOVERABLE jpayne@69: #define KJ_EXPECT_THROW_MESSAGE KJ_EXPECT_THROW_RECOVERABLE_MESSAGE jpayne@69: #endif jpayne@69: jpayne@69: #define KJ_EXPECT_EXIT(statusCode, code) \ jpayne@69: do { \ jpayne@69: KJ_EXPECT(::kj::_::expectExit(statusCode, [&]() { code; })); \ jpayne@69: } while (false) jpayne@69: // Forks the code and expects it to exit with a given code. jpayne@69: jpayne@69: #define KJ_EXPECT_SIGNAL(signal, code) \ jpayne@69: do { \ jpayne@69: KJ_EXPECT(::kj::_::expectSignal(signal, [&]() { code; })); \ jpayne@69: } while (false) jpayne@69: // Forks the code and expects it to trigger a signal. jpayne@69: // In the child resets all signal handlers as printStackTraceOnCrash sets. jpayne@69: jpayne@69: #define KJ_EXPECT_LOG(level, substring) \ jpayne@69: ::kj::_::LogExpectation KJ_UNIQUE_NAME(_kjLogExpectation)(::kj::LogSeverity::level, substring) jpayne@69: // Expects that a log message with the given level and substring text will be printed within jpayne@69: // the current scope. This message will not cause the test to fail, even if it is an error. jpayne@69: jpayne@69: // ======================================================================================= jpayne@69: jpayne@69: namespace _ { // private jpayne@69: jpayne@69: bool hasSubstring(kj::StringPtr haystack, kj::StringPtr needle); jpayne@69: jpayne@69: #if KJ_NO_EXCEPTIONS jpayne@69: bool expectFatalThrow(Maybe type, Maybe message, jpayne@69: Function code); jpayne@69: // Expects that the given code will throw a fatal exception matching the given type and/or message. jpayne@69: // Since exceptions are disabled, the test will fork() and run in a subprocess. On Windows, where jpayne@69: // fork() is not available, this always returns true. jpayne@69: #endif jpayne@69: jpayne@69: bool expectExit(Maybe statusCode, FunctionParam code) noexcept; jpayne@69: // Expects that the given code will exit with a given statusCode. jpayne@69: // The test will fork() and run in a subprocess. On Windows, where fork() is not available, jpayne@69: // this always returns true. jpayne@69: jpayne@69: bool expectSignal(Maybe signal, FunctionParam code) noexcept; jpayne@69: // Expects that the given code will trigger a signal. jpayne@69: // The test will fork() and run in a subprocess. On Windows, where fork() is not available, jpayne@69: // this always returns true. jpayne@69: // Resets signal handlers to default prior to running the code in the child process. jpayne@69: jpayne@69: class LogExpectation: public ExceptionCallback { jpayne@69: public: jpayne@69: LogExpectation(LogSeverity severity, StringPtr substring); jpayne@69: ~LogExpectation(); jpayne@69: jpayne@69: void logMessage(LogSeverity severity, const char* file, int line, int contextDepth, jpayne@69: String&& text) override; jpayne@69: jpayne@69: private: jpayne@69: LogSeverity severity; jpayne@69: StringPtr substring; jpayne@69: bool seen; jpayne@69: UnwindDetector unwindDetector; jpayne@69: }; jpayne@69: jpayne@69: class GlobFilter { jpayne@69: // Implements glob filters for the --filter flag. jpayne@69: // jpayne@69: // Exposed in header only for testing. jpayne@69: jpayne@69: public: jpayne@69: explicit GlobFilter(const char* pattern); jpayne@69: explicit GlobFilter(ArrayPtr pattern); jpayne@69: jpayne@69: bool matches(StringPtr name); jpayne@69: jpayne@69: private: jpayne@69: String pattern; jpayne@69: Vector states; jpayne@69: jpayne@69: void applyState(char c, int state); jpayne@69: }; jpayne@69: jpayne@69: } // namespace _ (private) jpayne@69: } // namespace kj jpayne@69: jpayne@69: KJ_END_HEADER