kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/cxx/common/path_utils_test.cc (about) 1 /* 2 * Copyright 2018 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "kythe/cxx/common/path_utils.h" 18 19 #include <stdio.h> 20 #include <sys/stat.h> 21 #include <unistd.h> 22 23 #include <algorithm> 24 #include <cerrno> 25 #include <string> 26 #include <system_error> 27 #include <vector> 28 29 #include "absl/log/check.h" 30 #include "absl/status/status.h" 31 #include "absl/status/statusor.h" 32 #include "gmock/gmock.h" 33 #include "gtest/gtest.h" 34 #include "kythe/cxx/common/regex.h" 35 36 namespace kythe { 37 namespace { 38 using ::testing::ElementsAre; 39 using ::testing::FieldsAre; 40 using ::testing::Property; 41 using ::testing::VariantWith; 42 43 std::error_code Symlink(const std::string& target, 44 const std::string& linkpath) { 45 if (::symlink(target.c_str(), linkpath.c_str()) < 0) { 46 return std::error_code(errno, std::generic_category()); 47 } 48 return std::error_code(); 49 } 50 51 std::error_code MakeDirectory(const std::string& path) { 52 if (::mkdir(path.c_str(), 0755) < 0) { 53 return std::error_code(errno, std::generic_category()); 54 } 55 return std::error_code(); 56 } 57 58 std::error_code Remove(const std::string& path) { 59 if (::remove(path.c_str()) < 0) { 60 return std::error_code(errno, std::generic_category()); 61 } 62 return std::error_code(); 63 } 64 65 TEST(PathUtilsTest, CleanPath) { 66 EXPECT_EQ("/a/c", CleanPath("/../../a/c")); 67 EXPECT_EQ("", CleanPath("")); 68 // CleanPath should match the semantics of Go's path.Clean (except for 69 // "" => "", not "" => "."); the examples from the documentation at 70 // http://golang.org/pkg/path/#example_Clean are checked here. 71 EXPECT_EQ("a/c", CleanPath("a/c")); 72 EXPECT_EQ("a/c", CleanPath("./a/c")); 73 EXPECT_EQ("a/c", CleanPath("a//c")); 74 EXPECT_EQ("a/c", CleanPath("a/c/.")); 75 EXPECT_EQ("a/c", CleanPath("a/c/b/..")); 76 EXPECT_EQ("/a/c", CleanPath("/../a/c")); 77 EXPECT_EQ("/a/c", CleanPath("/../a/b/../././/c")); 78 EXPECT_EQ("/Users", CleanPath("/Users")); 79 // "//Users" denotes a path with the root name "//Users" 80 EXPECT_EQ("//Users", CleanPath("//Users")); 81 EXPECT_EQ("/Users", CleanPath("///Users")); 82 EXPECT_EQ("..", CleanPath("a/../../")); 83 } 84 85 TEST(PathUtilsTest, JoinPath) { 86 EXPECT_EQ("a/c", JoinPath("a", "c")); 87 EXPECT_EQ("a/c", JoinPath("a/", "c")); 88 EXPECT_EQ("a/c", JoinPath("a", "/c")); 89 } 90 91 TEST(PathUtilsTest, RelativizePath) { 92 absl::StatusOr<std::string> current_dir = GetCurrentDirectory(); 93 ASSERT_TRUE(current_dir.ok()); 94 95 std::string cwd_foo = JoinPath(*current_dir, "foo"); 96 97 EXPECT_EQ("foo", RelativizePath("foo", ".")); 98 EXPECT_EQ("foo", RelativizePath("foo", *current_dir)); 99 EXPECT_EQ("foo", RelativizePath("./foo", ".")); 100 EXPECT_EQ("foo", RelativizePath("./foo", *current_dir)); 101 EXPECT_EQ("bar", RelativizePath("foo/bar", "foo")); 102 EXPECT_EQ("bar", RelativizePath("foo/bar", cwd_foo)); 103 EXPECT_EQ("foo", RelativizePath(cwd_foo, ".")); 104 EXPECT_EQ(cwd_foo, RelativizePath(cwd_foo, "bar")); 105 106 // If all paths are absolute, then relativizing is unaffected by current_dir. 107 EXPECT_EQ("bar", RelativizePath("/foo/bar", "/foo")); 108 EXPECT_EQ("foo", RelativizePath("/foo", "/")); 109 110 // Test that we only accept proper path prefixes as parent. 111 EXPECT_EQ("/foooo/bar", RelativizePath("/foooo/bar", "/foo")); 112 } 113 114 TEST(PathUtilsTest, MakeCleanAbsolutePath) { 115 std::string current_dir = GetCurrentDirectory().value(); 116 117 EXPECT_EQ(current_dir, MakeCleanAbsolutePath(".").value()); 118 119 EXPECT_EQ("/a/b/c", MakeCleanAbsolutePath("/a/b/c").value()); 120 EXPECT_EQ("/a/b/c", MakeCleanAbsolutePath("/a/b/c/.").value()); 121 EXPECT_EQ("/a/b", MakeCleanAbsolutePath("/a/b/c/./..").value()); 122 EXPECT_EQ("/a/b", MakeCleanAbsolutePath("/a/b/c/../.").value()); 123 EXPECT_EQ("/a/b", MakeCleanAbsolutePath("/a/b/c/..").value()); 124 EXPECT_EQ("/", MakeCleanAbsolutePath("/a/../c/..").value()); 125 EXPECT_EQ("/", MakeCleanAbsolutePath("/a/b/c/../../..").value()); 126 } 127 128 TEST(PathUtilsTest, RealPath) { 129 // Since RealPath accesses the filesystem, we can't make very many 130 // guarantees about its behavior, but we would like it to return an 131 // error if a path doesn't exist. 132 absl::StatusOr<std::string> result = RealPath("/this/path/should/not/exist"); 133 EXPECT_EQ(absl::StatusCode::kNotFound, result.status().code()); 134 135 // Some systems symlink their temporary directory, so use RealPath() 136 // here to deal with that. 137 const std::string temp_dir = RealPath(testing::TempDir()).value(); 138 139 // If so, check that RealPath resolves a known link. 140 const std::string link_path = JoinPath(temp_dir, "PathUtilsTestLink"); 141 ASSERT_EQ(std::error_code(), Symlink(temp_dir, link_path)); 142 result = RealPath(link_path); 143 ASSERT_TRUE(result.ok()); 144 EXPECT_EQ(temp_dir, *result); 145 EXPECT_EQ(std::error_code(), Remove(link_path)); 146 } 147 148 TEST(PathUtilsTest, CanonicalizerPolicyParseTest) { 149 std::string error; 150 PathCanonicalizer::Policy policy; 151 ASSERT_TRUE(AbslParseFlag("clean-only", &policy, &error)); 152 EXPECT_EQ(PathCanonicalizer::Policy::kCleanOnly, policy); 153 ASSERT_TRUE(AbslParseFlag("prefer-relative", &policy, &error)); 154 EXPECT_EQ(PathCanonicalizer::Policy::kPreferRelative, policy); 155 ASSERT_TRUE(AbslParseFlag("prefer-real", &policy, &error)); 156 EXPECT_EQ(PathCanonicalizer::Policy::kPreferReal, policy); 157 ASSERT_FALSE(AbslParseFlag("some-random-junk", &policy, &error)); 158 EXPECT_NE("", error); 159 } 160 161 TEST(PathUtilsTest, CanonicalizerPathEntryParseTest) { 162 std::string error; 163 std::vector<PathCanonicalizer::PathEntry> entries; 164 ASSERT_TRUE( 165 AbslParseFlag("a@clean-only b@prefer-relative", &entries, &error)); 166 EXPECT_THAT( 167 entries, 168 ElementsAre(FieldsAre(VariantWith<Regex>(Property(&Regex::pattern, "a")), 169 PathCanonicalizer::Policy::kCleanOnly), 170 FieldsAre(VariantWith<Regex>(Property(&Regex::pattern, "b")), 171 PathCanonicalizer::Policy::kPreferRelative))); 172 EXPECT_EQ(AbslUnparseFlag(entries), "a@clean-only b@prefer-relative"); 173 } 174 175 TEST(PathUtilsTest, CanonicalizerPathEntryParseFailuresTest) { 176 std::string error; 177 std::vector<PathCanonicalizer::PathEntry> entries; 178 ASSERT_FALSE(AbslParseFlag("clean-only b@prefer-relative", &entries, &error)); 179 EXPECT_THAT(error, "missing @ delimiter between path and policy"); 180 } 181 182 class CanonicalizerTest : public ::testing::Test { 183 public: 184 void SetUp() override { 185 const testing::TestInfo* test_info = 186 testing::UnitTest::GetInstance()->current_test_info(); 187 std::string root = JoinPath(JoinPath(RealPath(testing::TempDir()).value(), 188 test_info->test_suite_name()), 189 test_info->name()); 190 ASSERT_EQ(std::error_code(), MakeDirectory(root)); 191 filesystem_.push_back(root); 192 } 193 194 void TearDown() override { 195 std::sort(filesystem_.rbegin(), filesystem_.rend()); 196 for (const std::string& path : filesystem_) { 197 EXPECT_EQ(std::error_code(), Remove(path)); 198 } 199 } 200 201 void AddDirectory(const std::string& name) { 202 std::string path = JoinPath(root(), name); 203 ASSERT_EQ(std::error_code(), MakeDirectory(path)); 204 filesystem_.push_back(path); 205 } 206 207 void AddSymlink(const std::string& target, const std::string& name) { 208 std::string path = JoinPath(root(), name); 209 ASSERT_EQ(std::error_code(), Symlink(target, path)); 210 filesystem_.push_back(path); 211 } 212 213 const std::string& root() { return filesystem_.front(); } 214 215 static void SetUpTestSuite() { 216 CHECK_EQ(std::error_code(), 217 MakeDirectory(JoinPath(testing::TempDir(), "CanonicalizerTest"))); 218 } 219 220 static void TearDownTestSuite() { 221 CHECK_EQ(std::error_code(), 222 Remove(JoinPath(testing::TempDir(), "CanonicalizerTest"))); 223 } 224 225 private: 226 std::vector<std::string> filesystem_; 227 }; 228 229 TEST_F(CanonicalizerTest, CanonicalizerCleanPathOnly) { 230 AddDirectory("concrete"); 231 AddDirectory("concrete/subdir"); 232 AddDirectory("concrete/file"); 233 AddSymlink("concrete", "link"); 234 235 PathCanonicalizer canonicalizer = 236 PathCanonicalizer::Create(root(), PathCanonicalizer::Policy::kCleanOnly) 237 .value(); 238 EXPECT_EQ("link/file", 239 canonicalizer.Relativize(JoinPath(root(), "link/subdir/../file")) 240 .value_or("")); 241 EXPECT_EQ("link/file", 242 canonicalizer.Relativize(JoinPath(root(), "./link/subdir/../file")) 243 .value_or("")); 244 } 245 246 TEST_F(CanonicalizerTest, CanonicalizerPreferRelative) { 247 AddDirectory("base"); 248 AddDirectory("elsewhere"); 249 AddDirectory("elsewhere/subdir"); 250 AddDirectory("elsewhere/file"); 251 AddSymlink("../elsewhere", "base/link"); 252 253 const std::string base = JoinPath(root(), "base"); 254 PathCanonicalizer canonicalizer = 255 PathCanonicalizer::Create(base, 256 PathCanonicalizer::Policy::kPreferRelative) 257 .value(); 258 // link/file points somewhere outside of "base", so prefer the relative path, 259 // even if it's an unresolved symlink. 260 EXPECT_EQ("link/file", 261 canonicalizer.Relativize(JoinPath(base, "link/subdir/../file")) 262 .value_or("")); 263 } 264 265 TEST_F(CanonicalizerTest, CanonicalizerPreferReal) { 266 AddDirectory("base"); 267 AddDirectory("elsewhere"); 268 AddDirectory("elsewhere/subdir"); 269 AddDirectory("elsewhere/file"); 270 AddSymlink("../elsewhere", "base/link"); 271 272 const std::string base = JoinPath(root(), "base"); 273 PathCanonicalizer canonicalizer = 274 PathCanonicalizer::Create(base, PathCanonicalizer::Policy::kPreferReal) 275 .value(); 276 // Use the resolved path, even if it points outside of the base. 277 EXPECT_EQ(JoinPath(root(), "elsewhere/file"), 278 canonicalizer.Relativize(JoinPath(base, "link/subdir/../file")) 279 .value_or("")); 280 // Unless the link is bad, then use the cleaned path. 281 EXPECT_EQ("bad/file", 282 canonicalizer.Relativize(JoinPath(base, "bad/subdir/../file")) 283 .value_or("")); 284 } 285 286 TEST_F(CanonicalizerTest, CanonicalizerRealOverrideClean) { 287 AddDirectory("concrete"); 288 AddDirectory("concrete/subdir"); 289 AddDirectory("concrete/file"); 290 AddSymlink("concrete", "link"); 291 292 AddDirectory("base"); 293 AddDirectory("elsewhere"); 294 AddDirectory("elsewhere/subdir"); 295 AddDirectory("elsewhere/file"); 296 AddSymlink("../elsewhere", "base/link"); 297 298 const std::string base = JoinPath(root(), "base"); 299 PathCanonicalizer canonicalizer = 300 PathCanonicalizer::Create( 301 root(), PathCanonicalizer::Policy::kCleanOnly, 302 {{.path = ".*/base/link", 303 .policy = PathCanonicalizer::Policy::kPreferReal}}) 304 .value(); 305 306 // Use the default clean-only policy for links/paths which don't match. 307 EXPECT_EQ("link/file", 308 canonicalizer.Relativize(JoinPath(root(), "link/subdir/../file")) 309 .value_or("")); 310 EXPECT_EQ("link/file", 311 canonicalizer.Relativize(JoinPath(root(), "./link/subdir/../file")) 312 .value_or("")); 313 // And the prefer-real policy for those which do. 314 EXPECT_EQ("elsewhere/file", 315 canonicalizer.Relativize(JoinPath(base, "link/subdir/../file")) 316 .value_or("")); 317 EXPECT_EQ("base/bad/file", 318 canonicalizer.Relativize(JoinPath(base, "bad/subdir/../file")) 319 .value_or("")); 320 } 321 322 } // namespace 323 } // namespace kythe