From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../tests/gtest/DeserializedNodeUbiNodes.cpp | 95 +++++++++ .../gtest/DeserializedStackFrameUbiStackFrames.cpp | 96 +++++++++ .../shared/heapsnapshot/tests/gtest/DevTools.cpp | 7 + .../shared/heapsnapshot/tests/gtest/DevTools.h | 214 +++++++++++++++++++++ .../tests/gtest/DoesCrossCompartmentBoundaries.cpp | 67 +++++++ .../gtest/DoesntCrossCompartmentBoundaries.cpp | 58 ++++++ .../tests/gtest/SerializesEdgeNames.cpp | 49 +++++ .../gtest/SerializesEverythingInHeapGraphOnce.cpp | 34 ++++ .../tests/gtest/SerializesTypeNames.cpp | 27 +++ devtools/shared/heapsnapshot/tests/gtest/moz.build | 32 +++ 10 files changed, 679 insertions(+) create mode 100644 devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp create mode 100644 devtools/shared/heapsnapshot/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp create mode 100644 devtools/shared/heapsnapshot/tests/gtest/DevTools.cpp create mode 100644 devtools/shared/heapsnapshot/tests/gtest/DevTools.h create mode 100644 devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp create mode 100644 devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp create mode 100644 devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp create mode 100644 devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp create mode 100644 devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp create mode 100644 devtools/shared/heapsnapshot/tests/gtest/moz.build (limited to 'devtools/shared/heapsnapshot/tests/gtest') diff --git a/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp b/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp new file mode 100644 index 0000000000..dc24d13e98 --- /dev/null +++ b/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp @@ -0,0 +1,95 @@ +/* -*- 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/. */ + +// Test that the `JS::ubi::Node`s we create from +// `mozilla::devtools::DeserializedNode` instances look and behave as we would +// like. + +#include "DevTools.h" +#include "js/TypeDecls.h" +#include "mozilla/devtools/DeserializedNode.h" + +using testing::Field; +using testing::ReturnRef; + +// A mock DeserializedNode for testing. +struct MockDeserializedNode : public DeserializedNode { + MockDeserializedNode(NodeId id, const char16_t* typeName, uint64_t size) + : DeserializedNode(id, typeName, size) {} + + bool addEdge(DeserializedEdge&& edge) { + return edges.append(std::move(edge)); + } + + MOCK_METHOD1(getEdgeReferent, JS::ubi::Node(const DeserializedEdge&)); +}; + +size_t fakeMallocSizeOf(const void*) { + EXPECT_TRUE(false); + MOZ_ASSERT_UNREACHABLE( + "fakeMallocSizeOf should never be called because " + "DeserializedNodes report the deserialized size."); + return 0; +} + +DEF_TEST(DeserializedNodeUbiNodes, { + const char16_t* typeName = u"TestTypeName"; + const char* className = "MyObjectClassName"; + const char* filename = "my-cool-filename.js"; + + NodeId id = uint64_t(1) << 33; + uint64_t size = uint64_t(1) << 60; + MockDeserializedNode mocked(id, typeName, size); + mocked.coarseType = JS::ubi::CoarseType::Script; + mocked.jsObjectClassName = className; + mocked.scriptFilename = filename; + + DeserializedNode& deserialized = mocked; + JS::ubi::Node ubi(&deserialized); + + // Test the ubi::Node accessors. + + EXPECT_EQ(size, ubi.size(fakeMallocSizeOf)); + EXPECT_EQ(typeName, ubi.typeName()); + EXPECT_EQ(JS::ubi::CoarseType::Script, ubi.coarseType()); + EXPECT_EQ(id, ubi.identifier()); + EXPECT_FALSE(ubi.isLive()); + EXPECT_EQ(ubi.jsObjectClassName(), className); + EXPECT_EQ(ubi.scriptFilename(), filename); + + // Test the ubi::Node's edges. + + UniquePtr referent1( + new MockDeserializedNode(1, nullptr, 10)); + DeserializedEdge edge1(referent1->id); + mocked.addEdge(std::move(edge1)); + EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent1->id))) + .Times(1) + .WillOnce(Return(JS::ubi::Node(referent1.get()))); + + UniquePtr referent2( + new MockDeserializedNode(2, nullptr, 20)); + DeserializedEdge edge2(referent2->id); + mocked.addEdge(std::move(edge2)); + EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent2->id))) + .Times(1) + .WillOnce(Return(JS::ubi::Node(referent2.get()))); + + UniquePtr referent3( + new MockDeserializedNode(3, nullptr, 30)); + DeserializedEdge edge3(referent3->id); + mocked.addEdge(std::move(edge3)); + EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent3->id))) + .Times(1) + .WillOnce(Return(JS::ubi::Node(referent3.get()))); + + auto range = ubi.edges(cx); + ASSERT_TRUE(!!range); + + for (; !range->empty(); range->popFront()) { + // Nothing to do here. This loop ensures that we get each edge referent + // that we expect above. + } +}); diff --git a/devtools/shared/heapsnapshot/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp b/devtools/shared/heapsnapshot/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp new file mode 100644 index 0000000000..37e1d426f8 --- /dev/null +++ b/devtools/shared/heapsnapshot/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp @@ -0,0 +1,96 @@ +/* -*- 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/. */ + +// Test that the `JS::ubi::StackFrame`s we create from +// `mozilla::devtools::DeserializedStackFrame` instances look and behave as we +// would like. + +#include "DevTools.h" +#include "js/SavedFrameAPI.h" +#include "js/TypeDecls.h" +#include "mozilla/devtools/DeserializedNode.h" + +using testing::Field; +using testing::ReturnRef; + +// A mock DeserializedStackFrame for testing. +struct MockDeserializedStackFrame : public DeserializedStackFrame { + MockDeserializedStackFrame() : DeserializedStackFrame() {} +}; + +DEF_TEST(DeserializedStackFrameUbiStackFrames, { + StackFrameId id = uint64_t(1) << 42; + uint32_t line = 1337; + uint32_t column = 9; // 3 space tabs!? + const char16_t* source = u"my-javascript-file.js"; + const char16_t* functionDisplayName = u"myFunctionName"; + + MockDeserializedStackFrame mocked; + mocked.id = id; + mocked.line = line; + mocked.column = column; + mocked.source = source; + mocked.functionDisplayName = functionDisplayName; + + DeserializedStackFrame& deserialized = mocked; + JS::ubi::StackFrame ubiFrame(&deserialized); + + // Test the JS::ubi::StackFrame accessors. + + EXPECT_EQ(id, ubiFrame.identifier()); + EXPECT_EQ(JS::ubi::StackFrame(), ubiFrame.parent()); + EXPECT_EQ(line, ubiFrame.line()); + EXPECT_EQ(column, ubiFrame.column()); + EXPECT_EQ(JS::ubi::AtomOrTwoByteChars(source), ubiFrame.source()); + EXPECT_EQ(JS::ubi::AtomOrTwoByteChars(functionDisplayName), + ubiFrame.functionDisplayName()); + EXPECT_FALSE(ubiFrame.isSelfHosted(cx)); + EXPECT_FALSE(ubiFrame.isSystem()); + + JS::Rooted savedFrame(cx); + EXPECT_TRUE(ubiFrame.constructSavedFrameStack(cx, &savedFrame)); + + JSPrincipals* principals = JS::GetRealmPrincipals(js::GetContextRealm(cx)); + + uint32_t frameLine; + ASSERT_EQ(JS::SavedFrameResult::Ok, + JS::GetSavedFrameLine(cx, principals, savedFrame, &frameLine)); + EXPECT_EQ(line, frameLine); + + uint32_t frameColumn; + ASSERT_EQ(JS::SavedFrameResult::Ok, + JS::GetSavedFrameColumn(cx, principals, savedFrame, &frameColumn)); + EXPECT_EQ(column, frameColumn); + + JS::Rooted parent(cx); + ASSERT_EQ(JS::SavedFrameResult::Ok, + JS::GetSavedFrameParent(cx, principals, savedFrame, &parent)); + EXPECT_EQ(nullptr, parent); + + ASSERT_EQ(NS_strlen(source), 21U); + char16_t sourceBuf[21] = {}; + + // Test when the length is shorter than the string length. + auto written = ubiFrame.source(RangedPtr(sourceBuf), 3); + EXPECT_EQ(written, 3U); + for (size_t i = 0; i < 3; i++) { + EXPECT_EQ(sourceBuf[i], source[i]); + } + + written = ubiFrame.source(RangedPtr(sourceBuf), 21); + EXPECT_EQ(written, 21U); + for (size_t i = 0; i < 21; i++) { + EXPECT_EQ(sourceBuf[i], source[i]); + } + + ASSERT_EQ(NS_strlen(functionDisplayName), 14U); + char16_t nameBuf[14] = {}; + + written = ubiFrame.functionDisplayName(RangedPtr(nameBuf), 14); + EXPECT_EQ(written, 14U); + for (size_t i = 0; i < 14; i++) { + EXPECT_EQ(nameBuf[i], functionDisplayName[i]); + } +}); diff --git a/devtools/shared/heapsnapshot/tests/gtest/DevTools.cpp b/devtools/shared/heapsnapshot/tests/gtest/DevTools.cpp new file mode 100644 index 0000000000..8e89d5ecd7 --- /dev/null +++ b/devtools/shared/heapsnapshot/tests/gtest/DevTools.cpp @@ -0,0 +1,7 @@ +/* -*- 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/. */ + +#include "DevTools.h" +const char16_t JS::ubi::Concrete::concreteTypeName[] = u"FakeNode"; diff --git a/devtools/shared/heapsnapshot/tests/gtest/DevTools.h b/devtools/shared/heapsnapshot/tests/gtest/DevTools.h new file mode 100644 index 0000000000..35a16c9182 --- /dev/null +++ b/devtools/shared/heapsnapshot/tests/gtest/DevTools.h @@ -0,0 +1,214 @@ +/* -*- 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 + +#include "CoreDump.pb.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "js/Principals.h" +#include "js/UbiNode.h" +#include "js/UniquePtr.h" +#include "jsapi.h" +#include "jspubtd.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/devtools/HeapSnapshot.h" +#include "mozilla/dom/ChromeUtils.h" +#include "nsCRTGlue.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; + JSContext* cx; + JS::Compartment* compartment; + JS::Zone* zone; + JS::PersistentRooted global; + + DevTools() : _initialized(false), cx(nullptr) {} + + virtual void SetUp() { + MOZ_ASSERT(!_initialized); + + cx = getContext(); + if (!cx) return; + + global.init(cx, createGlobal()); + if (!global) return; + JS::EnterRealm(cx, global); + + compartment = js::GetContextCompartment(cx); + zone = js::GetContextZone(cx); + + _initialized = true; + } + + JSContext* getContext() { return CycleCollectedJSContext::Get()->Context(); } + + 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); + } + + static const JSClass* getGlobalClass() { + static const JSClass globalClass = {"global", JSCLASS_GLOBAL_FLAGS, + &JS::DefaultGlobalClassOps}; + return &globalClass; + } + + JSObject* createGlobal() { + /* Create the global object. */ + JS::RealmOptions options; + return JS_NewGlobalObject(cx, getGlobalClass(), nullptr, + JS::FireOnNewGlobalHook, options); + } + + virtual void TearDown() { + _initialized = false; + + if (global) { + JS::LeaveRealm(cx, nullptr); + global = 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::EdgeVector edges; + JS::Compartment* compartment; + JS::Zone* zone; + size_t size; + + explicit FakeNode() : edges(), compartment(nullptr), zone(nullptr), size(1) {} +}; + +namespace JS { +namespace ubi { + +template <> +class Concrete : public Base { + const char16_t* typeName() const override { return concreteTypeName; } + + js::UniquePtr edges(JSContext*, bool) const override { + return js::UniquePtr(js_new(get().edges)); + } + + Size size(mozilla::MallocSizeOf) const override { return get().size; } + + JS::Zone* zone() const override { return get().zone; } + + JS::Compartment* 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); + } +}; + +} // namespace ubi +} // namespace JS + +inline void AddEdge(FakeNode& node, FakeNode& referent, + const char16_t* edgeName = nullptr) { + char16_t* ownedEdgeName = nullptr; + if (edgeName) { + ownedEdgeName = NS_xstrdup(edgeName); + } + + JS::ubi::Edge edge(ownedEdgeName, &referent); + ASSERT_TRUE(node.edges.append(std::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; } + +MATCHER_P(UniqueUTF16StrEq, str, "") { return NS_strcmp(arg.get(), str) == 0; } + +MATCHER(UniqueIsNull, "") { return arg.get() == nullptr; } + +// Matches an edge whose referent is the node with the given id. +MATCHER_P(EdgeTo, id, "") { + return Matcher( + Field(&DeserializedEdge::referent, id)) + .MatchAndExplain(arg, result_listener); +} + +} // 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)); +}; + +inline 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__ diff --git a/devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp b/devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp new file mode 100644 index 0000000000..25c387d308 --- /dev/null +++ b/devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp @@ -0,0 +1,67 @@ +/* -*- 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/. */ + +// Test that heap snapshots cross compartment boundaries when expected. + +#include "DevTools.h" + +DEF_TEST(DoesCrossCompartmentBoundaries, { + // Create a new global to get a new compartment. + JS::RealmOptions options; + JS::Rooted newGlobal( + cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, + JS::FireOnNewGlobalHook, options)); + ASSERT_TRUE(newGlobal); + JS::Compartment* newCompartment = nullptr; + { + JSAutoRealm ar(cx, newGlobal); + ASSERT_TRUE(JS::InitRealmStandardClasses(cx)); + newCompartment = js::GetContextCompartment(cx); + } + ASSERT_TRUE(newCompartment); + ASSERT_NE(newCompartment, compartment); + + // Our set of target compartments is both the old and new compartments. + JS::CompartmentSet targetCompartments; + ASSERT_TRUE(targetCompartments.put(compartment)); + ASSERT_TRUE(targetCompartments.put(newCompartment)); + + FakeNode nodeA; + FakeNode nodeB; + FakeNode nodeC; + FakeNode nodeD; + + nodeA.compartment = compartment; + nodeB.compartment = nullptr; + nodeC.compartment = newCompartment; + nodeD.compartment = nullptr; + + AddEdge(nodeA, nodeB); + AddEdge(nodeA, nodeC); + AddEdge(nodeB, nodeD); + + ::testing::NiceMock writer; + + // Should serialize nodeA, because it is in one of our target compartments. + ExpectWriteNode(writer, nodeA); + + // Should serialize nodeB, because it doesn't belong to a compartment and is + // therefore assumed to be shared. + ExpectWriteNode(writer, nodeB); + + // Should also serialize nodeC, which is in our target compartments, but a + // different compartment than A. + ExpectWriteNode(writer, nodeC); + + // Should serialize nodeD because it's reachable via B and both nodes B and D + // don't belong to a specific compartment. + ExpectWriteNode(writer, nodeD); + + JS::AutoCheckCannotGC noGC(cx); + + ASSERT_TRUE(WriteHeapGraph(cx, JS::ubi::Node(&nodeA), writer, + /* wantNames = */ false, &targetCompartments, + noGC)); +}); diff --git a/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp b/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp new file mode 100644 index 0000000000..f6c6b11619 --- /dev/null +++ b/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp @@ -0,0 +1,58 @@ +/* -*- 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/. */ + +// Test that heap snapshots walk the compartment boundaries correctly. + +#include "DevTools.h" + +DEF_TEST(DoesntCrossCompartmentBoundaries, { + // Create a new global to get a new compartment. + JS::RealmOptions options; + JS::Rooted newGlobal( + cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, + JS::FireOnNewGlobalHook, options)); + ASSERT_TRUE(newGlobal); + JS::Compartment* newCompartment = nullptr; + { + JSAutoRealm ar(cx, newGlobal); + ASSERT_TRUE(JS::InitRealmStandardClasses(cx)); + newCompartment = js::GetContextCompartment(cx); + } + ASSERT_TRUE(newCompartment); + ASSERT_NE(newCompartment, compartment); + + // Our set of target compartments is only the pre-existing compartment and + // does not include the new compartment. + JS::CompartmentSet targetCompartments; + ASSERT_TRUE(targetCompartments.put(compartment)); + + FakeNode nodeA; + FakeNode nodeB; + FakeNode nodeC; + + nodeA.compartment = compartment; + nodeB.compartment = nullptr; + nodeC.compartment = newCompartment; + + AddEdge(nodeA, nodeB); + AddEdge(nodeB, nodeC); + + ::testing::NiceMock writer; + + // Should serialize nodeA, because it is in our target compartments. + ExpectWriteNode(writer, nodeA); + + // Should serialize nodeB, because it doesn't belong to a compartment and is + // therefore assumed to be shared. + ExpectWriteNode(writer, nodeB); + + // But we shouldn't ever serialize nodeC. + + JS::AutoCheckCannotGC noGC(cx); + + ASSERT_TRUE(WriteHeapGraph(cx, JS::ubi::Node(&nodeA), writer, + /* wantNames = */ false, &targetCompartments, + noGC)); +}); diff --git a/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp b/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp new file mode 100644 index 0000000000..ab47941e39 --- /dev/null +++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +// Test that edge names get serialized correctly. + +#include "DevTools.h" + +using testing::Field; +using testing::IsNull; +using testing::Property; +using testing::Return; + +DEF_TEST(SerializesEdgeNames, { + FakeNode node; + FakeNode referent; + + const char16_t edgeName[] = u"edge name"; + const char16_t emptyStr[] = u""; + + AddEdge(node, referent, edgeName); + AddEdge(node, referent, emptyStr); + AddEdge(node, referent, nullptr); + + ::testing::NiceMock writer; + + // Should get the node with edges once. + EXPECT_CALL( + writer, + writeNode( + AllOf(EdgesLength(cx, 3), + Edge(cx, 0, + Field(&JS::ubi::Edge::name, UniqueUTF16StrEq(edgeName))), + Edge(cx, 1, + Field(&JS::ubi::Edge::name, UniqueUTF16StrEq(emptyStr))), + Edge(cx, 2, Field(&JS::ubi::Edge::name, UniqueIsNull()))), + _)) + .Times(1) + .WillOnce(Return(true)); + + // Should get the referent node that doesn't have any edges once. + ExpectWriteNode(writer, referent); + + JS::AutoCheckCannotGC noGC(cx); + ASSERT_TRUE(WriteHeapGraph(cx, JS::ubi::Node(&node), writer, + /* wantNames = */ true, + /* zones = */ nullptr, noGC)); +}); diff --git a/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp b/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp new file mode 100644 index 0000000000..d71c86703c --- /dev/null +++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp @@ -0,0 +1,34 @@ +/* -*- 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/. */ + +// Test that everything in the heap graph gets serialized once, and only once. + +#include "DevTools.h" + +DEF_TEST(SerializesEverythingInHeapGraphOnce, { + FakeNode nodeA; + FakeNode nodeB; + FakeNode nodeC; + FakeNode nodeD; + + AddEdge(nodeA, nodeB); + AddEdge(nodeB, nodeC); + AddEdge(nodeC, nodeD); + AddEdge(nodeD, nodeA); + + ::testing::NiceMock writer; + + // Should serialize each node once. + ExpectWriteNode(writer, nodeA); + ExpectWriteNode(writer, nodeB); + ExpectWriteNode(writer, nodeC); + ExpectWriteNode(writer, nodeD); + + JS::AutoCheckCannotGC noGC(cx); + + ASSERT_TRUE(WriteHeapGraph(cx, JS::ubi::Node(&nodeA), writer, + /* wantNames = */ false, + /* zones = */ nullptr, noGC)); +}); diff --git a/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp b/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp new file mode 100644 index 0000000000..4c29b28832 --- /dev/null +++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp @@ -0,0 +1,27 @@ +/* -*- 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/. */ + +// Test that a ubi::Node's typeName gets properly serialized into a core dump. + +#include "DevTools.h" + +using testing::Property; +using testing::Return; + +DEF_TEST(SerializesTypeNames, { + FakeNode node; + + ::testing::NiceMock writer; + EXPECT_CALL( + writer, + writeNode(Property(&JS::ubi::Node::typeName, UTF16StrEq(u"FakeNode")), _)) + .Times(1) + .WillOnce(Return(true)); + + JS::AutoCheckCannotGC noGC(cx); + ASSERT_TRUE(WriteHeapGraph(cx, JS::ubi::Node(&node), writer, + /* wantNames = */ true, + /* zones = */ nullptr, noGC)); +}); diff --git a/devtools/shared/heapsnapshot/tests/gtest/moz.build b/devtools/shared/heapsnapshot/tests/gtest/moz.build new file mode 100644 index 0000000000..880d7b334e --- /dev/null +++ b/devtools/shared/heapsnapshot/tests/gtest/moz.build @@ -0,0 +1,32 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Library("devtoolstests") + +LOCAL_INCLUDES += [ + "../..", +] + +DEFINES["GOOGLE_PROTOBUF_NO_RTTI"] = True +DEFINES["GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER"] = True + +UNIFIED_SOURCES = [ + "DeserializedNodeUbiNodes.cpp", + "DeserializedStackFrameUbiStackFrames.cpp", + "DevTools.cpp", + "DoesCrossCompartmentBoundaries.cpp", + "DoesntCrossCompartmentBoundaries.cpp", + "SerializesEdgeNames.cpp", + "SerializesEverythingInHeapGraphOnce.cpp", + "SerializesTypeNames.cpp", +] + +# THE MOCK_METHOD2 macro from gtest triggers this clang warning and it's hard +# to work around, so we just ignore it. +if CONFIG["CC_TYPE"] == "clang": + CXXFLAGS += ["-Wno-inconsistent-missing-override"] + +FINAL_LIBRARY = "xul-gtest" -- cgit v1.2.3