sapling/eden/fs/utils/test/PathMapTest.cpp
Xavier Deguillard a7f0370727 pathmap: fix case sensitivity insertion ordering
Summary:
When the PathMap is case insensitive, its ordering must be the same as if only
lower cased elements were inserted into it. Unfortunately, due to the use of a
case sensitive std::lower_bound, this property is violated. To fix this, and
simplify the code a bit, the comparator passed to std::lower_bound must be case
insensitive so searching and inserting are looking in the same location.

To achieve this, a new generic and centralized way of comparing path is
introduced. Since A couple of places are interested in comparing paths, and
doing so with cross platform and case insensitively in mind can be tricky.
Let's add this code to the PathFuncs code directly so it's available everywhere
for every paths types.

Reviewed By: chadaustin

Differential Revision: D36119091

fbshipit-source-id: 3f7f6349c00d14bb9cb424d33328024f2962d7c8
2022-05-19 18:57:33 -07:00

264 lines
7.8 KiB
C++

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
#include "eden/fs/utils/PathMap.h"
#include <folly/portability/GTest.h>
#include <folly/portability/Unistd.h>
using namespace facebook::eden;
using namespace facebook::eden::path_literals;
TEST(PathMap, caseSensitive) {
// Explicitly a case sensitive map, regardless of the host OS
PathMap<bool> map(CaseSensitivity::Sensitive);
map.insert(std::make_pair(PathComponent("foo"), true));
EXPECT_TRUE(map.at("foo"_pc));
EXPECT_EQ(map.find("Foo"_pc), map.end());
EXPECT_TRUE(map.insert(std::make_pair(PathComponent("FOO"), false)).second);
EXPECT_EQ(map.size(), 2);
EXPECT_TRUE(map.at("foo"_pc));
EXPECT_FALSE(map.at("FOO"_pc));
EXPECT_EQ(map.erase("FOO"_pc), 1);
EXPECT_EQ(map.size(), 1);
map["FOO"_pc] = true;
map["Foo"_pc] = false;
EXPECT_EQ(map.size(), 3);
}
TEST(PathMap, caseSensitiveCopyMove) {
PathMap<bool> map(CaseSensitivity::Sensitive);
map.insert(std::make_pair(PathComponent("foo"), true));
PathMap<bool> copied(map);
EXPECT_TRUE(copied.at("foo"_pc));
EXPECT_EQ(copied.find("Foo"_pc), copied.end());
PathMap<bool> copy_assign(CaseSensitivity::Insensitive);
copy_assign = map;
EXPECT_TRUE(copy_assign.at("foo"_pc));
EXPECT_EQ(copy_assign.find("Foo"_pc), copy_assign.end());
PathMap<bool> moved(std::move(map));
EXPECT_TRUE(moved.at("foo"_pc));
EXPECT_EQ(moved.find("Foo"_pc), moved.end());
PathMap<bool> move_assign(CaseSensitivity::Insensitive);
move_assign = std::move(moved);
EXPECT_TRUE(move_assign.at("foo"_pc));
EXPECT_EQ(move_assign.find("Foo"_pc), move_assign.end());
}
TEST(PathMap, caseInSensitive) {
// Explicitly a case IN-sensitive map, regardless of the host OS
PathMap<bool> map(CaseSensitivity::Insensitive);
map.insert(std::make_pair(PathComponent("foo"), true));
EXPECT_TRUE(map.at("foo"_pc));
EXPECT_TRUE(map.at("Foo"_pc));
EXPECT_FALSE(map.insert(std::make_pair(PathComponent("FOO"), false)).second);
EXPECT_FALSE(map.emplace(PathComponent("FOO"), false).second);
EXPECT_EQ(map.size(), 1);
EXPECT_TRUE(map.at("foo"_pc));
EXPECT_TRUE(map.at("FOO"_pc));
EXPECT_EQ(map.erase("FOO"_pc), 1);
EXPECT_EQ(map.size(), 0);
// Case insensitive referencing
map["FOO"_pc] = true;
map["Foo"_pc] = false;
// Only one FOO entry
EXPECT_EQ(map.size(), 1);
// It shows as false
EXPECT_EQ(map["FOO"_pc], false);
// The assignment above didn't change the case of the key!
EXPECT_EQ(map.begin()->first, "FOO"_pc);
}
TEST(PathMap, caseInSensitiveOrdering) {
PathMap<bool> map1(CaseSensitivity::Insensitive);
map1.insert(std::make_pair(PathComponent("e"), true));
map1.insert(std::make_pair(PathComponent("g"), true));
map1.insert(std::make_pair(PathComponent("f"), true));
PathMap<bool> map2(CaseSensitivity::Insensitive);
map2.insert(std::make_pair(PathComponent("e"), true));
map2.insert(std::make_pair(PathComponent("g"), true));
map2.insert(std::make_pair(PathComponent("F"), true));
EXPECT_EQ(map1.size(), map2.size());
for (auto it1 = map1.cbegin(), it2 = map2.cbegin(); it1 != map1.cend();
++it1, ++it2) {
EXPECT_STRCASEEQ(
it1->first.stringPiece().str().c_str(),
it2->first.stringPiece().str().c_str());
}
}
TEST(PathMap, caseInSensitiveCopyMove) {
PathMap<bool> map(CaseSensitivity::Insensitive);
map.insert(std::make_pair(PathComponent("foo"), true));
PathMap<bool> copied(map);
EXPECT_TRUE(copied.at("foo"_pc));
EXPECT_TRUE(copied.at("Foo"_pc));
PathMap<bool> copy_assign(CaseSensitivity::Sensitive);
copy_assign = map;
EXPECT_TRUE(copy_assign.at("foo"_pc));
EXPECT_TRUE(copy_assign.at("Foo"_pc));
PathMap<bool> moved(std::move(map));
EXPECT_TRUE(moved.at("foo"_pc));
EXPECT_TRUE(moved.at("Foo"_pc));
PathMap<bool> move_assign(CaseSensitivity::Sensitive);
move_assign = std::move(moved);
EXPECT_TRUE(move_assign.at("foo"_pc));
EXPECT_TRUE(move_assign.at("Foo"_pc));
}
TEST(PathMap, insert) {
PathMap<bool> map(kPathMapDefaultCaseSensitive);
EXPECT_TRUE(map.empty());
map.insert(std::make_pair(PathComponent("foo"), true));
EXPECT_EQ(1, map.size());
EXPECT_NE(map.end(), map.find("foo"_pc));
EXPECT_TRUE(map.at("foo"_pc));
EXPECT_TRUE(map["foo"_pc]);
// operator[] creates an entry for missing key
map["bar"_pc] = false;
EXPECT_EQ(2, map.size());
EXPECT_NE(map.end(), map.find("bar"_pc));
EXPECT_FALSE(map.at("bar"_pc));
EXPECT_FALSE(map["bar"_pc]);
// at() throws for missing key
EXPECT_THROW(map.at("notpresent"_pc), std::out_of_range);
// Test the const version of find(), at() and operator[]
const PathMap<bool>& cmap = map;
EXPECT_NE(cmap.cend(), cmap.find("bar"_pc));
EXPECT_FALSE(cmap.at("bar"_pc));
EXPECT_FALSE(cmap["bar"_pc]);
// const operator[] throws for missing key
EXPECT_THROW(cmap["notpresent"_pc], std::out_of_range);
}
TEST(PathMap, iteration_and_erase) {
PathMap<int> map(
{
std::make_pair(PathComponent("foo"), 1),
std::make_pair(PathComponent("bar"), 2),
std::make_pair(PathComponent("baz"), 3),
},
kPathMapDefaultCaseSensitive);
std::vector<PathComponentPiece> keys;
for (const auto& it : map) {
keys.emplace_back(it.first);
}
// Keys have deterministic order
std::vector<PathComponentPiece> expect{
"bar"_pc,
"baz"_pc,
"foo"_pc,
};
EXPECT_EQ(expect, keys);
auto iter = map.find("baz"_pc);
EXPECT_EQ(3, iter->second);
iter = map.erase(iter);
EXPECT_EQ(2, map.size()) << "deleted 1";
EXPECT_EQ(PathComponent("foo"), iter->first) << "iter advanced to next item";
EXPECT_EQ(1, iter->second);
}
TEST(PathMap, copy) {
PathMap<int> map(
{
std::make_pair(PathComponent("foo"), 1),
std::make_pair(PathComponent("bar"), 2),
std::make_pair(PathComponent("baz"), 3),
},
kPathMapDefaultCaseSensitive);
PathMap<int> other = map;
EXPECT_EQ(3, other.size());
EXPECT_EQ(map, other);
}
TEST(PathMap, move) {
PathMap<int> map(
{
std::make_pair(PathComponent("foo"), 1),
std::make_pair(PathComponent("bar"), 2),
std::make_pair(PathComponent("baz"), 3),
},
kPathMapDefaultCaseSensitive);
PathMap<int> other = std::move(map);
EXPECT_EQ(3, other.size());
EXPECT_EQ(0, map.size());
}
struct EmplaceTest {
static int counter;
bool dummy;
// secondArg is present to validate that emplace is correctly
// forwarding multiple arguments.
EmplaceTest(bool value, int secondArg) : dummy(value) {
++counter;
++secondArg; // Suppress lint about secondArg being unused
}
};
int EmplaceTest::counter = 0;
TEST(PathMap, emplace) {
PathMap<EmplaceTest> map(kPathMapDefaultCaseSensitive);
auto result = map.emplace("one"_pc, true, 42);
EXPECT_EQ(1, EmplaceTest::counter)
<< "construct a single EmplaceTest instance";
EXPECT_NE(map.end(), result.first);
EXPECT_TRUE(result.second) << "inserted";
EXPECT_TRUE(map.at("one"_pc).dummy);
// Second emplace with the same key has no effect
result = map.emplace("one"_pc, false, 42);
EXPECT_EQ(1, EmplaceTest::counter)
<< "did not construct another EmplaceTest instance";
EXPECT_FALSE(result.second) << "did not insert";
EXPECT_TRUE(map.at("one"_pc).dummy) << "didn't change value to false";
}
TEST(PathMap, swap) {
PathMap<std::string> b(kPathMapDefaultCaseSensitive),
a({std::make_pair(PathComponent("foo"), "foo")},
kPathMapDefaultCaseSensitive);
b.swap(a);
EXPECT_EQ(0, a.size()) << "a now has 0 elements";
EXPECT_EQ(1, b.size()) << "b now has 1 element";
EXPECT_EQ("foo", b.at("foo"_pc));
a = std::move(b);
EXPECT_EQ(1, a.size()) << "a now has 1 element";
EXPECT_EQ(0, b.size()) << "b now has 0 elements";
EXPECT_EQ("foo", a.at("foo"_pc));
}