Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions xff/config/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ cc_library(
deps = [
":ini_cc",
":xffrc_cc",
"@abseil-cpp//absl/algorithm:container",
],
)

Expand Down
15 changes: 3 additions & 12 deletions xff/config/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,20 @@
#include "xff/config/config.h"

#include <string>
#include <string_view>
#include <vector>

#include "absl/algorithm/container.h"
#include "xff/config/ini.h"
#include "xff/config/xffrc.h"

namespace xff::config {
namespace {

bool Contains(const std::vector<std::string>& 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<std::string>& 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;
}

Expand Down
41 changes: 20 additions & 21 deletions xff/config/config_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,28 @@

#include "xff/config/config.h"

#include <string>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "xff/config/xffrc.h"

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<ResolvedFlag> 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"};
Expand All @@ -37,31 +48,22 @@ 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) {
ConfigInputs in;
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) {
Expand All @@ -70,21 +72,18 @@ 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) {
ConfigInputs in;
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
Expand Down
33 changes: 21 additions & 12 deletions xff/config/ini_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,32 @@

#include "xff/config/ini.h"

#include <string>
#include <vector>

#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<PolicyRule> PolicyRuleIs(
const std::string& layer,
bool allow,
const Matcher<std::vector<std::string>>& 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"));
Expand All @@ -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) {
Expand Down
62 changes: 31 additions & 31 deletions xff/config/xffrc_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,68 +15,68 @@

#include "xff/config/xffrc.h"

#include <string>
#include <vector>

#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<RcLine> RcLineIs(
const std::string& base,
const std::string& config,
const Matcher<std::vector<std::string>>& 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
Expand Down
Loading