/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ /* 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 mozilla_devtools_gtest_DevTools__ #define mozilla_devtools_gtest_DevTools__ #include "CoreDump.pb.h" #include "jsapi.h" #include "jspubtd.h" #include "nsCRTGlue.h" #include "gtest/gtest.h" #include "gmock/gmock.h" #include "mozilla/devtools/HeapSnapshot.h" #include "mozilla/dom/ChromeUtils.h" #include "mozilla/CycleCollectedJSRuntime.h" #include "mozilla/Move.h" #include "mozilla/UniquePtr.h" #include "js/UbiNode.h" using namespace mozilla; using namespace mozilla::devtools; using namespace mozilla::dom; using namespace testing; // GTest fixture class that all of our tests derive from. struct DevTools : public ::testing::Test { bool _initialized; JSRuntime* rt; JSContext* cx; JSCompartment* compartment; JS::Zone* zone; JS::PersistentRootedObject global; DevTools() : _initialized(false), rt(nullptr), cx(nullptr) { } virtual void SetUp() { MOZ_ASSERT(!_initialized); rt = getRuntime(); if (!rt) return; cx = createContext(); if (!cx) return; JS_BeginRequest(cx); global.init(rt, createGlobal()); if (!global) return; JS_EnterCompartment(cx, global); compartment = js::GetContextCompartment(cx); zone = js::GetContextZone(cx); _initialized = true; } JSRuntime* getRuntime() { return CycleCollectedJSRuntime::Get()->Runtime(); } static void setNativeStackQuota(JSRuntime* rt) { const size_t MAX_STACK_SIZE = /* Assume we can't use more than 5e5 bytes of C stack by default. */ #if (defined(DEBUG) && defined(__SUNPRO_CC)) || defined(JS_CPU_SPARC) /* * Sun compiler uses a larger stack space for js::Interpret() with * debug. Use a bigger gMaxStackSize to make "make check" happy. */ 5000000 #else 500000 #endif ; JS_SetNativeStackQuota(rt, MAX_STACK_SIZE); } static void reportError(JSContext* cx, const char* message, JSErrorReport* report) { fprintf(stderr, "%s:%u:%s\n", report->filename ? report->filename : "", (unsigned int) report->lineno, message); } JSContext* createContext() { return JS_NewContext(rt, 8192); } static const JSClass* getGlobalClass() { static const JSClass globalClass = { "global", JSCLASS_GLOBAL_FLAGS, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook }; return &globalClass; } JSObject* createGlobal() { /* Create the global object. */ JS::RootedObject newGlobal(cx); JS::CompartmentOptions options; options.setVersion(JSVERSION_LATEST); newGlobal = JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook, options); if (!newGlobal) return nullptr; JSAutoCompartment ac(cx, newGlobal); /* Populate the global object with the standard globals, like Object and Array. */ if (!JS_InitStandardClasses(cx, newGlobal)) return nullptr; return newGlobal; } virtual void TearDown() { _initialized = false; if (global) { JS_LeaveCompartment(cx, nullptr); global = nullptr; } if (cx) { JS_EndRequest(cx); JS_DestroyContext(cx); cx = nullptr; } } }; // Helper to define a test and ensure that the fixture is initialized properly. #define DEF_TEST(name, body) \ TEST_F(DevTools, name) { \ ASSERT_TRUE(_initialized); \ body \ } // Fake JS::ubi::Node implementation class MOZ_STACK_CLASS FakeNode { public: JS::ubi::SimpleEdgeVector edges; JSCompartment* compartment; JS::Zone* zone; size_t size; explicit FakeNode(JSContext* cx) : edges(cx), compartment(nullptr), zone(nullptr), size(1) { } }; namespace JS { namespace ubi { using mozilla::UniquePtr; template<> class Concrete : public Base { const char16_t* typeName() const override { return concreteTypeName; } UniquePtr edges(JSContext* cx, bool wantNames) const override { return UniquePtr(js_new(cx, get().edges)); } size_t size(mozilla::MallocSizeOf) const override { return get().size; } JS::Zone* zone() const override { return get().zone; } JSCompartment* compartment() const override { return get().compartment; } protected: explicit Concrete(FakeNode* ptr) : Base(ptr) { } FakeNode& get() const { return *static_cast(ptr); } public: static const char16_t concreteTypeName[]; static void construct(void* storage, FakeNode* ptr) { new (storage) Concrete(ptr); } }; const char16_t Concrete::concreteTypeName[] = MOZ_UTF16("FakeNode"); } // namespace ubi } // namespace JS void AddEdge(FakeNode& node, FakeNode& referent, const char16_t* edgeName = nullptr) { char16_t* ownedEdgeName = nullptr; if (edgeName) { ownedEdgeName = NS_strdup(edgeName); ASSERT_NE(ownedEdgeName, nullptr); } JS::ubi::SimpleEdge edge(ownedEdgeName, &referent); ASSERT_TRUE(node.edges.append(mozilla::Move(edge))); } // Custom GMock Matchers // Use the testing namespace to avoid static analysis failures in the gmock // matcher classes that get generated from MATCHER_P macros. namespace testing { // Ensure that given node has the expected number of edges. MATCHER_P2(EdgesLength, cx, expectedLength, "") { auto edges = arg.edges(cx); if (!edges) return false; int actualLength = 0; for ( ; !edges->empty(); edges->popFront()) actualLength++; return Matcher(Eq(expectedLength)) .MatchAndExplain(actualLength, result_listener); } // Get the nth edge and match it with the given matcher. MATCHER_P3(Edge, cx, n, matcher, "") { auto edges = arg.edges(cx); if (!edges) return false; int i = 0; for ( ; !edges->empty(); edges->popFront()) { if (i == n) { return Matcher(matcher) .MatchAndExplain(edges->front(), result_listener); } i++; } return false; } // Ensures that two char16_t* strings are equal. MATCHER_P(UTF16StrEq, str, "") { return NS_strcmp(arg, str) == 0; } } // namespace testing // A mock `Writer` class to be used with testing `WriteHeapGraph`. class MockWriter : public CoreDumpWriter { public: virtual ~MockWriter() override { } MOCK_METHOD2(writeNode, bool(const JS::ubi::Node&, CoreDumpWriter::EdgePolicy)); MOCK_METHOD1(writeMetadata, bool(uint64_t)); }; void ExpectWriteNode(MockWriter& writer, FakeNode& node) { EXPECT_CALL(writer, writeNode(Eq(JS::ubi::Node(&node)), _)) .Times(1) .WillOnce(Return(true)); } #endif // mozilla_devtools_gtest_DevTools__