/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2020 Blender Foundation. All rights reserved. */ #include "BKE_asset_catalog_path.hh" #include "BLI_set.hh" #include "BLI_vector.hh" #include #include #include "testing/testing.h" namespace blender::bke::tests { TEST(AssetCatalogPathTest, construction) { AssetCatalogPath default_constructed; /* Use `.str()` to use `std:string`'s comparison operators here, not our own (which are tested * later). */ EXPECT_EQ(default_constructed.str(), ""); /* C++ considers this construction special, it doesn't call the default constructor but does * recursive, member-wise value initialization. See https://stackoverflow.com/a/4982720. */ AssetCatalogPath value_initialized = AssetCatalogPath(); EXPECT_EQ(value_initialized.str(), ""); AssetCatalogPath from_char_literal("the/path"); const std::string str_const = "the/path"; AssetCatalogPath from_string_constant(str_const); std::string str_variable = "the/path"; AssetCatalogPath from_string_variable(str_variable); std::string long_string = "this is a long/string/with/a/path in the middle"; StringRef long_string_ref(long_string); StringRef middle_bit = long_string_ref.substr(10, 23); AssetCatalogPath from_string_ref(middle_bit); EXPECT_EQ(from_string_ref, "long/string/with/a/path"); } TEST(AssetCatalogPathTest, length) { const AssetCatalogPath one("1"); EXPECT_EQ(1, one.length()); const AssetCatalogPath empty(""); EXPECT_EQ(0, empty.length()); const AssetCatalogPath utf8("some/родитель"); EXPECT_EQ(21, utf8.length()) << "13 characters should be 21 bytes."; } TEST(AssetCatalogPathTest, name) { EXPECT_EQ(StringRefNull(""), AssetCatalogPath("").name()); EXPECT_EQ(StringRefNull("word"), AssetCatalogPath("word").name()); EXPECT_EQ(StringRefNull("Пермь"), AssetCatalogPath("дорога/в/Пермь").name()); EXPECT_EQ(StringRefNull("windows\\paths"), AssetCatalogPath("these/are/not/windows\\paths").name()); } TEST(AssetCatalogPathTest, comparison_operators) { const AssetCatalogPath empty(""); const AssetCatalogPath the_path("the/path"); const AssetCatalogPath the_path_child("the/path/child"); const AssetCatalogPath unrelated_path("unrelated/path"); const AssetCatalogPath other_instance_same_path("the/path"); EXPECT_LT(empty, the_path); EXPECT_LT(the_path, the_path_child); EXPECT_LT(the_path, unrelated_path); EXPECT_EQ(empty, empty) << "Identical empty instances should compare equal."; EXPECT_EQ(empty, "") << "Comparison to empty string should be possible."; EXPECT_EQ(the_path, the_path) << "Identical non-empty instances should compare equal."; EXPECT_EQ(the_path, "the/path") << "Comparison to string should be possible."; EXPECT_EQ(the_path, other_instance_same_path) << "Different instances with equal path should compare equal."; EXPECT_NE(the_path, the_path_child); EXPECT_NE(the_path, unrelated_path); EXPECT_NE(the_path, empty); EXPECT_FALSE(empty); EXPECT_TRUE(the_path); } TEST(AssetCatalogPathTest, move_semantics) { AssetCatalogPath source_path("source/path"); EXPECT_TRUE(source_path); AssetCatalogPath dest_path = std::move(source_path); EXPECT_FALSE(source_path); /* NOLINT: bugprone-use-after-move */ EXPECT_TRUE(dest_path); } TEST(AssetCatalogPathTest, concatenation) { AssetCatalogPath some_parent("some/родитель"); AssetCatalogPath child = some_parent / "ребенок"; EXPECT_EQ(some_parent, "some/родитель") << "Appending a child path should not modify the parent."; EXPECT_EQ(child, "some/родитель/ребенок"); AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук"; EXPECT_EQ(appended_compound_path, "some/родитель/ребенок/внук"); AssetCatalogPath empty(""); AssetCatalogPath child_of_the_void = empty / "child"; EXPECT_EQ(child_of_the_void, "child") << "Appending to an empty path should not create an initial slash."; AssetCatalogPath parent_of_the_void = some_parent / empty; EXPECT_EQ(parent_of_the_void, "some/родитель") << "Prepending to an empty path should not create a trailing slash."; std::string subpath = "child"; AssetCatalogPath concatenated_with_string = some_parent / subpath; EXPECT_EQ(concatenated_with_string, "some/родитель/child"); } TEST(AssetCatalogPathTest, hashable) { AssetCatalogPath path("heyyyyy"); std::set path_std_set; path_std_set.insert(path); blender::Set path_blender_set; path_blender_set.add(path); } TEST(AssetCatalogPathTest, stream_operator) { AssetCatalogPath path("путь/в/Пермь"); std::stringstream sstream; sstream << path; EXPECT_EQ("путь/в/Пермь", sstream.str()); } TEST(AssetCatalogPathTest, is_contained_in) { const AssetCatalogPath catpath("simple/path/child"); EXPECT_FALSE(catpath.is_contained_in("unrelated")); EXPECT_FALSE(catpath.is_contained_in("sim")); EXPECT_FALSE(catpath.is_contained_in("simple/pathx")); EXPECT_FALSE(catpath.is_contained_in("simple/path/c")); EXPECT_FALSE(catpath.is_contained_in("simple/path/child/grandchild")); EXPECT_FALSE(catpath.is_contained_in("simple/path/")) << "Non-normalized paths are not expected to work."; EXPECT_TRUE(catpath.is_contained_in("")); EXPECT_TRUE(catpath.is_contained_in("simple")); EXPECT_TRUE(catpath.is_contained_in("simple/path")); /* Test with some UTF8 non-ASCII characters. */ AssetCatalogPath some_parent("some/родитель"); AssetCatalogPath child = some_parent / "ребенок"; EXPECT_TRUE(child.is_contained_in(some_parent)); EXPECT_TRUE(child.is_contained_in("some")); AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук"; EXPECT_TRUE(appended_compound_path.is_contained_in(some_parent)); EXPECT_TRUE(appended_compound_path.is_contained_in(child)); /* Test "going up" directory-style. */ AssetCatalogPath child_with_dotdot = some_parent / "../../other/hierarchy/part"; EXPECT_TRUE(child_with_dotdot.is_contained_in(some_parent)) << "dotdot path components should have no meaning"; } TEST(AssetCatalogPathTest, cleanup) { { AssetCatalogPath ugly_path("/ some / родитель / "); AssetCatalogPath clean_path = ugly_path.cleanup(); EXPECT_EQ(AssetCatalogPath("/ some / родитель / "), ugly_path) << "cleanup should not modify the path instance itself"; EXPECT_EQ(AssetCatalogPath("some/родитель"), clean_path); } { AssetCatalogPath double_slashed("some//родитель"); EXPECT_EQ(AssetCatalogPath("some/родитель"), double_slashed.cleanup()); } { AssetCatalogPath with_colons("some/key:subkey=value/path"); EXPECT_EQ(AssetCatalogPath("some/key-subkey=value/path"), with_colons.cleanup()); } { const AssetCatalogPath with_backslashes("windows\\for\\life"); EXPECT_EQ(AssetCatalogPath("windows/for/life"), with_backslashes.cleanup()); } { const AssetCatalogPath with_mixed("windows\\for/life"); EXPECT_EQ(AssetCatalogPath("windows/for/life"), with_mixed.cleanup()); } { const AssetCatalogPath with_punctuation("is!/this?/¿valid?"); EXPECT_EQ(AssetCatalogPath("is!/this?/¿valid?"), with_punctuation.cleanup()); } } TEST(AssetCatalogPathTest, iterate_components) { AssetCatalogPath path("путь/в/Пермь"); Vector> seen_components; path.iterate_components([&seen_components](StringRef component_name, bool is_last_component) { std::pair parameter_pair = std::make_pair( component_name, bool(is_last_component)); seen_components.append(parameter_pair); }); ASSERT_EQ(3, seen_components.size()); EXPECT_EQ("путь", seen_components[0].first); EXPECT_EQ("в", seen_components[1].first); EXPECT_EQ("Пермь", seen_components[2].first); EXPECT_FALSE(seen_components[0].second); EXPECT_FALSE(seen_components[1].second); EXPECT_TRUE(seen_components[2].second); } TEST(AssetCatalogPathTest, rebase) { AssetCatalogPath path("some/path/to/some/catalog"); EXPECT_EQ(path.rebase("some/path", "new/base"), "new/base/to/some/catalog"); EXPECT_EQ(path.rebase("", "new/base"), "new/base/some/path/to/some/catalog"); EXPECT_EQ(path.rebase("some/path/to/some/catalog", "some/path/to/some/catalog"), "some/path/to/some/catalog") << "Rebasing to itself should not change the path."; EXPECT_EQ(path.rebase("path/to", "new/base"), "") << "Non-matching base path should return empty string to indicate 'NO'."; /* Empty strings should be handled without crashing or other nasty side-effects. */ AssetCatalogPath empty(""); EXPECT_EQ(empty.rebase("path/to", "new/base"), ""); EXPECT_EQ(empty.rebase("", "new/base"), "new/base"); EXPECT_EQ(empty.rebase("", ""), ""); } TEST(AssetCatalogPathTest, parent) { const AssetCatalogPath ascii_path("path/with/missing/parents"); EXPECT_EQ(ascii_path.parent(), "path/with/missing"); const AssetCatalogPath path("путь/в/Пермь/долог/и/далек"); EXPECT_EQ(path.parent(), "путь/в/Пермь/долог/и"); EXPECT_EQ(path.parent().parent(), "путь/в/Пермь/долог"); EXPECT_EQ(path.parent().parent().parent(), "путь/в/Пермь"); const AssetCatalogPath one_level("one"); EXPECT_EQ(one_level.parent(), ""); const AssetCatalogPath empty(""); EXPECT_EQ(empty.parent(), ""); } } // namespace blender::bke::tests