/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef jsapi_tests_tests_h #define jsapi_tests_tests_h #include #include #include "jsapi.h" #include "gc/GC.h" #include "js/AllocPolicy.h" #include "js/ArrayBuffer.h" // BufferContentsDeleter #include "js/Principals.h" // JSPrincipals #include "js/RegExpFlags.h" // JS::RegExpFlags #include "js/RootingAPI.h" // JS::PersistentRootedObject #include "js/Value.h" #include "js/Vector.h" #include "vm/JSContext.h" namespace jsapitest { enum class TestKind { Runtime, Frontend }; class TestBase { public: TestBase* next = nullptr; const TestKind kind; bool knownFail = false; std::string msgs; TestBase(TestKind kind); virtual ~TestBase() {} bool isRuntimeTest() const { return kind == TestKind::Runtime; } virtual const char* name() = 0; virtual bool run() = 0; // These methods may be overridden by the test to perform additional // initialization after any JSContext and global have been created. virtual bool init() { return true; } virtual void uninit() {} virtual void maybeAppendException(std::string& message) {} bool fail(const std::string& msg = std::string(), const char* filename = "-", int lineno = 0); std::string messages() const { return msgs; } }; class RuntimeTest : public TestBase { public: JSContext* cx = nullptr; JS::PersistentRootedObject global; // Whether this test is willing to skip its init() and reuse a global (and // JSContext etc.) from a previous test that also has reuseGlobal=true. It // also means this test is willing to skip its uninit() if it is followed by // another reuseGlobal test. bool reuseGlobal = false; // Downcase TestBase to RuntimeTest. static RuntimeTest* From(TestBase* test); RuntimeTest(); virtual ~RuntimeTest(); // Initialize the context, possibly with one from a previously run test. bool initContext(JSContext* maybeReusedContext); // If this test is ok with its cx and global being reused, release this // test's cx to be reused by another test. JSContext* maybeForgetContext(); static void MaybeFreeContext(JSContext* maybeCx); void uninit() override; #define EXEC(s) \ do { \ if (!exec(s, __FILE__, __LINE__)) { \ return false; \ } \ } while (false) bool exec(const char* utf8, const char* filename, int lineno); // Like exec(), but doesn't call fail() if JS::Evaluate returns false. bool execDontReport(const char* utf8, const char* filename, int lineno); #define EVAL(s, vp) \ do { \ if (!evaluate(s, __FILE__, __LINE__, vp)) { \ return false; \ } \ } while (false) bool evaluate(const char* utf8, const char* filename, int lineno, JS::MutableHandleValue vp); std::string jsvalToSource(JS::HandleValue v); std::string toSource(char c); std::string toSource(long v); std::string toSource(unsigned long v); std::string toSource(long long v); std::string toSource(unsigned long long v); std::string toSource(double d); std::string toSource(unsigned int v); std::string toSource(int v); std::string toSource(bool v); std::string toSource(JS::RegExpFlags flags); std::string toSource(JSAtom* v); // Note that in some still-supported GCC versions (we think anything before // GCC 4.6), this template does not work when the second argument is // nullptr. It infers type U = long int. Use CHECK_NULL instead. template bool checkEqual(const T& actual, const U& expected, const char* actualExpr, const char* expectedExpr, const char* filename, int lineno) { static_assert(std::is_signed_v == std::is_signed_v, "using CHECK_EQUAL with different-signed inputs triggers " "compiler warnings"); static_assert( std::is_unsigned_v == std::is_unsigned_v, "using CHECK_EQUAL with different-signed inputs triggers compiler " "warnings"); if (actual == expected) { return true; } fail(std::string("CHECK_EQUAL failed: expected (") + expectedExpr + ") = " + toSource(expected) + ", got (" + actualExpr + ") = " + toSource(actual), filename, lineno); return false; } #define CHECK_EQUAL(actual, expected) \ do { \ if (!checkEqual(actual, expected, #actual, #expected, __FILE__, \ __LINE__)) { \ return false; \ } \ } while (false) template bool checkNull(const T* actual, const char* actualExpr, const char* filename, int lineno) { if (actual == nullptr) { return true; } fail(std::string("CHECK_NULL failed: expected nullptr, got (") + actualExpr + ") = " + toSource(actual), filename, lineno); return false; } #define CHECK_NULL(actual) \ do { \ if (!checkNull(actual, #actual, __FILE__, __LINE__)) { \ return false; \ } \ } while (false) bool checkSame(const JS::Value& actualArg, const JS::Value& expectedArg, const char* actualExpr, const char* expectedExpr, const char* filename, int lineno); #define CHECK_SAME(actual, expected) \ do { \ if (!checkSame(actual, expected, #actual, #expected, __FILE__, \ __LINE__)) { \ return false; \ } \ } while (false) #define CHECK(expr) \ do { \ if (!(expr)) { \ return fail(std::string("CHECK failed: " #expr), __FILE__, __LINE__); \ } \ } while (false) void maybeAppendException(std::string& message) override; static const JSClass* basicGlobalClass(); protected: static void reportWarning(JSContext* cx, JSErrorReport* report); static bool print(JSContext* cx, unsigned argc, JS::Value* vp); bool definePrint(); virtual JSContext* createContext(); virtual const JSClass* getGlobalClass() { return basicGlobalClass(); } virtual JSObject* createGlobal(JSPrincipals* principals = nullptr); }; class FrontendTest : public TestBase { public: FrontendTest(); virtual ~FrontendTest() {} }; } // namespace jsapitest #define BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, extra) \ class cls_##testname : public jsapitest::RuntimeTest { \ public: \ virtual const char* name() override { return #testname; } \ extra bool run() override attrs #define BEGIN_TEST_WITH_ATTRIBUTES(testname, attrs) \ BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, ) #define BEGIN_TEST(testname) BEGIN_TEST_WITH_ATTRIBUTES(testname, ) #define BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, extra) \ class cls_##testname : public jsapitest::FrontendTest { \ public: \ virtual const char* name() override { return #testname; } \ extra bool run() override attrs #define BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES(testname, attrs) \ BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, ) #define BEGIN_FRONTEND_TEST(testname) \ BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES(testname, ) #define BEGIN_REUSABLE_TEST(testname) \ BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA( \ testname, , cls_##testname() : RuntimeTest() { reuseGlobal = true; }) #define END_TEST(testname) \ } \ ; \ MOZ_RUNINIT static cls_##testname cls_##testname##_instance; /* * A "fixture" is a subclass of RuntimeTest that holds common definitions * for a set of tests. Each test that wants to use the fixture should use * BEGIN_FIXTURE_TEST and END_FIXTURE_TEST, just as one would use BEGIN_TEST and * END_TEST, but include the fixture class as the first argument. The fixture * class's declarations are then in scope for the test bodies. */ #define BEGIN_FIXTURE_TEST(fixture, testname) \ class cls_##testname : public fixture { \ public: \ virtual const char* name() override { return #testname; } \ bool run() override #define END_FIXTURE_TEST(fixture, testname) \ } \ ; \ MOZ_RUNINIT static cls_##testname cls_##testname##_instance; /* * A class for creating and managing one temporary file. * * We could use the ISO C temporary file functions here, but those try to * create files in the root directory on Windows, which fails for users * without Administrator privileges. */ class TempFile { const char* name; FILE* stream; public: TempFile(); ~TempFile(); /* * Return a stream for a temporary file named |fileName|. Infallible. * Use only once per TempFile instance. If the file is not explicitly * closed and deleted via the member functions below, this object's * destructor will clean them up. */ FILE* open(const char* fileName); /* Close the temporary file's stream. */ void close(); /* Delete the temporary file. */ void remove(); }; // Just a wrapper around JSPrincipals that allows static construction. class TestJSPrincipals : public JSPrincipals { public: explicit TestJSPrincipals(int rc = 0); bool write(JSContext* cx, JSStructuredCloneWriter* writer) override; bool isSystemPrincipal() override { return true; } bool isAddonPrincipal() override { return true; } }; // A class that simulates externally memory-managed data, for testing with // array buffers. class ExternalData { char* contents_; size_t len_; bool uniquePointerCreated_ = false; public: explicit ExternalData(const char* str); size_t len() const { return len_; } void* contents() const { return contents_; } char* asString() const { return contents_; } bool wasFreed() const { return !contents_; } void free(); mozilla::UniquePtr pointer(); static void freeCallback(void* contents, void* userData); }; class AutoGCParameter { JSContext* cx_; JSGCParamKey key_; uint32_t value_; public: AutoGCParameter(JSContext* cx, JSGCParamKey key, uint32_t value); ~AutoGCParameter(); }; /* * Temporarily disable the GC zeal setting. This is only useful in tests that * need very explicit GC behavior and should not be used elsewhere. */ class AutoLeaveZeal { #ifdef JS_GC_ZEAL JSContext* cx_; uint32_t zealBits_; uint32_t frequency_; #endif public: explicit AutoLeaveZeal(JSContext* cx); ~AutoLeaveZeal(); }; #endif /* jsapi_tests_tests_h */