diff --git a/xff/config/BUILD.bazel b/xff/config/BUILD.bazel index 8d3058e..ddac57f 100644 --- a/xff/config/BUILD.bazel +++ b/xff/config/BUILD.bazel @@ -72,6 +72,7 @@ cc_library( deps = [ ":ini_cc", ":xffrc_cc", + "@abseil-cpp//absl/algorithm:container", ], ) diff --git a/xff/config/config.cc b/xff/config/config.cc index 404b852..7693065 100644 --- a/xff/config/config.cc +++ b/xff/config/config.cc @@ -16,29 +16,20 @@ #include "xff/config/config.h" #include -#include #include +#include "absl/algorithm/container.h" #include "xff/config/ini.h" #include "xff/config/xffrc.h" namespace xff::config { namespace { -bool Contains(const std::vector& haystack, std::string_view needle) { - for (const std::string& item : haystack) { - if (item == needle) { - return true; - } - } - return false; -} - // An .xffrc line applies under the active --config selectors when its base is // "common"/empty or names an active config, AND its config is empty or names one. bool LineApplies(const RcLine& line, const std::vector& configs) { - const bool base_ok = line.base.empty() || line.base == "common" || Contains(configs, line.base); - const bool config_ok = line.config.empty() || Contains(configs, line.config); + const bool base_ok = line.base.empty() || line.base == "common" || absl::c_contains(configs, line.base); + const bool config_ok = line.config.empty() || absl::c_contains(configs, line.config); return base_ok && config_ok; } diff --git a/xff/config/config_test.cc b/xff/config/config_test.cc index c33682c..56a8383 100644 --- a/xff/config/config_test.cc +++ b/xff/config/config_test.cc @@ -15,6 +15,8 @@ #include "xff/config/config.h" +#include + #include "gmock/gmock.h" #include "gtest/gtest.h" #include "xff/config/xffrc.h" @@ -22,10 +24,19 @@ namespace xff::config { namespace { +using ::testing::AllOf; +using ::testing::ElementsAre; +using ::testing::Field; using ::testing::IsEmpty; struct ConfigTest : ::testing::Test {}; +// Matches a ResolvedFlag by both its flag text and its provenance, so an +// ElementsAre(...) assertion folds size, order, flag, and Source into one check. +testing::Matcher FlagIs(const std::string& flag, Source source) { + return AllOf(Field(&ResolvedFlag::flag, flag), Field(&ResolvedFlag::source, source)); +} + TEST_F(ConfigTest, NoConfigYieldsEmpty) { ConfigInputs in; in.system.defaults = {"--color=auto"}; @@ -37,21 +48,14 @@ TEST_F(ConfigTest, NoConfigYieldsEmpty) { TEST_F(ConfigTest, SystemDefaultsAreLowestPrecedence) { ConfigInputs in; in.system.defaults = {"--color=auto", "--threads=4"}; - const auto resolved = ResolveConfig(in); - ASSERT_EQ(resolved.size(), 2U); - EXPECT_EQ(resolved[0].flag, "--color=auto"); - EXPECT_EQ(resolved[0].source, Source::kSystem); - EXPECT_EQ(resolved[1].flag, "--threads=4"); + EXPECT_THAT( + ResolveConfig(in), ElementsAre(FlagIs("--color=auto", Source::kSystem), FlagIs("--threads=4", Source::kSystem))); } TEST_F(ConfigTest, CommonAndBareLinesAlwaysApply) { ConfigInputs in; in.user = ParseXffrc("common: --color=never\n--sort"); - const auto resolved = ResolveConfig(in); - ASSERT_EQ(resolved.size(), 2U); - EXPECT_EQ(resolved[0].flag, "--color=never"); - EXPECT_EQ(resolved[0].source, Source::kUser); - EXPECT_EQ(resolved[1].flag, "--sort"); + EXPECT_THAT(ResolveConfig(in), ElementsAre(FlagIs("--color=never", Source::kUser), FlagIs("--sort", Source::kUser))); } TEST_F(ConfigTest, BaseSelectorGatedByActiveConfig) { @@ -59,9 +63,7 @@ TEST_F(ConfigTest, BaseSelectorGatedByActiveConfig) { in.user = ParseXffrc("xff: --feature=long-paths\nfind: --warn"); EXPECT_THAT(ResolveConfig(in), IsEmpty()); // no active --config -> neither base applies in.configs = {"xff"}; - const auto resolved = ResolveConfig(in); - ASSERT_EQ(resolved.size(), 1U); - EXPECT_EQ(resolved[0].flag, "--feature=long-paths"); + EXPECT_THAT(ResolveConfig(in), ElementsAre(FlagIs("--feature=long-paths", Source::kUser))); } TEST_F(ConfigTest, ConfigSelectorGatedByNamedConfig) { @@ -70,9 +72,7 @@ TEST_F(ConfigTest, ConfigSelectorGatedByNamedConfig) { in.configs = {"xff"}; // style active, but not the :debug named config EXPECT_THAT(ResolveConfig(in), IsEmpty()); in.configs = {"xff", "debug"}; - const auto resolved = ResolveConfig(in); - ASSERT_EQ(resolved.size(), 1U); - EXPECT_EQ(resolved[0].flag, "--threads=1"); + EXPECT_THAT(ResolveConfig(in), ElementsAre(FlagIs("--threads=1", Source::kUser))); } TEST_F(ConfigTest, LayerPrecedenceSystemThenUserThenProject) { @@ -80,11 +80,10 @@ TEST_F(ConfigTest, LayerPrecedenceSystemThenUserThenProject) { in.system.defaults = {"--color=auto"}; in.user = ParseXffrc("common: --sort"); in.project = ParseXffrc("common: --color=never"); - const auto resolved = ResolveConfig(in); - ASSERT_EQ(resolved.size(), 3U); - EXPECT_EQ(resolved[0].source, Source::kSystem); // --color=auto - EXPECT_EQ(resolved[1].source, Source::kUser); // --sort - EXPECT_EQ(resolved[2].source, Source::kProject); // --color=never (later wins when applied) + EXPECT_THAT( + ResolveConfig(in), ElementsAre( + FlagIs("--color=auto", Source::kSystem), FlagIs("--sort", Source::kUser), + FlagIs("--color=never", Source::kProject))); } } // namespace diff --git a/xff/config/ini_test.cc b/xff/config/ini_test.cc index e885053..b8e4719 100644 --- a/xff/config/ini_test.cc +++ b/xff/config/ini_test.cc @@ -15,17 +15,32 @@ #include "xff/config/ini.h" +#include +#include + #include "gmock/gmock.h" #include "gtest/gtest.h" namespace xff::config { namespace { +using ::testing::AllOf; using ::testing::ElementsAre; +using ::testing::Field; using ::testing::IsEmpty; +using ::testing::Matcher; struct IniTest : ::testing::Test {}; +// Matches a PolicyRule by its layer, allow/deny flag, and a matcher over its +// tokens, so one ElementsAre(...) covers rule count, order, and every field. +Matcher PolicyRuleIs( + const std::string& layer, + bool allow, + const Matcher>& tokens) { + return AllOf(Field(&PolicyRule::layer, layer), Field(&PolicyRule::allow, allow), Field(&PolicyRule::tokens, tokens)); +} + TEST_F(IniTest, DefaultsRenderToCliTokens) { const SystemConfig c = ParseIni("[defaults]\n--color = auto\n--warn\n"); EXPECT_THAT(c.defaults, ElementsAre("--color=auto", "--warn")); @@ -38,24 +53,18 @@ TEST_F(IniTest, PolicyAllowDenyAndClassTokens) { "project.allow = --sort, --color, --format\n" "project.deny = --threads\n" "user.allow = @sensitive\n"); - ASSERT_EQ(c.policy.size(), 3U); - EXPECT_EQ(c.policy[0].layer, "project"); - EXPECT_TRUE(c.policy[0].allow); - EXPECT_THAT(c.policy[0].tokens, ElementsAre("--sort", "--color", "--format")); - EXPECT_EQ(c.policy[1].layer, "project"); - EXPECT_FALSE(c.policy[1].allow); - EXPECT_THAT(c.policy[1].tokens, ElementsAre("--threads")); - EXPECT_EQ(c.policy[2].layer, "user"); - EXPECT_TRUE(c.policy[2].allow); - EXPECT_THAT(c.policy[2].tokens, ElementsAre("@sensitive")); + EXPECT_THAT( + c.policy, ElementsAre( + PolicyRuleIs("project", true, ElementsAre("--sort", "--color", "--format")), + PolicyRuleIs("project", false, ElementsAre("--threads")), + PolicyRuleIs("user", true, ElementsAre("@sensitive")))); } TEST_F(IniTest, CommentsBlanksAndBothSections) { const SystemConfig c = ParseIni("; a comment\n# another\n[defaults]\n\n--color = never\n[policy]\nproject.allow = --sort\n"); EXPECT_THAT(c.defaults, ElementsAre("--color=never")); - ASSERT_EQ(c.policy.size(), 1U); - EXPECT_THAT(c.policy[0].tokens, ElementsAre("--sort")); + EXPECT_THAT(c.policy, ElementsAre(PolicyRuleIs("project", true, ElementsAre("--sort")))); } TEST_F(IniTest, MalformedPolicyLinesIgnored) { diff --git a/xff/config/xffrc_test.cc b/xff/config/xffrc_test.cc index fd8b261..efd1094 100644 --- a/xff/config/xffrc_test.cc +++ b/xff/config/xffrc_test.cc @@ -15,68 +15,68 @@ #include "xff/config/xffrc.h" +#include +#include + #include "gmock/gmock.h" #include "gtest/gtest.h" namespace xff::config { namespace { +using ::testing::AllOf; using ::testing::ElementsAre; +using ::testing::Field; using ::testing::IsEmpty; +using ::testing::Matcher; struct XffrcTest : ::testing::Test {}; +// Matches an RcLine by its base/config selectors and a matcher over its flags, +// so one ElementsAre(...) covers line count, order, selectors, and flags at once. +Matcher RcLineIs( + const std::string& base, + const std::string& config, + const Matcher>& flags) { + return AllOf(Field(&RcLine::base, base), Field(&RcLine::config, config), Field(&RcLine::flags, flags)); +} + TEST_F(XffrcTest, SkipsBlanksAndComments) { EXPECT_THAT(ParseXffrc("\n \n# a comment\n # indented comment\n"), IsEmpty()); } TEST_F(XffrcTest, BareFlagsAreCommonAnyConfig) { - const auto lines = ParseXffrc("--color=auto --sort"); - ASSERT_EQ(lines.size(), 1U); - EXPECT_EQ(lines[0].base, ""); // no selector -> common - EXPECT_EQ(lines[0].config, ""); - EXPECT_THAT(lines[0].flags, ElementsAre("--color=auto", "--sort")); + EXPECT_THAT(ParseXffrc("--color=auto --sort"), ElementsAre(RcLineIs("", "", ElementsAre("--color=auto", "--sort")))); } TEST_F(XffrcTest, BaseSelector) { - const auto lines = ParseXffrc("xff: --feature=long-paths"); - ASSERT_EQ(lines.size(), 1U); - EXPECT_EQ(lines[0].base, "xff"); - EXPECT_EQ(lines[0].config, ""); - EXPECT_THAT(lines[0].flags, ElementsAre("--feature=long-paths")); + EXPECT_THAT( + ParseXffrc("xff: --feature=long-paths"), ElementsAre(RcLineIs("xff", "", ElementsAre("--feature=long-paths")))); } TEST_F(XffrcTest, BaseAndConfigSelector) { - const auto lines = ParseXffrc("xff:debug: --feature=trace --threads=1"); - ASSERT_EQ(lines.size(), 1U); - EXPECT_EQ(lines[0].base, "xff"); - EXPECT_EQ(lines[0].config, "debug"); - EXPECT_THAT(lines[0].flags, ElementsAre("--feature=trace", "--threads=1")); + EXPECT_THAT( + ParseXffrc("xff:debug: --feature=trace --threads=1"), + ElementsAre(RcLineIs("xff", "debug", ElementsAre("--feature=trace", "--threads=1")))); } TEST_F(XffrcTest, CommonSelectorPreservedVerbatim) { - const auto lines = ParseXffrc("common: --color=auto"); - ASSERT_EQ(lines.size(), 1U); - EXPECT_EQ(lines[0].base, "common"); // the loader treats "common" == "" - EXPECT_THAT(lines[0].flags, ElementsAre("--color=auto")); + // The loader treats "common" == "", but the parser preserves it verbatim. + EXPECT_THAT(ParseXffrc("common: --color=auto"), ElementsAre(RcLineIs("common", "", ElementsAre("--color=auto")))); } TEST_F(XffrcTest, SelectorOnlyLineHasNoFlags) { - const auto lines = ParseXffrc("find: --warn\nxff:\nxff: --feature=x"); - ASSERT_EQ(lines.size(), 3U); - EXPECT_EQ(lines[0].base, "find"); - EXPECT_THAT(lines[0].flags, ElementsAre("--warn")); - EXPECT_EQ(lines[1].base, "xff"); // selector with no flags - EXPECT_THAT(lines[1].flags, IsEmpty()); - EXPECT_EQ(lines[2].base, "xff"); - EXPECT_THAT(lines[2].flags, ElementsAre("--feature=x")); + EXPECT_THAT( + ParseXffrc("find: --warn\nxff:\nxff: --feature=x"), + ElementsAre( + RcLineIs("find", "", ElementsAre("--warn")), RcLineIs("xff", "", IsEmpty()), // selector with no flags + RcLineIs("xff", "", ElementsAre("--feature=x")))); } TEST_F(XffrcTest, FlagValueWithColonIsNotASelector) { - const auto lines = ParseXffrc("--config=xff:2"); // ends in '2', not ':' - ASSERT_EQ(lines.size(), 1U); - EXPECT_EQ(lines[0].base, ""); - EXPECT_THAT(lines[0].flags, ElementsAre("--config=xff:2")); + EXPECT_THAT( + ParseXffrc("--config=xff:2"), // ends in '2', not ':' + ElementsAre(RcLineIs("", "", ElementsAre("--config=xff:2")))); } } // namespace