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