Skip to content

Commit 0bcee67

Browse files
committed
test: add basic checker scheduling test cases
1 parent ab8621f commit 0bcee67

File tree

4 files changed

+416
-0
lines changed

4 files changed

+416
-0
lines changed

test/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ set(base_test_SOURCES
129129
$<TARGET_OBJECTS:methods>
130130
)
131131

132+
if (ICINGA2_WITH_CHECKER)
133+
list(APPEND base_test_SOURCES
134+
checker.cpp
135+
checker-fixture.hpp
136+
$<TARGET_OBJECTS:checker>
137+
)
138+
endif()
139+
132140
if(ICINGA2_UNITY_BUILD)
133141
mkunity_target(base test base_test_SOURCES)
134142
endif()

test/base-testloggerfixture.hpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,27 @@ class TestLogger : public Logger
5252
return ret;
5353
}
5454

55+
/**
56+
* Counts the number of log entries that match the given regex pattern.
57+
*
58+
* This only counts existing log entries, it does not wait for new ones to arrive.
59+
*
60+
* @param pattern The regex pattern the log message needs to match
61+
*
62+
* @return The number of log entries that match the given pattern
63+
*/
64+
auto CountExpectedLogPattern(const std::string& pattern)
65+
{
66+
std::unique_lock lock(m_Mutex);
67+
int count = 0;
68+
for (const auto& logEntry : m_LogEntries) {
69+
if (boost::regex_match(logEntry.Message.GetData(), boost::regex(pattern))) {
70+
++count;
71+
}
72+
}
73+
return count;
74+
}
75+
5576
private:
5677
void ProcessLogEntry(const LogEntry& entry) override
5778
{

test/checker-fixture.hpp

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
2+
3+
#pragma once
4+
5+
#include "base/atomic.hpp"
6+
#include "checker/checkercomponent.hpp"
7+
#include "config/configcompiler.hpp"
8+
#include "icinga/checkcommand.hpp"
9+
#include "test/base-testloggerfixture.hpp"
10+
#include "test/icingaapplication-fixture.hpp"
11+
12+
namespace icinga {
13+
/**
14+
* Test fixture for tests involving the @c CheckerComponent.
15+
*
16+
* This fixture sets up a CheckerComponent instance and provides utility functions to register
17+
* checkable objects. It is derived from @c TestLoggerFixture to capture log output during tests,
18+
* so tests can verify that expected log messages are produced. The fixture also connects to the
19+
* @c Checkable::OnNewCheckResult signal to count the number of check results produced during tests.
20+
*/
21+
struct CheckerFixture : TestLoggerFixture
22+
{
23+
CheckerFixture()
24+
{
25+
checker = new CheckerComponent;
26+
checker->SetResultTimerInterval(.4); // Speed up result timer for tests
27+
checker->SetName("randomizer", true);
28+
checker->Register();
29+
checker->OnConfigLoaded();
30+
checker->PreActivate();
31+
checker->Activate();
32+
33+
// Manually building and registering the command won't work here as we need a real callable
34+
// function that produces cr and calls ProcessCheckResult on the checkable. So we use a small
35+
// config snippet that imports the random and sleep check commands registered by the "methods-itl.cpp"
36+
// file in the methods lib.
37+
ConfigItem::RunWithActivationContext(
38+
new Function{
39+
"checker-fixture",
40+
[] {
41+
std::unique_ptr<Expression> expression = ConfigCompiler::CompileText(
42+
"<checker-fixture>",
43+
R"CONFIG(
44+
object CheckCommand "random" {
45+
import "random-check-command"
46+
}
47+
48+
object CheckCommand "sleep" {
49+
import "sleep-check-command"
50+
}
51+
)CONFIG"
52+
);
53+
BOOST_REQUIRE(expression);
54+
ScriptFrame frame(true);
55+
BOOST_CHECK_NO_THROW(expression->Evaluate(frame));
56+
}
57+
}
58+
);
59+
60+
Checkable::OnNewCheckResult.connect(
61+
[this](const Checkable::Ptr&, const CheckResult::Ptr& cr, const MessageOrigin::Ptr&) {
62+
++resultCount;
63+
BOOST_REQUIRE(cr);
64+
BOOST_CHECK_EQUAL(0, cr->GetExitStatus());
65+
BOOST_CHECK(!cr->GetOutput().IsEmpty());
66+
}
67+
);
68+
}
69+
70+
~CheckerFixture()
71+
{
72+
Checkable::OnNextCheckChanged.disconnect_all_slots();
73+
Checkable::OnNewCheckResult.disconnect_all_slots();
74+
checker->Deactivate();
75+
}
76+
77+
/**
78+
* Registers a fully configured set of checkable hosts that execute the "random" check command.
79+
*
80+
* Each host is configured with a random check command, check interval, and retry interval.
81+
* If @c unreachable is true, each host is made unreachable by adding a dependency on a parent
82+
* host that is in a critical state. This prevents the checker from executing checks for the
83+
* child hosts. The check and retry intervals are kept low to allow for quick test execution,
84+
* but they can be adjusted via the @c interval and @c retry parameters.
85+
*
86+
* @param count Number of checkable hosts to register.
87+
* @param disableChecks If true, disables active checks for each host.
88+
* @param unreachable If true, makes each host unreachable via a dependency.
89+
*/
90+
void RegisterCheckablesRandom(int count, bool disableChecks = false, bool unreachable = false) const
91+
{
92+
for (int i = 1; i <= count; ++i) {
93+
RegisterCheckable("host-" + std::to_string(i), "random", "", disableChecks, unreachable);
94+
}
95+
}
96+
97+
/**
98+
* Registers a fully configured set of checkable hosts that execute the "sleep" command.
99+
*
100+
* Each host is configured with a sleep check command that sleeps for the specified duration.
101+
* The check and retry intervals can be adjusted via the @c checkInterval and @c retryInterval
102+
* member variables of the fixture. If @c unreachable is true, each host is made unreachable by
103+
* adding a dependency on a parent host that is in a critical state. This prevents the checker
104+
* from executing checks for the child hosts.
105+
*
106+
* @param count Number of checkable hosts to register.
107+
* @param sleepTime Duration (in seconds) that the sleep command should sleep. Defaults to 1.0 second.
108+
* @param disableChecks If true, disables active checks for each host.
109+
* @param unreachable If true, makes each host unreachable via a dependency.
110+
*/
111+
void RegisterCheckablesSleep(
112+
int count,
113+
double sleepTime = 1.0,
114+
bool disableChecks = false,
115+
bool unreachable = false
116+
) const
117+
{
118+
for (int i = 1; i <= count; ++i) {
119+
auto h = RegisterCheckable("host-" + std::to_string(i), "sleep", "", disableChecks, unreachable);
120+
h->SetVars(new Dictionary{{"sleep_time", sleepTime}});
121+
}
122+
}
123+
124+
Host::Ptr RegisterCheckable(
125+
std::string name,
126+
std::string cmd,
127+
std::string period,
128+
bool disableChecks = false,
129+
bool unreachable = false
130+
) const
131+
{
132+
Host::Ptr host = new Host;
133+
host->SetName(std::move(name), true);
134+
host->SetCheckCommandRaw(std::move(cmd), true);
135+
host->SetCheckInterval(checkInterval, true);
136+
host->SetRetryInterval(retryInterval, true);
137+
host->SetHAMode(HARunEverywhere, true); // Disable HA for tests
138+
host->SetEnableActiveChecks(!disableChecks, true);
139+
host->SetCheckPeriodRaw(std::move(period), true);
140+
host->Register();
141+
host->OnAllConfigLoaded();
142+
143+
if (unreachable) {
144+
Host::Ptr parent = new Host;
145+
parent->SetName(Utility::NewUniqueID(), true);
146+
parent->SetStateRaw(ServiceCritical, true);
147+
parent->SetStateType(StateTypeHard, true);
148+
parent->SetLastCheckResult(new CheckResult, true);
149+
parent->Register();
150+
151+
Dependency::Ptr dep = new Dependency;
152+
dep->SetName(Utility::NewUniqueID(), true);
153+
dep->SetStateFilter(StateFilterUp, true);
154+
dep->SetDisableChecks(true, true);
155+
dep->SetParent(parent);
156+
dep->SetChild(host);
157+
dep->Register();
158+
159+
host->AddDependency(dep);
160+
}
161+
162+
host->PreActivate();
163+
host->Activate();
164+
return host;
165+
}
166+
167+
/**
168+
* resultCount tracks the number of check results produced by the checker.
169+
*
170+
* This is used in tests to verify that checks are actually being executed and results processed.
171+
* It is incremented from within the OnNewCheckResult signal handler, thus must be atomic.
172+
*/
173+
Atomic<int> resultCount{0};
174+
double checkInterval{.1}; // Interval in seconds between regular checks for each checkable.
175+
double retryInterval{.1}; // Interval in seconds between retry checks for each checkable.
176+
CheckerComponent::Ptr checker;
177+
};
178+
179+
} // namespace icinga

0 commit comments

Comments
 (0)