DevTools.h (6107B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #ifndef mozilla_devtools_gtest_DevTools__ 7 #define mozilla_devtools_gtest_DevTools__ 8 9 #include <utility> 10 11 #include "CoreDump.pb.h" 12 #include "gmock/gmock.h" 13 #include "gtest/gtest.h" 14 #include "js/Principals.h" 15 #include "js/UbiNode.h" 16 #include "js/UniquePtr.h" 17 #include "jsapi.h" 18 #include "jspubtd.h" 19 #include "mozilla/CycleCollectedJSContext.h" 20 #include "mozilla/devtools/HeapSnapshot.h" 21 #include "mozilla/dom/ChromeUtils.h" 22 #include "nsCRTGlue.h" 23 24 using namespace mozilla; 25 using namespace mozilla::devtools; 26 using namespace mozilla::dom; 27 using namespace testing; 28 29 // GTest fixture class that all of our tests derive from. 30 struct DevTools : public ::testing::Test { 31 bool _initialized; 32 JSContext* cx; 33 JS::Compartment* compartment; 34 JS::Zone* zone; 35 JS::PersistentRooted<JSObject*> global; 36 37 DevTools() : _initialized(false), cx(nullptr) {} 38 39 virtual void SetUp() { 40 MOZ_ASSERT(!_initialized); 41 42 cx = getContext(); 43 if (!cx) return; 44 45 global.init(cx, createGlobal()); 46 if (!global) return; 47 JS::EnterRealm(cx, global); 48 49 compartment = js::GetContextCompartment(cx); 50 zone = js::GetContextZone(cx); 51 52 _initialized = true; 53 } 54 55 JSContext* getContext() { return CycleCollectedJSContext::Get()->Context(); } 56 57 static void reportError(JSContext* cx, const char* message, 58 JSErrorReport* report) { 59 fprintf(stderr, "%s:%u:%s\n", 60 report->filename ? report->filename.c_str() : "<no filename>", 61 (unsigned int)report->lineno, message); 62 } 63 64 static const JSClass* getGlobalClass() { 65 static const JSClass globalClass = {"global", JSCLASS_GLOBAL_FLAGS, 66 &JS::DefaultGlobalClassOps}; 67 return &globalClass; 68 } 69 70 JSObject* createGlobal() { 71 /* Create the global object. */ 72 JS::RealmOptions options; 73 // dummy 74 options.behaviors().setReduceTimerPrecisionCallerType( 75 JS::RTPCallerTypeToken{0}); 76 return JS_NewGlobalObject(cx, getGlobalClass(), nullptr, 77 JS::FireOnNewGlobalHook, options); 78 } 79 80 virtual void TearDown() { 81 _initialized = false; 82 83 if (global) { 84 JS::LeaveRealm(cx, nullptr); 85 global = nullptr; 86 } 87 } 88 }; 89 90 // Helper to define a test and ensure that the fixture is initialized properly. 91 #define DEF_TEST(name, body) \ 92 TEST_F(DevTools, name) { \ 93 ASSERT_TRUE(_initialized); \ 94 body \ 95 } 96 97 // Fake JS::ubi::Node implementation 98 class MOZ_STACK_CLASS FakeNode { 99 public: 100 JS::ubi::EdgeVector edges; 101 JS::Compartment* compartment; 102 JS::Zone* zone; 103 size_t size; 104 105 explicit FakeNode() : compartment(nullptr), zone(nullptr), size(1) {} 106 }; 107 108 namespace JS { 109 namespace ubi { 110 111 template <> 112 class Concrete<FakeNode> : public Base { 113 const char16_t* typeName() const override { return concreteTypeName; } 114 115 js::UniquePtr<EdgeRange> edges(JSContext*, bool) const override { 116 return js::UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges)); 117 } 118 119 Size size(mozilla::MallocSizeOf) const override { return get().size; } 120 121 JS::Zone* zone() const override { return get().zone; } 122 123 JS::Compartment* compartment() const override { return get().compartment; } 124 125 protected: 126 explicit Concrete(FakeNode* ptr) : Base(ptr) {} 127 FakeNode& get() const { return *static_cast<FakeNode*>(ptr); } 128 129 public: 130 static const char16_t concreteTypeName[]; 131 static void construct(void* storage, FakeNode* ptr) { 132 new (storage) Concrete(ptr); 133 } 134 }; 135 136 } // namespace ubi 137 } // namespace JS 138 139 inline void AddEdge(FakeNode& node, FakeNode& referent, 140 const char16_t* edgeName = nullptr) { 141 char16_t* ownedEdgeName = nullptr; 142 if (edgeName) { 143 ownedEdgeName = NS_xstrdup(edgeName); 144 } 145 146 JS::ubi::Edge edge(ownedEdgeName, &referent); 147 ASSERT_TRUE(node.edges.append(std::move(edge))); 148 } 149 150 // Custom GMock Matchers 151 152 // Use the testing namespace to avoid static analysis failures in the gmock 153 // matcher classes that get generated from MATCHER_P macros. 154 namespace testing { 155 156 // Ensure that given node has the expected number of edges. 157 MATCHER_P2(EdgesLength, cx, expectedLength, "") { 158 auto edges = arg.edges(cx); 159 if (!edges) return false; 160 161 int actualLength = 0; 162 for (; !edges->empty(); edges->popFront()) actualLength++; 163 164 return Matcher<int>(Eq(expectedLength)) 165 .MatchAndExplain(actualLength, result_listener); 166 } 167 168 // Get the nth edge and match it with the given matcher. 169 MATCHER_P3(Edge, cx, n, matcher, "") { 170 auto edges = arg.edges(cx); 171 if (!edges) return false; 172 173 int i = 0; 174 for (; !edges->empty(); edges->popFront()) { 175 if (i == n) { 176 return Matcher<const JS::ubi::Edge&>(matcher).MatchAndExplain( 177 edges->front(), result_listener); 178 } 179 180 i++; 181 } 182 183 return false; 184 } 185 186 // Ensures that two char16_t* strings are equal. 187 MATCHER_P(UTF16StrEq, str, "") { return NS_strcmp(arg, str) == 0; } 188 189 MATCHER_P(UniqueUTF16StrEq, str, "") { return NS_strcmp(arg.get(), str) == 0; } 190 191 MATCHER(UniqueIsNull, "") { return arg.get() == nullptr; } 192 193 // Matches an edge whose referent is the node with the given id. 194 MATCHER_P(EdgeTo, id, "") { 195 return Matcher<const DeserializedEdge&>( 196 Field(&DeserializedEdge::referent, id)) 197 .MatchAndExplain(arg, result_listener); 198 } 199 200 } // namespace testing 201 202 // A mock `Writer` class to be used with testing `WriteHeapGraph`. 203 class MockWriter : public CoreDumpWriter { 204 public: 205 virtual ~MockWriter() override = default; 206 MOCK_METHOD2(writeNode, 207 bool(const JS::ubi::Node&, CoreDumpWriter::EdgePolicy)); 208 MOCK_METHOD1(writeMetadata, bool(uint64_t)); 209 }; 210 211 inline void ExpectWriteNode(MockWriter& writer, FakeNode& node) { 212 EXPECT_CALL(writer, writeNode(Eq(JS::ubi::Node(&node)), _)) 213 .Times(1) 214 .WillOnce(Return(true)); 215 } 216 217 #endif // mozilla_devtools_gtest_DevTools__