Commit b95f5bc3 authored by vabr's avatar vabr Committed by Commit bot

SerializeHostDescriptions ignores hosts with duplicated names

|HostDescriptionNode.name| identifies the node and should not be shared by two
different nodes. If multiple nodes are passed to SerializeHostDescriptions with
the same name, currently SerializeHostDescriptions ends up using
DictionaryValue after performing std::move on it, because of mixing nodes with
the same name.

This CL replaces some internal use of std::vector with std::unordered_set to
discard duplicities.

The CL also improves the unit test to be less dependent on the resulting order
of the serialized descriptions.

The CL also fixes the test name to match its filename and the name of the tested
function.

BUG=714368

Review-Url: https://codereview.chromium.org/2835823002
Cr-Commit-Position: refs/heads/master@{#466913}
parent 72254d51
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "chrome/browser/devtools/serialize_host_descriptions.h" #include "chrome/browser/devtools/serialize_host_descriptions.h"
#include <map> #include <map>
#include <unordered_set>
#include <utility> #include <utility>
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
...@@ -40,14 +41,14 @@ base::DictionaryValue Serialize( ...@@ -40,14 +41,14 @@ base::DictionaryValue Serialize(
// Takes a vector of host description and converts it into: // Takes a vector of host description and converts it into:
// |children|: a map from a host's representation to representations of its // |children|: a map from a host's representation to representations of its
// children, // children,
// |roots|: a vector of representations of hosts with no parents, and // |roots|: a set of representations of hosts with no parents, and
// |representations|: a vector actually storing all those representations to // |representations|: a vector actually storing all those representations to
// which the rest just points. // which the rest just points.
void CreateDictionaryForest( void CreateDictionaryForest(
std::vector<HostDescriptionNode> hosts, std::vector<HostDescriptionNode> hosts,
std::map<base::DictionaryValue*, std::vector<base::DictionaryValue*>>* std::map<base::DictionaryValue*, std::vector<base::DictionaryValue*>>*
children, children,
std::vector<base::DictionaryValue*>* roots, std::unordered_set<base::DictionaryValue*>* roots,
std::vector<base::DictionaryValue>* representations) { std::vector<base::DictionaryValue>* representations) {
representations->reserve(hosts.size()); representations->reserve(hosts.size());
children->clear(); children->clear();
...@@ -59,7 +60,10 @@ void CreateDictionaryForest( ...@@ -59,7 +60,10 @@ void CreateDictionaryForest(
// First move the representations and map the names to them. // First move the representations and map the names to them.
for (HostDescriptionNode& node : hosts) { for (HostDescriptionNode& node : hosts) {
representations->push_back(std::move(node.representation)); representations->push_back(std::move(node.representation));
name_to_representation[node.name] = &representations->back(); // If there are multiple nodes with the same name, subsequent insertions
// will be ignored, so only the first node with a given name will be
// referenced by |roots| and |children|.
name_to_representation.emplace(node.name, &representations->back());
} }
// Now compute children. // Now compute children.
...@@ -67,12 +71,12 @@ void CreateDictionaryForest( ...@@ -67,12 +71,12 @@ void CreateDictionaryForest(
base::DictionaryValue* node_rep = name_to_representation[node.name]; base::DictionaryValue* node_rep = name_to_representation[node.name];
base::StringPiece parent_name = node.parent_name; base::StringPiece parent_name = node.parent_name;
if (parent_name.empty()) { if (parent_name.empty()) {
roots->push_back(node_rep); roots->insert(node_rep);
continue; continue;
} }
auto node_it = name_to_representation.find(parent_name); auto node_it = name_to_representation.find(parent_name);
if (node_it == name_to_representation.end()) { if (node_it == name_to_representation.end()) {
roots->push_back(node_rep); roots->insert(node_rep);
continue; continue;
} }
(*children)[name_to_representation[parent_name]].push_back(node_rep); (*children)[name_to_representation[parent_name]].push_back(node_rep);
...@@ -89,7 +93,7 @@ base::ListValue SerializeHostDescriptions( ...@@ -89,7 +93,7 @@ base::ListValue SerializeHostDescriptions(
std::vector<base::DictionaryValue> representations; std::vector<base::DictionaryValue> representations;
std::map<base::DictionaryValue*, std::vector<base::DictionaryValue*>> std::map<base::DictionaryValue*, std::vector<base::DictionaryValue*>>
children; children;
std::vector<base::DictionaryValue*> roots; std::unordered_set<base::DictionaryValue*> roots;
CreateDictionaryForest(std::move(hosts), &children, &roots, &representations); CreateDictionaryForest(std::move(hosts), &children, &roots, &representations);
......
...@@ -11,42 +11,78 @@ ...@@ -11,42 +11,78 @@
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
using ::testing::UnorderedElementsAre;
namespace { namespace {
HostDescriptionNode GetDummyNode() { HostDescriptionNode GetNodeWithLabel(const char* name, int label) {
return {std::string(), std::string(), base::DictionaryValue()}; HostDescriptionNode node = {name, std::string(), base::DictionaryValue()};
node.representation.SetInteger("label", label);
return node;
} }
HostDescriptionNode GetNodeWithLabel(int id) { // Returns the list of children of |arg|.
base::DictionaryValue dict; const base::Value* GetChildren(const base::Value& arg) {
dict.SetInteger("label", id); const base::DictionaryValue* dict = nullptr;
return {std::string(), std::string(), std::move(dict)}; EXPECT_TRUE(arg.GetAsDictionary(&dict));
const base::Value* children = nullptr;
if (!dict->Get("children", &children))
return nullptr;
EXPECT_EQ(base::Value::Type::LIST, children->type());
return children;
} }
int GetLabel(const base::DictionaryValue* dict) { // Checks that |arg| is a description of a node with label |l|.
bool CheckLabel(const base::Value& arg, int l) {
const base::DictionaryValue* dict = nullptr;
EXPECT_TRUE(arg.GetAsDictionary(&dict));
int result = 0; int result = 0;
EXPECT_TRUE(dict->GetInteger("label", &result)); if (!dict->GetInteger("label", &result))
return result; return false;
return l == result;
}
// Matches every |arg| with label |label| and checks that it has no children.
MATCHER_P(EmptyNode, label, "") {
if (!CheckLabel(arg, label))
return false;
EXPECT_FALSE(GetChildren(arg));
return true;
} }
} // namespace } // namespace
TEST(SerializeDictionaryForestTest, Empty) { TEST(SerializeHostDescriptionTest, Empty) {
base::ListValue result = base::ListValue result =
SerializeHostDescriptions(std::vector<HostDescriptionNode>(), "123"); SerializeHostDescriptions(std::vector<HostDescriptionNode>(), "123");
EXPECT_TRUE(result.empty()); EXPECT_THAT(result.base::Value::GetList(), ::testing::IsEmpty());
} }
// Test serializing a forest of stubs (no edges). // Test serializing a forest of stubs (no edges).
TEST(SerializeDictionaryForestTest, Stubs) { TEST(SerializeHostDescriptionTest, Stubs) {
base::ListValue result = SerializeHostDescriptions( base::ListValue result = SerializeHostDescriptions(
{GetDummyNode(), GetDummyNode(), GetDummyNode()}, "123"); {GetNodeWithLabel("1", 1), GetNodeWithLabel("2", 2),
EXPECT_EQ(3u, result.GetSize()); GetNodeWithLabel("3", 3)},
for (const base::Value& value : result) { "children");
const base::DictionaryValue* dict = nullptr; EXPECT_THAT(result.base::Value::GetList(),
ASSERT_TRUE(value.GetAsDictionary(&dict)); UnorderedElementsAre(EmptyNode(1), EmptyNode(2), EmptyNode(3)));
EXPECT_FALSE(dict->HasKey("123")); }
}
// Test handling multiple nodes sharing the same name.
TEST(SerializeHostDescriptionTest, SameNames) {
std::vector<HostDescriptionNode> nodes = {
GetNodeWithLabel("A", 1), GetNodeWithLabel("A", 2),
GetNodeWithLabel("A", 3), GetNodeWithLabel("B", 4),
GetNodeWithLabel("C", 5)};
base::ListValue result =
SerializeHostDescriptions(std::move(nodes), "children");
// Only the first node called "A", and both nodes "B" and "C" should be
// returned.
EXPECT_THAT(result.base::Value::GetList(),
UnorderedElementsAre(EmptyNode(1), EmptyNode(4), EmptyNode(5)));
} }
// Test serializing a small forest, of this structure: // Test serializing a small forest, of this structure:
...@@ -54,12 +90,39 @@ TEST(SerializeDictionaryForestTest, Stubs) { ...@@ -54,12 +90,39 @@ TEST(SerializeDictionaryForestTest, Stubs) {
// 0 -- 6 // 0 -- 6
// \ 1 // \ 1
// \ 3 // \ 3
TEST(SerializeDictionaryForestTest, Forest) {
namespace {
// Matchers for non-empty nodes specifically in this test:
MATCHER(Node2, "") {
if (!CheckLabel(arg, 2))
return false;
EXPECT_THAT(GetChildren(arg)->GetList(), UnorderedElementsAre(EmptyNode(4)));
return true;
}
MATCHER(Node5, "") {
if (!CheckLabel(arg, 5))
return false;
EXPECT_THAT(GetChildren(arg)->GetList(), UnorderedElementsAre(Node2()));
return true;
}
MATCHER(Node0, "") {
if (!CheckLabel(arg, 0))
return false;
EXPECT_THAT(GetChildren(arg)->GetList(),
UnorderedElementsAre(EmptyNode(1), EmptyNode(3), EmptyNode(6)));
return true;
}
} // namespace
TEST(SerializeHostDescriptionTest, Forest) {
std::vector<HostDescriptionNode> nodes(7); std::vector<HostDescriptionNode> nodes(7);
const char* kNames[] = {"0", "1", "2", "3", "4", "5", "6"}; const char* kNames[] = {"0", "1", "2", "3", "4", "5", "6"};
for (size_t i = 0; i < 7; ++i) { for (size_t i = 0; i < 7; ++i) {
nodes[i] = GetNodeWithLabel(i); nodes[i] = GetNodeWithLabel(kNames[i], i);
nodes[i].name = kNames[i];
} }
nodes[2].parent_name = "5"; nodes[2].parent_name = "5";
nodes[4].parent_name = "2"; nodes[4].parent_name = "2";
...@@ -70,47 +133,6 @@ TEST(SerializeDictionaryForestTest, Forest) { ...@@ -70,47 +133,6 @@ TEST(SerializeDictionaryForestTest, Forest) {
base::ListValue result = base::ListValue result =
SerializeHostDescriptions(std::move(nodes), "children"); SerializeHostDescriptions(std::move(nodes), "children");
EXPECT_EQ(2u, result.GetSize()); EXPECT_THAT(result.base::Value::GetList(),
const base::Value* value = nullptr; UnorderedElementsAre(Node0(), Node5()));
const base::DictionaryValue* dict = nullptr;
const base::ListValue* list = nullptr;
// Check the result. Note that sibling nodes are in the same order in which
// they appear in |nodes|.
// Node 0
ASSERT_TRUE(result.Get(0, &value));
ASSERT_TRUE(value->GetAsDictionary(&dict));
EXPECT_EQ(0, GetLabel(dict));
ASSERT_TRUE(dict->GetList("children", &list));
EXPECT_EQ(3u, list->GetSize());
// Nodes 1, 3, 6
constexpr int kLabels[] = {1, 3, 6};
for (int i = 0; i < 3; ++i) {
ASSERT_TRUE(list->Get(i, &value));
ASSERT_TRUE(value->GetAsDictionary(&dict));
EXPECT_EQ(kLabels[i], GetLabel(dict));
EXPECT_FALSE(dict->HasKey("children"));
}
// Node 5
ASSERT_TRUE(result.Get(1, &value));
ASSERT_TRUE(value->GetAsDictionary(&dict));
EXPECT_EQ(5, GetLabel(dict));
ASSERT_TRUE(dict->GetList("children", &list));
EXPECT_EQ(1u, list->GetSize());
// Node 2
ASSERT_TRUE(list->Get(0, &value));
ASSERT_TRUE(value->GetAsDictionary(&dict));
EXPECT_EQ(2, GetLabel(dict));
ASSERT_TRUE(dict->GetList("children", &list));
EXPECT_EQ(1u, list->GetSize());
// Node 4
ASSERT_TRUE(list->Get(0, &value));
ASSERT_TRUE(value->GetAsDictionary(&dict));
EXPECT_EQ(4, GetLabel(dict));
EXPECT_FALSE(dict->HasKey("children"));
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment