Skip to content

Commit 7563575

Browse files
Add tests for ConfigAggregator
1 parent 8143a94 commit 7563575

2 files changed

Lines changed: 115 additions & 0 deletions

File tree

tests/miral/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ find_program(XWAYLAND_EXECUTABLE Xwayland REQUIRED)
8080
mir_add_wrapped_executable(miral-test NOINSTALL
8181
external_client.cpp
8282
config_file.cpp
83+
config_aggregator.cpp
8384
live_config.cpp
8485
live_config_ini_file.cpp
8586
magnifier.cpp

tests/miral/config_aggregator.cpp

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,3 +610,117 @@ TEST_F(ConfigAggregatorTest, override_wins_for_shared_key_after_base_update)
610610
load_all({{std::ref(updated_base), "base.conf"},
611611
{std::ref(override_again), "base.conf.d/99-override.conf"}});
612612
}
613+
614+
// Invalid scalar in the second file causes the handler to not be called. So
615+
// the aggregator is only notified of the final value in the first file.
616+
TEST_F(ConfigAggregatorTest, invalid_scalar_value_in_one_of_multiple_yields_last_valid_value_1)
617+
{
618+
aggregator.add_int_attribute(a_key, "a scoped int", [this](auto... args) { int_handler(args...); });
619+
620+
// stream1 has a valid value; stream2 (higher priority) has an unparseable value
621+
std::istringstream stream1{a_key.to_string() + "=10\n"};
622+
std::istringstream stream2{a_key.to_string() + "=not_a_number\n"};
623+
624+
// The last (highest-priority) file wins for scalars, and it holds an invalid value → nullopt
625+
EXPECT_CALL(*this, int_handler(a_key, Optional(10)));
626+
627+
load_all({{std::ref(stream1), "base.conf"},
628+
{std::ref(stream2), "base.conf.d/10-override.conf"}});
629+
}
630+
631+
// Same as the previous case, except that the 30 is overriden by an invalid
632+
// value. Handlers are still not called.
633+
TEST_F(ConfigAggregatorTest, invalid_scalar_value_in_one_of_multiple_yields_last_valid_value_2)
634+
{
635+
aggregator.add_int_attribute(a_key, "a scoped int", [this](auto... args) { int_handler(args...); });
636+
637+
std::istringstream stream1{a_key.to_string() + "=10\n" + a_key.to_string() + "=20\n"};
638+
std::istringstream stream2{a_key.to_string() + "=30\n" + a_key.to_string() + "=not_a_number\n"};
639+
640+
EXPECT_CALL(*this, int_handler(a_key, Optional(20)));
641+
642+
load_all({{std::ref(stream1), "base.conf"},
643+
{std::ref(stream2), "base.conf.d/10-override.conf"}});
644+
}
645+
646+
// Since the invalid value in the second file is overridden by a valid value
647+
// after, its handlers are called and the config aggregator is notified of its
648+
// value (40)
649+
TEST_F(ConfigAggregatorTest, invalid_scalar_value_followed_by_valid_value_in_later_file_yields_valid)
650+
{
651+
aggregator.add_int_attribute(a_key, "a scoped int", [this](auto... args) { int_handler(args...); });
652+
653+
std::istringstream stream1{a_key.to_string() + "=10\n" + a_key.to_string() + "=20\n"};
654+
std::istringstream stream2{a_key.to_string() + "=30\n" + a_key.to_string() + "=also_not_a_number\n" + a_key.to_string() + "=40\n"};
655+
656+
// The last valid assignment in the highest-priority file wins for scalars
657+
EXPECT_CALL(*this, int_handler(a_key, Optional(40)));
658+
659+
load_all({{std::ref(stream1), "base.conf"},
660+
{std::ref(stream2), "base.conf.d/10-override.conf"}});
661+
}
662+
663+
TEST_F(ConfigAggregatorTest, invalid_array_value_in_one_of_multiple_files_is_ignored)
664+
{
665+
aggregator.add_ints_attribute(an_ints_key, "ints", [this](auto... args) { ints_handler(args...); });
666+
667+
// stream1 contributes valid entries; stream2 contributes one valid and one invalid entry
668+
std::istringstream stream1{an_ints_key.to_string() + "=1\n" + an_ints_key.to_string() + "=2\n"};
669+
std::istringstream stream2{an_ints_key.to_string() + "=3\n" + an_ints_key.to_string() + "=not_a_number\n"};
670+
671+
EXPECT_CALL(*this, ints_handler(an_ints_key, Optional(ElementsAre(1, 2, 3))));
672+
673+
load_all({{std::ref(stream1), "base.conf"},
674+
{std::ref(stream2), "base.conf.d/10-override.conf"}});
675+
}
676+
677+
TEST_F(ConfigAggregatorTest, invalid_ini_file_among_multiple_files_leaves_scalar_as_nullopt)
678+
{
679+
aggregator.add_int_attribute(a_key, "a scoped int", [this](auto... args) { int_handler(args...); });
680+
681+
// stream1 is a valid ini file; stream2 is JSON (not valid ini)
682+
std::istringstream stream1{a_key.to_string() + "=42\n"};
683+
std::istringstream stream2{R"({"key": "value"})"};
684+
685+
// JSON content cannot be parsed as ini, so only stream1's value is available.
686+
// Because stream2 is the higher-priority source but provides nothing parseable for a_key,
687+
// the scalar from stream1 should be used (no override occurred).
688+
EXPECT_CALL(*this, int_handler(a_key, Optional(42)));
689+
690+
load_all({{std::ref(stream1), "base.conf"},
691+
{std::ref(stream2), "base.conf.d/10-override.conf"}});
692+
}
693+
694+
TEST_F(ConfigAggregatorTest, invalid_ini_file_among_multiple_files_leaves_array_values_from_valid_files)
695+
{
696+
aggregator.add_strings_attribute(a_strings_key, "strings", [this](auto... args) { strings_handler(args...); });
697+
698+
// stream1 is a valid ini file; stream2 is JSON (not valid ini)
699+
std::istringstream stream1{a_strings_key.to_string() + "=foo\n" + a_strings_key.to_string() + "=bar\n"};
700+
std::istringstream stream2{R"({"strings": ["baz"]})"};
701+
702+
// JSON content cannot be parsed as ini; array entries from stream1 remain intact
703+
EXPECT_CALL(*this, strings_handler(a_strings_key, Optional(ElementsAre("foo", "bar"))));
704+
705+
load_all({{std::ref(stream1), "base.conf"},
706+
{std::ref(stream2), "base.conf.d/10-override.conf"}});
707+
}
708+
709+
TEST_F(ConfigAggregatorTest, clear_followed_by_invalid_array_value_yields_nullopt)
710+
{
711+
aggregator.add_ints_attribute(an_ints_key, "ints", [this](auto... args) { ints_handler(args...); });
712+
713+
// stream1 provides initial values; stream2 first clears the array (empty assignment)
714+
// then tries to append an invalid value
715+
std::istringstream stream1{an_ints_key.to_string() + "=1\n" + an_ints_key.to_string() + "=2\n"};
716+
std::istringstream stream2{
717+
an_ints_key.to_string() + "=\n" + // clears entries from stream1
718+
an_ints_key.to_string() + "=bad_val\n" // invalid integer after the clear
719+
};
720+
721+
// After clearing and then encountering an invalid value, the result is nullopt
722+
EXPECT_CALL(*this, ints_handler(an_ints_key, Optional(IsEmpty())));
723+
724+
load_all({{std::ref(stream1), "base.conf"},
725+
{std::ref(stream2), "base.conf.d/10-override.conf"}});
726+
}

0 commit comments

Comments
 (0)