summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--test/CMakeLists.txt259
-rw-r--r--test/base-array.cpp162
-rw-r--r--test/base-base64.cpp45
-rw-r--r--test/base-convert.cpp60
-rw-r--r--test/base-dictionary.cpp200
-rw-r--r--test/base-fifo.cpp43
-rw-r--r--test/base-json.cpp110
-rw-r--r--test/base-match.cpp27
-rw-r--r--test/base-netstring.cpp25
-rw-r--r--test/base-object-packer.cpp264
-rw-r--r--test/base-object.cpp39
-rw-r--r--test/base-serialize.cpp68
-rw-r--r--test/base-shellescape.cpp32
-rw-r--r--test/base-stacktrace.cpp72
-rw-r--r--test/base-stream.cpp39
-rw-r--r--test/base-string.cpp104
-rw-r--r--test/base-timer.cpp61
-rw-r--r--test/base-tlsutility.cpp135
-rw-r--r--test/base-type.cpp47
-rw-r--r--test/base-utility.cpp138
-rw-r--r--test/base-value.cpp53
-rw-r--r--test/config-apply.cpp251
-rw-r--r--test/config-ops.cpp246
-rw-r--r--test/config/2742.conf21
-rw-r--r--test/config/5872.conf72
-rw-r--r--test/config/5912.conf.dis14
-rw-r--r--test/config/5926.conf23
-rw-r--r--test/config/5927.conf44
-rw-r--r--test/config/5980.conf58
-rw-r--r--test/config/6105.conf25
-rw-r--r--test/config/6479.conf44
-rw-r--r--test/config/6608.conf16
-rw-r--r--test/config/6968.conf27
-rw-r--r--test/config/7560.conf41
-rw-r--r--test/config/7683.conf27
-rw-r--r--test/config/8063.conf73
-rw-r--r--test/config/README2
-rw-r--r--test/config/templates.conf80
-rw-r--r--test/icinga-checkable-fixture.cpp28
-rw-r--r--test/icinga-checkable-flapping.cpp248
-rw-r--r--test/icinga-checkresult.cpp1032
-rw-r--r--test/icinga-dependencies.cpp101
-rw-r--r--test/icinga-legacytimeperiod.cpp694
-rw-r--r--test/icinga-macros.cpp50
-rw-r--r--test/icinga-notification.cpp215
-rw-r--r--test/icinga-perfdata.cpp407
-rw-r--r--test/icingaapplication-fixture.cpp32
-rw-r--r--test/icingaapplication-fixture.hpp21
-rw-r--r--test/livestatus-fixture.cpp53
-rw-r--r--test/livestatus.cpp107
-rw-r--r--test/livestatus/README12
-rw-r--r--test/livestatus/queries/commands/command4
-rw-r--r--test/livestatus/queries/commands/modattr4
-rw-r--r--test/livestatus/queries/comments/comment3
-rw-r--r--test/livestatus/queries/comments/comment_short4
-rw-r--r--test/livestatus/queries/contacts/contacts3
-rw-r--r--test/livestatus/queries/contacts/group3
-rw-r--r--test/livestatus/queries/contacts/modattr4
-rw-r--r--test/livestatus/queries/custom/scrambled2
-rw-r--r--test/livestatus/queries/custom/thruk_alert_history19
-rw-r--r--test/livestatus/queries/custom/thruk_comments7
-rw-r--r--test/livestatus/queries/downtimes/downtime3
-rw-r--r--test/livestatus/queries/downtimes/downtime_short4
-rw-r--r--test/livestatus/queries/endpoints/endpoints3
-rw-r--r--test/livestatus/queries/hosts/bygroup4
-rw-r--r--test/livestatus/queries/hosts/check4
-rw-r--r--test/livestatus/queries/hosts/command4
-rw-r--r--test/livestatus/queries/hosts/comment4
-rw-r--r--test/livestatus/queries/hosts/contact4
-rw-r--r--test/livestatus/queries/hosts/customvar4
-rw-r--r--test/livestatus/queries/hosts/downtime4
-rw-r--r--test/livestatus/queries/hosts/extra4
-rw-r--r--test/livestatus/queries/hosts/group3
-rw-r--r--test/livestatus/queries/hosts/host4
-rw-r--r--test/livestatus/queries/hosts/host_nagvis6
-rw-r--r--test/livestatus/queries/hosts/legacy4
-rw-r--r--test/livestatus/queries/hosts/modattr4
-rw-r--r--test/livestatus/queries/hosts/notification4
-rw-r--r--test/livestatus/queries/hosts/services4
-rw-r--r--test/livestatus/queries/hosts/state4
-rw-r--r--test/livestatus/queries/hosts/stats_sum5
-rw-r--r--test/livestatus/queries/log/alerts6
-rw-r--r--test/livestatus/queries/log/avail12
-rw-r--r--test/livestatus/queries/log/avail_svc13
-rw-r--r--test/livestatus/queries/log/class5
-rw-r--r--test/livestatus/queries/log/localhost_disk7
-rw-r--r--test/livestatus/queries/log/log4
-rw-r--r--test/livestatus/queries/log/minimal5
-rw-r--r--test/livestatus/queries/log/trend26
-rw-r--r--test/livestatus/queries/services/bygroup4
-rw-r--r--test/livestatus/queries/services/byhostgroup4
-rw-r--r--test/livestatus/queries/services/check4
-rw-r--r--test/livestatus/queries/services/command4
-rw-r--r--test/livestatus/queries/services/comment4
-rw-r--r--test/livestatus/queries/services/contact4
-rw-r--r--test/livestatus/queries/services/customvar4
-rw-r--r--test/livestatus/queries/services/downtime4
-rw-r--r--test/livestatus/queries/services/extra4
-rw-r--r--test/livestatus/queries/services/group3
-rw-r--r--test/livestatus/queries/services/legacy4
-rw-r--r--test/livestatus/queries/services/modattr4
-rw-r--r--test/livestatus/queries/services/notification4
-rw-r--r--test/livestatus/queries/services/services3
-rw-r--r--test/livestatus/queries/services/state4
-rw-r--r--test/livestatus/queries/special/services5
-rw-r--r--test/livestatus/queries/statehist/duration5
-rw-r--r--test/livestatus/queries/statehist/statehist5
-rw-r--r--test/livestatus/queries/statehist/statehist_disk6
-rw-r--r--test/livestatus/queries/statehist/sum9
-rw-r--r--test/livestatus/queries/status/checks4
-rw-r--r--test/livestatus/queries/status/custom4
-rw-r--r--test/livestatus/queries/status/livestatus4
-rw-r--r--test/livestatus/queries/status/program4
-rw-r--r--test/livestatus/queries/status/status3
-rw-r--r--test/livestatus/queries/timeperiods/timeperiod3
-rwxr-xr-xtest/livestatus/run_queries26
-rw-r--r--test/methods-pluginnotificationtask.cpp88
-rw-r--r--test/remote-configpackageutility.cpp25
-rw-r--r--test/remote-url.cpp128
-rw-r--r--test/test-runner.cpp21
120 files changed, 6656 insertions, 0 deletions
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..1c972d6
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,259 @@
+# Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+
+
+include(BoostTestTargets)
+
+set(base_test_SOURCES
+ icingaapplication-fixture.cpp
+ base-array.cpp
+ base-base64.cpp
+ base-convert.cpp
+ base-dictionary.cpp
+ base-fifo.cpp
+ base-json.cpp
+ base-match.cpp
+ base-netstring.cpp
+ base-object.cpp
+ base-object-packer.cpp
+ base-serialize.cpp
+ base-shellescape.cpp
+ base-stacktrace.cpp
+ base-stream.cpp
+ base-string.cpp
+ base-timer.cpp
+ base-tlsutility.cpp
+ base-type.cpp
+ base-utility.cpp
+ base-value.cpp
+ config-apply.cpp
+ config-ops.cpp
+ icinga-checkresult.cpp
+ icinga-dependencies.cpp
+ icinga-legacytimeperiod.cpp
+ icinga-macros.cpp
+ icinga-notification.cpp
+ icinga-perfdata.cpp
+ methods-pluginnotificationtask.cpp
+ remote-configpackageutility.cpp
+ remote-url.cpp
+ ${base_OBJS}
+ $<TARGET_OBJECTS:config>
+ $<TARGET_OBJECTS:remote>
+ $<TARGET_OBJECTS:icinga>
+ $<TARGET_OBJECTS:methods>
+)
+
+if(ICINGA2_UNITY_BUILD)
+ mkunity_target(base test base_test_SOURCES)
+endif()
+
+add_boost_test(base
+ SOURCES test-runner.cpp ${base_test_SOURCES}
+ LIBRARIES ${base_DEPS}
+ TESTS
+ base_array/construct
+ base_array/getset
+ base_array/resize
+ base_array/insert
+ base_array/remove
+ base_array/unique
+ base_array/foreach
+ base_array/clone
+ base_array/json
+ base_base64/base64
+ base_convert/tolong
+ base_convert/todouble
+ base_convert/tostring
+ base_convert/tobool
+ base_dictionary/construct
+ base_dictionary/initializer1
+ base_dictionary/initializer2
+ base_dictionary/get1
+ base_dictionary/get2
+ base_dictionary/foreach
+ base_dictionary/remove
+ base_dictionary/clone
+ base_dictionary/json
+ base_dictionary/keys_ordered
+ base_fifo/construct
+ base_fifo/io
+ base_json/encode
+ base_json/decode
+ base_json/invalid1
+ base_object_packer/pack_null
+ base_object_packer/pack_false
+ base_object_packer/pack_true
+ base_object_packer/pack_number
+ base_object_packer/pack_string
+ base_object_packer/pack_array
+ base_object_packer/pack_object
+ base_match/tolong
+ base_netstring/netstring
+ base_object/construct
+ base_object/getself
+ base_serialize/scalar
+ base_serialize/array
+ base_serialize/dictionary
+ base_serialize/object
+ base_shellescape/escape_basic
+ base_shellescape/escape_quoted
+ base_stacktrace/stacktrace
+ base_stream/readline_stdio
+ base_string/construct
+ base_string/equal
+ base_string/clear
+ base_string/append
+ base_string/trim
+ base_string/contains
+ base_string/replace
+ base_string/index
+ base_string/find
+ base_timer/construct
+ base_timer/interval
+ base_timer/invoke
+ base_timer/scope
+ base_tlsutility/sha1
+ base_tlsutility/iscauptodate_ok
+ base_tlsutility/iscauptodate_expiring
+ base_tlsutility/iscertuptodate_ok
+ base_tlsutility/iscertuptodate_expiring
+ base_tlsutility/iscertuptodate_old
+ base_type/gettype
+ base_type/assign
+ base_type/byname
+ base_type/instantiate
+ base_utility/parse_version
+ base_utility/compare_version
+ base_utility/comparepasswords_works
+ base_utility/comparepasswords_issafe
+ base_utility/validateutf8
+ base_utility/EscapeCreateProcessArg
+ base_utility/TruncateUsingHash
+ base_value/scalar
+ base_value/convert
+ base_value/format
+ config_apply/gettargethosts_literal
+ config_apply/gettargethosts_const
+ config_apply/gettargethosts_swapped
+ config_apply/gettargethosts_two
+ config_apply/gettargethosts_three
+ config_apply/gettargethosts_mixed
+ config_apply/gettargethosts_redundant
+ config_apply/gettargethosts_badconst
+ config_apply/gettargethosts_notliteral
+ config_apply/gettargethosts_wrongop
+ config_apply/gettargethosts_wrongattr
+ config_apply/gettargethosts_wrongvar
+ config_apply/gettargethosts_noindexer
+ config_apply/gettargetservices_literal
+ config_apply/gettargetservices_const
+ config_apply/gettargetservices_swapped_outer
+ config_apply/gettargetservices_swapped_inner
+ config_apply/gettargetservices_two
+ config_apply/gettargetservices_three
+ config_apply/gettargetservices_mixed
+ config_apply/gettargetservices_redundant
+ config_apply/gettargetservices_badconst
+ config_apply/gettargetservices_notliteral
+ config_apply/gettargetservices_wrongop_outer
+ config_apply/gettargetservices_wrongop_host
+ config_apply/gettargetservices_wrongop_service
+ config_apply/gettargetservices_wrongattr_host
+ config_apply/gettargetservices_wrongattr_service
+ config_apply/gettargetservices_wrongvar_host
+ config_apply/gettargetservices_wrongvar_service
+ config_apply/gettargetservices_noindexer_host
+ config_apply/gettargetservices_noindexer_service
+ config_ops/simple
+ config_ops/advanced
+ icinga_checkresult/host_1attempt
+ icinga_checkresult/host_2attempts
+ icinga_checkresult/host_3attempts
+ icinga_checkresult/service_1attempt
+ icinga_checkresult/service_2attempts
+ icinga_checkresult/service_3attempts
+ icinga_checkresult/host_flapping_notification
+ icinga_checkresult/service_flapping_notification
+ icinga_checkresult/suppressed_notification
+ icinga_dependencies/multi_parent
+ icinga_notification/strings
+ icinga_notification/state_filter
+ icinga_notification/type_filter
+ icinga_notification/no_filter_problem_no_duplicate
+ icinga_notification/filter_problem_no_duplicate
+ icinga_notification/volatile_filter_problem_duplicate
+ icinga_notification/no_recovery_filter_no_duplicate
+ icinga_notification/recovery_filter_duplicate
+ icinga_macros/simple
+ icinga_legacytimeperiod/simple
+ icinga_legacytimeperiod/advanced
+ icinga_legacytimeperiod/dst
+ icinga_legacytimeperiod/dst_isinside
+ icinga_perfdata/empty
+ icinga_perfdata/simple
+ icinga_perfdata/quotes
+ icinga_perfdata/multiple
+ icinga_perfdata/multiline
+ icinga_perfdata/normalize
+ icinga_perfdata/uom
+ icinga_perfdata/warncritminmax
+ icinga_perfdata/ignore_invalid_warn_crit_min_max
+ icinga_perfdata/invalid
+ icinga_perfdata/multi
+ icinga_perfdata/scientificnotation
+ icinga_perfdata/parse_edgecases
+ methods_pluginnotificationtask/truncate_long_output
+ remote_configpackageutility/ValidateName
+ remote_url/id_and_path
+ remote_url/parameters
+ remote_url/get_and_set
+ remote_url/format
+ remote_url/illegal_legal_strings
+)
+
+if(ICINGA2_WITH_LIVESTATUS)
+ set(livestatus_test_SOURCES
+ icingaapplication-fixture.cpp
+ livestatus-fixture.cpp
+ livestatus.cpp
+ ${base_OBJS}
+ $<TARGET_OBJECTS:config>
+ $<TARGET_OBJECTS:remote>
+ $<TARGET_OBJECTS:icinga>
+ $<TARGET_OBJECTS:livestatus>
+ $<TARGET_OBJECTS:methods>
+ )
+
+ if(ICINGA2_UNITY_BUILD)
+ mkunity_target(livestatus test livestatus_test_SOURCES)
+ endif()
+
+ add_boost_test(livestatus
+ SOURCES test-runner.cpp ${livestatus_test_SOURCES}
+ LIBRARIES ${base_DEPS}
+ TESTS livestatus/hosts livestatus/services
+ )
+endif()
+
+set(icinga_checkable_test_SOURCES
+ icingaapplication-fixture.cpp
+ icinga-checkable-fixture.cpp
+ icinga-checkable-flapping.cpp
+ ${base_OBJS}
+ $<TARGET_OBJECTS:config>
+ $<TARGET_OBJECTS:remote>
+ $<TARGET_OBJECTS:icinga>
+ $<TARGET_OBJECTS:cli>
+)
+
+if(ICINGA2_UNITY_BUILD)
+ mkunity_target(icinga_checkable test icinga_checkable_test_SOURCES)
+endif()
+
+add_boost_test(icinga_checkable
+ SOURCES test-runner.cpp ${icinga_checkable_test_SOURCES}
+ LIBRARIES ${base_DEPS}
+ TESTS icinga_checkable_flapping/host_not_flapping
+ icinga_checkable_flapping/host_flapping
+ icinga_checkable_flapping/host_flapping_recover
+ icinga_checkable_flapping/host_flapping_docs_example
+)
diff --git a/test/base-array.cpp b/test/base-array.cpp
new file mode 100644
index 0000000..33e54e8
--- /dev/null
+++ b/test/base-array.cpp
@@ -0,0 +1,162 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/array.hpp"
+#include "base/objectlock.hpp"
+#include "base/json.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_array)
+
+BOOST_AUTO_TEST_CASE(construct)
+{
+ Array::Ptr array = new Array();
+ BOOST_CHECK(array);
+ BOOST_CHECK(array->GetLength() == 0);
+}
+
+BOOST_AUTO_TEST_CASE(getset)
+{
+ Array::Ptr array = new Array();
+ array->Add(7);
+ array->Add(2);
+ array->Add(5);
+ BOOST_CHECK(array->GetLength() == 3);
+ BOOST_CHECK(array->Get(0) == 7);
+ BOOST_CHECK(array->Get(1) == 2);
+ BOOST_CHECK(array->Get(2) == 5);
+
+ array->Set(1, 9);
+ BOOST_CHECK(array->Get(1) == 9);
+
+ array->Remove(1);
+ BOOST_CHECK(array->GetLength() == 2);
+ BOOST_CHECK(array->Get(1) == 5);
+}
+
+BOOST_AUTO_TEST_CASE(resize)
+{
+ Array::Ptr array = new Array();
+ array->Resize(2);
+ BOOST_CHECK(array->GetLength() == 2);
+ BOOST_CHECK(array->Get(0) == Empty);
+ BOOST_CHECK(array->Get(1) == Empty);
+}
+
+BOOST_AUTO_TEST_CASE(insert)
+{
+ Array::Ptr array = new Array();
+
+ array->Insert(0, 11);
+ array->Insert(1, 22);
+ BOOST_CHECK(array->GetLength() == 2);
+ BOOST_CHECK(array->Get(1) == 22);
+
+ array->Insert(0, 33);
+ BOOST_CHECK(array->GetLength() == 3);
+ BOOST_CHECK(array->Get(0) == 33);
+ BOOST_CHECK(array->Get(1) == 11);
+
+ array->Insert(1, 44);
+ BOOST_CHECK(array->GetLength() == 4);
+ BOOST_CHECK(array->Get(0) == 33);
+ BOOST_CHECK(array->Get(1) == 44);
+ BOOST_CHECK(array->Get(2) == 11);
+}
+
+BOOST_AUTO_TEST_CASE(remove)
+{
+ Array::Ptr array = new Array();
+ array->Add(7);
+ array->Add(2);
+ array->Add(5);
+
+ {
+ ObjectLock olock(array);
+ auto it = array->Begin();
+ array->Remove(it);
+ }
+
+ BOOST_CHECK(array->GetLength() == 2);
+ BOOST_CHECK(array->Get(0) == 2);
+
+ array->Clear();
+ BOOST_CHECK(array->GetLength() == 0);
+}
+
+BOOST_AUTO_TEST_CASE(unique)
+{
+ Array::Ptr array = new Array();
+ array->Add("group1");
+ array->Add("group2");
+ array->Add("group1");
+ array->Add("group2");
+
+ Array::Ptr result;
+
+ {
+ ObjectLock olock(array);
+ result = array->Unique();
+ }
+
+ BOOST_CHECK(result->GetLength() == 2);
+ result->Sort();
+
+ BOOST_CHECK(result->Get(0) == "group1");
+ BOOST_CHECK(result->Get(1) == "group2");
+}
+BOOST_AUTO_TEST_CASE(foreach)
+{
+ Array::Ptr array = new Array();
+ array->Add(7);
+ array->Add(2);
+ array->Add(5);
+
+ ObjectLock olock(array);
+
+ int n = 0;
+
+ for (const Value& item : array) {
+ BOOST_CHECK(n != 0 || item == 7);
+ BOOST_CHECK(n != 1 || item == 2);
+ BOOST_CHECK(n != 2 || item == 5);
+
+ n++;
+ }
+}
+
+BOOST_AUTO_TEST_CASE(clone)
+{
+ Array::Ptr array = new Array();
+ array->Add(7);
+ array->Add(2);
+ array->Add(5);
+
+ Array::Ptr clone = array->ShallowClone();
+
+ BOOST_CHECK(clone->GetLength() == 3);
+ BOOST_CHECK(clone->Get(0) == 7);
+ BOOST_CHECK(clone->Get(1) == 2);
+ BOOST_CHECK(clone->Get(2) == 5);
+}
+
+BOOST_AUTO_TEST_CASE(json)
+{
+ Array::Ptr array = new Array();
+ array->Add(7);
+ array->Add(2);
+ array->Add(5);
+
+ String json = JsonEncode(array);
+ BOOST_CHECK(json.GetLength() > 0);
+
+ Array::Ptr deserialized = JsonDecode(json);
+ BOOST_CHECK(deserialized);
+ BOOST_CHECK(deserialized->GetLength() == 3);
+ BOOST_CHECK(deserialized->Get(0) == 7);
+ BOOST_CHECK(deserialized->Get(1) == 2);
+ BOOST_CHECK(deserialized->Get(2) == 5);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-base64.cpp b/test/base-base64.cpp
new file mode 100644
index 0000000..f9e6aec
--- /dev/null
+++ b/test/base-base64.cpp
@@ -0,0 +1,45 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/base64.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_base64)
+
+BOOST_AUTO_TEST_CASE(base64)
+{
+ std::vector<String> clearText;
+ clearText.emplace_back("");
+ clearText.emplace_back("1");
+ clearText.emplace_back("12");
+ clearText.emplace_back("123");
+ clearText.emplace_back("1234");
+ clearText.emplace_back("VsowLvPqEiAeITDmo-5L_NB-k7fsT3sT2d3K9O4iC2uBk41hvCPAxrgGSxrdeX5s"
+ "Zo0Z9b1kxDZlzf8GHQ9ARW6YLeGODMtiZo8cKkUzfSbxyZ_wlE9u6pCTTg9kODCM"
+ "Ve-X_a3jWkOy89RoDkT5ahKBY-8S25L6wlvWt8ZyQ2bLxfplzEzuHgEknTMKKp2K"
+ "jRlwI2p3gF4FYeQM7dx0E5O782Lh1P3IC6jPNqiZgTgWmsRYZbAN8oU2V626bQxD"
+ "n8prQ0Xr_3aPdP7VIVgxNZMnF0NJrQvB_rzq1Dip1UM_xH_9nansbX25E64OQU-r"
+ "q54EdO-vb_9FvyqdeVTJ3UTgXIP7OXtz4K8xlEHWdb1-hJChVvDc0KSnN5HVN2NJ"
+ "yJrAofVyHBxXGRnGMdN8cOwvxzBFsz2Hih_lIqm1NVULm9_J9GoesY-aN8JzGocU"
+ "U3hbhFQBiUlzliuodhwg4RXRcfmPHQRo7kWKaohpySkvqmWcXEAt2LPJ8nH70fW7"
+ "vudgzwwWTnNcMlf0Wa-nKL4xXNNPQD0obDCfogN8uKuGqi0DltOUmFK62Zkkb0_d"
+ "45grssnD5q89MjDGBkGMXuLY_JLOqc7Y9VV6H48vzoTNK1a2kOGV2TrAD8syuA5Z"
+ "o8RLKjTqAYjKTUqEJjg0MflpiBnbDQvRqiSXs1cJuFNXRLpEC5GoqGqMd0zAGn4u"
+ "5J3OurVd0SFp8_vkYUI6YwNUe00y8_Dn6DOBh_0KKADphZBgple82_8HrnQNreQn"
+ "GkB2TpIsjwWud0yuhI-jQZEMNNlhEYMLwx7B-xTGhn0LFC1pLEXn_kZ2NOgDgUHd"
+ "bdj906o3N2Jjo9Fb5GXkCrt-fNEYBjeXvIu73yeTGmsiAzfiICNHi_PmGkgq8fYQ"
+ "O9lQgyRHCMic8zU7ffWuSoUPRgHsqztLHaCDbYIrNmgrn2taxcXSb57Xm_l-1xBH"
+ "bZqdMvBziapJXaLJmhUg03lgdsIc_OuJmzt-sytDLVGIuNqpa4dETdhLsI7qis4B"
+ );
+
+ // 1024 chars
+
+ for (const String& str : clearText) {
+ String enc = Base64::Encode(str);
+ String dec = Base64::Decode(enc);
+ BOOST_CHECK(str == dec);
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-convert.cpp b/test/base-convert.cpp
new file mode 100644
index 0000000..bc7c61b
--- /dev/null
+++ b/test/base-convert.cpp
@@ -0,0 +1,60 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/convert.hpp"
+#include "base/object.hpp"
+#include <BoostTestTargetConfig.h>
+#include <iostream>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_convert)
+
+BOOST_AUTO_TEST_CASE(tolong)
+{
+ BOOST_CHECK_THROW(Convert::ToLong(" 7"), boost::exception);
+ BOOST_CHECK(Convert::ToLong("-7") == -7);
+ BOOST_CHECK_THROW(Convert::ToLong("7a"), boost::exception);
+
+ BOOST_CHECK(Convert::ToLong(Value(-7)) == -7);
+
+ BOOST_CHECK(Convert::ToLong(3.141386593) == 3);
+}
+
+BOOST_AUTO_TEST_CASE(todouble)
+{
+ BOOST_CHECK_THROW(Convert::ToDouble(" 7.3"), boost::exception);
+ BOOST_CHECK(Convert::ToDouble("-7.3") == -7.3);
+ BOOST_CHECK_THROW(Convert::ToDouble("7.3a"), boost::exception);
+ BOOST_CHECK(Convert::ToDouble(Value(-7.3)) == -7.3);
+}
+
+BOOST_AUTO_TEST_CASE(tostring)
+{
+ BOOST_CHECK(Convert::ToString(7) == "7");
+ BOOST_CHECK(Convert::ToString(7.5) == "7.500000");
+ BOOST_CHECK(Convert::ToString("hello") == "hello");
+ BOOST_CHECK(Convert::ToString(18446744073709551616.0) == "18446744073709551616"); // pow(2, 64)
+
+ String str = "hello";
+ BOOST_CHECK(Convert::ToString(str) == "hello");
+
+ BOOST_CHECK(Convert::ToString(Value(7)) == "7");
+ BOOST_CHECK(Convert::ToString(Value(7.5)) == "7.500000");
+ BOOST_CHECK(Convert::ToString(Value(18446744073709551616.0)) == "18446744073709551616"); // pow(2, 64)
+ BOOST_CHECK(Convert::ToString(Value("hello")) == "hello");
+ BOOST_CHECK(Convert::ToString(Value("hello hello")) == "hello hello");
+}
+
+BOOST_AUTO_TEST_CASE(tobool)
+{
+ BOOST_CHECK(Convert::ToBool("a") == true);
+ BOOST_CHECK(Convert::ToBool("0") == true);
+ BOOST_CHECK(Convert::ToBool("1") == true);
+ BOOST_CHECK(Convert::ToBool("2") == true);
+ BOOST_CHECK(Convert::ToBool(1) == true);
+ BOOST_CHECK(Convert::ToBool(0) == false);
+ BOOST_CHECK(Convert::ToBool(Value(true)) == true);
+ BOOST_CHECK(Convert::ToBool(Value(false)) == false);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-dictionary.cpp b/test/base-dictionary.cpp
new file mode 100644
index 0000000..3469be7
--- /dev/null
+++ b/test/base-dictionary.cpp
@@ -0,0 +1,200 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/dictionary.hpp"
+#include "base/objectlock.hpp"
+#include "base/json.hpp"
+#include "base/string.hpp"
+#include "base/utility.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_dictionary)
+
+BOOST_AUTO_TEST_CASE(construct)
+{
+ Dictionary::Ptr dictionary = new Dictionary();
+ BOOST_CHECK(dictionary);
+}
+
+BOOST_AUTO_TEST_CASE(initializer1)
+{
+ DictionaryData dict;
+
+ dict.emplace_back("test1", "Gin-o-clock");
+
+ Dictionary::Ptr dictionary = new Dictionary(std::move(dict));
+
+ Value test1;
+ test1 = dictionary->Get("test1");
+ BOOST_CHECK(test1 == "Gin-o-clock");
+}
+
+BOOST_AUTO_TEST_CASE(initializer2)
+{
+ Dictionary::Ptr dictionary = new Dictionary({ {"test1", "Gin-for-the-win"} });
+
+ Value test1;
+ test1 = dictionary->Get("test1");
+ BOOST_CHECK(test1 == "Gin-for-the-win");
+}
+
+BOOST_AUTO_TEST_CASE(get1)
+{
+ Dictionary::Ptr dictionary = new Dictionary();
+ dictionary->Set("test1", 7);
+ dictionary->Set("test2", "hello world");
+
+ BOOST_CHECK(dictionary->GetLength() == 2);
+
+ Value test1;
+ test1 = dictionary->Get("test1");
+ BOOST_CHECK(test1 == 7);
+
+ Value test2;
+ test2 = dictionary->Get("test2");
+ BOOST_CHECK(test2 == "hello world");
+
+ String key3 = "test3";
+ Value test3;
+ test3 = dictionary->Get(key3);
+ BOOST_CHECK(test3.IsEmpty());
+}
+
+BOOST_AUTO_TEST_CASE(get2)
+{
+ Dictionary::Ptr dictionary = new Dictionary();
+ Dictionary::Ptr other = new Dictionary();
+
+ dictionary->Set("test1", other);
+
+ BOOST_CHECK(dictionary->GetLength() == 1);
+
+ Dictionary::Ptr test1 = dictionary->Get("test1");
+ BOOST_CHECK(other == test1);
+
+ Dictionary::Ptr test2 = dictionary->Get("test2");
+ BOOST_CHECK(!test2);
+}
+
+BOOST_AUTO_TEST_CASE(foreach)
+{
+ Dictionary::Ptr dictionary = new Dictionary();
+ dictionary->Set("test1", 7);
+ dictionary->Set("test2", "hello world");
+
+ ObjectLock olock(dictionary);
+
+ bool seen_test1 = false, seen_test2 = false;
+
+ for (const Dictionary::Pair& kv : dictionary) {
+ BOOST_CHECK(kv.first == "test1" || kv.first == "test2");
+
+ if (kv.first == "test1") {
+ BOOST_CHECK(!seen_test1);
+ seen_test1 = true;
+
+ BOOST_CHECK(kv.second == 7);
+
+ continue;
+ } else if (kv.first == "test2") {
+ BOOST_CHECK(!seen_test2);
+ seen_test2 = true;
+
+ BOOST_CHECK(kv.second == "hello world");
+ }
+ }
+
+ BOOST_CHECK(seen_test1);
+ BOOST_CHECK(seen_test2);
+}
+
+BOOST_AUTO_TEST_CASE(remove)
+{
+ Dictionary::Ptr dictionary = new Dictionary();
+
+ dictionary->Set("test1", 7);
+ dictionary->Set("test2", "hello world");
+
+ BOOST_CHECK(dictionary->Contains("test1"));
+ BOOST_CHECK(dictionary->GetLength() == 2);
+
+ dictionary->Set("test1", Empty);
+
+ BOOST_CHECK(dictionary->Contains("test1"));
+ BOOST_CHECK(dictionary->GetLength() == 2);
+
+ dictionary->Remove("test1");
+
+ BOOST_CHECK(!dictionary->Contains("test1"));
+ BOOST_CHECK(dictionary->GetLength() == 1);
+
+ dictionary->Remove("test2");
+
+ BOOST_CHECK(!dictionary->Contains("test2"));
+ BOOST_CHECK(dictionary->GetLength() == 0);
+
+ dictionary->Set("test1", 7);
+ dictionary->Set("test2", "hello world");
+
+ {
+ ObjectLock olock(dictionary);
+
+ auto it = dictionary->Begin();
+ dictionary->Remove(it);
+ }
+
+ BOOST_CHECK(dictionary->GetLength() == 1);
+}
+
+BOOST_AUTO_TEST_CASE(clone)
+{
+ Dictionary::Ptr dictionary = new Dictionary();
+
+ dictionary->Set("test1", 7);
+ dictionary->Set("test2", "hello world");
+
+ Dictionary::Ptr clone = dictionary->ShallowClone();
+
+ BOOST_CHECK(dictionary != clone);
+
+ BOOST_CHECK(clone->GetLength() == 2);
+ BOOST_CHECK(clone->Get("test1") == 7);
+ BOOST_CHECK(clone->Get("test2") == "hello world");
+
+ clone->Set("test3", 5);
+ BOOST_CHECK(!dictionary->Contains("test3"));
+ BOOST_CHECK(dictionary->GetLength() == 2);
+
+ clone->Set("test2", "test");
+ BOOST_CHECK(dictionary->Get("test2") == "hello world");
+}
+
+BOOST_AUTO_TEST_CASE(json)
+{
+ Dictionary::Ptr dictionary = new Dictionary();
+
+ dictionary->Set("test1", 7);
+ dictionary->Set("test2", "hello world");
+
+ String json = JsonEncode(dictionary);
+ BOOST_CHECK(json.GetLength() > 0);
+ Dictionary::Ptr deserialized = JsonDecode(json);
+ BOOST_CHECK(deserialized->GetLength() == 2);
+ BOOST_CHECK(deserialized->Get("test1") == 7);
+ BOOST_CHECK(deserialized->Get("test2") == "hello world");
+}
+
+BOOST_AUTO_TEST_CASE(keys_ordered)
+{
+ Dictionary::Ptr dictionary = new Dictionary();
+
+ for (int i = 0; i < 100; i++) {
+ dictionary->Set(std::to_string(Utility::Random()), Utility::Random());
+ }
+
+ std::vector<String> keys = dictionary->GetKeys();
+ BOOST_CHECK(std::is_sorted(keys.begin(), keys.end()));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-fifo.cpp b/test/base-fifo.cpp
new file mode 100644
index 0000000..5ecf1ac
--- /dev/null
+++ b/test/base-fifo.cpp
@@ -0,0 +1,43 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/fifo.hpp"
+#include "base/objectlock.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_fifo)
+
+BOOST_AUTO_TEST_CASE(construct)
+{
+ FIFO::Ptr fifo = new FIFO();
+ BOOST_CHECK(fifo);
+ BOOST_CHECK(fifo->GetAvailableBytes() == 0);
+
+ fifo->Close();
+}
+
+BOOST_AUTO_TEST_CASE(io)
+{
+ FIFO::Ptr fifo = new FIFO();
+
+ fifo->Write("hello", 5);
+ BOOST_CHECK(fifo->GetAvailableBytes() == 5);
+
+ char buffer1[2];
+ fifo->Read(buffer1, 2, true);
+ BOOST_CHECK(memcmp(buffer1, "he", 2) == 0);
+ BOOST_CHECK(fifo->GetAvailableBytes() == 3);
+
+ char buffer2[5];
+ size_t rc = fifo->Read(buffer2, 5, true);
+ BOOST_CHECK(rc == 3);
+ BOOST_CHECK(memcmp(buffer2, "llo", 3) == 0);
+ BOOST_CHECK(fifo->GetAvailableBytes() == 0);
+
+ BOOST_CHECK(!fifo->IsEof());
+
+ fifo->Close();
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-json.cpp b/test/base-json.cpp
new file mode 100644
index 0000000..02bbebb
--- /dev/null
+++ b/test/base-json.cpp
@@ -0,0 +1,110 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/dictionary.hpp"
+#include "base/function.hpp"
+#include "base/namespace.hpp"
+#include "base/array.hpp"
+#include "base/objectlock.hpp"
+#include "base/json.hpp"
+#include <boost/algorithm/string/replace.hpp>
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_json)
+
+BOOST_AUTO_TEST_CASE(encode)
+{
+ Dictionary::Ptr input (new Dictionary({
+ { "array", new Array({ new Namespace() }) },
+ { "false", false },
+ { "float", -1.25 },
+ { "fx", new Function("<test>", []() {}) },
+ { "int", -42 },
+ { "null", Value() },
+ { "string", "LF\nTAB\tAUml\xC3\xA4Ill\xC3" },
+ { "true", true },
+ { "uint", 23u }
+ }));
+
+ String output (R"EOF({
+ "array": [
+ {}
+ ],
+ "false": false,
+ "float": -1.25,
+ "fx": "Object of type 'Function'",
+ "int": -42,
+ "null": null,
+ "string": "LF\nTAB\tAUml\u00e4Ill\ufffd",
+ "true": true,
+ "uint": 23
+}
+)EOF");
+
+ BOOST_CHECK(JsonEncode(input, true) == output);
+
+ boost::algorithm::replace_all(output, " ", "");
+ boost::algorithm::replace_all(output, "Objectoftype'Function'", "Object of type 'Function'");
+ boost::algorithm::replace_all(output, "\n", "");
+
+ BOOST_CHECK(JsonEncode(input, false) == output);
+}
+
+BOOST_AUTO_TEST_CASE(decode)
+{
+ String input (R"EOF({
+ "array": [
+ {}
+ ],
+ "false": false,
+ "float": -1.25,
+ "int": -42,
+ "null": null,
+ "string": "LF\nTAB\tAUmlIll",
+ "true": true,
+ "uint": 23
+}
+)EOF");
+
+ boost::algorithm::replace_all(input, "AUml", "AUml\xC3\xA4");
+ boost::algorithm::replace_all(input, "Ill", "Ill\xC3");
+
+ auto output ((Dictionary::Ptr)JsonDecode(input));
+ BOOST_CHECK(output->GetKeys() == std::vector<String>({"array", "false", "float", "int", "null", "string", "true", "uint"}));
+
+ auto array ((Array::Ptr)output->Get("array"));
+ BOOST_CHECK(array->GetLength() == 1u);
+
+ auto array0 ((Dictionary::Ptr)array->Get(0));
+ BOOST_CHECK(array0->GetKeys() == std::vector<String>());
+
+ auto fAlse (output->Get("false"));
+ BOOST_CHECK(fAlse.IsBoolean() && !fAlse.ToBool());
+
+ auto fLoat (output->Get("float"));
+ BOOST_CHECK(fLoat.IsNumber() && fLoat.Get<double>() == -1.25);
+
+ auto iNt (output->Get("int"));
+ BOOST_CHECK(iNt.IsNumber() && iNt.Get<double>() == -42.0);
+
+ BOOST_CHECK(output->Get("null").IsEmpty());
+
+ auto string (output->Get("string"));
+ BOOST_CHECK(string.IsString() && string.Get<String>() == "LF\nTAB\tAUml\xC3\xA4Ill\xEF\xBF\xBD");
+
+ auto tRue (output->Get("true"));
+ BOOST_CHECK(tRue.IsBoolean() && tRue.ToBool());
+
+ auto uint (output->Get("uint"));
+ BOOST_CHECK(uint.IsNumber() && uint.Get<double>() == 23.0);
+}
+
+BOOST_AUTO_TEST_CASE(invalid1)
+{
+ BOOST_CHECK_THROW(JsonDecode("\"1.7"), std::exception);
+ BOOST_CHECK_THROW(JsonDecode("{8: \"test\"}"), std::exception);
+ BOOST_CHECK_THROW(JsonDecode("{\"test\": \"test\""), std::exception);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-match.cpp b/test/base-match.cpp
new file mode 100644
index 0000000..7fad3cb
--- /dev/null
+++ b/test/base-match.cpp
@@ -0,0 +1,27 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/utility.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_match)
+
+BOOST_AUTO_TEST_CASE(tolong)
+{
+ BOOST_CHECK(Utility::Match("*", "hello"));
+ BOOST_CHECK(!Utility::Match("\\**", "hello"));
+ BOOST_CHECK(Utility::Match("\\**", "*ello"));
+ BOOST_CHECK(Utility::Match("?e*l?", "hello"));
+ BOOST_CHECK(Utility::Match("?e*l?", "helo"));
+ BOOST_CHECK(!Utility::Match("world", "hello"));
+ BOOST_CHECK(!Utility::Match("hee*", "hello"));
+ BOOST_CHECK(Utility::Match("he??o", "hello"));
+ BOOST_CHECK(Utility::Match("he?", "hel"));
+ BOOST_CHECK(Utility::Match("he*", "hello"));
+ BOOST_CHECK(Utility::Match("he*o", "heo"));
+ BOOST_CHECK(Utility::Match("he**o", "heo"));
+ BOOST_CHECK(Utility::Match("he**o", "hello"));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-netstring.cpp b/test/base-netstring.cpp
new file mode 100644
index 0000000..faa7eb5
--- /dev/null
+++ b/test/base-netstring.cpp
@@ -0,0 +1,25 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/netstring.hpp"
+#include "base/fifo.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_netstring)
+
+BOOST_AUTO_TEST_CASE(netstring)
+{
+ FIFO::Ptr fifo = new FIFO();
+
+ NetString::WriteStringToStream(fifo, "hello");
+
+ String s;
+ StreamReadContext src;
+ BOOST_CHECK(NetString::ReadStringFromStream(fifo, &s, src) == StatusNewItem);
+ BOOST_CHECK(s == "hello");
+
+ fifo->Close();
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-object-packer.cpp b/test/base-object-packer.cpp
new file mode 100644
index 0000000..b84705d
--- /dev/null
+++ b/test/base-object-packer.cpp
@@ -0,0 +1,264 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/object-packer.hpp"
+#include "base/value.hpp"
+#include "base/string.hpp"
+#include "base/array.hpp"
+#include "base/dictionary.hpp"
+#include <BoostTestTargetConfig.h>
+#include <climits>
+#include <initializer_list>
+#include <iomanip>
+#include <sstream>
+
+using namespace icinga;
+
+#if CHAR_MIN != 0
+union CharU2SConverter
+{
+ CharU2SConverter()
+ {
+ s = 0;
+ }
+
+ unsigned char u;
+ signed char s;
+};
+#endif
+
+/**
+ * Avoid implementation-defined overflows during unsigned to signed casts
+ */
+static inline char UIntToByte(unsigned i)
+{
+#if CHAR_MIN == 0
+ return i;
+#else
+ CharU2SConverter converter;
+
+ converter.u = i;
+ return converter.s;
+#endif
+}
+
+#if CHAR_MIN != 0
+union CharS2UConverter
+{
+ CharS2UConverter()
+ {
+ u = 0;
+ }
+
+ unsigned char u;
+ signed char s;
+};
+#endif
+
+/**
+ * Avoid implementation-defined underflows during signed to unsigned casts
+ */
+static inline unsigned ByteToUInt(char c)
+{
+#if CHAR_MIN == 0
+ return c;
+#else
+ CharS2UConverter converter;
+
+ converter.s = c;
+ return converter.u;
+#endif
+}
+
+/**
+ * Compare the expected output with the actual output
+ */
+static inline bool ComparePackObjectResult(const String& actualOutput, const std::initializer_list<int>& out)
+{
+ if (actualOutput.GetLength() != out.size())
+ return false;
+
+ auto actualOutputPos = actualOutput.Begin();
+ for (auto byte : out) {
+ if (*actualOutputPos != UIntToByte(byte))
+ return false;
+
+ ++actualOutputPos;
+ }
+
+ return true;
+}
+
+/**
+ * Pack the given input and compare with the expected output
+ */
+static inline bool AssertPackObjectResult(Value in, std::initializer_list<int> out)
+{
+ auto actualOutput = PackObject(in);
+ bool equal = ComparePackObjectResult(actualOutput, out);
+
+ if (!equal) {
+ std::ostringstream buf;
+ buf << std::setw(2) << std::setfill('0') << std::setbase(16);
+
+ buf << "--- ";
+ for (int c : out) {
+ buf << c;
+ }
+ buf << std::endl;
+
+ buf << "+++ ";
+ for (char c : actualOutput) {
+ buf << ByteToUInt(c);
+ }
+ buf << std::endl;
+
+ BOOST_TEST_MESSAGE(buf.str());
+ }
+
+ return equal;
+}
+
+BOOST_AUTO_TEST_SUITE(base_object_packer)
+
+BOOST_AUTO_TEST_CASE(pack_null)
+{
+ BOOST_CHECK(AssertPackObjectResult(Empty, {0}));
+}
+
+BOOST_AUTO_TEST_CASE(pack_false)
+{
+ BOOST_CHECK(AssertPackObjectResult(false, {1}));
+}
+
+BOOST_AUTO_TEST_CASE(pack_true)
+{
+ BOOST_CHECK(AssertPackObjectResult(true, {2}));
+}
+
+BOOST_AUTO_TEST_CASE(pack_number)
+{
+ BOOST_CHECK(AssertPackObjectResult(42.125, {
+ // type
+ 3,
+ // IEEE 754
+ 64, 69, 16, 0, 0, 0, 0, 0
+ }));
+}
+
+BOOST_AUTO_TEST_CASE(pack_string)
+{
+ BOOST_CHECK(AssertPackObjectResult(
+ String(
+ // ASCII (1 to 127)
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
+ "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
+ "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
+ "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
+ "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
+ "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
+ "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
+ // some keyboard-independent non-ASCII unicode characters
+ "áéíóú"
+ ),
+ {
+ // type
+ 4,
+ // length
+ 0, 0, 0, 0, 0, 0, 0, 137,
+ // ASCII
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
+ 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
+ 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
+ // UTF-8
+ 195, 161, 195, 169, 195, 173, 195, 179, 195, 186
+ }
+ ));
+}
+
+BOOST_AUTO_TEST_CASE(pack_array)
+{
+ BOOST_CHECK(AssertPackObjectResult(
+ (Array::Ptr)new Array({Empty, false, true, 42.125, "foobar"}),
+ {
+ // type
+ 5,
+ // length
+ 0, 0, 0, 0, 0, 0, 0, 5,
+ // Empty
+ 0,
+ // false
+ 1,
+ // true
+ 2,
+ // 42.125
+ 3,
+ 64, 69, 16, 0, 0, 0, 0, 0,
+ // "foobar"
+ 4,
+ 0, 0, 0, 0, 0, 0, 0, 6,
+ 102, 111, 111, 98, 97, 114
+ }
+ ));
+}
+
+BOOST_AUTO_TEST_CASE(pack_object)
+{
+ BOOST_CHECK(AssertPackObjectResult(
+ (Dictionary::Ptr)new Dictionary({
+ {"null", Empty},
+ {"false", false},
+ {"true", true},
+ {"42.125", 42.125},
+ {"foobar", "foobar"},
+ {"[]", (Array::Ptr)new Array()}
+ }),
+ {
+ // type
+ 6,
+ // length
+ 0, 0, 0, 0, 0, 0, 0, 6,
+ // "42.125"
+ 0, 0, 0, 0, 0, 0, 0, 6,
+ 52, 50, 46, 49, 50, 53,
+ // 42.125
+ 3,
+ 64, 69, 16, 0, 0, 0, 0, 0,
+ // "[]"
+ 0, 0, 0, 0, 0, 0, 0, 2,
+ 91, 93,
+ // (Array::Ptr)new Array()
+ 5,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ // "false"
+ 0, 0, 0, 0, 0, 0, 0, 5,
+ 102, 97, 108, 115, 101,
+ // false
+ 1,
+ // "foobar"
+ 0, 0, 0, 0, 0, 0, 0, 6,
+ 102, 111, 111, 98, 97, 114,
+ // "foobar"
+ 4,
+ 0, 0, 0, 0, 0, 0, 0, 6,
+ 102, 111, 111, 98, 97, 114,
+ // "null"
+ 0, 0, 0, 0, 0, 0, 0, 4,
+ 110, 117, 108, 108,
+ // Empty
+ 0,
+ // "true"
+ 0, 0, 0, 0, 0, 0, 0, 4,
+ 116, 114, 117, 101,
+ // true
+ 2
+ }
+ ));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-object.cpp b/test/base-object.cpp
new file mode 100644
index 0000000..fb3c2b3
--- /dev/null
+++ b/test/base-object.cpp
@@ -0,0 +1,39 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/object.hpp"
+#include "base/value.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+class TestObject : public Object
+{
+public:
+ DECLARE_PTR_TYPEDEFS(TestObject);
+
+ TestObject::Ptr GetTestRef()
+ {
+ return this;
+ }
+};
+
+BOOST_AUTO_TEST_SUITE(base_object)
+
+BOOST_AUTO_TEST_CASE(construct)
+{
+ Object::Ptr tobject = new TestObject();
+ BOOST_CHECK(tobject);
+}
+
+BOOST_AUTO_TEST_CASE(getself)
+{
+ TestObject::Ptr tobject = new TestObject();
+ TestObject::Ptr tobject_self = tobject->GetTestRef();
+ BOOST_CHECK(tobject == tobject_self);
+
+ Value vobject = tobject;
+ BOOST_CHECK(!vobject.IsEmpty());
+ BOOST_CHECK(vobject.IsObjectType<TestObject>());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-serialize.cpp b/test/base-serialize.cpp
new file mode 100644
index 0000000..3293f86
--- /dev/null
+++ b/test/base-serialize.cpp
@@ -0,0 +1,68 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/perfdatavalue.hpp"
+#include "base/dictionary.hpp"
+#include "base/objectlock.hpp"
+#include "base/serializer.hpp"
+#include "base/array.hpp"
+#include "base/dictionary.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_serialize)
+
+BOOST_AUTO_TEST_CASE(scalar)
+{
+ BOOST_CHECK(Deserialize(Serialize(7)) == 7);
+ BOOST_CHECK(Deserialize(Serialize(7.3)) == 7.3);
+ BOOST_CHECK(Deserialize(Serialize(Empty)) == Empty);
+ BOOST_CHECK(Deserialize(Serialize("hello")) == "hello");
+}
+
+BOOST_AUTO_TEST_CASE(array)
+{
+ Array::Ptr array = new Array();
+ array->Add(7);
+ array->Add(7.3);
+ array->Add(Empty);
+ array->Add("hello");
+
+ Array::Ptr result = Deserialize(Serialize(array));
+
+ BOOST_CHECK(result->GetLength() == array->GetLength());
+
+ BOOST_CHECK(result->Get(0) == 7);
+ BOOST_CHECK(result->Get(1) == 7.3);
+ BOOST_CHECK(result->Get(2) == Empty);
+ BOOST_CHECK(result->Get(3) == "hello");
+}
+
+BOOST_AUTO_TEST_CASE(dictionary)
+{
+ Dictionary::Ptr dict = new Dictionary();
+ dict->Set("k1", 7);
+ dict->Set("k2", 7.3);
+ dict->Set("k3", Empty);
+ dict->Set("k4", "hello");
+
+ Dictionary::Ptr result = Deserialize(Serialize(dict));
+
+ BOOST_CHECK(result->GetLength() == dict->GetLength());
+
+ BOOST_CHECK(result->Get("k1") == 7);
+ BOOST_CHECK(result->Get("k2") == 7.3);
+ BOOST_CHECK(result->Get("k3") == Empty);
+ BOOST_CHECK(result->Get("k4") == "hello");
+}
+
+BOOST_AUTO_TEST_CASE(object)
+{
+ PerfdataValue::Ptr pdv = new PerfdataValue("size", 100, true, "bytes");
+
+ PerfdataValue::Ptr result = Deserialize(Serialize(pdv));
+
+ BOOST_CHECK(result->GetValue() == pdv->GetValue());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-shellescape.cpp b/test/base-shellescape.cpp
new file mode 100644
index 0000000..1eb0eae
--- /dev/null
+++ b/test/base-shellescape.cpp
@@ -0,0 +1,32 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/utility.hpp"
+#include <BoostTestTargetConfig.h>
+#include <iostream>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_shellescape)
+
+BOOST_AUTO_TEST_CASE(escape_basic)
+{
+#ifdef _WIN32
+ BOOST_CHECK(Utility::EscapeShellCmd("%PATH%") == "^%PATH^%");
+#else /* _WIN32 */
+ BOOST_CHECK(Utility::EscapeShellCmd("$PATH") == "\\$PATH");
+ BOOST_CHECK(Utility::EscapeShellCmd("\\$PATH") == "\\\\\\$PATH");
+#endif /* _WIN32 */
+}
+
+BOOST_AUTO_TEST_CASE(escape_quoted)
+{
+#ifdef _WIN32
+ BOOST_CHECK(Utility::EscapeShellCmd("'hello'") == "^'hello^'");
+ BOOST_CHECK(Utility::EscapeShellCmd("\"hello\"") == "^\"hello^\"");
+#else /* _WIN32 */
+ BOOST_CHECK(Utility::EscapeShellCmd("'hello'") == "'hello'");
+ BOOST_CHECK(Utility::EscapeShellCmd("'hello") == "\\'hello");
+#endif /* _WIN32 */
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-stacktrace.cpp b/test/base-stacktrace.cpp
new file mode 100644
index 0000000..f0b87e2
--- /dev/null
+++ b/test/base-stacktrace.cpp
@@ -0,0 +1,72 @@
+/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+#include "base/stacktrace.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+
+/* If you are reading this, you are probably doing so because this test case just failed. This might happen as it
+ * heavily depends on platform and compiler behavior. There are two likely causes why this could break:
+ *
+ * - Your compiler found new ways to optimize the functions that are called to create a stack, even though we tried
+ * to disable optimizations using #pragmas for some compilers. If you know a way to disable (more) optimizations for
+ * your compiler, you can try if this helps.
+ *
+ * - Boost fails to resolve symbol names as we've already seen on some platforms. In this case, you can try again
+ * passing the additional flag `-DICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS=ON` to CMake and see if this helps.
+ *
+ * In any case, please report a bug. If you run `make CTEST_OUTPUT_ON_FAILURE=1 test`, the stack trace in question
+ * should be printed. If it looks somewhat meaningful, you can probably ignore a failure of this test case.
+ */
+
+#pragma GCC push_options
+#pragma GCC optimize ("O0")
+#pragma clang optimize off
+#ifdef _MSVC_VER
+#pragma optimize("", off)
+#endif /* _MSVC_VER */
+
+BOOST_AUTO_TEST_SUITE(base_stacktrace)
+
+[[gnu::noinline]]
+void stack_test_func_b()
+{
+ boost::stacktrace::stacktrace stack;
+ std::ostringstream obuf;
+ obuf << StackTraceFormatter(stack);
+ std::string result = obuf.str();
+ BOOST_CHECK_MESSAGE(!result.empty(), "stack trace must not be empty");
+ size_t pos_a = result.find("stack_test_func_a");
+ size_t pos_b = result.find("stack_test_func_b");
+ BOOST_CHECK_MESSAGE(pos_a != std::string::npos, "'stack_test_func_a' not found\n\n" << result);
+ BOOST_CHECK_MESSAGE(pos_b != std::string::npos, "'stack_test_func_b' not found\n\n" << result);
+ BOOST_CHECK_MESSAGE(pos_a > pos_b, "'stack_test_func_a' must appear after 'stack_test_func_b'\n\n" << result);
+}
+
+[[gnu::noinline]]
+void stack_test_func_a()
+{
+ boost::stacktrace::stacktrace stack;
+ std::ostringstream obuf;
+ obuf << StackTraceFormatter(stack);
+ std::string result = obuf.str();
+ BOOST_CHECK_MESSAGE(!result.empty(), "stack trace must not be empty");
+ size_t pos_a = result.find("stack_test_func_a");
+ BOOST_CHECK_MESSAGE(pos_a != std::string::npos, "'stack_test_func_a' not found\n\n" << result);
+
+ stack_test_func_b();
+}
+
+BOOST_AUTO_TEST_CASE(stacktrace)
+{
+ stack_test_func_a();
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+#pragma GCC pop_options
+#pragma clang optimize on
+#ifdef _MSVC_VER
+#pragma optimize("", on)
+#endif /* _MSVC_VER */
diff --git a/test/base-stream.cpp b/test/base-stream.cpp
new file mode 100644
index 0000000..34a93a2
--- /dev/null
+++ b/test/base-stream.cpp
@@ -0,0 +1,39 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/stdiostream.hpp"
+#include "base/string.hpp"
+#include <BoostTestTargetConfig.h>
+#include <sstream>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_stream)
+
+BOOST_AUTO_TEST_CASE(readline_stdio)
+{
+ std::stringstream msgbuf;
+ msgbuf << "Hello\nWorld\n\n";
+
+ StdioStream::Ptr stdstream = new StdioStream(&msgbuf, false);
+
+ StreamReadContext rlc;
+
+ String line;
+ BOOST_CHECK(stdstream->ReadLine(&line, rlc) == StatusNewItem);
+ BOOST_CHECK(line == "Hello");
+
+ BOOST_CHECK(stdstream->ReadLine(&line, rlc) == StatusNewItem);
+ BOOST_CHECK(line == "World");
+
+ BOOST_CHECK(stdstream->ReadLine(&line, rlc) == StatusNewItem);
+ BOOST_CHECK(line == "");
+
+ BOOST_CHECK(stdstream->ReadLine(&line, rlc) == StatusNewItem);
+ BOOST_CHECK(line == "");
+
+ BOOST_CHECK(stdstream->ReadLine(&line, rlc) == StatusEof);
+
+ stdstream->Close();
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-string.cpp b/test/base-string.cpp
new file mode 100644
index 0000000..835b1a6
--- /dev/null
+++ b/test/base-string.cpp
@@ -0,0 +1,104 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/string.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_string)
+
+BOOST_AUTO_TEST_CASE(construct)
+{
+ BOOST_CHECK(String() == "");
+ BOOST_CHECK(String(5, 'n') == "nnnnn");
+}
+
+BOOST_AUTO_TEST_CASE(equal)
+{
+ BOOST_CHECK(String("hello") == String("hello"));
+ BOOST_CHECK("hello" == String("hello"));
+ BOOST_CHECK(String("hello") == String("hello"));
+
+ BOOST_CHECK(String("hello") != String("helloworld"));
+ BOOST_CHECK("hello" != String("helloworld"));
+ BOOST_CHECK(String("hello") != "helloworld");
+}
+
+BOOST_AUTO_TEST_CASE(clear)
+{
+ String s = "hello";
+ s.Clear();
+ BOOST_CHECK(s == "");
+ BOOST_CHECK(s.IsEmpty());
+}
+
+BOOST_AUTO_TEST_CASE(append)
+{
+ String s;
+ s += "he";
+ s += String("ll");
+ s += 'o';
+
+ BOOST_CHECK(s == "hello");
+}
+
+BOOST_AUTO_TEST_CASE(trim)
+{
+ String s1 = "hello";
+ BOOST_CHECK(s1.Trim() == "hello");
+
+ String s2 = " hello";
+ BOOST_CHECK(s2.Trim() == "hello");
+
+ String s3 = "hello ";
+ BOOST_CHECK(s3.Trim() == "hello");
+
+ String s4 = " hello ";
+ BOOST_CHECK(s4.Trim() == "hello");
+}
+
+BOOST_AUTO_TEST_CASE(contains)
+{
+ String s1 = "hello world";
+ String s2 = "hello";
+ BOOST_CHECK(s1.Contains(s2));
+
+ String s3 = " hello world ";
+ String s4 = " hello";
+ BOOST_CHECK(s3.Contains(s4));
+
+ String s5 = " hello world ";
+ String s6 = "world ";
+ BOOST_CHECK(s5.Contains(s6));
+}
+
+BOOST_AUTO_TEST_CASE(replace)
+{
+ String s = "hello";
+
+ s.Replace(0, 2, "x");
+ BOOST_CHECK(s == "xllo");
+}
+
+BOOST_AUTO_TEST_CASE(index)
+{
+ String s = "hello";
+ BOOST_CHECK(s[0] == 'h');
+
+ s[0] = 'x';
+ BOOST_CHECK(s == "xello");
+
+ for (char& ch : s) {
+ ch = 'y';
+ }
+ BOOST_CHECK(s == "yyyyy");
+}
+
+BOOST_AUTO_TEST_CASE(find)
+{
+ String s = "hello";
+ BOOST_CHECK(s.Find("ll") == 2);
+ BOOST_CHECK(s.FindFirstOf("xl") == 2);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-timer.cpp b/test/base-timer.cpp
new file mode 100644
index 0000000..696a474
--- /dev/null
+++ b/test/base-timer.cpp
@@ -0,0 +1,61 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/timer.hpp"
+#include "base/utility.hpp"
+#include "base/application.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_timer)
+
+BOOST_AUTO_TEST_CASE(construct)
+{
+ Timer::Ptr timer = Timer::Create();
+ BOOST_CHECK(timer);
+}
+
+BOOST_AUTO_TEST_CASE(interval)
+{
+ Timer::Ptr timer = Timer::Create();
+ timer->SetInterval(1.5);
+ BOOST_CHECK(timer->GetInterval() == 1.5);
+}
+
+int counter = 0;
+
+static void Callback(const Timer * const&)
+{
+ counter++;
+}
+
+BOOST_AUTO_TEST_CASE(invoke)
+{
+ Timer::Ptr timer = Timer::Create();
+ timer->OnTimerExpired.connect(&Callback);
+ timer->SetInterval(1);
+
+ counter = 0;
+ timer->Start();
+ Utility::Sleep(5.5);
+ timer->Stop();
+
+ BOOST_CHECK(counter >= 4 && counter <= 6);
+}
+
+BOOST_AUTO_TEST_CASE(scope)
+{
+ Timer::Ptr timer = Timer::Create();
+ timer->OnTimerExpired.connect(&Callback);
+ timer->SetInterval(1);
+
+ counter = 0;
+ timer->Start();
+ Utility::Sleep(5.5);
+ timer.reset();
+ Utility::Sleep(5.5);
+
+ BOOST_CHECK(counter >= 4 && counter <= 6);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-tlsutility.cpp b/test/base-tlsutility.cpp
new file mode 100644
index 0000000..2e611e4
--- /dev/null
+++ b/test/base-tlsutility.cpp
@@ -0,0 +1,135 @@
+/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+#include "base/tlsutility.hpp"
+#include <BoostTestTargetConfig.h>
+#include <functional>
+#include <memory>
+#include <openssl/asn1.h>
+#include <openssl/bn.h>
+#include <openssl/evp.h>
+#include <openssl/obj_mac.h>
+#include <openssl/rsa.h>
+#include <openssl/x509.h>
+#include <utility>
+#include <vector>
+
+using namespace icinga;
+
+static EVP_PKEY* GenKeypair()
+{
+ InitializeOpenSSL();
+
+ auto e (BN_new());
+ BOOST_REQUIRE(e);
+
+ auto rsa (RSA_new());
+ BOOST_REQUIRE(rsa);
+
+ auto key (EVP_PKEY_new());
+ BOOST_REQUIRE(key);
+
+ BOOST_REQUIRE(BN_set_word(e, RSA_F4));
+ BOOST_REQUIRE(RSA_generate_key_ex(rsa, 4096, e, nullptr));
+ BOOST_REQUIRE(EVP_PKEY_assign_RSA(key, rsa));
+
+ return key;
+}
+
+static std::shared_ptr<X509> MakeCert(const char* issuer, EVP_PKEY* signer, const char* subject, EVP_PKEY* pubkey, std::function<void(ASN1_TIME*, ASN1_TIME*)> setTimes)
+{
+ auto cert (X509_new());
+ BOOST_REQUIRE(cert);
+
+ auto serial (BN_new());
+ BOOST_REQUIRE(serial);
+
+ BOOST_REQUIRE(X509_set_version(cert, 0x2));
+ BOOST_REQUIRE(BN_to_ASN1_INTEGER(serial, X509_get_serialNumber(cert)));
+ BOOST_REQUIRE(X509_NAME_add_entry_by_NID(X509_get_issuer_name(cert), NID_commonName, MBSTRING_ASC, (unsigned char*)issuer, -1, -1, 0));
+ setTimes(X509_get_notBefore(cert), X509_get_notAfter(cert));
+ BOOST_REQUIRE(X509_NAME_add_entry_by_NID(X509_get_subject_name(cert), NID_commonName, MBSTRING_ASC, (unsigned char*)subject, -1, -1, 0));
+ BOOST_REQUIRE(X509_set_pubkey(cert, pubkey));
+ BOOST_REQUIRE(X509_sign(cert, signer, EVP_sha256()));
+
+ return std::shared_ptr<X509>(cert, X509_free);
+}
+
+static const long l_2016 = 1480000000; // Thu Nov 24 15:06:40 UTC 2016
+static const long l_2017 = 1490000000; // Mon Mar 20 08:53:20 UTC 2017
+
+BOOST_AUTO_TEST_SUITE(base_tlsutility)
+
+BOOST_AUTO_TEST_CASE(sha1)
+{
+ std::string allchars;
+ for (size_t i = 0; i < 256; i++) {
+ allchars.push_back(i);
+ }
+
+ std::vector<std::pair<std::string,std::string>> testdata = {
+ {"", "da39a3ee5e6b4b0d3255bfef95601890afd80709"},
+ {"icinga", "f172c5e9e4d840a55356882a2b644846b302b216"},
+ {"Icinga", "b3bdae77f60d9065f6152c7e3bbd351fa65e6fab"},
+ {"ICINGA", "335da1d814abeef09b4623e2ce5169140c267a39"},
+ {"#rX|wlcM:.8)uVmxz", "99dc4d34caf36c6d6b08404135f1a7286211be1e"},
+ {"AgbM;Z8Tz1!Im,kecZWs", "aa793bef1ca307012980ae5ae046b7e929f6ed99"},
+ {"yLUA4vKQ~24W}ahI;i?NLLS", "5e1a5ee3bd9fae5150681ef656ad43d9cb8e7005"},
+ {allchars, "4916d6bdb7f78e6803698cab32d1586ea457dfc8"},
+ };
+
+ for (const auto& p : testdata) {
+ const auto& input = p.first;
+ const auto& expected = p.second;
+ auto output = SHA1(input);
+ BOOST_CHECK_MESSAGE(output == expected, "SHA1('" << input << "') should be " << expected << ", got " << output);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(iscauptodate_ok)
+{
+ auto key (GenKeypair());
+
+ BOOST_CHECK(IsCaUptodate(MakeCert("Icinga CA", key, "Icinga CA", key, [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
+ BOOST_REQUIRE(X509_gmtime_adj(notBefore, 0));
+ BOOST_REQUIRE(X509_gmtime_adj(notAfter, LEAF_VALID_FOR + 60 * 60));
+ }).get()));
+}
+
+BOOST_AUTO_TEST_CASE(iscauptodate_expiring)
+{
+ auto key (GenKeypair());
+
+ BOOST_CHECK(!IsCaUptodate(MakeCert("Icinga CA", key, "Icinga CA", key, [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
+ BOOST_REQUIRE(X509_gmtime_adj(notBefore, 0));
+ BOOST_REQUIRE(X509_gmtime_adj(notAfter, LEAF_VALID_FOR - 60 * 60));
+ }).get()));
+}
+
+BOOST_AUTO_TEST_CASE(iscertuptodate_ok)
+{
+ BOOST_CHECK(IsCertUptodate(MakeCert("Icinga CA", GenKeypair(), "example.com", GenKeypair(), [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
+ time_t epoch = 0;
+ BOOST_REQUIRE(X509_time_adj(notBefore, l_2017, &epoch));
+ BOOST_REQUIRE(X509_gmtime_adj(notAfter, RENEW_THRESHOLD + 60 * 60));
+ })));
+}
+
+BOOST_AUTO_TEST_CASE(iscertuptodate_expiring)
+{
+ BOOST_CHECK(!IsCertUptodate(MakeCert("Icinga CA", GenKeypair(), "example.com", GenKeypair(), [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
+ time_t epoch = 0;
+ BOOST_REQUIRE(X509_time_adj(notBefore, l_2017, &epoch));
+ BOOST_REQUIRE(X509_gmtime_adj(notAfter, RENEW_THRESHOLD - 60 * 60));
+ })));
+}
+
+BOOST_AUTO_TEST_CASE(iscertuptodate_old)
+{
+ BOOST_CHECK(!IsCertUptodate(MakeCert("Icinga CA", GenKeypair(), "example.com", GenKeypair(), [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
+ time_t epoch = 0;
+ BOOST_REQUIRE(X509_time_adj(notBefore, l_2016, &epoch));
+ BOOST_REQUIRE(X509_gmtime_adj(notAfter, RENEW_THRESHOLD + 60 * 60));
+ })));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-type.cpp b/test/base-type.cpp
new file mode 100644
index 0000000..21bcf43
--- /dev/null
+++ b/test/base-type.cpp
@@ -0,0 +1,47 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/perfdatavalue.hpp"
+#include "base/dictionary.hpp"
+#include "base/objectlock.hpp"
+#include "base/application.hpp"
+#include "base/type.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_type)
+
+BOOST_AUTO_TEST_CASE(gettype)
+{
+ Type::Ptr t = Type::GetByName("Application");
+
+ BOOST_CHECK(t);
+}
+
+BOOST_AUTO_TEST_CASE(assign)
+{
+ Type::Ptr t1 = Type::GetByName("Application");
+ Type::Ptr t2 = Type::GetByName("ConfigObject");
+
+ BOOST_CHECK(t1->IsAssignableFrom(t1));
+ BOOST_CHECK(t2->IsAssignableFrom(t1));
+ BOOST_CHECK(!t1->IsAssignableFrom(t2));
+}
+
+BOOST_AUTO_TEST_CASE(byname)
+{
+ Type::Ptr t = Type::GetByName("Application");
+
+ BOOST_CHECK(t);
+}
+
+BOOST_AUTO_TEST_CASE(instantiate)
+{
+ Type::Ptr t = Type::GetByName("PerfdataValue");
+
+ Object::Ptr p = t->Instantiate(std::vector<Value>());
+
+ BOOST_CHECK(p);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-utility.cpp b/test/base-utility.cpp
new file mode 100644
index 0000000..65222e1
--- /dev/null
+++ b/test/base-utility.cpp
@@ -0,0 +1,138 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/utility.hpp"
+#include <chrono>
+#include <BoostTestTargetConfig.h>
+
+#ifdef _WIN32
+# include <windows.h>
+# include <shellapi.h>
+#endif /* _WIN32 */
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_utility)
+
+BOOST_AUTO_TEST_CASE(parse_version)
+{
+ BOOST_CHECK(Utility::ParseVersion("2.11.0-0.rc1.1") == "2.11.0");
+ BOOST_CHECK(Utility::ParseVersion("v2.10.5") == "2.10.5");
+ BOOST_CHECK(Utility::ParseVersion("r2.11.1") == "2.11.1");
+ BOOST_CHECK(Utility::ParseVersion("v2.11.0-rc1-58-g7c1f716da") == "2.11.0");
+
+ BOOST_CHECK(Utility::ParseVersion("v2.11butactually3.0") == "v2.11butactually3.0");
+}
+
+BOOST_AUTO_TEST_CASE(compare_version)
+{
+ BOOST_CHECK(Utility::CompareVersion("2.10.5", Utility::ParseVersion("v2.10.4")) < 0);
+ BOOST_CHECK(Utility::CompareVersion("2.11.0", Utility::ParseVersion("2.11.0-0")) == 0);
+ BOOST_CHECK(Utility::CompareVersion("2.10.5", Utility::ParseVersion("2.11.0-0.rc1.1")) > 0);
+}
+
+BOOST_AUTO_TEST_CASE(comparepasswords_works)
+{
+ BOOST_CHECK(Utility::ComparePasswords("", ""));
+
+ BOOST_CHECK(!Utility::ComparePasswords("x", ""));
+ BOOST_CHECK(!Utility::ComparePasswords("", "x"));
+
+ BOOST_CHECK(Utility::ComparePasswords("x", "x"));
+ BOOST_CHECK(!Utility::ComparePasswords("x", "y"));
+
+ BOOST_CHECK(Utility::ComparePasswords("abcd", "abcd"));
+ BOOST_CHECK(!Utility::ComparePasswords("abc", "abcd"));
+ BOOST_CHECK(!Utility::ComparePasswords("abcde", "abcd"));
+}
+
+BOOST_AUTO_TEST_CASE(comparepasswords_issafe)
+{
+ using std::chrono::duration_cast;
+ using std::chrono::microseconds;
+ using std::chrono::steady_clock;
+
+ String a, b;
+
+ a.Append(200000001, 'a');
+ b.Append(200000002, 'b');
+
+ auto start1 (steady_clock::now());
+
+ Utility::ComparePasswords(a, a);
+
+ auto duration1 (steady_clock::now() - start1);
+
+ auto start2 (steady_clock::now());
+
+ Utility::ComparePasswords(a, b);
+
+ auto duration2 (steady_clock::now() - start2);
+
+ double diff = (double)duration_cast<microseconds>(duration1).count() / (double)duration_cast<microseconds>(duration2).count();
+ BOOST_WARN(0.9 <= diff && diff <= 1.1);
+}
+
+BOOST_AUTO_TEST_CASE(validateutf8)
+{
+ BOOST_CHECK(Utility::ValidateUTF8("") == "");
+ BOOST_CHECK(Utility::ValidateUTF8("a") == "a");
+ BOOST_CHECK(Utility::ValidateUTF8("\xC3") == "\xEF\xBF\xBD");
+ BOOST_CHECK(Utility::ValidateUTF8("\xC3\xA4") == "\xC3\xA4");
+}
+
+BOOST_AUTO_TEST_CASE(EscapeCreateProcessArg)
+{
+#ifdef _WIN32
+ using convert = std::wstring_convert<std::codecvt<wchar_t, char, std::mbstate_t>, wchar_t>;
+
+ std::vector<std::string> testdata = {
+ R"(foobar)",
+ R"(foo bar)",
+ R"(foo"bar)",
+ R"("foo bar")",
+ R"(" \" \\" \\\" \\\\")",
+ R"( !"#$$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~ " \" \\" \\\" \\\\")",
+ "'foo\nbar'",
+ };
+
+ for (const auto& t : testdata) {
+ // Prepend some fake exec name as the first argument is handled differently.
+ std::string escaped = "some.exe " + Utility::EscapeCreateProcessArg(t);
+ int argc;
+ std::shared_ptr<LPWSTR> argv(CommandLineToArgvW(convert{}.from_bytes(escaped.c_str()).data(), &argc), LocalFree);
+ BOOST_CHECK_MESSAGE(argv != nullptr, "CommandLineToArgvW() should not return nullptr for " << t);
+ BOOST_CHECK_MESSAGE(argc == 2, "CommandLineToArgvW() should find 2 arguments for " << t);
+ if (argc >= 2) {
+ std::string unescaped = convert{}.to_bytes(argv.get()[1]);
+ BOOST_CHECK_MESSAGE(unescaped == t,
+ "CommandLineToArgvW() should return original value for " << t << " (got: " << unescaped << ")");
+ }
+ }
+#endif /* _WIN32 */
+}
+
+BOOST_AUTO_TEST_CASE(TruncateUsingHash)
+{
+ /*
+ * Note: be careful when changing the output of TruncateUsingHash as it is used to derive file names that should not
+ * change between versions or would need special handling if they do (/var/lib/icinga2/api/packages/_api).
+ */
+
+ /* minimum allowed value for maxLength template parameter */
+ BOOST_CHECK_EQUAL(Utility::TruncateUsingHash<44>(std::string(64, 'a')),
+ "a...0098ba824b5c16427bd7a1122a5a442a25ec644d");
+
+ BOOST_CHECK_EQUAL(Utility::TruncateUsingHash<80>(std::string(100, 'a')),
+ std::string(37, 'a') + "...7f9000257a4918d7072655ea468540cdcbd42e0c");
+
+ /* short enough values should not be truncated */
+ BOOST_CHECK_EQUAL(Utility::TruncateUsingHash<80>(""), "");
+ BOOST_CHECK_EQUAL(Utility::TruncateUsingHash<80>(std::string(60, 'a')), std::string(60, 'a'));
+ BOOST_CHECK_EQUAL(Utility::TruncateUsingHash<80>(std::string(79, 'a')), std::string(79, 'a'));
+
+ /* inputs of maxLength are hashed to avoid collisions */
+ BOOST_CHECK_EQUAL(Utility::TruncateUsingHash<80>(std::string(80, 'a')),
+ std::string(37, 'a') + "...86f33652fcffd7fa1443e246dd34fe5d00e25ffd");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/base-value.cpp b/test/base-value.cpp
new file mode 100644
index 0000000..c53b8e9
--- /dev/null
+++ b/test/base-value.cpp
@@ -0,0 +1,53 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/value.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(base_value)
+
+BOOST_AUTO_TEST_CASE(scalar)
+{
+ Value v;
+
+ v = 3;
+ BOOST_CHECK(v.IsScalar());
+
+ v = "hello";
+ BOOST_CHECK(v.IsScalar());
+
+ v = Empty;
+ BOOST_CHECK(!v.IsScalar());
+}
+
+BOOST_AUTO_TEST_CASE(convert)
+{
+ Value v;
+ BOOST_CHECK(v.IsEmpty());
+ BOOST_CHECK(v == "");
+ BOOST_CHECK(static_cast<double>(v) == 0);
+ BOOST_CHECK(!v.IsScalar());
+ BOOST_CHECK(!v.IsObjectType<Object>());
+
+ BOOST_CHECK(v + "hello" == "hello");
+ BOOST_CHECK("hello" + v == "hello");
+}
+
+BOOST_AUTO_TEST_CASE(format)
+{
+ Value v = 3;
+
+ std::ostringstream obuf;
+ obuf << v;
+
+ BOOST_CHECK(obuf.str() == "3");
+
+ std::istringstream ibuf("3");
+ ibuf >> v;
+
+ BOOST_CHECK_MESSAGE(v.IsString(), "type of v should be String (is " << v.GetTypeName() << ")");
+ BOOST_CHECK_MESSAGE(v == "3", "v should be '3' (is '" << v << "')");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/config-apply.cpp b/test/config-apply.cpp
new file mode 100644
index 0000000..c207ddd
--- /dev/null
+++ b/test/config-apply.cpp
@@ -0,0 +1,251 @@
+/* Icinga 2 | (c) 2023 Icinga GmbH | GPLv2+ */
+
+#include "config/applyrule.hpp"
+#include "config/configcompiler.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+static Expression* RequireActualExpression(const std::unique_ptr<Expression>& compiledExpression)
+{
+ BOOST_REQUIRE_NE(compiledExpression.get(), nullptr);
+
+ auto dict (dynamic_cast<DictExpression*>(compiledExpression.get()));
+ BOOST_REQUIRE_NE(dict, nullptr);
+
+ auto& subex (dict->GetExpressions());
+ BOOST_REQUIRE_EQUAL(subex.size(), 1u);
+
+ auto sub0 (subex.at(0).get());
+ BOOST_REQUIRE_NE(sub0, nullptr);
+
+ return sub0;
+}
+
+template<>
+struct boost::test_tools::tt_detail::print_log_value<std::pair<String, String>>
+{
+ inline void operator()(std::ostream& os, const std::pair<String, String>& hs)
+ {
+ os << hs.first << "!" << hs.second;
+ }
+};
+
+static void GetTargetHostsHelper(
+ const String& filter, const Dictionary::Ptr& constants, bool targeted, const std::vector<String>& hosts = {}
+)
+{
+ auto compiled (ConfigCompiler::CompileText("<test>", filter));
+ auto expr (RequireActualExpression(compiled));
+ std::vector<const String*> actualHosts;
+
+ BOOST_CHECK_EQUAL(ApplyRule::GetTargetHosts(expr, actualHosts, constants), targeted);
+
+ if (targeted) {
+ std::vector<String> actualHostNames;
+
+ actualHostNames.reserve(actualHosts.size());
+
+ for (auto h : actualHosts) {
+ actualHostNames.emplace_back(*h);
+ }
+
+ BOOST_CHECK_EQUAL_COLLECTIONS(actualHostNames.begin(), actualHostNames.end(), hosts.begin(), hosts.end());
+ }
+}
+
+static void GetTargetServicesHelper(
+ const String& filter, const Dictionary::Ptr& constants, bool targeted, const std::vector<std::pair<String, String>>& services = {}
+)
+{
+ auto compiled (ConfigCompiler::CompileText("<test>", filter));
+ auto expr (RequireActualExpression(compiled));
+ std::vector<std::pair<const String*, const String*>> actualServices;
+
+ BOOST_CHECK_EQUAL(ApplyRule::GetTargetServices(expr, actualServices, constants), targeted);
+
+ if (targeted) {
+ std::vector<std::pair<String, String>> actualServiceNames;
+
+ actualServiceNames.reserve(actualServices.size());
+
+ for (auto s : actualServices) {
+ actualServiceNames.emplace_back(*s.first, *s.second);
+ }
+
+ BOOST_CHECK_EQUAL_COLLECTIONS(actualServiceNames.begin(), actualServiceNames.end(), services.begin(), services.end());
+ }
+}
+
+BOOST_AUTO_TEST_SUITE(config_apply)
+
+BOOST_AUTO_TEST_CASE(gettargethosts_literal)
+{
+ GetTargetHostsHelper("host.name == \"foo\"", nullptr, true, {"foo"});
+}
+
+BOOST_AUTO_TEST_CASE(gettargethosts_const)
+{
+ GetTargetHostsHelper("host.name == x", new Dictionary({{"x", "foo"}}), true, {"foo"});
+}
+
+BOOST_AUTO_TEST_CASE(gettargethosts_swapped)
+{
+ GetTargetHostsHelper("\"foo\" == host.name", nullptr, true, {"foo"});
+}
+
+BOOST_AUTO_TEST_CASE(gettargethosts_two)
+{
+ GetTargetHostsHelper("host.name == \"foo\" || host.name == \"bar\"", nullptr, true, {"foo", "bar"});
+}
+
+BOOST_AUTO_TEST_CASE(gettargethosts_three)
+{
+ GetTargetHostsHelper(
+ "host.name == \"foo\" || host.name == \"bar\" || host.name == \"foobar\"",
+ nullptr, true, {"foo", "bar", "foobar"}
+ );
+}
+
+BOOST_AUTO_TEST_CASE(gettargethosts_mixed)
+{
+ GetTargetHostsHelper("host.name == x || \"bar\" == host.name", new Dictionary({{"x", "foo"}}), true, {"foo", "bar"});
+}
+
+BOOST_AUTO_TEST_CASE(gettargethosts_redundant)
+{
+ GetTargetHostsHelper("host.name == \"foo\" && 1", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargethosts_badconst)
+{
+ GetTargetHostsHelper("host.name == NodeName", new Dictionary({{"x", "foo"}}), false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargethosts_notliteral)
+{
+ GetTargetHostsHelper("host.name == \"foo\" + \"bar\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargethosts_wrongop)
+{
+ GetTargetHostsHelper("host.name != \"foo\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargethosts_wrongattr)
+{
+ GetTargetHostsHelper("host.__name == \"foo\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargethosts_wrongvar)
+{
+ GetTargetHostsHelper("service.name == \"foo\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargethosts_noindexer)
+{
+ GetTargetHostsHelper("name == \"foo\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_literal)
+{
+ GetTargetServicesHelper("host.name == \"foo\" && service.name == \"bar\"", nullptr, true, {{"foo", "bar"}});
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_const)
+{
+ GetTargetServicesHelper("host.name == x && service.name == y", new Dictionary({{"x", "foo"}, {"y", "bar"}}), true, {{"foo", "bar"}});
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_swapped_outer)
+{
+ GetTargetServicesHelper("service.name == \"bar\" && host.name == \"foo\"", nullptr, true, {{"foo", "bar"}});
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_swapped_inner)
+{
+ GetTargetServicesHelper("\"foo\" == host.name && \"bar\" == service.name", nullptr, true, {{"foo", "bar"}});
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_two)
+{
+ GetTargetServicesHelper(
+ "host.name == \"foo\" && service.name == \"bar\" || host.name == \"oof\" && service.name == \"rab\"",
+ nullptr, true, {{"foo", "bar"}, {"oof", "rab"}}
+ );
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_three)
+{
+ GetTargetServicesHelper(
+ "host.name == \"foo\" && service.name == \"bar\" || host.name == \"oof\" && service.name == \"rab\" || host.name == \"ofo\" && service.name == \"rba\"",
+ nullptr, true, {{"foo", "bar"}, {"oof", "rab"}, {"ofo", "rba"}}
+ );
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_mixed)
+{
+ GetTargetServicesHelper("\"bar\" == service.name && x == host.name", new Dictionary({{"x", "foo"}}), true, {{"foo", "bar"}});
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_redundant)
+{
+ GetTargetServicesHelper("host.name == \"foo\" && service.name == \"bar\" && 1", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_badconst)
+{
+ GetTargetServicesHelper("host.name == NodeName && service.name == \"bar\"", new Dictionary({{"x", "foo"}}), false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_notliteral)
+{
+ GetTargetServicesHelper("host.name == \"foo\" && service.name == \"b\" + \"ar\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_wrongop_outer)
+{
+ GetTargetServicesHelper("host.name == \"foo\" & service.name == \"bar\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_wrongop_host)
+{
+ GetTargetServicesHelper("host.name != \"foo\" && service.name == \"bar\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_wrongop_service)
+{
+ GetTargetServicesHelper("host.name == \"foo\" && service.name != \"bar\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_wrongattr_host)
+{
+ GetTargetServicesHelper("host.__name == \"foo\" && service.name == \"bar\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_wrongattr_service)
+{
+ GetTargetServicesHelper("host.name == \"foo\" && service.__name == \"bar\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_wrongvar_host)
+{
+ GetTargetServicesHelper("horst.name == \"foo\" && service.name == \"bar\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_wrongvar_service)
+{
+ GetTargetServicesHelper("host.name == \"foo\" && sehrvice.name == \"bar\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_noindexer_host)
+{
+ GetTargetServicesHelper("name == \"foo\" && service.name == \"bar\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_CASE(gettargetservices_noindexer_service)
+{
+ GetTargetServicesHelper("host.name == \"foo\" && name == \"bar\"", nullptr, false);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/config-ops.cpp b/test/config-ops.cpp
new file mode 100644
index 0000000..dfbef25
--- /dev/null
+++ b/test/config-ops.cpp
@@ -0,0 +1,246 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "config/configcompiler.hpp"
+#include "base/exception.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(config_ops)
+
+BOOST_AUTO_TEST_CASE(simple)
+{
+ ScriptFrame frame(true);
+ std::unique_ptr<Expression> expr;
+ Dictionary::Ptr dict;
+
+ expr = ConfigCompiler::CompileText("<test>", "");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == Empty);
+
+ expr = ConfigCompiler::CompileText("<test>", "\n3");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 3);
+
+ expr = ConfigCompiler::CompileText("<test>", "{ 3\n\n5 }");
+ BOOST_CHECK_THROW(expr->Evaluate(frame).GetValue(), ScriptError);
+
+ expr = ConfigCompiler::CompileText("<test>", "1 + 3");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 4);
+
+ expr = ConfigCompiler::CompileText("<test>", "3 - 1");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 2);
+
+ expr = ConfigCompiler::CompileText("<test>", "5m * 10");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 3000);
+
+ expr = ConfigCompiler::CompileText("<test>", "5m / 5");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 60);
+
+ expr = ConfigCompiler::CompileText("<test>", "7 & 3");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 3);
+
+ expr = ConfigCompiler::CompileText("<test>", "2 | 3");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 3);
+
+ expr = ConfigCompiler::CompileText("<test>", "true && false");
+ BOOST_CHECK(!expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "true || false");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "3 < 5");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "3 > 5");
+ BOOST_CHECK(!expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "3 <= 3");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "3 >= 3");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "2 + 3 * 4");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 14);
+
+ expr = ConfigCompiler::CompileText("<test>", "(2 + 3) * 4");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 20);
+
+ expr = ConfigCompiler::CompileText("<test>", "2 * - 3");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == -6);
+
+ expr = ConfigCompiler::CompileText("<test>", "-(2 + 3)");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == -5);
+
+ expr = ConfigCompiler::CompileText("<test>", "- 2 * 2 - 2 * 3 - 4 * - 5");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 10);
+
+ expr = ConfigCompiler::CompileText("<test>", "!0 == true");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "~0");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == (double)~(long)0);
+
+ expr = ConfigCompiler::CompileText("<test>", "4 << 8");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 1024);
+
+ expr = ConfigCompiler::CompileText("<test>", "1024 >> 4");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 64);
+
+ expr = ConfigCompiler::CompileText("<test>", "2 << 3 << 4");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 256);
+
+ expr = ConfigCompiler::CompileText("<test>", "256 >> 4 >> 3");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 2);
+
+ expr = ConfigCompiler::CompileText("<test>", R"("hello" == "hello")");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", R"("hello" != "hello")");
+ BOOST_CHECK(!expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", R"("foo" in [ "foo", "bar" ])");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", R"("foo" in [ "bar", "baz" ])");
+ BOOST_CHECK(!expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "\"foo\" in null");
+ BOOST_CHECK(!expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", R"("foo" in "bar")");
+ BOOST_CHECK_THROW(expr->Evaluate(frame).GetValue(), ScriptError);
+
+ expr = ConfigCompiler::CompileText("<test>", R"("foo" !in [ "bar", "baz" ])");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", R"("foo" !in [ "foo", "bar" ])");
+ BOOST_CHECK(!expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "\"foo\" !in null");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", R"("foo" !in "bar")");
+ BOOST_CHECK_THROW(expr->Evaluate(frame).GetValue(), ScriptError);
+
+ expr = ConfigCompiler::CompileText("<test>", "{ a += 3 }");
+ dict = expr->Evaluate(frame).GetValue();
+ BOOST_CHECK(dict->GetLength() == 1);
+ BOOST_CHECK(dict->Get("a") == 3);
+
+ expr = ConfigCompiler::CompileText("<test>", "test");
+ BOOST_CHECK_THROW(expr->Evaluate(frame).GetValue(), ScriptError);
+
+ expr = ConfigCompiler::CompileText("<test>", "null + 3");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 3);
+
+ expr = ConfigCompiler::CompileText("<test>", "3 + null");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 3);
+
+ expr = ConfigCompiler::CompileText("<test>", "\"test\" + 3");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == "test3");
+
+ expr = ConfigCompiler::CompileText("<test>", R"("\"te\\st")");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == "\"te\\st");
+
+ expr = ConfigCompiler::CompileText("<test>", R"("\'test")");
+ BOOST_CHECK_THROW(expr->Evaluate(frame).GetValue(), ScriptError);
+
+ expr = ConfigCompiler::CompileText("<test>", "({ a = 3\nb = 3 })");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue().IsObjectType<Dictionary>());
+}
+
+BOOST_AUTO_TEST_CASE(advanced)
+{
+ ScriptFrame frame(true);
+ std::unique_ptr<Expression> expr;
+ Function::Ptr func;
+
+ expr = ConfigCompiler::CompileText("<test>", R"(regex("^Hello", "Hello World"))");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "__boost_test()");
+ BOOST_CHECK_THROW(expr->Evaluate(frame).GetValue(), ScriptError);
+
+ Object::Ptr self = new Object();
+ ScriptFrame frame2(true, self);
+ expr = ConfigCompiler::CompileText("<test>", "this");
+ BOOST_CHECK(expr->Evaluate(frame2).GetValue() == Value(self));
+
+ expr = ConfigCompiler::CompileText("<test>", "var v = 7; v");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "{ a = 3 }.a");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 3);
+
+ expr = ConfigCompiler::CompileText("<test>", "[ 2, 3 ][1]");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 3);
+
+ expr = ConfigCompiler::CompileText("<test>", "var v = { a = 3}; v.a");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 3);
+
+ expr = ConfigCompiler::CompileText("<test>", "a = 3 b = 3");
+ BOOST_CHECK_THROW(expr->Evaluate(frame).GetValue(), ScriptError);
+
+ expr = ConfigCompiler::CompileText("<test>", "function() { 3 }()");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 3);
+
+ expr = ConfigCompiler::CompileText("<test>", "function() { return 3, 5 }()");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 3);
+
+ expr = ConfigCompiler::CompileText("<test>", "typeof([]) == Array");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "typeof({}) == Dictionary");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "typeof(3) == Number");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "typeof(\"test\") == String");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "(7 | 8) == 15");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "(7 ^ 8) == 15");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "(7 & 15) == 7");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "7 in [7] == true");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "7 !in [7] == false");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "(7 | 8) > 14");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "(7 ^ 8) > 14");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "(7 & 15) > 6");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue());
+
+ expr = ConfigCompiler::CompileText("<test>", "\"a\" = 3");
+ BOOST_CHECK_THROW(expr->Evaluate(frame).GetValue(), ScriptError);
+
+ expr = ConfigCompiler::CompileText("<test>", "3 = 3");
+ BOOST_CHECK_THROW(expr->Evaluate(frame).GetValue(), ScriptError);
+
+ expr = ConfigCompiler::CompileText("<test>", "var e; e");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue().IsEmpty());
+
+ expr = ConfigCompiler::CompileText("<test>", "var e = 3; e");
+ BOOST_CHECK(expr->Evaluate(frame).GetValue() == 3);
+
+ expr = ConfigCompiler::CompileText("<test>", "Array.x");
+ BOOST_CHECK_THROW(expr->Evaluate(frame).GetValue(), ScriptError);
+
+ expr = ConfigCompiler::CompileText("<test>", "{{ 3 }}");
+ func = expr->Evaluate(frame).GetValue();
+ BOOST_CHECK(func->Invoke() == 3);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/config/2742.conf b/test/config/2742.conf
new file mode 100644
index 0000000..555e714
--- /dev/null
+++ b/test/config/2742.conf
@@ -0,0 +1,21 @@
+
+object CheckCommand "2742-macro-command" {
+ command = "echo UPTIME: $icinga.uptime$ SERVICES warn: $icinga.num_services_warning$ crit: $icinga.num_services_critical$ unknown: $icinga.num_services_unknown$ ackd: $icinga.num_services_acknowledged$ HOST: down: $icinga.num_hosts_down$ unreachable: $icinga.num_hosts_unreachable$"
+}
+
+object HostGroup "2742-windows-servers"{
+ display_name = "2742-windows-servers"
+ assign where match("2742-*", host.name)
+}
+
+apply Service "2742-macro-test" {
+ import "test-generic-service"
+ check_command = "2742-macro-command"
+ assign where match("2742-*", host.name)
+}
+
+object Host "2742-server" {
+ import "test-generic-host"
+ address = "192.168.1.5",
+}
+
diff --git a/test/config/5872.conf b/test/config/5872.conf
new file mode 100644
index 0000000..0405516
--- /dev/null
+++ b/test/config/5872.conf
@@ -0,0 +1,72 @@
+
+object HostGroup "5872-windows-servers"{
+ display_name = "5872-windows-servers"
+ assign where match("5872-*", host.name)
+}
+
+apply Service "5872-ping4" {
+ import "test-generic-service"
+ check_command = "ping4"
+ assign where match("5872-*", host.name)
+}
+
+object Host "5872-server" {
+ import "test-generic-host"
+ address = "192.168.1.5",
+}
+
+object Host "5872-pc" {
+ import "test-generic-host"
+ address = "192.168.1.101",
+}
+
+object Host "5872-router" {
+ import "test-generic-host"
+ address = "192.168.1.1",
+}
+
+object Host "5872-switch" {
+ import "test-generic-host"
+ address = "192.168.1.2",
+}
+
+apply Dependency "5872-host-switch" to Host {
+ parent_host_name = "5872-router"
+ disable_checks = true
+ assign where host.name == "5872-switch"
+}
+
+apply Dependency "5872-host-pc" to Host {
+ parent_host_name = "5872-switch"
+ disable_checks = true
+ assign where host.name == "5872-pc"
+}
+
+apply Dependency "5872-host-server" to Host {
+ parent_host_name = "5872-switch"
+ disable_checks = true
+ assign where host.name == "5872-server"
+}
+
+apply Dependency "5872-service-switch" to Service {
+ parent_host_name = "5872-router"
+ parent_service_name = "5872-ping4"
+ disable_checks = true
+ assign where host.name == "5872-switch"
+}
+
+apply Dependency "5872-service-pc" to Service {
+ parent_host_name = "5872-switch"
+ parent_service_name = "5872-ping4"
+ disable_checks = true
+ assign where host.name == "5872-pc"
+}
+
+apply Dependency "5872-service-server" to Service {
+ parent_host_name = "5872-switch"
+ parent_service_name = "5872-ping4"
+ states = [ Warning, Critical ]
+ disable_checks = true
+ assign where host.name == "5872-server"
+}
+
diff --git a/test/config/5912.conf.dis b/test/config/5912.conf.dis
new file mode 100644
index 0000000..0366b06
--- /dev/null
+++ b/test/config/5912.conf.dis
@@ -0,0 +1,14 @@
+
+apply Service "5912-ping4" {
+ import "test-generic-service"
+ check_command = "ping4"
+ host_name = "foo"
+ service_name = "bar"
+ assign where match("5912-*", host.name)
+}
+
+object Host "5912-server" {
+ import "test-generic-host"
+ address = "192.168.1.5",
+}
+
diff --git a/test/config/5926.conf b/test/config/5926.conf
new file mode 100644
index 0000000..e4060a6
--- /dev/null
+++ b/test/config/5926.conf
@@ -0,0 +1,23 @@
+
+
+object CheckCommand "5926-macro-test" {
+ command = "echo \"address: $address$ address_service: $service.vars.address$ foo: $foo$ keks: $keks$ god: $god$\""
+ //command = "echo \"address: $address$ address_service: $service.vars.address$\""
+}
+
+object Host "5926-macro-test-host" {
+ import "test-generic-host"
+ check_command = "5926-macro-test"
+ address = "1.2.3.4"
+ vars.god = "father"
+}
+
+apply Service "5926-macro-test-service" {
+ import "test-generic-service"
+ check_command = "5926-macro-test"
+ vars.address = "5.6.7.8"
+ vars.foo = "bar"
+ vars.keks = "schaschlik"
+
+ assign where host.name == "5926-macro-test-host"
+}
diff --git a/test/config/5927.conf b/test/config/5927.conf
new file mode 100644
index 0000000..b7041b6
--- /dev/null
+++ b/test/config/5927.conf
@@ -0,0 +1,44 @@
+
+object EventCommand "5927-handle" {
+ command = "echo \"event handler triggered.\""
+}
+
+object NotificationCommand "5927-notification" {
+ command = "echo \"notification triggered.\""
+}
+
+object HostGroup "5927-bar" {
+ assign where match("5927-keks*", host.name)
+}
+
+object Host "5927-keks" {
+ import "test-generic-host"
+ event_command = "5927-handle"
+ address = "1.2.3.4"
+}
+
+apply Service "5927-foo" {
+ import "test-generic-service"
+ check_command = "ping4"
+ event_command = "5927-handle"
+ assign where "5927-bar" in host.groups
+}
+
+apply Notification "5927-host-notification" to Host {
+ import "test-mail-host-notification"
+ command = "5927-notification"
+ assign where "5927-bar" in host.groups
+}
+
+apply Notification "5927-service-notification" to Service {
+ import "test-mail-service-notification"
+ command = "5927-notification"
+ assign where "5927-bar" in host.groups
+}
+
+object ServiceGroup "5927-bar" {
+ assign where service.name == "5927-foo"
+}
+
+
+
diff --git a/test/config/5980.conf b/test/config/5980.conf
new file mode 100644
index 0000000..494b5bd
--- /dev/null
+++ b/test/config/5980.conf
@@ -0,0 +1,58 @@
+
+
+object Host "5980-host" {
+ import "test-generic-host"
+ address = "127.0.0.1"
+}
+
+object Service "5980-service1" {
+ import "test-generic-service"
+ host_name = "5980-host"
+ check_command = "dummy"
+}
+
+object Service "5980-service2" {
+ import "test-generic-service"
+ host_name = "5980-host"
+ check_command = "dummy"
+}
+
+
+template ScheduledDowntime "5980-test-downtime" {
+ author = "icingaadmin"
+ comment = "Scheduled downtime for tests"
+
+ ranges = {
+ monday = "02:00-03:00"
+ tuesday = "02:00-03:00"
+ wednesday = "02:00-03:00"
+ thursday = "02:00-03:00"
+ friday = "02:00-03:00"
+ saturday = "02:00-03:00"
+ sunday = "02:00-03:00"
+ }
+}
+
+
+apply ScheduledDowntime "5980-test-service-downtime" to Host {
+ import "5980-test-downtime"
+ comment = "Scheduled host downtime for tests"
+
+ ranges = {
+ tuesday = "09:37-09:40"
+ }
+
+ assign where host.name == "5980-host"
+}
+
+apply ScheduledDowntime "5980-test-service-downtime" to Service {
+ import "5980-test-downtime"
+ comment = "Scheduled service downtime for tests"
+
+ ranges = {
+ tuesday = "09:37-09:40"
+ }
+
+ assign where host.name == "5980-host"
+}
+
diff --git a/test/config/6105.conf b/test/config/6105.conf
new file mode 100644
index 0000000..6bfccff
--- /dev/null
+++ b/test/config/6105.conf
@@ -0,0 +1,25 @@
+
+
+object HostGroup "6105-bar" {
+ assign where match("6105-keks*", host.name)
+ vars.foo = "bar"
+}
+
+object Host "6105-keks" {
+ import "test-generic-host"
+ address = "12.3.4"
+}
+
+apply Service "6105-foo" {
+ import "test-generic-service"
+ check_command = "ping4"
+ assign where "6105-bar" in host.groups
+}
+
+object ServiceGroup "6105-bar" {
+ assign where service.name == "6105-foo"
+ vars.bar = "foo"
+}
+
+
+
diff --git a/test/config/6479.conf b/test/config/6479.conf
new file mode 100644
index 0000000..68b08a3
--- /dev/null
+++ b/test/config/6479.conf
@@ -0,0 +1,44 @@
+
+object EventCommand "6479-handle" {
+ command = "echo \"event handler triggered.\""
+}
+
+object NotificationCommand "6479-notification" {
+ command = "echo \"notification triggered.\""
+}
+
+object HostGroup "6479-bar" {
+ assign where match("6479-keks*", host.name)
+}
+
+object Host "6479-keks" {
+ import "test-generic-host"
+ event_command = "6479-handle"
+ address = "1.2.3.4"
+}
+
+apply Service "6479-foo" {
+ import "test-generic-service"
+ check_command = "ping4"
+ event_command = "6479-handle"
+ assign where "6479-bar" in host.groups
+}
+
+apply Notification "6479-host-notification" to Host {
+ import "test-mail-host-notification"
+ command = "6479-notification"
+ assign where "6479-bar" in host.groups
+}
+
+apply Notification "6479-service-notification" to Service {
+ import "test-mail-service-notification"
+ command = "6479-notification"
+ assign where "6479-bar" in host.groups
+}
+
+object ServiceGroup "6479-bar" {
+ assign where service.name == "6479-foo"
+}
+
+
+
diff --git a/test/config/6608.conf b/test/config/6608.conf
new file mode 100644
index 0000000..e24d4c8
--- /dev/null
+++ b/test/config/6608.conf
@@ -0,0 +1,16 @@
+
+
+object Host "6608-host" {
+ import "test-generic-host"
+ vars.BUMSTI = "keks"
+ vars.bumsti = "schaschlik"
+}
+
+object Service "6608-service" {
+ import "test-generic-service"
+ check_command = "dummy"
+ host_name = "6608-host"
+ vars.DINGDONG = "$BUMSTI$"
+ vars.dingdong = "$bumsti$"
+}
+
diff --git a/test/config/6968.conf b/test/config/6968.conf
new file mode 100644
index 0000000..9882727
--- /dev/null
+++ b/test/config/6968.conf
@@ -0,0 +1,27 @@
+object Host "6968-server" {
+ import "test-generic-host"
+ address = "127.0.0.1"
+}
+
+object Service "6968-test" {
+ import "test-generic-service"
+
+ host_name = "6968-server"
+ check_command = "6968-check_vmware"
+ vars.vmware_check = "vCenter_License_Status"
+}
+
+object CheckCommand "6968-check_vmware" {
+ command = [ PluginDir + "/check_vmware.pl" ]
+
+ arguments = {
+ "--server" = "$address$"
+ "--username" = "***"
+ "--password" = "***"
+ "--check" = {
+ set_if = "$vmware_check$"
+ }
+ }
+}
+
+
diff --git a/test/config/7560.conf b/test/config/7560.conf
new file mode 100644
index 0000000..422cc04
--- /dev/null
+++ b/test/config/7560.conf
@@ -0,0 +1,41 @@
+object Host "7560-server" {
+ import "test-generic-host"
+ address = "127.0.0.1"
+ check_command = "hostalive"
+
+ vars.interfaces += {
+ eth0 = {
+ port = 1
+ vlan = "internal"
+ address = "127.0.0.2"
+ qos = "enabled"
+ }
+ eth1 = {
+ port = 2
+ vlan = "mgmt"
+ address = "127.0.1.2"
+ }
+ eth2 = {
+ port = 3
+ vlan = "remote"
+ address = "127.0.2.2"
+ }
+ }
+}
+
+apply Service "if-" for (if_name => config in host.vars.interfaces) {
+ import "test-generic-service"
+ check_command = "ping4"
+
+ vars.qos = "disabled"
+ vars += config
+
+ display_name = "if-" + if_name + "-" + vars.vlan
+
+ notes = "Interface check for Port " + string(vars.port) + " in VLAN " + vars.vlan + " on Address " + vars.address + " QoS " + vars.qos
+ notes_url = "http://foreman.company.com/hosts/" + host.name
+ action_url = "http://snmp.checker.company.com/" + host.name + "if-" + if_name
+
+ assign where match("7560-*", host.name) && typeof(host.vars.interfaces) == typeof({})
+}
+
diff --git a/test/config/7683.conf b/test/config/7683.conf
new file mode 100644
index 0000000..4e1a986
--- /dev/null
+++ b/test/config/7683.conf
@@ -0,0 +1,27 @@
+object Host "7683-parent" {
+ check_command = "dummy"
+ vars.dummy_state = 0
+}
+
+
+object Host "7683-child1" {
+ check_command = "dummy"
+ vars.dummy_state = 0
+}
+
+object Host "7683-child2" {
+ check_command = "dummy"
+ vars.dummy_state = 0
+}
+
+object Service "7683-service" {
+ check_command = "dummy"
+ host_name = "7683-parent"
+ vars.dummy_state = 0
+}
+
+apply Dependency "test-host" to Host {
+ parent_host_name = "7683-parent"
+ assign where match("7683-child*", host.name)
+}
+
diff --git a/test/config/8063.conf b/test/config/8063.conf
new file mode 100644
index 0000000..75676d1
--- /dev/null
+++ b/test/config/8063.conf
@@ -0,0 +1,73 @@
+object CheckCommand "8063-my-disk" {
+ command = [ PluginDir + "/check_disk" ]
+
+ arguments = {
+ "-w" = {
+ value = "$disk_wfree$"
+ description = "Exit with WARNING status if less than INTEGER units of disk are free or Exit with WARNING status if less than PERCENT of disk space is free"
+ required = true
+ }
+ "-c" = {
+ value = "$disk_cfree$"
+ description = "Exit with CRITICAL status if less than INTEGER units of disk are free or Exit with CRITCAL status if less than PERCENT of disk space is free"
+ required = true
+ }
+ "-W" = {
+ value = "$disk_inode_wfree$"
+ description = "Exit with WARNING status if less than PERCENT of inode space is free"
+ }
+ "-K" = {
+ value = "$disk_inode_cfree$"
+ description = "Exit with CRITICAL status if less than PERCENT of inode space is free"
+ }
+ "-p" = {
+ value = "$disk_partitions$"
+ description = "Path or partition (may be repeated)"
+ repeat_key = true
+ order = 1
+ }
+ "-x" = {
+ value = "$disk_partitions_excluded$"
+ description = "Ignore device (only works if -p unspecified)"
+ }
+ }
+
+ vars.disk_wfree = "20%"
+ vars.disk_cfree = "10%"
+}
+
+object Host "8063-my-server" {
+ import "generic-host"
+ address = "127.0.0.1"
+ address6 = "::1"
+
+ vars.local_disks["basic-partitions"] = {
+ disk_partitions = [ "/", "/tmp", "/var", "/home", "/run/user/1000/gvfs" ]
+ }
+}
+
+apply Service "8063-" for (disk => config in host.vars.local_disks) {
+ import "generic-service"
+ check_command = "8063-my-disk"
+ check_interval = 5s
+ retry_interval = 5s
+
+ volatile = true
+ vars.volatile_check = true
+
+ vars += config
+
+ vars.disk_wfree = "10%"
+ vars.disk_cfree = "5%"
+
+ assign where host.vars.local_disks
+}
+
+apply Notification "disk-notification" to Service {
+ import "test-mail-service-notification"
+
+ users = [ "test-icingaadmin" ]
+
+ assign where service.vars.volatile_check == true
+}
+
diff --git a/test/config/README b/test/config/README
new file mode 100644
index 0000000..5e8385f
--- /dev/null
+++ b/test/config/README
@@ -0,0 +1,2 @@
+Contains various test configuration for fixed issues.
+May be used for regression tests too.
diff --git a/test/config/templates.conf b/test/config/templates.conf
new file mode 100644
index 0000000..4a15bb8
--- /dev/null
+++ b/test/config/templates.conf
@@ -0,0 +1,80 @@
+/**
+ * test templates
+ */
+
+template Service "test-generic-service" {
+ max_check_attempts = 3
+ check_interval = 5m
+ retry_interval = 1m
+}
+
+template Host "test-generic-host" {
+ check_command = "hostalive"
+}
+
+template User "test-generic-user" {
+
+}
+
+template Notification "test-mail-host-notification" {
+ command = "mail-host-notification"
+
+ states = [ Up, Down ]
+ types = [ Problem, Acknowledgement, Recovery, Custom,
+ FlappingStart, FlappingEnd,
+ DowntimeStart, DowntimeEnd, DowntimeRemoved ]
+
+ period = "test-24x7"
+
+ user_groups = [ "test-icingaadmins" ]
+}
+
+/**
+ * Provides default settings for service notifications.
+ * By convention all service notifications should import
+ * this template.
+ */
+template Notification "test-mail-service-notification" {
+ command = "mail-service-notification"
+
+ states = [ OK, Warning, Critical, Unknown ]
+ types = [ Problem, Acknowledgement, Recovery, Custom,
+ FlappingStart, FlappingEnd,
+ DowntimeStart, DowntimeEnd, DowntimeRemoved ]
+
+ period = "test-24x7"
+
+ user_groups = [ "test-icingaadmins" ]
+}
+
+
+/* users */
+
+object User "test-icingaadmin" {
+ import "test-generic-user"
+
+ display_name = "Test Icinga 2 Admin"
+ groups = [ "test-icingaadmins" ]
+
+ email = "icinga@localhost"
+}
+
+object UserGroup "test-icingaadmins" {
+ display_name = "Test Icinga 2 Admin Group"
+}
+
+/* timeperiods */
+object TimePeriod "test-24x7" {
+ display_name = "Test Icinga 2 24x7 TimePeriod"
+
+ ranges = {
+ "monday" = "00:00-24:00"
+ "tuesday" = "00:00-24:00"
+ "wednesday" = "00:00-24:00"
+ "thursday" = "00:00-24:00"
+ "friday" = "00:00-24:00"
+ "saturday" = "00:00-24:00"
+ "sunday" = "00:00-24:00"
+ }
+}
+
diff --git a/test/icinga-checkable-fixture.cpp b/test/icinga-checkable-fixture.cpp
new file mode 100644
index 0000000..67fab1b
--- /dev/null
+++ b/test/icinga-checkable-fixture.cpp
@@ -0,0 +1,28 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "cli/daemonutility.hpp"
+#include "base/application.hpp"
+#include "base/loader.hpp"
+#include <BoostTestTargetConfig.h>
+#include <fstream>
+
+using namespace icinga;
+
+struct IcingaCheckableFixture
+{
+ IcingaCheckableFixture()
+ {
+ BOOST_TEST_MESSAGE("setup running Icinga 2 core");
+
+ Application::InitializeBase();
+ }
+
+ ~IcingaCheckableFixture()
+ {
+ BOOST_TEST_MESSAGE("cleanup Icinga 2 core");
+ Application::UninitializeBase();
+ }
+};
+
+BOOST_GLOBAL_FIXTURE(IcingaCheckableFixture);
+
diff --git a/test/icinga-checkable-flapping.cpp b/test/icinga-checkable-flapping.cpp
new file mode 100644
index 0000000..bc30564
--- /dev/null
+++ b/test/icinga-checkable-flapping.cpp
@@ -0,0 +1,248 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/host.hpp"
+#include <bitset>
+#include <iostream>
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+#ifdef I2_DEBUG
+static CheckResult::Ptr MakeCheckResult(ServiceState state)
+{
+ CheckResult::Ptr cr = new CheckResult();
+
+ cr->SetState(state);
+
+ double now = Utility::GetTime();
+ cr->SetScheduleStart(now);
+ cr->SetScheduleEnd(now);
+ cr->SetExecutionStart(now);
+ cr->SetExecutionEnd(now);
+
+ Utility::IncrementTime(60);
+
+ return cr;
+}
+
+static void LogFlapping(const Checkable::Ptr& obj)
+{
+ std::bitset<20> stateChangeBuf = obj->GetFlappingBuffer();
+ int oldestIndex = (obj->GetFlappingBuffer() & 0xFF00000) >> 20;
+
+ std::cout << "Flapping: " << obj->IsFlapping() << "\nHT: " << obj->GetFlappingThresholdHigh() << " LT: "
+ << obj->GetFlappingThresholdLow() << "\nOur value: " << obj->GetFlappingCurrent() << "\nPtr: " << oldestIndex
+ << " Buf: " << stateChangeBuf.to_ulong() << '\n';
+}
+
+
+static void LogHostStatus(const Host::Ptr &host)
+{
+ std::cout << "Current status: state: " << host->GetState() << " state_type: " << host->GetStateType()
+ << " check attempt: " << host->GetCheckAttempt() << "/" << host->GetMaxCheckAttempts() << " Active: " << host->IsActive() << std::endl;
+}
+#endif /* I2_DEBUG */
+
+BOOST_AUTO_TEST_SUITE(icinga_checkable_flapping)
+
+BOOST_AUTO_TEST_CASE(host_not_flapping)
+{
+#ifndef I2_DEBUG
+ BOOST_WARN_MESSAGE(false, "This test can only be run in a debug build!");
+#else /* I2_DEBUG */
+ std::cout << "Running test with a non-flapping host...\n";
+
+ Host::Ptr host = new Host();
+ host->SetName("test");
+ host->SetEnableFlapping(true);
+ host->SetMaxCheckAttempts(5);
+ host->SetActive(true);
+
+ // Host otherwise is soft down
+ host->SetState(HostUp);
+ host->SetStateType(StateTypeHard);
+
+ Utility::SetTime(0);
+
+ BOOST_CHECK(host->GetFlappingCurrent() == 0);
+
+ LogFlapping(host);
+ LogHostStatus(host);
+
+ // watch the state being stable
+ int i = 0;
+ while (i++ < 10) {
+ // For some reason, elusive to me, the first check is a state change
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+
+ LogFlapping(host);
+ LogHostStatus(host);
+
+ BOOST_CHECK(host->GetState() == 0);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+
+ //Should not be flapping
+ BOOST_CHECK(!host->IsFlapping());
+ BOOST_CHECK(host->GetFlappingCurrent() < 30.0);
+ }
+#endif /* I2_DEBUG */
+}
+
+BOOST_AUTO_TEST_CASE(host_flapping)
+{
+#ifndef I2_DEBUG
+ BOOST_WARN_MESSAGE(false, "This test can only be run in a debug build!");
+#else /* I2_DEBUG */
+ std::cout << "Running test with host changing state with every check...\n";
+
+ Host::Ptr host = new Host();
+ host->SetName("test");
+ host->SetEnableFlapping(true);
+ host->SetMaxCheckAttempts(5);
+ host->SetActive(true);
+
+ Utility::SetTime(0);
+
+ int i = 0;
+ while (i++ < 25) {
+ if (i % 2)
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ else
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+
+ LogFlapping(host);
+ LogHostStatus(host);
+
+ //30 Percent is our high Threshold
+ if (i >= 6) {
+ BOOST_CHECK(host->IsFlapping());
+ } else {
+ BOOST_CHECK(!host->IsFlapping());
+ }
+ }
+#endif /* I2_DEBUG */
+}
+
+BOOST_AUTO_TEST_CASE(host_flapping_recover)
+{
+#ifndef I2_DEBUG
+ BOOST_WARN_MESSAGE(false, "This test can only be run in a debug build!");
+#else /* I2_DEBUG */
+ std::cout << "Running test with flapping recovery...\n";
+
+ Host::Ptr host = new Host();
+ host->SetName("test");
+ host->SetEnableFlapping(true);
+ host->SetMaxCheckAttempts(5);
+ host->SetActive(true);
+
+ // Host otherwise is soft down
+ host->SetState(HostUp);
+ host->SetStateType(StateTypeHard);
+
+ Utility::SetTime(0);
+
+ // A few warning
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+
+ LogFlapping(host);
+ LogHostStatus(host);
+ for (int i = 0; i <= 7; i++) {
+ if (i % 2)
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ else
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+ }
+
+ LogFlapping(host);
+ LogHostStatus(host);
+
+ // We should be flapping now
+ BOOST_CHECK(host->GetFlappingCurrent() > 30.0);
+ BOOST_CHECK(host->IsFlapping());
+
+ // Now recover from flapping
+ int count = 0;
+ while (host->IsFlapping()) {
+ BOOST_CHECK(host->GetFlappingCurrent() > 25.0);
+ BOOST_CHECK(host->IsFlapping());
+
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+ LogFlapping(host);
+ LogHostStatus(host);
+ count++;
+ }
+
+ std::cout << "Recovered from flapping after " << count << " Warning results.\n";
+
+ BOOST_CHECK(host->GetFlappingCurrent() < 25.0);
+ BOOST_CHECK(!host->IsFlapping());
+#endif /* I2_DEBUG */
+}
+
+BOOST_AUTO_TEST_CASE(host_flapping_docs_example)
+{
+#ifndef I2_DEBUG
+ BOOST_WARN_MESSAGE(false, "This test can only be run in a debug build!");
+#else /* I2_DEBUG */
+ std::cout << "Simulating the documentation example...\n";
+
+ Host::Ptr host = new Host();
+ host->SetName("test");
+ host->SetEnableFlapping(true);
+ host->SetMaxCheckAttempts(5);
+ host->SetActive(true);
+
+ // Host otherwise is soft down
+ host->SetState(HostUp);
+ host->SetStateType(StateTypeHard);
+
+ Utility::SetTime(0);
+
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+ host->ProcessCheckResult(MakeCheckResult(ServiceWarning));
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+
+ LogFlapping(host);
+ LogHostStatus(host);
+ BOOST_CHECK(host->GetFlappingCurrent() == 39.1);
+ BOOST_CHECK(host->IsFlapping());
+
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+
+ LogFlapping(host);
+ LogHostStatus(host);
+ BOOST_CHECK(host->GetFlappingCurrent() < 25.0);
+ BOOST_CHECK(!host->IsFlapping());
+#endif
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/icinga-checkresult.cpp b/test/icinga-checkresult.cpp
new file mode 100644
index 0000000..fdc7891
--- /dev/null
+++ b/test/icinga-checkresult.cpp
@@ -0,0 +1,1032 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/downtime.hpp"
+#include "icinga/host.hpp"
+#include "icinga/service.hpp"
+#include <BoostTestTargetConfig.h>
+#include <iostream>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(icinga_checkresult)
+
+static CheckResult::Ptr MakeCheckResult(ServiceState state)
+{
+ CheckResult::Ptr cr = new CheckResult();
+
+ cr->SetState(state);
+
+ double now = Utility::GetTime();
+ cr->SetScheduleStart(now);
+ cr->SetScheduleEnd(now);
+ cr->SetExecutionStart(now);
+ cr->SetExecutionEnd(now);
+
+ return cr;
+}
+
+static void NotificationHandler(const Checkable::Ptr& checkable, NotificationType type)
+{
+ std::cout << "Notification triggered: " << Notification::NotificationTypeToString(type) << std::endl;
+
+ checkable->SetExtension("requested_notifications", true);
+ checkable->SetExtension("notification_type", type);
+}
+
+static void CheckNotification(const Checkable::Ptr& checkable, bool expected, NotificationType type = NotificationRecovery)
+{
+ BOOST_CHECK((expected && checkable->GetExtension("requested_notifications").ToBool()) || (!expected && !checkable->GetExtension("requested_notifications").ToBool()));
+
+ if (expected && checkable->GetExtension("requested_notifications").ToBool())
+ BOOST_CHECK(checkable->GetExtension("notification_type") == type);
+
+ checkable->SetExtension("requested_notifications", false);
+}
+
+BOOST_AUTO_TEST_CASE(host_1attempt)
+{
+ boost::signals2::connection c = Checkable::OnNotificationsRequested.connect([](const Checkable::Ptr& checkable, NotificationType type,
+ const CheckResult::Ptr&, const String&, const String&, const MessageOrigin::Ptr&) {
+ NotificationHandler(checkable, type);
+ });
+
+ Host::Ptr host = new Host();
+ host->SetActive(true);
+ host->SetMaxCheckAttempts(1);
+ host->Activate();
+ host->SetAuthority(true);
+ host->SetStateRaw(ServiceOK);
+ host->SetStateType(StateTypeHard);
+
+ std::cout << "Before first check result (ok, hard)" << std::endl;
+ BOOST_CHECK(host->GetState() == HostUp);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, false);
+
+ std::cout << "First check result (unknown)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceUnknown));
+ BOOST_CHECK(host->GetState() == HostDown);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, true, NotificationProblem);
+
+ std::cout << "Second check result (ok)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ BOOST_CHECK(host->GetState() == HostUp);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, true, NotificationRecovery);
+
+ std::cout << "Third check result (critical)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ BOOST_CHECK(host->GetState() == HostDown);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, true, NotificationProblem);
+
+ std::cout << "Fourth check result (ok)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ BOOST_CHECK(host->GetState() == HostUp);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, true, NotificationRecovery);
+
+ c.disconnect();
+}
+
+BOOST_AUTO_TEST_CASE(host_2attempts)
+{
+ boost::signals2::connection c = Checkable::OnNotificationsRequested.connect([](const Checkable::Ptr& checkable, NotificationType type,
+ const CheckResult::Ptr&, const String&, const String&, const MessageOrigin::Ptr&) {
+ NotificationHandler(checkable, type);
+ });
+
+ Host::Ptr host = new Host();
+ host->SetActive(true);
+ host->SetMaxCheckAttempts(2);
+ host->Activate();
+ host->SetAuthority(true);
+ host->SetStateRaw(ServiceOK);
+ host->SetStateType(StateTypeHard);
+
+ std::cout << "Before first check result (ok, hard)" << std::endl;
+ BOOST_CHECK(host->GetState() == HostUp);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, false);
+
+ std::cout << "First check result (unknown)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceUnknown));
+ BOOST_CHECK(host->GetState() == HostDown);
+ BOOST_CHECK(host->GetStateType() == StateTypeSoft);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, false);
+
+ std::cout << "Second check result (critical)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ BOOST_CHECK(host->GetState() == HostDown);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, true, NotificationProblem);
+
+ std::cout << "Third check result (ok)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ BOOST_CHECK(host->GetState() == HostUp);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, true, NotificationRecovery);
+
+ std::cout << "Fourth check result (critical)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ BOOST_CHECK(host->GetState() == HostDown);
+ BOOST_CHECK(host->GetStateType() == StateTypeSoft);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, false);
+
+ std::cout << "Fifth check result (ok)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ BOOST_CHECK(host->GetState() == HostUp);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, false);
+
+ c.disconnect();
+}
+
+BOOST_AUTO_TEST_CASE(host_3attempts)
+{
+ boost::signals2::connection c = Checkable::OnNotificationsRequested.connect([](const Checkable::Ptr& checkable, NotificationType type,
+ const CheckResult::Ptr&, const String&, const String&, const MessageOrigin::Ptr&) {
+ NotificationHandler(checkable, type);
+ });
+
+ Host::Ptr host = new Host();
+ host->SetActive(true);
+ host->SetMaxCheckAttempts(3);
+ host->Activate();
+ host->SetAuthority(true);
+ host->SetStateRaw(ServiceOK);
+ host->SetStateType(StateTypeHard);
+
+ std::cout << "Before first check result (ok, hard)" << std::endl;
+ BOOST_CHECK(host->GetState() == HostUp);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, false);
+
+ std::cout << "First check result (unknown)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceUnknown));
+ BOOST_CHECK(host->GetState() == HostDown);
+ BOOST_CHECK(host->GetStateType() == StateTypeSoft);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, false);
+
+ std::cout << "Second check result (critical)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ BOOST_CHECK(host->GetState() == HostDown);
+ BOOST_CHECK(host->GetStateType() == StateTypeSoft);
+ BOOST_CHECK(host->GetCheckAttempt() == 2);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, false);
+
+ std::cout << "Third check result (critical)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ BOOST_CHECK(host->GetState() == HostDown);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, true, NotificationProblem);
+
+ std::cout << "Fourth check result (ok)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ BOOST_CHECK(host->GetState() == HostUp);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, true, NotificationRecovery);
+
+ std::cout << "Fifth check result (critical)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ BOOST_CHECK(host->GetState() == HostDown);
+ BOOST_CHECK(host->GetStateType() == StateTypeSoft);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, false);
+
+ std::cout << "Sixth check result (ok)" << std::endl;
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ BOOST_CHECK(host->GetState() == HostUp);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+ BOOST_CHECK(host->IsReachable() == true);
+ CheckNotification(host, false);
+
+ c.disconnect();
+}
+
+BOOST_AUTO_TEST_CASE(service_1attempt)
+{
+ boost::signals2::connection c = Checkable::OnNotificationsRequested.connect([](const Checkable::Ptr& checkable, NotificationType type,
+ const CheckResult::Ptr&, const String&, const String&, const MessageOrigin::Ptr&) {
+ NotificationHandler(checkable, type);
+ });
+
+ Service::Ptr service = new Service();
+ service->SetActive(true);
+ service->SetMaxCheckAttempts(1);
+ service->Activate();
+ service->SetAuthority(true);
+ service->SetStateRaw(ServiceOK);
+ service->SetStateType(StateTypeHard);
+
+ std::cout << "Before first check result (ok, hard)" << std::endl;
+ BOOST_CHECK(service->GetState() == ServiceOK);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, false);
+
+ std::cout << "First check result (unknown)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceUnknown));
+ BOOST_CHECK(service->GetState() == ServiceUnknown);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, true, NotificationProblem);
+
+ std::cout << "Second check result (ok)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ BOOST_CHECK(service->GetState() == ServiceOK);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, true, NotificationRecovery);
+
+ std::cout << "Third check result (critical)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ BOOST_CHECK(service->GetState() == ServiceCritical);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, true, NotificationProblem);
+
+ std::cout << "Fourth check result (ok)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ BOOST_CHECK(service->GetState() == ServiceOK);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, true, NotificationRecovery);
+
+ c.disconnect();
+}
+
+BOOST_AUTO_TEST_CASE(service_2attempts)
+{
+ boost::signals2::connection c = Checkable::OnNotificationsRequested.connect([](const Checkable::Ptr& checkable, NotificationType type,
+ const CheckResult::Ptr&, const String&, const String&, const MessageOrigin::Ptr&) {
+ NotificationHandler(checkable, type);
+ });
+
+ Service::Ptr service = new Service();
+ service->SetActive(true);
+ service->SetMaxCheckAttempts(2);
+ service->Activate();
+ service->SetAuthority(true);
+ service->SetStateRaw(ServiceOK);
+ service->SetStateType(StateTypeHard);
+
+ std::cout << "Before first check result (ok, hard)" << std::endl;
+ BOOST_CHECK(service->GetState() == ServiceOK);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, false);
+
+ std::cout << "First check result (unknown)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceUnknown));
+ BOOST_CHECK(service->GetState() == ServiceUnknown);
+ BOOST_CHECK(service->GetStateType() == StateTypeSoft);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, false);
+
+ std::cout << "Second check result (critical)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ BOOST_CHECK(service->GetState() == ServiceCritical);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, true, NotificationProblem);
+
+ std::cout << "Third check result (ok)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ BOOST_CHECK(service->GetState() == ServiceOK);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, true, NotificationRecovery);
+
+ std::cout << "Fourth check result (critical)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ BOOST_CHECK(service->GetState() == ServiceCritical);
+ BOOST_CHECK(service->GetStateType() == StateTypeSoft);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, false);
+
+ std::cout << "Fifth check result (ok)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ BOOST_CHECK(service->GetState() == ServiceOK);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, false);
+
+ c.disconnect();
+}
+
+BOOST_AUTO_TEST_CASE(service_3attempts)
+{
+ boost::signals2::connection c = Checkable::OnNotificationsRequested.connect([](const Checkable::Ptr& checkable, NotificationType type,
+ const CheckResult::Ptr&, const String&, const String&, const MessageOrigin::Ptr&) {
+ NotificationHandler(checkable, type);
+ });
+
+ Service::Ptr service = new Service();
+ service->SetActive(true);
+ service->SetMaxCheckAttempts(3);
+ service->Activate();
+ service->SetAuthority(true);
+ service->SetStateRaw(ServiceOK);
+ service->SetStateType(StateTypeHard);
+
+ std::cout << "Before first check result (ok, hard)" << std::endl;
+ BOOST_CHECK(service->GetState() == ServiceOK);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, false);
+
+ std::cout << "First check result (unknown)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceUnknown));
+ BOOST_CHECK(service->GetState() == ServiceUnknown);
+ BOOST_CHECK(service->GetStateType() == StateTypeSoft);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, false);
+
+ std::cout << "Second check result (critical)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ BOOST_CHECK(service->GetState() == ServiceCritical);
+ BOOST_CHECK(service->GetStateType() == StateTypeSoft);
+ BOOST_CHECK(service->GetCheckAttempt() == 2);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, false);
+
+ std::cout << "Third check result (critical)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ BOOST_CHECK(service->GetState() == ServiceCritical);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, true, NotificationProblem);
+
+ std::cout << "Fourth check result (ok)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ BOOST_CHECK(service->GetState() == ServiceOK);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, true, NotificationRecovery);
+
+ std::cout << "Fifth check result (critical)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ BOOST_CHECK(service->GetState() == ServiceCritical);
+ BOOST_CHECK(service->GetStateType() == StateTypeSoft);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, false);
+
+ std::cout << "Sixth check result (ok)" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ BOOST_CHECK(service->GetState() == ServiceOK);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+ BOOST_CHECK(service->IsReachable() == true);
+ CheckNotification(service, false);
+
+ c.disconnect();
+}
+
+BOOST_AUTO_TEST_CASE(host_flapping_notification)
+{
+#ifndef I2_DEBUG
+ BOOST_WARN_MESSAGE(false, "This test can only be run in a debug build!");
+#else /* I2_DEBUG */
+ boost::signals2::connection c = Checkable::OnNotificationsRequested.connect([](const Checkable::Ptr& checkable, NotificationType type,
+ const CheckResult::Ptr&, const String&, const String&, const MessageOrigin::Ptr&) {
+ NotificationHandler(checkable, type);
+ });
+
+ int timeStepInterval = 60;
+
+ Host::Ptr host = new Host();
+ host->SetActive(true);
+ host->Activate();
+ host->SetAuthority(true);
+ host->SetStateRaw(ServiceOK);
+ host->SetStateType(StateTypeHard);
+ host->SetEnableFlapping(true);
+
+ /* Initialize start time */
+ Utility::SetTime(0);
+
+ std::cout << "Before first check result (ok, hard)" << std::endl;
+ BOOST_CHECK(host->GetState() == HostUp);
+ BOOST_CHECK(host->GetStateType() == StateTypeHard);
+ BOOST_CHECK(host->GetCheckAttempt() == 1);
+
+ Utility::IncrementTime(timeStepInterval);
+
+ std::cout << "Inserting flapping check results" << std::endl;
+
+ for (int i = 0; i < 10; i++) {
+ ServiceState state = (i % 2 == 0 ? ServiceOK : ServiceCritical);
+ host->ProcessCheckResult(MakeCheckResult(state));
+ Utility::IncrementTime(timeStepInterval);
+ }
+
+ BOOST_CHECK(host->IsFlapping() == true);
+
+ CheckNotification(host, true, NotificationFlappingStart);
+
+ std::cout << "Now calm down..." << std::endl;
+
+ for (int i = 0; i < 20; i++) {
+ host->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ Utility::IncrementTime(timeStepInterval);
+ }
+
+ CheckNotification(host, true, NotificationFlappingEnd);
+
+
+ c.disconnect();
+
+#endif /* I2_DEBUG */
+}
+
+BOOST_AUTO_TEST_CASE(service_flapping_notification)
+{
+#ifndef I2_DEBUG
+ BOOST_WARN_MESSAGE(false, "This test can only be run in a debug build!");
+#else /* I2_DEBUG */
+ boost::signals2::connection c = Checkable::OnNotificationsRequested.connect([](const Checkable::Ptr& checkable, NotificationType type,
+ const CheckResult::Ptr&, const String&, const String&, const MessageOrigin::Ptr&) {
+ NotificationHandler(checkable, type);
+ });
+
+ int timeStepInterval = 60;
+
+ Service::Ptr service = new Service();
+ service->SetActive(true);
+ service->Activate();
+ service->SetAuthority(true);
+ service->SetStateRaw(ServiceOK);
+ service->SetStateType(StateTypeHard);
+ service->SetEnableFlapping(true);
+
+ /* Initialize start time */
+ Utility::SetTime(0);
+
+ std::cout << "Before first check result (ok, hard)" << std::endl;
+ BOOST_CHECK(service->GetState() == ServiceOK);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+
+ Utility::IncrementTime(timeStepInterval);
+
+ std::cout << "Inserting flapping check results" << std::endl;
+
+ for (int i = 0; i < 10; i++) {
+ ServiceState state = (i % 2 == 0 ? ServiceOK : ServiceCritical);
+ service->ProcessCheckResult(MakeCheckResult(state));
+ Utility::IncrementTime(timeStepInterval);
+ }
+
+ BOOST_CHECK(service->IsFlapping() == true);
+
+ CheckNotification(service, true, NotificationFlappingStart);
+
+
+
+ std::cout << "Now calm down..." << std::endl;
+
+ for (int i = 0; i < 20; i++) {
+ service->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ Utility::IncrementTime(timeStepInterval);
+ }
+
+ CheckNotification(service, true, NotificationFlappingEnd);
+
+ c.disconnect();
+
+#endif /* I2_DEBUG */
+}
+
+BOOST_AUTO_TEST_CASE(service_flapping_problem_notifications)
+{
+#ifndef I2_DEBUG
+ BOOST_WARN_MESSAGE(false, "This test can only be run in a debug build!");
+#else /* I2_DEBUG */
+ boost::signals2::connection c = Checkable::OnNotificationsRequested.connect([](const Checkable::Ptr& checkable, NotificationType type,
+ const CheckResult::Ptr&, const String&, const String&, const MessageOrigin::Ptr&) {
+ NotificationHandler(checkable, type);
+ });
+
+ int timeStepInterval = 60;
+
+ Service::Ptr service = new Service();
+ service->Activate();
+ service->SetAuthority(true);
+ service->SetStateRaw(ServiceOK);
+ service->SetStateType(StateTypeHard);
+ service->SetEnableFlapping(true);
+ service->SetMaxCheckAttempts(3);
+
+ /* Initialize start time */
+ Utility::SetTime(0);
+
+ std::cout << "Before first check result (ok, hard)" << std::endl;
+ BOOST_CHECK(service->GetState() == ServiceOK);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+
+ Utility::IncrementTime(timeStepInterval);
+
+ std::cout << "Inserting flapping check results" << std::endl;
+
+ for (int i = 0; i < 10; i++) {
+ ServiceState state = (i % 2 == 0 ? ServiceOK : ServiceCritical);
+ service->ProcessCheckResult(MakeCheckResult(state));
+ Utility::IncrementTime(timeStepInterval);
+ }
+
+ BOOST_CHECK(service->IsFlapping() == true);
+
+ CheckNotification(service, true, NotificationFlappingStart);
+
+ //Insert enough check results to get into hard problem state but staying flapping
+
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ Utility::IncrementTime(timeStepInterval);
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ Utility::IncrementTime(timeStepInterval);
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ Utility::IncrementTime(timeStepInterval);
+
+
+ BOOST_CHECK(service->IsFlapping() == true);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetState() == ServiceCritical);
+
+ CheckNotification(service, false, NotificationProblem);
+
+ // Calm down
+ while (service->IsFlapping()) {
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ Utility::IncrementTime(timeStepInterval);
+ }
+
+ CheckNotification(service, true, NotificationFlappingEnd);
+
+ /* Intended behaviour is a Problem notification being sent as well, but there are is a Problem:
+ * We don't know whether the Object was Critical before we started flapping and sent out a Notification.
+ * A notification will not be sent, no matter how many criticals follow.
+ *
+ * service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ * CheckNotification(service, true, NotificationProblem);
+ * ^ This fails, no notification will be sent
+ *
+ * There is also a different issue, when we receive a OK check result, a Recovery Notification will be sent
+ * since the service went from hard critical into soft ok. Yet there is no fitting critical notification.
+ * This should not happen:
+ *
+ * service->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ * CheckNotification(service, false, NotificationRecovery);
+ * ^ This fails, recovery is sent
+ */
+
+ BOOST_CHECK(service->IsFlapping() == false);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetState() == ServiceCritical);
+
+ // Known failure, see #5713
+ // CheckNotification(service, true, NotificationProblem);
+
+ service->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ Utility::IncrementTime(timeStepInterval);
+
+ // Known failure, see #5713
+ // CheckNotification(service, true, NotificationRecovery);
+
+ c.disconnect();
+
+#endif /* I2_DEBUG */
+}
+
+BOOST_AUTO_TEST_CASE(service_flapping_ok_into_bad)
+{
+#ifndef I2_DEBUG
+ BOOST_WARN_MESSAGE(false, "This test can only be run in a debug build!");
+#else /* I2_DEBUG */
+ boost::signals2::connection c = Checkable::OnNotificationsRequested.connect([](const Checkable::Ptr& checkable, NotificationType type,
+ const CheckResult::Ptr&, const String&, const String&, const MessageOrigin::Ptr&) {
+ NotificationHandler(checkable, type);
+ });
+
+ int timeStepInterval = 60;
+
+ Service::Ptr service = new Service();
+ service->Activate();
+ service->SetAuthority(true);
+ service->SetStateRaw(ServiceOK);
+ service->SetStateType(StateTypeHard);
+ service->SetEnableFlapping(true);
+ service->SetMaxCheckAttempts(3);
+
+ /* Initialize start time */
+ Utility::SetTime(0);
+
+ std::cout << "Before first check result (ok, hard)" << std::endl;
+ BOOST_CHECK(service->GetState() == ServiceOK);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+
+ Utility::IncrementTime(timeStepInterval);
+
+ std::cout << "Inserting flapping check results" << std::endl;
+
+ for (int i = 0; i < 10; i++) {
+ ServiceState state = (i % 2 == 0 ? ServiceOK : ServiceCritical);
+ service->ProcessCheckResult(MakeCheckResult(state));
+ Utility::IncrementTime(timeStepInterval);
+ }
+
+ BOOST_CHECK(service->IsFlapping() == true);
+
+ CheckNotification(service, true, NotificationFlappingStart);
+
+ //Insert enough check results to get into hard problem state but staying flapping
+
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ Utility::IncrementTime(timeStepInterval);
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ Utility::IncrementTime(timeStepInterval);
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ Utility::IncrementTime(timeStepInterval);
+
+
+ BOOST_CHECK(service->IsFlapping() == true);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetState() == ServiceCritical);
+
+ CheckNotification(service, false, NotificationProblem);
+
+ // Calm down
+ while (service->IsFlapping()) {
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ Utility::IncrementTime(timeStepInterval);
+ }
+
+ CheckNotification(service, true, NotificationFlappingEnd);
+
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ Utility::IncrementTime(timeStepInterval);
+
+ BOOST_CHECK(service->IsFlapping() == false);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetState() == ServiceCritical);
+
+ // We expect a problem notification here
+ // Known failure, see #5713
+ // CheckNotification(service, true, NotificationProblem);
+
+ c.disconnect();
+
+#endif /* I2_DEBUG */
+}
+BOOST_AUTO_TEST_CASE(service_flapping_ok_over_bad_into_ok)
+{
+#ifndef I2_DEBUG
+ BOOST_WARN_MESSAGE(false, "This test can only be run in a debug build!");
+#else /* I2_DEBUG */
+ boost::signals2::connection c = Checkable::OnNotificationsRequested.connect([](const Checkable::Ptr& checkable, NotificationType type,
+ const CheckResult::Ptr&, const String&, const String&, const MessageOrigin::Ptr&) {
+ NotificationHandler(checkable, type);
+ });
+
+ int timeStepInterval = 60;
+
+ Service::Ptr service = new Service();
+ service->Activate();
+ service->SetAuthority(true);
+ service->SetStateRaw(ServiceOK);
+ service->SetStateType(StateTypeHard);
+ service->SetEnableFlapping(true);
+ service->SetMaxCheckAttempts(3);
+
+ /* Initialize start time */
+ Utility::SetTime(0);
+
+ std::cout << "Before first check result (ok, hard)" << std::endl;
+ BOOST_CHECK(service->GetState() == ServiceOK);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetCheckAttempt() == 1);
+
+ Utility::IncrementTime(timeStepInterval);
+
+ std::cout << "Inserting flapping check results" << std::endl;
+
+ for (int i = 0; i < 10; i++) {
+ ServiceState state = (i % 2 == 0 ? ServiceOK : ServiceCritical);
+ service->ProcessCheckResult(MakeCheckResult(state));
+ Utility::IncrementTime(timeStepInterval);
+ }
+
+ BOOST_CHECK(service->IsFlapping() == true);
+
+ CheckNotification(service, true, NotificationFlappingStart);
+
+ //Insert enough check results to get into hard problem state but staying flapping
+
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ Utility::IncrementTime(timeStepInterval);
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ Utility::IncrementTime(timeStepInterval);
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ Utility::IncrementTime(timeStepInterval);
+
+
+ BOOST_CHECK(service->IsFlapping() == true);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetState() == ServiceCritical);
+
+ CheckNotification(service, false, NotificationProblem);
+
+ // Calm down
+ while (service->IsFlapping()) {
+ service->ProcessCheckResult(MakeCheckResult(ServiceCritical));
+ Utility::IncrementTime(timeStepInterval);
+ }
+
+ CheckNotification(service, true, NotificationFlappingEnd);
+
+ service->ProcessCheckResult(MakeCheckResult(ServiceOK));
+ Utility::IncrementTime(timeStepInterval);
+
+ BOOST_CHECK(service->IsFlapping() == false);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ BOOST_CHECK(service->GetState() == ServiceOK);
+
+ // There should be no recovery
+ // Known failure, see #5713
+ // CheckNotification(service, false, NotificationRecovery);
+
+ c.disconnect();
+
+#endif /* I2_DEBUG */
+}
+
+BOOST_AUTO_TEST_CASE(suppressed_notification)
+{
+ /* Tests that suppressed notifications on a Checkable are sent after the suppression ends if and only if the first
+ * hard state after the suppression is different from the last hard state before the suppression. The test works
+ * by bringing a service in a defined hard state, creating a downtime, performing some state changes, removing the
+ * downtime, bringing the service into another defined hard state (if not already) and checking the requested
+ * notifications.
+ */
+
+ struct NotificationLog {
+ std::vector<std::pair<NotificationType, ServiceState>> GetAndClear() {
+ std::lock_guard<std::mutex> lock (mutex);
+
+ std::vector<std::pair<NotificationType, ServiceState>> ret;
+ std::swap(ret, log);
+ return ret;
+ }
+
+ void Add(std::pair<NotificationType, ServiceState> notification) {
+ std::lock_guard<std::mutex> lock (mutex);
+
+ log.emplace_back(notification);
+ }
+
+ private:
+ std::mutex mutex;
+ std::vector<std::pair<NotificationType, ServiceState>> log;
+ };
+
+ const std::vector<ServiceState> states {ServiceOK, ServiceWarning, ServiceCritical, ServiceUnknown};
+
+ for (bool isVolatile : {false, true}) {
+ for (int checkAttempts : {1, 2}) {
+ for (ServiceState initialState : states) {
+ for (ServiceState s1 : states)
+ for (ServiceState s2 : states)
+ for (ServiceState s3 : states) {
+ const std::vector<ServiceState> sequence {s1, s2, s3};
+
+ std::string testcase;
+
+ {
+ std::ostringstream buf;
+ buf << "volatile=" << isVolatile
+ << " checkAttempts=" << checkAttempts
+ << " sequence={" << Service::StateToString(initialState);
+
+ for (ServiceState s : sequence) {
+ buf << " " << Service::StateToString(s);
+ }
+
+ buf << "}";
+ testcase = buf.str();
+ }
+
+ std::cout << "Test case: " << testcase << std::endl;
+
+ // Create host and service for the test.
+ Host::Ptr host = new Host();
+ host->SetName("suppressed_notifications");
+ host->Register();
+
+ Service::Ptr service = new Service();
+ service->SetHostName(host->GetName());
+ service->SetName("service");
+ service->SetActive(true);
+ service->SetVolatile(isVolatile);
+ service->SetMaxCheckAttempts(checkAttempts);
+ service->Activate();
+ service->SetAuthority(true);
+ service->Register();
+
+ host->OnAllConfigLoaded();
+ service->OnAllConfigLoaded();
+
+ // Bring service into the initial hard state.
+ for (int i = 0; i < checkAttempts; i++) {
+ std::cout << " ProcessCheckResult("
+ << Service::StateToString(initialState) << ")" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(initialState));
+ }
+
+ BOOST_CHECK(service->GetState() == initialState);
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+
+ /* Keep track of all notifications requested from now on.
+ *
+ * Boost.Signal2 handler may still be executing from another thread after they were disconnected.
+ * Make the structures accessed by the handlers shared pointers so that they remain valid as long
+ * as they may be accessed from one of these handlers.
+ */
+ auto notificationLog = std::make_shared<NotificationLog>();
+
+ boost::signals2::scoped_connection c (Checkable::OnNotificationsRequested.connect(
+ [notificationLog,service](
+ const Checkable::Ptr& checkable, NotificationType type, const CheckResult::Ptr& cr,
+ const String&, const String&, const MessageOrigin::Ptr&
+ ) {
+ BOOST_CHECK_EQUAL(checkable, service);
+ std::cout << " -> OnNotificationsRequested(" << Notification::NotificationTypeToString(type)
+ << ", " << Service::StateToString(cr->GetState()) << ")" << std::endl;
+
+ notificationLog->Add({type, cr->GetState()});
+ }
+ ));
+
+ // Helper to assert which notifications were requested. Implicitly clears the stored notifications.
+ auto assertNotifications = [notificationLog](
+ const std::vector<std::pair<NotificationType, ServiceState>>& expected,
+ const std::string& extraMessage
+ ) {
+ // Pretty-printer for the vectors of requested and expected notifications.
+ auto pretty = [](const std::vector<std::pair<NotificationType, ServiceState>>& vec) {
+ std::ostringstream s;
+
+ s << "{";
+ bool first = true;
+ for (const auto &v : vec) {
+ if (first) {
+ first = false;
+ } else {
+ s << ", ";
+ }
+ s << Notification::NotificationTypeToString(v.first)
+ << "/" << Service::StateToString(v.second);
+ }
+ s << "}";
+
+ return s.str();
+ };
+
+ auto got (notificationLog->GetAndClear());
+
+ BOOST_CHECK_MESSAGE(got == expected, "expected=" << pretty(expected)
+ << " got=" << pretty(got)
+ << (extraMessage.empty() ? "" : " ") << extraMessage);
+ };
+
+ // Start a downtime for the service.
+ std::cout << " Downtime Start" << std::endl;
+ Downtime::Ptr downtime = new Downtime();
+ downtime->SetHostName(host->GetName());
+ downtime->SetServiceName(service->GetName());
+ downtime->SetName("downtime");
+ downtime->SetFixed(true);
+ downtime->SetStartTime(Utility::GetTime() - 3600);
+ downtime->SetEndTime(Utility::GetTime() + 3600);
+ service->RegisterDowntime(downtime);
+ downtime->Register();
+ downtime->OnAllConfigLoaded();
+ downtime->TriggerDowntime(Utility::GetTime());
+
+ BOOST_CHECK(service->IsInDowntime());
+
+ // Process check results for the state sequence.
+ for (ServiceState s : sequence) {
+ std::cout << " ProcessCheckResult(" << Service::StateToString(s) << ")" << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(s));
+ BOOST_CHECK(service->GetState() == s);
+ if (checkAttempts == 1) {
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+ }
+ }
+
+ assertNotifications({}, "(no notifications in downtime)");
+
+ if (service->GetSuppressedNotifications()) {
+ BOOST_CHECK_EQUAL(service->GetStateBeforeSuppression(), initialState);
+ }
+
+ // Remove the downtime.
+ std::cout << " Downtime End" << std::endl;
+ service->UnregisterDowntime(downtime);
+ downtime->Unregister();
+ BOOST_CHECK(!service->IsInDowntime());
+
+ if (service->GetStateType() == icinga::StateTypeSoft) {
+ // When the current state is a soft state, no notification should be sent just yet.
+ std::cout << " FireSuppressedNotifications()" << std::endl;
+ service->FireSuppressedNotifications();
+
+ assertNotifications({}, testcase + " (should not fire in soft state)");
+
+ // Repeat the last check result until reaching a hard state.
+ for (int i = 0; i < checkAttempts && service->GetStateType() == StateTypeSoft; i++) {
+ std::cout << " ProcessCheckResult(" << Service::StateToString(sequence.back()) << ")"
+ << std::endl;
+ service->ProcessCheckResult(MakeCheckResult(sequence.back()));
+ BOOST_CHECK(service->GetState() == sequence.back());
+ }
+ }
+
+ // The service should be in a hard state now and notifications should now be sent if applicable.
+ BOOST_CHECK(service->GetStateType() == StateTypeHard);
+
+ std::cout << " FireSuppressedNotifications()" << std::endl;
+ service->FireSuppressedNotifications();
+
+ if (initialState != sequence.back()) {
+ NotificationType t = sequence.back() == ServiceOK ? NotificationRecovery : NotificationProblem;
+ assertNotifications({{t, sequence.back()}}, testcase);
+ } else {
+ assertNotifications({}, testcase);
+ }
+
+ // Remove host and service.
+ service->Unregister();
+ host->Unregister();
+ }
+ }
+ }
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/icinga-dependencies.cpp b/test/icinga-dependencies.cpp
new file mode 100644
index 0000000..929b6ca
--- /dev/null
+++ b/test/icinga-dependencies.cpp
@@ -0,0 +1,101 @@
+/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+#include "icinga/host.hpp"
+#include "icinga/dependency.hpp"
+#include <BoostTestTargetConfig.h>
+#include <iostream>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(icinga_dependencies)
+
+BOOST_AUTO_TEST_CASE(multi_parent)
+{
+ /* One child host, two parent hosts. Simulate multi-parent dependencies. */
+ std::cout << "Testing reachability for multi parent dependencies." << std::endl;
+
+ /*
+ * Our mock requires:
+ * - SetParent/SetChild functions for the dependency
+ * - Parent objects need a CheckResult object
+ * - Dependencies need a StateFilter
+ */
+ Host::Ptr parentHost1 = new Host();
+ parentHost1->SetActive(true);
+ parentHost1->SetMaxCheckAttempts(1);
+ parentHost1->Activate();
+ parentHost1->SetAuthority(true);
+ parentHost1->SetStateRaw(ServiceCritical);
+ parentHost1->SetStateType(StateTypeHard);
+ parentHost1->SetLastCheckResult(new CheckResult());
+
+ Host::Ptr parentHost2 = new Host();
+ parentHost2->SetActive(true);
+ parentHost2->SetMaxCheckAttempts(1);
+ parentHost2->Activate();
+ parentHost2->SetAuthority(true);
+ parentHost2->SetStateRaw(ServiceOK);
+ parentHost2->SetStateType(StateTypeHard);
+ parentHost2->SetLastCheckResult(new CheckResult());
+
+ Host::Ptr childHost = new Host();
+ childHost->SetActive(true);
+ childHost->SetMaxCheckAttempts(1);
+ childHost->Activate();
+ childHost->SetAuthority(true);
+ childHost->SetStateRaw(ServiceOK);
+ childHost->SetStateType(StateTypeHard);
+
+ /* Build the dependency tree. */
+ Dependency::Ptr dep1 = new Dependency();
+
+ dep1->SetParent(parentHost1);
+ dep1->SetChild(childHost);
+ dep1->SetStateFilter(StateFilterUp);
+
+ // Reverse dependencies
+ childHost->AddDependency(dep1);
+ parentHost1->AddReverseDependency(dep1);
+
+ Dependency::Ptr dep2 = new Dependency();
+
+ dep2->SetParent(parentHost2);
+ dep2->SetChild(childHost);
+ dep2->SetStateFilter(StateFilterUp);
+
+ // Reverse dependencies
+ childHost->AddDependency(dep2);
+ parentHost2->AddReverseDependency(dep2);
+
+
+ /* Test the reachability from this point.
+ * parentHost1 is DOWN, parentHost2 is UP.
+ * Expected result: childHost is unreachable.
+ */
+ parentHost1->SetStateRaw(ServiceCritical); // parent Host 1 DOWN
+ parentHost2->SetStateRaw(ServiceOK); // parent Host 2 UP
+
+ BOOST_CHECK(childHost->IsReachable() == false);
+
+ /* The only DNS server is DOWN.
+ * Expected result: childHost is unreachable.
+ */
+ dep1->SetRedundancyGroup("DNS");
+ BOOST_CHECK(childHost->IsReachable() == false);
+
+ /* 1/2 DNS servers is DOWN.
+ * Expected result: childHost is reachable.
+ */
+ dep2->SetRedundancyGroup("DNS");
+ BOOST_CHECK(childHost->IsReachable() == true);
+
+ /* Both DNS servers are DOWN.
+ * Expected result: childHost is unreachable.
+ */
+ parentHost1->SetStateRaw(ServiceCritical); // parent Host 1 DOWN
+ parentHost2->SetStateRaw(ServiceCritical); // parent Host 2 DOWN
+
+ BOOST_CHECK(childHost->IsReachable() == false);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/icinga-legacytimeperiod.cpp b/test/icinga-legacytimeperiod.cpp
new file mode 100644
index 0000000..e1150be
--- /dev/null
+++ b/test/icinga-legacytimeperiod.cpp
@@ -0,0 +1,694 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/utility.hpp"
+#include "icinga/legacytimeperiod.hpp"
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/date_time/posix_time/ptime.hpp>
+#include <boost/date_time/posix_time/posix_time_duration.hpp>
+#include <boost/date_time/gregorian/conversion.hpp>
+#include <boost/date_time/date.hpp>
+#include <boost/optional.hpp>
+#include <iomanip>
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(icinga_legacytimeperiod);
+
+struct GlobalTimezoneFixture
+{
+ char *tz;
+
+ GlobalTimezoneFixture(const char *fixed_tz = "")
+ {
+ tz = getenv("TZ");
+#ifdef _WIN32
+ _putenv_s("TZ", fixed_tz == "" ? "UTC" : fixed_tz);
+#else
+ setenv("TZ", fixed_tz, 1);
+#endif
+ tzset();
+ }
+
+ ~GlobalTimezoneFixture()
+ {
+#ifdef _WIN32
+ if (tz)
+ _putenv_s("TZ", tz);
+ else
+ _putenv_s("TZ", "");
+#else
+ if (tz)
+ setenv("TZ", tz, 1);
+ else
+ unsetenv("TZ");
+#endif
+ tzset();
+ }
+};
+
+BOOST_GLOBAL_FIXTURE(GlobalTimezoneFixture);
+
+// DST changes in America/Los_Angeles:
+// 2021-03-14: 01:59:59 PST (UTC-8) -> 03:00:00 PDT (UTC-7)
+// 2021-11-07: 01:59:59 PDT (UTC-7) -> 01:00:00 PST (UTC-8)
+#ifndef _WIN32
+static const char *dst_test_timezone = "America/Los_Angeles";
+#else /* _WIN32 */
+// Tests are using pacific time because Windows only really supports timezones following US DST rules with the TZ
+// environment variable. Format is "[Standard TZ][negative UTC offset][DST TZ]".
+// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset?view=msvc-160#remarks
+static const char *dst_test_timezone = "PST8PDT";
+#endif /* _WIN32 */
+
+BOOST_AUTO_TEST_CASE(simple)
+{
+ tm tm_beg, tm_end, tm_ref;
+ String timestamp;
+ boost::posix_time::ptime begin;
+ boost::posix_time::ptime end;
+ boost::posix_time::ptime expectedBegin;
+ boost::posix_time::ptime expectedEnd;
+
+ //-----------------------------------------------------
+ // check parsing of "YYYY-MM-DD" specs
+ timestamp = "2016-01-01";
+
+ expectedBegin = boost::posix_time::ptime(boost::gregorian::date(2016, 1, 1), boost::posix_time::time_duration(0, 0, 0));
+
+ expectedEnd = boost::posix_time::ptime(boost::gregorian::date(2016, 1, 2), boost::posix_time::time_duration(0, 0, 0));
+
+ // Run test
+ LegacyTimePeriod::ParseTimeSpec(timestamp, &tm_beg, &tm_end, &tm_ref);
+
+ // Compare times
+ begin = boost::posix_time::ptime_from_tm(tm_beg);
+ end = boost::posix_time::ptime_from_tm(tm_end);
+
+ BOOST_CHECK_EQUAL(begin, expectedBegin);
+ BOOST_CHECK_EQUAL(end, expectedEnd);
+
+ //-----------------------------------------------------
+ timestamp = "2015-12-31";
+
+ expectedBegin = boost::posix_time::ptime(boost::gregorian::date(2015, 12, 31), boost::posix_time::time_duration(0, 0, 0));
+
+ expectedEnd = boost::posix_time::ptime(boost::gregorian::date(2016, 1, 1), boost::posix_time::time_duration(0, 0, 0));
+
+ // Run test
+ LegacyTimePeriod::ParseTimeSpec(timestamp, &tm_beg, &tm_end, &tm_ref);
+
+ // Compare times
+ begin = boost::posix_time::ptime_from_tm(tm_beg);
+ end = boost::posix_time::ptime_from_tm(tm_end);
+
+ BOOST_CHECK_EQUAL(begin, expectedBegin);
+ BOOST_CHECK_EQUAL(end, expectedEnd);
+
+ //-----------------------------------------------------
+ // Break things forcefully
+ BOOST_CHECK_THROW(LegacyTimePeriod::ParseTimeSpec("2015-12-32", &tm_beg, &tm_end, &tm_ref),
+ std::invalid_argument);
+
+ BOOST_CHECK_THROW(LegacyTimePeriod::ParseTimeSpec("2015-28-01", &tm_beg, &tm_end, &tm_ref),
+ std::invalid_argument);
+
+ //-----------------------------------------------------
+ // check parsing of "day X" and "day -X" specs
+ timestamp = "day 2";
+ tm_ref.tm_year = 2016 - 1900;
+ tm_ref.tm_mon = 2 - 1;
+
+ expectedBegin = boost::posix_time::ptime(boost::gregorian::date(2016, 2, 2), boost::posix_time::time_duration(0, 0, 0));
+
+ expectedEnd = boost::posix_time::ptime(boost::gregorian::date(2016, 2, 3), boost::posix_time::time_duration(0, 0, 0));
+
+ // Run Tests
+ LegacyTimePeriod::ParseTimeSpec(timestamp, &tm_beg, &tm_end, &tm_ref);
+
+ // Compare times
+ begin = boost::posix_time::ptime_from_tm(tm_beg);
+ end = boost::posix_time::ptime_from_tm(tm_end);
+
+ BOOST_CHECK_EQUAL(begin, expectedBegin);
+ BOOST_CHECK_EQUAL(end, expectedEnd);
+
+ //-----------------------------------------------------
+ timestamp = "day 31";
+ tm_ref.tm_year = 2018 - 1900;
+ tm_ref.tm_mon = 12 - 1;
+
+ expectedBegin = boost::posix_time::ptime(boost::gregorian::date(2018, 12, 31), boost::posix_time::time_duration(0, 0, 0));
+
+ expectedEnd = boost::posix_time::ptime(boost::gregorian::date(2019, 1, 1), boost::posix_time::time_duration(0, 0, 0));
+
+ // Run Tests
+ LegacyTimePeriod::ParseTimeSpec(timestamp, &tm_beg, &tm_end, &tm_ref);
+
+ // Compare times
+ begin = boost::posix_time::ptime_from_tm(tm_beg);
+ end = boost::posix_time::ptime_from_tm(tm_end);
+
+ BOOST_CHECK_EQUAL(begin, expectedBegin);
+ BOOST_CHECK_EQUAL(end, expectedEnd);
+
+ //-----------------------------------------------------
+ // Last day of the month
+ timestamp = "day -1";
+ tm_ref.tm_year = 2012 - 1900;
+ tm_ref.tm_mon = 7 - 1;
+
+ expectedBegin = boost::posix_time::ptime(boost::gregorian::date(2012, 7, 31), boost::posix_time::time_duration(0, 0, 0));
+
+ expectedEnd = boost::posix_time::ptime(boost::gregorian::date(2012, 8, 1), boost::posix_time::time_duration(0, 0, 0));
+
+ // Run Tests
+ LegacyTimePeriod::ParseTimeSpec(timestamp, &tm_beg, &tm_end, &tm_ref);
+
+ // Compare times
+ begin = boost::posix_time::ptime_from_tm(tm_beg);
+ end = boost::posix_time::ptime_from_tm(tm_end);
+
+ BOOST_CHECK_EQUAL(begin, expectedBegin);
+ BOOST_CHECK_EQUAL(end, expectedEnd);
+
+ //-----------------------------------------------------
+ // Third last day of the month
+ timestamp = "day -3";
+ tm_ref.tm_year = 2019 - 1900;
+ tm_ref.tm_mon = 7 - 1;
+
+ expectedBegin = boost::posix_time::ptime(boost::gregorian::date(2019, 7, 29), boost::posix_time::time_duration(0, 0, 0));
+
+ expectedEnd = boost::posix_time::ptime(boost::gregorian::date(2019, 7, 30), boost::posix_time::time_duration(0, 0, 0));
+
+ // Run Tests
+ LegacyTimePeriod::ParseTimeSpec(timestamp, &tm_beg, &tm_end, &tm_ref);
+
+ // Compare times
+ begin = boost::posix_time::ptime_from_tm(tm_beg);
+ end = boost::posix_time::ptime_from_tm(tm_end);
+
+ BOOST_CHECK_EQUAL(begin, expectedBegin);
+ BOOST_CHECK_EQUAL(end, expectedEnd);
+
+ //-----------------------------------------------------
+ // Leap year with the last day of the month
+ timestamp = "day -1";
+ tm_ref.tm_year = 2016 - 1900; // leap year
+ tm_ref.tm_mon = 2 - 1;
+
+ expectedBegin = boost::posix_time::ptime(boost::gregorian::date(2016, 2, 29), boost::posix_time::time_duration(0, 0, 0));
+
+ expectedEnd = boost::posix_time::ptime(boost::gregorian::date(2016, 3, 1), boost::posix_time::time_duration(0, 0, 0));
+
+ // Run Tests
+ LegacyTimePeriod::ParseTimeSpec("day -1", &tm_beg, &tm_end, &tm_ref);
+
+ // Compare times
+ begin = boost::posix_time::ptime_from_tm(tm_beg);
+ end = boost::posix_time::ptime_from_tm(tm_end);
+
+ BOOST_CHECK_EQUAL(begin, expectedBegin);
+ BOOST_CHECK_EQUAL(end, expectedEnd);
+}
+
+struct DateTime
+{
+ struct {
+ int Year, Month, Day;
+ } Date;
+ struct {
+ int Hour, Minute, Second;
+ } Time;
+};
+
+static inline
+void AdvancedHelper(const char *timestamp, DateTime from, DateTime to)
+{
+ using boost::gregorian::date;
+ using boost::posix_time::ptime;
+ using boost::posix_time::ptime_from_tm;
+ using boost::posix_time::time_duration;
+
+ tm tm_beg, tm_end, tm_ref;
+
+ tm_ref.tm_year = from.Date.Year - 1900;
+ tm_ref.tm_mon = from.Date.Month - 1;
+ tm_ref.tm_mday = from.Date.Day;
+
+ // Run test
+ LegacyTimePeriod::ProcessTimeRangeRaw(timestamp, &tm_ref, &tm_beg, &tm_end);
+
+ // Compare times
+ BOOST_CHECK_EQUAL(ptime_from_tm(tm_beg), ptime(date(from.Date.Year, from.Date.Month, from.Date.Day), time_duration(from.Time.Hour, from.Time.Minute, from.Time.Second)));
+ BOOST_CHECK_EQUAL(ptime_from_tm(tm_end), ptime(date(to.Date.Year, to.Date.Month, to.Date.Day), time_duration(to.Time.Hour, to.Time.Minute, to.Time.Second)));
+}
+
+BOOST_AUTO_TEST_CASE(advanced)
+{
+ tm tm_beg, tm_end, tm_ref;
+ String timestamp;
+ boost::posix_time::ptime begin;
+ boost::posix_time::ptime end;
+ boost::posix_time::ptime expectedBegin;
+ boost::posix_time::ptime expectedEnd;
+
+ //-----------------------------------------------------
+ // 2019-05-06 where Icinga celebrates 10 years #monitoringlove
+ // 2019-05-06 22:00:00 - 2019-05-07 06:00:00
+ AdvancedHelper("22:00-06:00", {{2019, 5, 6}, {22, 0, 0}}, {{2019, 5, 7}, {6, 0, 0}});
+ AdvancedHelper("22:00:01-06:00", {{2019, 5, 6}, {22, 0, 1}}, {{2019, 5, 7}, {6, 0, 0}});
+ AdvancedHelper("22:00-06:00:02", {{2019, 5, 6}, {22, 0, 0}}, {{2019, 5, 7}, {6, 0, 2}});
+ AdvancedHelper("22:00:03-06:00:04", {{2019, 5, 6}, {22, 0, 3}}, {{2019, 5, 7}, {6, 0, 4}});
+
+ //-----------------------------------------------------
+ // 2019-05-06 Icinga is unleashed.
+ // 09:00:00 - 17:00:00
+ AdvancedHelper("09:00-17:00", {{2009, 5, 6}, {9, 0, 0}}, {{2009, 5, 6}, {17, 0, 0}});
+ AdvancedHelper("09:00:01-17:00", {{2009, 5, 6}, {9, 0, 1}}, {{2009, 5, 6}, {17, 0, 0}});
+ AdvancedHelper("09:00-17:00:02", {{2009, 5, 6}, {9, 0, 0}}, {{2009, 5, 6}, {17, 0, 2}});
+ AdvancedHelper("09:00:03-17:00:04", {{2009, 5, 6}, {9, 0, 3}}, {{2009, 5, 6}, {17, 0, 4}});
+
+ //-----------------------------------------------------
+ // At our first Icinga Camp in SFO 2014 at GitHub HQ, we partied all night long with an overflow.
+ // 2014-09-24 09:00:00 - 2014-09-25 06:00:00
+ AdvancedHelper("09:00-30:00", {{2014, 9, 24}, {9, 0, 0}}, {{2014, 9, 25}, {6, 0, 0}});
+ AdvancedHelper("09:00:01-30:00", {{2014, 9, 24}, {9, 0, 1}}, {{2014, 9, 25}, {6, 0, 0}});
+ AdvancedHelper("09:00-30:00:02", {{2014, 9, 24}, {9, 0, 0}}, {{2014, 9, 25}, {6, 0, 2}});
+ AdvancedHelper("09:00:03-30:00:04", {{2014, 9, 24}, {9, 0, 3}}, {{2014, 9, 25}, {6, 0, 4}});
+}
+
+tm make_tm(std::string s)
+{
+ int dst = -1;
+ size_t l = strlen("YYYY-MM-DD HH:MM:SS");
+ if (s.size() > l) {
+ std::string zone = s.substr(l);
+ if (zone == " PST") {
+ dst = 0;
+ } else if (zone == " PDT") {
+ dst = 1;
+ } else {
+ // tests should only use PST/PDT (for now)
+ BOOST_CHECK_MESSAGE(false, "invalid or unknown time time: " << zone);
+ }
+ }
+
+ std::tm t = {};
+#if defined(__GNUC__) && __GNUC__ < 5
+ // GCC did not implement std::get_time() until version 5
+ strptime(s.c_str(), "%Y-%m-%d %H:%M:%S", &t);
+#else /* defined(__GNUC__) && __GNUC__ < 5 */
+ std::istringstream stream(s);
+ stream >> std::get_time(&t, "%Y-%m-%d %H:%M:%S");
+#endif /* defined(__GNUC__) && __GNUC__ < 5 */
+ t.tm_isdst = dst;
+
+ return t;
+}
+
+time_t make_time_t(const tm* t)
+{
+ tm copy = *t;
+ return mktime(&copy);
+}
+
+time_t make_time_t(std::string s)
+{
+ tm t = make_tm(s);
+ return mktime(&t);
+}
+
+struct Segment
+{
+ time_t begin, end;
+
+ Segment(time_t begin, time_t end) : begin(begin), end(end) {}
+ Segment(std::string begin, std::string end) : begin(make_time_t(begin)), end(make_time_t(end)) {}
+
+ bool operator==(const Segment& o) const
+ {
+ return o.begin == begin && o.end == end;
+ }
+};
+
+std::string pretty_time(const tm& t)
+{
+#if defined(__GNUC__) && __GNUC__ < 5
+ // GCC did not implement std::put_time() until version 5
+ char buf[128];
+ size_t n = strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %Z", &t);
+ return std::string(buf, n);
+#else /* defined(__GNUC__) && __GNUC__ < 5 */
+ std::ostringstream stream;
+ stream << std::put_time(&t, "%Y-%m-%d %H:%M:%S %Z");
+ return stream.str();
+#endif /* defined(__GNUC__) && __GNUC__ < 5 */
+}
+
+std::string pretty_time(time_t t)
+{
+ return pretty_time(Utility::LocalTime(t));
+}
+
+std::ostream& operator<<(std::ostream& o, const Segment& s)
+{
+ return o << "(" << pretty_time(s.begin) << " (" << s.begin << ") .. " << pretty_time(s.end) << " (" << s.end << "))";
+}
+
+std::ostream& operator<<(std::ostream& o, const boost::optional<Segment>& s)
+{
+ if (s) {
+ return o << *s;
+ } else {
+ return o << "none";
+ }
+}
+
+BOOST_AUTO_TEST_CASE(dst)
+{
+ GlobalTimezoneFixture tz(dst_test_timezone);
+
+ // Self-tests for helper functions
+ BOOST_CHECK_EQUAL(make_tm("2021-11-07 02:30:00").tm_isdst, -1);
+ BOOST_CHECK_EQUAL(make_tm("2021-11-07 02:30:00 PST").tm_isdst, 0);
+ BOOST_CHECK_EQUAL(make_tm("2021-11-07 02:30:00 PDT").tm_isdst, 1);
+ BOOST_CHECK_EQUAL(make_time_t("2021-11-07 01:30:00 PST"), 1636277400); // date -d '2021-11-07 01:30:00 PST' +%s
+ BOOST_CHECK_EQUAL(make_time_t("2021-11-07 01:30:00 PDT"), 1636273800); // date -d '2021-11-07 01:30:00 PDT' +%s
+
+ struct TestData {
+ std::string day;
+ std::string ranges;
+ std::vector<tm> before;
+ std::vector<tm> during;
+ boost::optional<Segment> expected;
+ };
+
+ // Some of the following test cases have comments describing the current behavior. This might not necessarily be the
+ // best possible behavior, especially that it differs on Windows. So it might be perfectly valid to change this.
+ // These cases are just there to actually notice these changes in this case.
+ std::vector<TestData> tests;
+
+ // 2021-03-14: 01:59:59 PST (UTC-8) -> 03:00:00 PDT (UTC-7)
+ for (const std::string& day : {"2021-03-14", "sunday", "sunday 2", "sunday -3"}) {
+ // range before DST change
+ tests.push_back(TestData{
+ day, "00:30-01:30",
+ {make_tm("2021-03-14 00:00:00 PST")},
+ {make_tm("2021-03-14 01:00:00 PST")},
+ Segment("2021-03-14 00:30:00 PST", "2021-03-14 01:30:00 PST"),
+ });
+
+ if (day.find("sunday") == std::string::npos) { // skip for non-absolute day specs (would find another sunday)
+ // range end actually does not exist on that day
+ tests.push_back(TestData{
+ day, "01:30-02:30",
+ {make_tm("2021-03-14 01:00:00 PST")},
+ {make_tm("2021-03-14 01:59:59 PST")},
+#ifndef _WIN32
+ // As 02:30 does not exist on this day, it is parsed as if it was 02:30 PST which is actually 03:30 PDT.
+ Segment("2021-03-14 01:30:00 PST", "2021-03-14 03:30:00 PDT"),
+#else
+ // Windows interpretes 02:30 as 01:30 PST, so it is an empty segment.
+ boost::none,
+#endif
+ });
+ }
+
+ if (day.find("sunday") == std::string::npos) { // skip for non-absolute day specs (would find another sunday)
+ // range beginning does not actually exist on that day
+ tests.push_back(TestData{
+ day, "02:30-03:30",
+ {make_tm("2021-03-14 01:00:00 PST")},
+ {make_tm("2021-03-14 03:00:00 PDT")},
+#ifndef _WIN32
+ // As 02:30 does not exist on this day, it is parsed as if it was 02:30 PST which is actually 03:30 PDT.
+ // Therefore, the result is a segment from 03:30 PDT to 03:30 PDT with a duration of 0, i.e. no segment.
+ boost::none,
+#else
+ // Windows parses non-existing 02:30 as 01:30 PST, resulting in an 1 hour segment.
+ Segment("2021-03-14 01:30:00 PST", "2021-03-14 03:30:00 PDT"),
+#endif
+ });
+ }
+
+ // another range where the beginning does not actually exist on that day
+ tests.push_back(TestData{
+ day, "02:15-03:45",
+ {make_tm("2021-03-14 01:00:00 PST")},
+ {make_tm("2021-03-14 03:30:00 PDT")},
+#ifndef _WIN32
+ // As 02:15 does not exist on this day, it is parsed as if it was 02:15 PST which is actually 03:15 PDT.
+ Segment("2021-03-14 03:15:00 PDT", "2021-03-14 03:45:00 PDT"),
+#else
+ // Windows interprets 02:15 as 01:15 PST though.
+ Segment("2021-03-14 01:15:00 PST", "2021-03-14 03:45:00 PDT"),
+#endif
+ });
+
+ // range after DST change
+ tests.push_back(TestData{
+ day, "03:30-04:30",
+ {make_tm("2021-03-14 01:00:00 PST"), make_tm("2021-03-14 03:00:00 PDT")},
+ {make_tm("2021-03-14 04:00:00 PDT")},
+ Segment("2021-03-14 03:30:00 PDT", "2021-03-14 04:30:00 PDT"),
+ });
+
+ // range containing DST change
+ tests.push_back(TestData{
+ day, "01:30-03:30",
+ {make_tm("2021-03-14 01:00:00 PST")},
+ {make_tm("2021-03-14 01:45:00 PST"), make_tm("2021-03-14 03:15:00 PDT")},
+ Segment("2021-03-14 01:30:00 PST", "2021-03-14 03:30:00 PDT"),
+ });
+ }
+
+ // 2021-11-07: 01:59:59 PDT (UTC-7) -> 01:00:00 PST (UTC-8)
+ for (const std::string& day : {"2021-11-07", "sunday", "sunday 1", "sunday -4"}) {
+ // range before DST change
+ tests.push_back(TestData{
+ day, "00:15-00:45",
+ {make_tm("2021-11-07 00:00:00 PDT")},
+ {make_tm("2021-11-07 00:30:00 PDT")},
+ Segment("2021-11-07 00:15:00 PDT", "2021-11-07 00:45:00 PDT"),
+ });
+
+ if (day.find("sunday") == std::string::npos) { // skip for non-absolute day specs (would find another sunday)
+ // range existing twice during DST change (first instance)
+#ifndef _WIN32
+ tests.push_back(TestData{
+ day, "01:15-01:45",
+ {make_tm("2021-11-07 01:00:00 PDT")},
+ {make_tm("2021-11-07 01:30:00 PDT")},
+ // Duplicate times are interpreted as the first occurrence.
+ Segment("2021-11-07 01:15:00 PDT", "2021-11-07 01:45:00 PDT"),
+ });
+#else
+ tests.push_back(TestData{
+ day, "01:15-01:45",
+ {make_tm("2021-11-07 01:00:00 PDT")},
+ {make_tm("2021-11-07 01:30:00 PST")},
+ // However, Windows always uses the second occurrence.
+ Segment("2021-11-07 01:15:00 PST", "2021-11-07 01:45:00 PST"),
+ });
+#endif
+ }
+
+ if (day.find("sunday") == std::string::npos) { // skip for non-absolute day specs (would find another sunday)
+ // range existing twice during DST change (second instance)
+ tests.push_back(TestData{
+ day, "01:15-01:45",
+ {make_tm("2021-11-07 01:00:00 PST")},
+ {make_tm("2021-11-07 01:30:00 PST")},
+#ifndef _WIN32
+ // Interpreted as the first occurrence, so it's in the past.
+ boost::none,
+#else
+ // On Windows, it's the second occurrence, so it's still in the present/future and is found.
+ Segment("2021-11-07 01:15:00 PST", "2021-11-07 01:45:00 PST"),
+#endif
+ });
+ }
+
+ // range after DST change
+ tests.push_back(TestData{
+ day, "03:30-04:30",
+ {make_tm("2021-11-07 01:00:00 PDT"), make_tm("2021-11-07 03:00:00 PST")},
+ {make_tm("2021-11-07 04:00:00 PST")},
+ Segment("2021-11-07 03:30:00 PST", "2021-11-07 04:30:00 PST"),
+ });
+
+ // range containing DST change
+ tests.push_back(TestData{
+ day, "00:30-02:30",
+ {make_tm("2021-11-07 00:00:00 PDT")},
+ {make_tm("2021-11-07 00:45:00 PDT"), make_tm("2021-11-07 01:30:00 PDT"),
+ make_tm("2021-11-07 01:30:00 PST"), make_tm("2021-11-07 02:15:00 PST")},
+ Segment("2021-11-07 00:30:00 PDT", "2021-11-07 02:30:00 PST"),
+ });
+
+ // range ending during duplicate DST hour (first instance)
+ tests.push_back(TestData{
+ day, "00:30-01:30",
+ {make_tm("2021-11-07 00:00:00 PDT")},
+ {make_tm("2021-11-07 01:00:00 PDT")},
+#ifndef _WIN32
+ // Both times are interpreted as the first instance on that day (i.e both PDT).
+ Segment("2021-11-07 00:30:00 PDT", "2021-11-07 01:30:00 PDT")
+#else
+ // Windows interprets duplicate times as the second instance (i.e. both PST).
+ Segment("2021-11-07 00:30:00 PDT", "2021-11-07 01:30:00 PST")
+#endif
+ });
+
+ // range beginning during duplicate DST hour (first instance)
+ tests.push_back(TestData{
+ day, "01:30-02:30",
+ {make_tm("2021-11-07 01:00:00 PDT")},
+ {make_tm("2021-11-07 02:00:00 PST")},
+#ifndef _WIN32
+ // 01:30 is interpreted as the first occurrence (PDT) but since there's no 02:30 PDT, it's PST.
+ Segment("2021-11-07 01:30:00 PDT", "2021-11-07 02:30:00 PST")
+#else
+ // Windows interprets both as PST though.
+ Segment("2021-11-07 01:30:00 PST", "2021-11-07 02:30:00 PST")
+#endif
+ });
+
+ if (day.find("sunday") == std::string::npos) { // skip for non-absolute day specs (would find another sunday)
+ // range ending during duplicate DST hour (second instance)
+#ifndef _WIN32
+ tests.push_back(TestData{
+ day, "00:30-01:30",
+ {make_tm("2021-11-07 00:00:00 PST")},
+ {make_tm("2021-11-07 01:00:00 PST")},
+ // Both times are parsed as PDT. Thus, 00:00 PST (01:00 PDT) is during the segment and
+ // 01:00 PST (02:00 PDT) is after the segment.
+ boost::none,
+ });
+#else
+ tests.push_back(TestData{
+ day, "00:30-01:30",
+ {make_tm("2021-11-07 00:00:00 PDT")},
+ {make_tm("2021-11-07 01:00:00 PST")},
+ // As Windows interprets the end as PST, it's still in the future and the segment is found.
+ Segment("2021-11-07 00:30:00 PDT", "2021-11-07 01:30:00 PST"),
+ });
+#endif
+ }
+
+ // range beginning during duplicate DST hour (second instance)
+ tests.push_back(TestData{
+ day, "01:30-02:30",
+ {make_tm("2021-11-07 01:00:00 PDT")},
+ {make_tm("2021-11-07 02:00:00 PST")},
+#ifndef _WIN32
+ // As 01:30 always refers to the first occurrence (PDT), this is actually a 2 hour segment.
+ Segment("2021-11-07 01:30:00 PDT", "2021-11-07 02:30:00 PST"),
+#else
+ // On Windows, it refers t the second occurrence (PST), therefore it's an 1 hour segment.
+ Segment("2021-11-07 01:30:00 PST", "2021-11-07 02:30:00 PST"),
+#endif
+ });
+ }
+
+ auto seg = [](const Dictionary::Ptr& segment) -> boost::optional<Segment> {
+ if (segment == nullptr) {
+ return boost::none;
+ }
+
+ BOOST_CHECK(segment->Contains("begin"));
+ BOOST_CHECK(segment->Contains("end"));
+
+ return Segment{time_t(segment->Get("begin")), time_t(segment->Get("end"))};
+ };
+
+ for (const TestData& t : tests) {
+ for (const tm& ref : t.during) {
+ if (t.expected) {
+ // test data sanity check
+ time_t ref_ts = make_time_t(&ref);
+ BOOST_CHECK_MESSAGE(t.expected->begin < ref_ts, "[day='" << t.day << "' ranges='" << t.ranges
+ << "'] expected.begin='"<< pretty_time(t.expected->begin) << "' < ref='" << pretty_time(ref_ts)
+ << "' violated");
+ BOOST_CHECK_MESSAGE(ref_ts < t.expected->end, "[day='" << t.day << "' ranges='" << t.ranges
+ << "'] ref='" << pretty_time(ref_ts) << "' < expected.end='" << pretty_time(t.expected->end)
+ << "' violated");
+ }
+
+ tm mutRef = ref;
+ auto runningSeg = seg(LegacyTimePeriod::FindRunningSegment(t.day, t.ranges, &mutRef));
+ BOOST_CHECK_MESSAGE(runningSeg == t.expected, "FindRunningSegment(day='" << t.day
+ << "' ranges='" << t.ranges << "' ref='" << pretty_time(ref) << "'): got=" << runningSeg
+ << " expected=" << t.expected);
+ }
+
+ for (const tm& ref : t.before) {
+ if (t.expected) {
+ // test data sanity check
+ time_t ref_ts = make_time_t(&ref);
+ BOOST_CHECK_MESSAGE(ref_ts < t.expected->begin, "[day='" << t.day << "' ranges='" << t.ranges
+ << "'] ref='"<< pretty_time(ref_ts) << "' < expected.begin='" << pretty_time(t.expected->begin)
+ << "' violated");
+ BOOST_CHECK_MESSAGE(t.expected->begin < t.expected->end, "[day='" << t.day << "' ranges='" << t.ranges
+ << "'] expected.begin='" << pretty_time(t.expected->begin)
+ << "' < expected.end='" << pretty_time(t.expected->end) << "' violated");
+ }
+
+ tm mutRef = ref;
+ auto nextSeg = seg(LegacyTimePeriod::FindNextSegment(t.day, t.ranges, &mutRef));
+ BOOST_CHECK_MESSAGE(nextSeg == t.expected, "FindNextSegment(day='" << t.day << "' ranges='" << t.ranges
+ << "' ref='" << pretty_time(ref) << "'): got=" << nextSeg << " expected=" << t.expected);
+ }
+ }
+}
+
+// This tests checks that TimePeriod::IsInside() always returns true for a 24x7 period, even around DST changes.
+BOOST_AUTO_TEST_CASE(dst_isinside)
+{
+ GlobalTimezoneFixture tz(dst_test_timezone);
+
+ Function::Ptr update = new Function("LegacyTimePeriod", LegacyTimePeriod::ScriptFunc, {"tp", "begin", "end"});
+ Dictionary::Ptr ranges = new Dictionary({
+ {"monday", "00:00-24:00"},
+ {"tuesday", "00:00-24:00"},
+ {"wednesday", "00:00-24:00"},
+ {"thursday", "00:00-24:00"},
+ {"friday", "00:00-24:00"},
+ {"saturday", "00:00-24:00"},
+ {"sunday", "00:00-24:00"},
+ });
+
+ // Vary begin from Sat 06 Nov 2021 00:00:00 PDT to Mon 08 Nov 2021 00:00:00 PST in 30 minute intervals.
+ for (time_t t_begin = 1636182000; t_begin <= 1636358400; t_begin += 30*60) {
+ // Test varying interval lengths: 60 minutes, 24 hours, 48 hours.
+ for (time_t len : {60*60, 24*60*60, 48*60*60}) {
+ time_t t_end = t_begin + len;
+
+ TimePeriod::Ptr p = new TimePeriod();
+ p->SetUpdate(update, true);
+ p->SetRanges(ranges, true);
+
+ p->UpdateRegion(double(t_begin), double(t_end), true);
+
+ {
+ // Print resulting segments for easier debugging.
+ Array::Ptr segments = p->GetSegments();
+ ObjectLock lock(segments);
+ for (Dictionary::Ptr segment: segments) {
+ BOOST_TEST_MESSAGE("t_begin=" << t_begin << " t_end=" << t_end
+ << " segment.begin=" << segment->Get("begin") << " segment.end=" << segment->Get("end"));
+ }
+ }
+
+ time_t step = 10*60;
+ for (time_t t = t_begin+step; t < t_end; t += step) {
+ BOOST_CHECK_MESSAGE(p->IsInside(double(t)),
+ t << " should be inside for t_begin=" << t_begin << " t_end=" << t_end);
+ }
+ }
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/icinga-macros.cpp b/test/icinga-macros.cpp
new file mode 100644
index 0000000..e7c789c
--- /dev/null
+++ b/test/icinga-macros.cpp
@@ -0,0 +1,50 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/macroprocessor.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(icinga_macros)
+
+BOOST_AUTO_TEST_CASE(simple)
+{
+ Dictionary::Ptr macrosA = new Dictionary();
+ macrosA->Set("testA", 7);
+ macrosA->Set("testB", "hello");
+
+ Dictionary::Ptr macrosB = new Dictionary();
+ macrosB->Set("testA", 3);
+ macrosB->Set("testC", "world");
+
+ Array::Ptr testD = new Array();
+ testD->Add(3);
+ testD->Add("test");
+
+ macrosB->Set("testD", testD);
+
+ MacroProcessor::ResolverList resolvers;
+ resolvers.emplace_back("macrosA", macrosA);
+ resolvers.emplace_back("macrosB", macrosB);
+
+ BOOST_CHECK(MacroProcessor::ResolveMacros("$macrosA.testB$ $macrosB.testC$", resolvers) == "hello world");
+ BOOST_CHECK(MacroProcessor::ResolveMacros("$testA$", resolvers) == "7");
+ BOOST_CHECK(MacroProcessor::ResolveMacros("$testA$$testB$", resolvers) == "7hello");
+
+ Array::Ptr result = MacroProcessor::ResolveMacros("$testD$", resolvers);
+ BOOST_CHECK(result->GetLength() == 2);
+
+ /* verify the config validator macro checks */
+ BOOST_CHECK(MacroProcessor::ValidateMacroString("$host.address") == false);
+ BOOST_CHECK(MacroProcessor::ValidateMacroString("host.vars.test$") == false);
+
+ BOOST_CHECK(MacroProcessor::ValidateMacroString("host.vars.test$") == false);
+ BOOST_CHECK(MacroProcessor::ValidateMacroString("$template::test$abc$") == false);
+
+ BOOST_CHECK(MacroProcessor::ValidateMacroString("$$test $host.vars.test$") == true);
+
+ BOOST_CHECK(MacroProcessor::ValidateMacroString("test $host.vars.test$") == true);
+
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/icinga-notification.cpp b/test/icinga-notification.cpp
new file mode 100644
index 0000000..a0aeb7d
--- /dev/null
+++ b/test/icinga-notification.cpp
@@ -0,0 +1,215 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/host.hpp"
+#include "icinga/notification.hpp"
+#include "icinga/notificationcommand.hpp"
+#include "icinga/service.hpp"
+#include "icinga/user.hpp"
+#include <BoostTestTargetConfig.h>
+#include <iostream>
+
+using namespace icinga;
+
+struct DuplicateDueToFilterHelper
+{
+ Host::Ptr h = new Host();
+ Service::Ptr s = new Service();
+ User::Ptr u = new User();
+ NotificationCommand::Ptr nc = new NotificationCommand();
+ Notification::Ptr n = new Notification();
+ unsigned int called = 0;
+
+ DuplicateDueToFilterHelper(int typeFilter, int stateFilter)
+ {
+ h->SetName("example.com", true);
+ h->Register();
+
+ s->SetShortName("disk", true);
+ h->AddService(s);
+
+ u->SetName("jdoe", true);
+ u->SetTypeFilter(~0);
+ u->SetStateFilter(~0);
+ u->Register();
+
+ nc->SetName("mail", true);
+ nc->SetExecute(new Function("", [this]() { ++called; }), true);
+ nc->Register();
+
+ n->SetFieldByName("host_name", "example.com", false, DebugInfo());
+ n->SetFieldByName("service_name", "disk", false, DebugInfo());
+ n->SetFieldByName("command", "mail", false, DebugInfo());
+ n->SetUsersRaw(new Array({"jdoe"}), true);
+ n->SetTypeFilter(typeFilter);
+ n->SetStateFilter(stateFilter);
+ n->OnAllConfigLoaded(); // link Service
+ }
+
+ ~DuplicateDueToFilterHelper()
+ {
+ h->Unregister();
+ u->Unregister();
+ nc->Unregister();
+ }
+
+ void SendStateNotification(ServiceState state, bool isSent)
+ {
+ auto calledBefore (called);
+
+ s->SetStateRaw(state, true);
+ Application::GetTP().Start();
+
+ n->BeginExecuteNotification(
+ state == ServiceOK ? NotificationRecovery : NotificationProblem,
+ nullptr, false, false, "", ""
+ );
+
+ Application::GetTP().Stop();
+ BOOST_CHECK_EQUAL(called > calledBefore, isSent);
+ }
+};
+
+BOOST_AUTO_TEST_SUITE(icinga_notification)
+
+BOOST_AUTO_TEST_CASE(strings)
+{
+ // States
+ BOOST_CHECK("OK" == Notification::NotificationServiceStateToString(ServiceOK));
+ BOOST_CHECK("Critical" == Notification::NotificationServiceStateToString(ServiceCritical));
+ BOOST_CHECK("Up" == Notification::NotificationHostStateToString(HostUp));
+
+ // Types
+ BOOST_CHECK("DowntimeStart" == Notification::NotificationTypeToString(NotificationDowntimeStart));
+ BOOST_CHECK("Problem" == Notification::NotificationTypeToString(NotificationProblem));
+
+ // Compat
+ BOOST_CHECK("DOWNTIMECANCELLED" == Notification::NotificationTypeToStringCompat(NotificationDowntimeRemoved));
+}
+
+BOOST_AUTO_TEST_CASE(state_filter)
+{
+ unsigned long fstate;
+
+ Array::Ptr states = new Array();
+ states->Add("OK");
+ states->Add("Warning");
+
+ Notification::Ptr notification = new Notification();
+
+ notification->SetStateFilter(FilterArrayToInt(states, notification->GetStateFilterMap(), ~0));
+ notification->Activate();
+ notification->SetAuthority(true);
+
+ /* Test passing notification state */
+ fstate = StateFilterWarning;
+ std::cout << "#1 Notification state: " << fstate << " against " << notification->GetStateFilter() << " must pass. " << std::endl;
+ BOOST_CHECK(notification->GetStateFilter() & fstate);
+
+ /* Test filtered notification state */
+ fstate = StateFilterUnknown;
+ std::cout << "#2 Notification state: " << fstate << " against " << notification->GetStateFilter() << " must fail." << std::endl;
+ BOOST_CHECK(!(notification->GetStateFilter() & fstate));
+
+ /* Test unset states filter configuration */
+ notification->SetStateFilter(FilterArrayToInt(Array::Ptr(), notification->GetStateFilterMap(), ~0));
+
+ fstate = StateFilterOK;
+ std::cout << "#3 Notification state: " << fstate << " against " << notification->GetStateFilter() << " must pass." << std::endl;
+ BOOST_CHECK(notification->GetStateFilter() & fstate);
+
+ /* Test empty states filter configuration */
+ states->Clear();
+ notification->SetStateFilter(FilterArrayToInt(states, notification->GetStateFilterMap(), ~0));
+
+ fstate = StateFilterCritical;
+ std::cout << "#4 Notification state: " << fstate << " against " << notification->GetStateFilter() << " must fail." << std::endl;
+ BOOST_CHECK(!(notification->GetStateFilter() & fstate));
+}
+BOOST_AUTO_TEST_CASE(type_filter)
+{
+ unsigned long ftype;
+
+ Array::Ptr types = new Array();
+ types->Add("Problem");
+ types->Add("DowntimeStart");
+ types->Add("DowntimeEnd");
+
+ Notification::Ptr notification = new Notification();
+
+ notification->SetTypeFilter(FilterArrayToInt(types, notification->GetTypeFilterMap(), ~0));
+ notification->Activate();
+ notification->SetAuthority(true);
+
+ /* Test passing notification type */
+ ftype = NotificationProblem;
+ std::cout << "#1 Notification type: " << ftype << " against " << notification->GetTypeFilter() << " must pass." << std::endl;
+ BOOST_CHECK(notification->GetTypeFilter() & ftype);
+
+ /* Test filtered notification type */
+ ftype = NotificationCustom;
+ std::cout << "#2 Notification type: " << ftype << " against " << notification->GetTypeFilter() << " must fail." << std::endl;
+ BOOST_CHECK(!(notification->GetTypeFilter() & ftype));
+
+ /* Test unset types filter configuration */
+ notification->SetTypeFilter(FilterArrayToInt(Array::Ptr(), notification->GetTypeFilterMap(), ~0));
+
+ ftype = NotificationRecovery;
+ std::cout << "#3 Notification type: " << ftype << " against " << notification->GetTypeFilter() << " must pass." << std::endl;
+ BOOST_CHECK(notification->GetTypeFilter() & ftype);
+
+ /* Test empty types filter configuration */
+ types->Clear();
+ notification->SetTypeFilter(FilterArrayToInt(types, notification->GetTypeFilterMap(), ~0));
+
+ ftype = NotificationProblem;
+ std::cout << "#4 Notification type: " << ftype << " against " << notification->GetTypeFilter() << " must fail." << std::endl;
+ BOOST_CHECK(!(notification->GetTypeFilter() & ftype));
+}
+
+BOOST_AUTO_TEST_CASE(no_filter_problem_no_duplicate)
+{
+ DuplicateDueToFilterHelper helper (~0, ~0);
+
+ helper.SendStateNotification(ServiceCritical, true);
+ helper.SendStateNotification(ServiceWarning, true);
+ helper.SendStateNotification(ServiceCritical, true);
+}
+
+BOOST_AUTO_TEST_CASE(filter_problem_no_duplicate)
+{
+ DuplicateDueToFilterHelper helper (~0, ~StateFilterWarning);
+
+ helper.SendStateNotification(ServiceCritical, true);
+ helper.SendStateNotification(ServiceWarning, false);
+ helper.SendStateNotification(ServiceCritical, false);
+}
+
+BOOST_AUTO_TEST_CASE(volatile_filter_problem_duplicate)
+{
+ DuplicateDueToFilterHelper helper (~0, ~StateFilterWarning);
+
+ helper.s->SetVolatile(true, true);
+ helper.SendStateNotification(ServiceCritical, true);
+ helper.SendStateNotification(ServiceWarning, false);
+ helper.SendStateNotification(ServiceCritical, true);
+}
+
+BOOST_AUTO_TEST_CASE(no_recovery_filter_no_duplicate)
+{
+ DuplicateDueToFilterHelper helper (~0, ~0);
+
+ helper.SendStateNotification(ServiceCritical, true);
+ helper.SendStateNotification(ServiceOK, true);
+ helper.SendStateNotification(ServiceCritical, true);
+}
+
+BOOST_AUTO_TEST_CASE(recovery_filter_duplicate)
+{
+ DuplicateDueToFilterHelper helper (~NotificationRecovery, ~0);
+
+ helper.SendStateNotification(ServiceCritical, true);
+ helper.SendStateNotification(ServiceOK, false);
+ helper.SendStateNotification(ServiceCritical, true);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/icinga-perfdata.cpp b/test/icinga-perfdata.cpp
new file mode 100644
index 0000000..12e1c28
--- /dev/null
+++ b/test/icinga-perfdata.cpp
@@ -0,0 +1,407 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/perfdatavalue.hpp"
+#include "icinga/pluginutility.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(icinga_perfdata)
+
+BOOST_AUTO_TEST_CASE(empty)
+{
+ Array::Ptr pd = PluginUtility::SplitPerfdata("");
+ BOOST_CHECK(pd->GetLength() == 0);
+}
+
+BOOST_AUTO_TEST_CASE(simple)
+{
+ PerfdataValue::Ptr pdv = PerfdataValue::Parse("test=123456");
+ BOOST_CHECK(pdv->GetLabel() == "test");
+ BOOST_CHECK(pdv->GetValue() == 123456);
+
+ String str = pdv->Format();
+ BOOST_CHECK(str == "test=123456");
+}
+
+BOOST_AUTO_TEST_CASE(quotes)
+{
+ Array::Ptr pd = PluginUtility::SplitPerfdata("'hello world'=123456");
+ BOOST_CHECK(pd->GetLength() == 1);
+
+ PerfdataValue::Ptr pdv = PerfdataValue::Parse("'hello world'=123456");
+ BOOST_CHECK(pdv->GetLabel() == "hello world");
+ BOOST_CHECK(pdv->GetValue() == 123456);
+}
+
+BOOST_AUTO_TEST_CASE(multiple)
+{
+ Array::Ptr pd = PluginUtility::SplitPerfdata("testA=123456 testB=123456");
+ BOOST_CHECK(pd->GetLength() == 2);
+
+ String str = PluginUtility::FormatPerfdata(pd);
+ BOOST_CHECK(str == "testA=123456 testB=123456");
+}
+
+BOOST_AUTO_TEST_CASE(multiline)
+{
+ Array::Ptr pd = PluginUtility::SplitPerfdata(" 'testA'=123456 'testB'=123456");
+ BOOST_CHECK(pd->GetLength() == 2);
+
+ String str = PluginUtility::FormatPerfdata(pd);
+ BOOST_CHECK(str == "testA=123456 testB=123456");
+
+ pd = PluginUtility::SplitPerfdata(" 'testA'=123456 \n'testB'=123456");
+ BOOST_CHECK(pd->GetLength() == 2);
+
+ str = PluginUtility::FormatPerfdata(pd);
+ BOOST_CHECK(str == "testA=123456 testB=123456");
+}
+
+BOOST_AUTO_TEST_CASE(normalize)
+{
+ Array::Ptr pd = PluginUtility::SplitPerfdata("testA=2m;3;4;1;5 testB=2foobar");
+ BOOST_CHECK(pd->GetLength() == 2);
+
+ String str = PluginUtility::FormatPerfdata(pd, true);
+ BOOST_CHECK(str == "testA=120s;180;240;60;300 testB=2");
+}
+
+BOOST_AUTO_TEST_CASE(uom)
+{
+ PerfdataValue::Ptr pv = PerfdataValue::Parse("test=123456B");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 123456);
+ BOOST_CHECK(!pv->GetCounter());
+ BOOST_CHECK(pv->GetUnit() == "bytes");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ String str = pv->Format();
+ BOOST_CHECK(str == "test=123456B");
+
+ pv = PerfdataValue::Parse("test=1000ms;200;500");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 1);
+ BOOST_CHECK(pv->GetUnit() == "seconds");
+ BOOST_CHECK(pv->GetWarn() == 0.2);
+ BOOST_CHECK(pv->GetCrit() == 0.5);
+
+ pv = PerfdataValue::Parse("test=1000ms");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 1);
+ BOOST_CHECK(pv->GetUnit() == "seconds");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=1s");
+
+ pv = PerfdataValue::Parse("test=1kAm");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 60 * 1000);
+ BOOST_CHECK(pv->GetUnit() == "ampere-seconds");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=60000As");
+
+ pv = PerfdataValue::Parse("test=1MA");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 1000 * 1000);
+ BOOST_CHECK(pv->GetUnit() == "amperes");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=1000000A");
+
+ pv = PerfdataValue::Parse("test=1gib");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 1024 * 1024 * 1024);
+ BOOST_CHECK(pv->GetUnit() == "bits");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=1073741824b");
+
+ pv = PerfdataValue::Parse("test=1dBm");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 1);
+ BOOST_CHECK(pv->GetUnit() == "decibel-milliwatts");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=1dBm");
+
+ pv = PerfdataValue::Parse("test=1C");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 1);
+ BOOST_CHECK(pv->GetUnit() == "degrees-celsius");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=1C");
+
+ pv = PerfdataValue::Parse("test=1F");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 1);
+ BOOST_CHECK(pv->GetUnit() == "degrees-fahrenheit");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=1F");
+
+ pv = PerfdataValue::Parse("test=1K");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 1);
+ BOOST_CHECK(pv->GetUnit() == "degrees-kelvin");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=1K");
+
+ pv = PerfdataValue::Parse("test=1t");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 1000 * 1000);
+ BOOST_CHECK(pv->GetUnit() == "grams");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=1000000g");
+
+ pv = PerfdataValue::Parse("test=1hl");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 100);
+ BOOST_CHECK(pv->GetUnit() == "liters");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=100l");
+
+ pv = PerfdataValue::Parse("test=1lm");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 1);
+ BOOST_CHECK(pv->GetUnit() == "lumens");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=1lm");
+
+ pv = PerfdataValue::Parse("test=1TO");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 1000.0 * 1000 * 1000 * 1000);
+ BOOST_CHECK(pv->GetUnit() == "ohms");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=1000000000000O");
+
+ pv = PerfdataValue::Parse("test=1PV");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 1000.0 * 1000 * 1000 * 1000 * 1000);
+ BOOST_CHECK(pv->GetUnit() == "volts");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=1000000000000000V");
+
+ pv = PerfdataValue::Parse("test=1EWh");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 1000.0 * 1000 * 1000 * 1000 * 1000 * 1000);
+ BOOST_CHECK(pv->GetUnit() == "watt-hours");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=1000000000000000000Wh");
+
+ pv = PerfdataValue::Parse("test=1000mW");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 1);
+ BOOST_CHECK(pv->GetUnit() == "watts");
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetMin() == Empty);
+ BOOST_CHECK(pv->GetMax() == Empty);
+
+ str = pv->Format();
+ BOOST_CHECK(str == "test=1W");
+}
+
+BOOST_AUTO_TEST_CASE(warncritminmax)
+{
+ PerfdataValue::Ptr pv = PerfdataValue::Parse("test=123456B;1000;2000;3000;4000");
+ BOOST_CHECK(pv);
+
+ BOOST_CHECK(pv->GetValue() == 123456);
+ BOOST_CHECK(!pv->GetCounter());
+ BOOST_CHECK(pv->GetUnit() == "bytes");
+ BOOST_CHECK(pv->GetWarn() == 1000);
+ BOOST_CHECK(pv->GetCrit() == 2000);
+ BOOST_CHECK(pv->GetMin() == 3000);
+ BOOST_CHECK(pv->GetMax() == 4000);
+
+ BOOST_CHECK(pv->Format() == "test=123456B;1000;2000;3000;4000");
+}
+
+BOOST_AUTO_TEST_CASE(ignore_invalid_warn_crit_min_max)
+{
+ PerfdataValue::Ptr pv = PerfdataValue::Parse("test=123456;1000:2000;0:3000;3000;4000");
+ BOOST_CHECK(pv);
+ BOOST_CHECK(pv->GetValue() == 123456);
+ BOOST_CHECK(pv->GetWarn() == Empty);
+ BOOST_CHECK(pv->GetCrit() == Empty);
+ BOOST_CHECK(pv->GetMin() == 3000);
+ BOOST_CHECK(pv->GetMax() == 4000);
+
+ BOOST_CHECK(pv->Format() == "test=123456");
+}
+
+BOOST_AUTO_TEST_CASE(invalid)
+{
+ BOOST_CHECK_THROW(PerfdataValue::Parse("123456"), boost::exception);
+ BOOST_CHECK_THROW(PerfdataValue::Parse("test=1,23456"), boost::exception);
+ BOOST_CHECK_THROW(PerfdataValue::Parse("test=123_456"), boost::exception);
+ BOOST_CHECK_THROW(PerfdataValue::Parse("test="), boost::exception);
+ BOOST_CHECK_THROW(PerfdataValue::Parse("test=123,456;1;1;1;1"), boost::exception);
+ BOOST_CHECK_THROW(PerfdataValue::Parse("test=1;123,456;1;1;1"), boost::exception);
+ BOOST_CHECK_THROW(PerfdataValue::Parse("test=1;1;123,456;1;1"), boost::exception);
+ BOOST_CHECK_THROW(PerfdataValue::Parse("test=1;1;1;123,456;1"), boost::exception);
+ BOOST_CHECK_THROW(PerfdataValue::Parse("test=1;1;1;1;123,456"), boost::exception);
+}
+
+BOOST_AUTO_TEST_CASE(multi)
+{
+ Array::Ptr pd = PluginUtility::SplitPerfdata("test::a=3 b=4");
+ BOOST_CHECK(pd->Get(0) == "test::a=3");
+ BOOST_CHECK(pd->Get(1) == "test::b=4");
+}
+
+BOOST_AUTO_TEST_CASE(scientificnotation)
+{
+ PerfdataValue::Ptr pdv = PerfdataValue::Parse("test=1.1e+1");
+ BOOST_CHECK(pdv->GetLabel() == "test");
+ BOOST_CHECK(pdv->GetValue() == 11);
+
+ String str = pdv->Format();
+ BOOST_CHECK(str == "test=11");
+
+ pdv = PerfdataValue::Parse("test=1.1e1");
+ BOOST_CHECK(pdv->GetLabel() == "test");
+ BOOST_CHECK(pdv->GetValue() == 11);
+
+ str = pdv->Format();
+ BOOST_CHECK(str == "test=11");
+
+ pdv = PerfdataValue::Parse("test=1.1e-1");
+ BOOST_CHECK(pdv->GetLabel() == "test");
+ BOOST_CHECK(pdv->GetValue() == 0.11);
+
+ str = pdv->Format();
+ BOOST_CHECK(str == "test=0.110000");
+
+ pdv = PerfdataValue::Parse("test=1.1E1");
+ BOOST_CHECK(pdv->GetLabel() == "test");
+ BOOST_CHECK(pdv->GetValue() == 11);
+
+ str = pdv->Format();
+ BOOST_CHECK(str == "test=11");
+
+ pdv = PerfdataValue::Parse("test=1.1E-1");
+ BOOST_CHECK(pdv->GetLabel() == "test");
+ BOOST_CHECK(pdv->GetValue() == 0.11);
+
+ str = pdv->Format();
+ BOOST_CHECK(str == "test=0.110000");
+
+ pdv = PerfdataValue::Parse("test=1.1E-1;1.2e+1;1.3E-1;1.4e-2;1.5E2");
+ BOOST_CHECK(pdv->GetLabel() == "test");
+ BOOST_CHECK(pdv->GetValue() == 0.11);
+ BOOST_CHECK(pdv->GetWarn() == 12);
+ BOOST_CHECK(pdv->GetCrit() == 0.13);
+ BOOST_CHECK(pdv->GetMin() == 0.014);
+ BOOST_CHECK(pdv->GetMax() == 150);
+
+ str = pdv->Format();
+ BOOST_CHECK(str == "test=0.110000;12;0.130000;0.014000;150");
+}
+
+BOOST_AUTO_TEST_CASE(parse_edgecases)
+{
+ // Trailing decimal point
+ PerfdataValue::Ptr pv = PerfdataValue::Parse("test=23.");
+ BOOST_CHECK(pv);
+ BOOST_CHECK(pv->GetValue() == 23.0);
+
+ // Leading decimal point
+ pv = PerfdataValue::Parse("test=.42");
+ BOOST_CHECK(pv);
+ BOOST_CHECK(pv->GetValue() == 0.42);
+
+ // E both as exponent and unit prefix
+ pv = PerfdataValue::Parse("test=+1.5E-15EB");
+ BOOST_CHECK(pv);
+ BOOST_CHECK(pv->GetValue() == 1.5e3);
+ BOOST_CHECK(pv->GetUnit() == "bytes");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/icingaapplication-fixture.cpp b/test/icingaapplication-fixture.cpp
new file mode 100644
index 0000000..80fa4bf
--- /dev/null
+++ b/test/icingaapplication-fixture.cpp
@@ -0,0 +1,32 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icingaapplication-fixture.hpp"
+
+using namespace icinga;
+
+static bool IcingaInitialized = false;
+
+IcingaApplicationFixture::IcingaApplicationFixture()
+{
+ if (!IcingaInitialized)
+ InitIcingaApplication();
+}
+
+void IcingaApplicationFixture::InitIcingaApplication()
+{
+ BOOST_TEST_MESSAGE("Initializing Application...");
+ Application::InitializeBase();
+
+ BOOST_TEST_MESSAGE("Initializing IcingaApplication...");
+ IcingaApplication::Ptr appInst = new IcingaApplication();
+ static_pointer_cast<ConfigObject>(appInst)->OnConfigLoaded();
+
+ IcingaInitialized = true;
+}
+
+IcingaApplicationFixture::~IcingaApplicationFixture()
+{
+ IcingaApplication::GetInstance().reset();
+}
+
+BOOST_GLOBAL_FIXTURE(IcingaApplicationFixture);
diff --git a/test/icingaapplication-fixture.hpp b/test/icingaapplication-fixture.hpp
new file mode 100644
index 0000000..23f4c9c
--- /dev/null
+++ b/test/icingaapplication-fixture.hpp
@@ -0,0 +1,21 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef ICINGAAPPLICATION_FIXTURE_H
+#define ICINGAAPPLICATION_FIXTURE_H
+
+#include "icinga/icingaapplication.hpp"
+#include "base/application.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+struct IcingaApplicationFixture
+{
+ IcingaApplicationFixture();
+
+ void InitIcingaApplication();
+
+ ~IcingaApplicationFixture();
+};
+
+#endif // ICINGAAPPLICATION_FIXTURE_H
diff --git a/test/livestatus-fixture.cpp b/test/livestatus-fixture.cpp
new file mode 100644
index 0000000..aaa0e07
--- /dev/null
+++ b/test/livestatus-fixture.cpp
@@ -0,0 +1,53 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "config/configcompiler.hpp"
+#include "config/configitem.hpp"
+#include "base/application.hpp"
+#include "base/loader.hpp"
+#include "icingaapplication-fixture.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+struct LivestatusFixture
+{
+ LivestatusFixture()
+ {
+ // ensure IcingaApplication is initialized before we try to add config
+ IcingaApplicationFixture icinga;
+
+ BOOST_TEST_MESSAGE("Preparing config objects...");
+
+ ConfigItem::RunWithActivationContext(new Function("CreateTestObjects", CreateTestObjects));
+ }
+
+ static void CreateTestObjects()
+ {
+ String config = R"CONFIG(
+object CheckCommand "dummy" {
+ command = "/bin/echo"
+}
+
+object Host "test-01" {
+ address = "127.0.0.1"
+ check_command = "dummy"
+}
+
+object Host "test-02" {
+ address = "127.0.0.2"
+ check_command = "dummy"
+}
+
+apply Service "livestatus" {
+ check_command = "dummy"
+ notes = "test livestatus"
+ assign where match("test-*", host.name)
+}
+)CONFIG";
+
+ std::unique_ptr<Expression> expr = ConfigCompiler::CompileText("<livestatus>", config);
+ expr->Evaluate(*ScriptFrame::GetCurrentFrame());
+ }
+};
+
+BOOST_GLOBAL_FIXTURE(LivestatusFixture);
diff --git a/test/livestatus.cpp b/test/livestatus.cpp
new file mode 100644
index 0000000..6aafa3b
--- /dev/null
+++ b/test/livestatus.cpp
@@ -0,0 +1,107 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "livestatus/livestatusquery.hpp"
+#include "base/application.hpp"
+#include "base/stdiostream.hpp"
+#include "base/json.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+String LivestatusQueryHelper(const std::vector<String>& lines)
+{
+ LivestatusQuery::Ptr query = new LivestatusQuery(lines, "");
+
+ std::stringstream stream;
+ StdioStream::Ptr sstream = new StdioStream(&stream, false);
+
+ query->Execute(sstream);
+
+ String output;
+ String result;
+
+ StreamReadContext src;
+ for (;;) {
+ StreamReadStatus srs = sstream->ReadLine(&result, src);
+
+ if (srs == StatusEof)
+ break;
+
+ if (srs != StatusNewItem)
+ continue;
+
+ if (result.GetLength() > 0)
+ output += result + "\n";
+ else
+ break;
+ }
+
+ BOOST_TEST_MESSAGE("Query Result: " + output);
+
+ return output;
+}
+
+//____________________________________________________________________________//
+
+BOOST_AUTO_TEST_SUITE(livestatus)
+
+BOOST_AUTO_TEST_CASE(hosts)
+{
+ BOOST_TEST_MESSAGE( "Querying Livestatus...");
+
+ std::vector<String> lines;
+ lines.emplace_back("GET hosts");
+ lines.emplace_back("Columns: host_name address check_command");
+ lines.emplace_back("OutputFormat: json");
+ lines.emplace_back("\n");
+
+ /* use our query helper */
+ String output = LivestatusQueryHelper(lines);
+
+ Array::Ptr query_result = JsonDecode(output);
+
+ /* the outer elements */
+ BOOST_CHECK(query_result->GetLength() > 1);
+
+ Array::Ptr res1 = query_result->Get(0);
+ Array::Ptr res2 = query_result->Get(1);
+
+ /* results are non-deterministic and not sorted by livestatus */
+ BOOST_CHECK(res1->Contains("test-01") || res2->Contains("test-01"));
+ BOOST_CHECK(res1->Contains("test-02") || res2->Contains("test-02"));
+ BOOST_CHECK(res1->Contains("127.0.0.1") || res2->Contains("127.0.0.1"));
+ BOOST_CHECK(res1->Contains("127.0.0.2") || res2->Contains("127.0.0.2"));
+
+ BOOST_TEST_MESSAGE("Done with testing livestatus hosts...");
+}
+
+BOOST_AUTO_TEST_CASE(services)
+{
+ BOOST_TEST_MESSAGE( "Querying Livestatus...");
+
+ std::vector<String> lines;
+ lines.emplace_back("GET services");
+ lines.emplace_back("Columns: host_name service_description check_command notes");
+ lines.emplace_back("OutputFormat: json");
+ lines.emplace_back("\n");
+
+ /* use our query helper */
+ String output = LivestatusQueryHelper(lines);
+
+ Array::Ptr query_result = JsonDecode(output);
+
+ /* the outer elements */
+ BOOST_CHECK(query_result->GetLength() > 1);
+
+ Array::Ptr res1 = query_result->Get(0);
+ Array::Ptr res2 = query_result->Get(1);
+
+ /* results are non-deterministic and not sorted by livestatus */
+ BOOST_CHECK(res1->Contains("livestatus") || res2->Contains("livestatus")); //service_description
+ BOOST_CHECK(res1->Contains("test livestatus") || res2->Contains("test livestatus")); //notes
+
+ BOOST_TEST_MESSAGE("Done with testing livestatus services...");
+}
+//____________________________________________________________________________//
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/livestatus/README b/test/livestatus/README
new file mode 100644
index 0000000..0c839e0
--- /dev/null
+++ b/test/livestatus/README
@@ -0,0 +1,12 @@
+Compat Livestatus Component Query Tests
+=======================================
+
+Collection of queries used for execution
+on the livestatus socket.
+
+
+$ ./run_queries host/services
+
+or
+
+$ ./run_queries
diff --git a/test/livestatus/queries/commands/command b/test/livestatus/queries/commands/command
new file mode 100644
index 0000000..2e87152
--- /dev/null
+++ b/test/livestatus/queries/commands/command
@@ -0,0 +1,4 @@
+GET commands
+Columns: name line custom_variables
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/commands/modattr b/test/livestatus/queries/commands/modattr
new file mode 100644
index 0000000..90aaea7
--- /dev/null
+++ b/test/livestatus/queries/commands/modattr
@@ -0,0 +1,4 @@
+GET commands
+Columns: name modified_attributes modified_attributes_list
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/comments/comment b/test/livestatus/queries/comments/comment
new file mode 100644
index 0000000..18d7db9
--- /dev/null
+++ b/test/livestatus/queries/comments/comment
@@ -0,0 +1,3 @@
+GET comments
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/comments/comment_short b/test/livestatus/queries/comments/comment_short
new file mode 100644
index 0000000..e5c0072
--- /dev/null
+++ b/test/livestatus/queries/comments/comment_short
@@ -0,0 +1,4 @@
+GET comments
+Columns: id type is_service host_name service_description author comment
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/contacts/contacts b/test/livestatus/queries/contacts/contacts
new file mode 100644
index 0000000..a81c463
--- /dev/null
+++ b/test/livestatus/queries/contacts/contacts
@@ -0,0 +1,3 @@
+GET contacts
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/contacts/group b/test/livestatus/queries/contacts/group
new file mode 100644
index 0000000..a15b3a5
--- /dev/null
+++ b/test/livestatus/queries/contacts/group
@@ -0,0 +1,3 @@
+GET contactgroups
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/contacts/modattr b/test/livestatus/queries/contacts/modattr
new file mode 100644
index 0000000..8b520a0
--- /dev/null
+++ b/test/livestatus/queries/contacts/modattr
@@ -0,0 +1,4 @@
+GET contacts
+Columns: name modified_attributes modified_attributes_list
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/custom/scrambled b/test/livestatus/queries/custom/scrambled
new file mode 100644
index 0000000..01fe008
--- /dev/null
+++ b/test/livestatus/queries/custom/scrambled
@@ -0,0 +1,2 @@
+D
+
diff --git a/test/livestatus/queries/custom/thruk_alert_history b/test/livestatus/queries/custom/thruk_alert_history
new file mode 100644
index 0000000..de04c46
--- /dev/null
+++ b/test/livestatus/queries/custom/thruk_alert_history
@@ -0,0 +1,19 @@
+GET log
+Columns: class time type state host_name service_description plugin_output message options state_type contact_name
+Filter: time >= 1383692400
+Filter: time <= 1383778800
+Filter: type = SERVICE ALERT
+And: 1
+Filter: type = HOST ALERT
+And: 1
+Filter: type = SERVICE FLAPPING ALERT
+Filter: type = HOST FLAPPING ALERT
+Filter: type = SERVICE DOWNTIME ALERT
+Filter: type = HOST DOWNTIME ALERT
+Filter: message ~ starting\.\.\.
+Filter: message ~ shutting\ down\.\.\.
+Or: 8
+And: 3
+OutputFormat: json
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/custom/thruk_comments b/test/livestatus/queries/custom/thruk_comments
new file mode 100644
index 0000000..fc454ab
--- /dev/null
+++ b/test/livestatus/queries/custom/thruk_comments
@@ -0,0 +1,7 @@
+GET comments
+Columns: author comment entry_time entry_type expires expire_time host_name id persistent service_description source type
+Filter: host_name = localhost
+Filter: service_description = processes
+OutputFormat: json
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/downtimes/downtime b/test/livestatus/queries/downtimes/downtime
new file mode 100644
index 0000000..263dd51
--- /dev/null
+++ b/test/livestatus/queries/downtimes/downtime
@@ -0,0 +1,3 @@
+GET downtimes
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/downtimes/downtime_short b/test/livestatus/queries/downtimes/downtime_short
new file mode 100644
index 0000000..c2e1cf5
--- /dev/null
+++ b/test/livestatus/queries/downtimes/downtime_short
@@ -0,0 +1,4 @@
+GET downtimes
+Columns: id type is_service host_name service_description author comment start_time end_time
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/endpoints/endpoints b/test/livestatus/queries/endpoints/endpoints
new file mode 100644
index 0000000..3ff4a02
--- /dev/null
+++ b/test/livestatus/queries/endpoints/endpoints
@@ -0,0 +1,3 @@
+GET endpoints
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/bygroup b/test/livestatus/queries/hosts/bygroup
new file mode 100644
index 0000000..7485efc
--- /dev/null
+++ b/test/livestatus/queries/hosts/bygroup
@@ -0,0 +1,4 @@
+GET hostsbygroup
+Columns: hostgroup_name host_name
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/check b/test/livestatus/queries/hosts/check
new file mode 100644
index 0000000..bf4c216
--- /dev/null
+++ b/test/livestatus/queries/hosts/check
@@ -0,0 +1,4 @@
+GET hosts
+Columns: name plugin_output check_source
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/command b/test/livestatus/queries/hosts/command
new file mode 100644
index 0000000..61dfe83
--- /dev/null
+++ b/test/livestatus/queries/hosts/command
@@ -0,0 +1,4 @@
+GET hosts
+Columns: check_command check_command_expanded
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/comment b/test/livestatus/queries/hosts/comment
new file mode 100644
index 0000000..1ac1e84
--- /dev/null
+++ b/test/livestatus/queries/hosts/comment
@@ -0,0 +1,4 @@
+GET hosts
+Columns: comments comments_with_info comments_with_extra_info
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/contact b/test/livestatus/queries/hosts/contact
new file mode 100644
index 0000000..9322944
--- /dev/null
+++ b/test/livestatus/queries/hosts/contact
@@ -0,0 +1,4 @@
+GET hosts
+Columns: contacts contact_groups
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/customvar b/test/livestatus/queries/hosts/customvar
new file mode 100644
index 0000000..57f2e00
--- /dev/null
+++ b/test/livestatus/queries/hosts/customvar
@@ -0,0 +1,4 @@
+GET hosts
+Columns: name custom_variable_names custom_variable_values custom_variables cv_is_json
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/downtime b/test/livestatus/queries/hosts/downtime
new file mode 100644
index 0000000..db20ae7
--- /dev/null
+++ b/test/livestatus/queries/hosts/downtime
@@ -0,0 +1,4 @@
+GET hosts
+Columns: downtimes downtimes_with_info
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/extra b/test/livestatus/queries/hosts/extra
new file mode 100644
index 0000000..d6a583a
--- /dev/null
+++ b/test/livestatus/queries/hosts/extra
@@ -0,0 +1,4 @@
+GET hosts
+Columns: name address notes notes_expanded notes_url notes_url_expanded action_url action_url_expanded icon_image icon_image_expanded icon_image_alt x_2d y_2d
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/group b/test/livestatus/queries/hosts/group
new file mode 100644
index 0000000..cc60032
--- /dev/null
+++ b/test/livestatus/queries/hosts/group
@@ -0,0 +1,3 @@
+GET hostgroups
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/host b/test/livestatus/queries/hosts/host
new file mode 100644
index 0000000..4390fa9
--- /dev/null
+++ b/test/livestatus/queries/hosts/host
@@ -0,0 +1,4 @@
+GET hosts
+Columns: name parents childs
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/host_nagvis b/test/livestatus/queries/hosts/host_nagvis
new file mode 100644
index 0000000..a09d013
--- /dev/null
+++ b/test/livestatus/queries/hosts/host_nagvis
@@ -0,0 +1,6 @@
+GET hosts
+Columns: name alias host_name
+OutputFormat:json
+KeepAlive: on
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/legacy b/test/livestatus/queries/hosts/legacy
new file mode 100644
index 0000000..7a94932
--- /dev/null
+++ b/test/livestatus/queries/hosts/legacy
@@ -0,0 +1,4 @@
+GET hosts
+Columns: name notes notes_url action_url icon_image icon_image_alt
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/modattr b/test/livestatus/queries/hosts/modattr
new file mode 100644
index 0000000..b34e828
--- /dev/null
+++ b/test/livestatus/queries/hosts/modattr
@@ -0,0 +1,4 @@
+GET hosts
+Columns: name modified_attributes modified_attributes_list original_attributes
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/notification b/test/livestatus/queries/hosts/notification
new file mode 100644
index 0000000..27600ce
--- /dev/null
+++ b/test/livestatus/queries/hosts/notification
@@ -0,0 +1,4 @@
+GET hosts
+Columns: name current_notification_number notification_period notification_interval notifications_enabled no_more_notifications last_notification next_notification
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/services b/test/livestatus/queries/hosts/services
new file mode 100644
index 0000000..a6e10ba
--- /dev/null
+++ b/test/livestatus/queries/hosts/services
@@ -0,0 +1,4 @@
+GET hosts
+Columns: name num_services worst_service_state num_services_ok num_services_warn num_services_crit num_services_unknown num_services_pending worst_service_hard_state num_services_hard_ok num_services_hard_warn num_services_hard_crit num_services_hard_unknown services services_with_state services_with_info
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/state b/test/livestatus/queries/hosts/state
new file mode 100644
index 0000000..ba59c9e
--- /dev/null
+++ b/test/livestatus/queries/hosts/state
@@ -0,0 +1,4 @@
+GET hosts
+Columns: name last_state_change last_hard_state_change last_time_up last_time_down last_time_unreachable staleness is_reachable
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/hosts/stats_sum b/test/livestatus/queries/hosts/stats_sum
new file mode 100644
index 0000000..8a44ff4
--- /dev/null
+++ b/test/livestatus/queries/hosts/stats_sum
@@ -0,0 +1,5 @@
+GET hosts
+ResponseHeader: fixed16
+Stats: latency = 3
+Stats: sum latency
+
diff --git a/test/livestatus/queries/log/alerts b/test/livestatus/queries/log/alerts
new file mode 100644
index 0000000..8c4740a
--- /dev/null
+++ b/test/livestatus/queries/log/alerts
@@ -0,0 +1,6 @@
+GET log
+Columns: host_name service_description time lineno class type options plugin_output state state_type comment contact_name command_name
+Filter: time >= 1348657741
+Filter: message ~ ALERT
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/log/avail b/test/livestatus/queries/log/avail
new file mode 100644
index 0000000..b37162c
--- /dev/null
+++ b/test/livestatus/queries/log/avail
@@ -0,0 +1,12 @@
+GET log
+Columns: time type message class
+Filter: type = HOST ALERT
+Filter: state_type = HARD
+Filter: type = INITIAL HOST STATE
+Filter: state_type = HARD
+Filter: type = CURRENT HOST STATE
+Filter: state_type = HARD
+Filter: type = HOST DOWNTIME ALERT
+Or: 7
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/log/avail_svc b/test/livestatus/queries/log/avail_svc
new file mode 100644
index 0000000..9e3712b
--- /dev/null
+++ b/test/livestatus/queries/log/avail_svc
@@ -0,0 +1,13 @@
+GET log
+Columns: time type message class
+Filter: type = HOST DOWNTIME ALERT
+Filter: type = SERVICE ALERT
+Filter: state_type = HARD
+Filter: type = INITIAL SERVICE STATE
+Filter: state_type = HARD
+Filter: type = CURRENT SERVICE STATE
+Filter: state_type = HARD
+Filter: type = SERVICE DOWNTIME ALERT
+Or: 8
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/log/class b/test/livestatus/queries/log/class
new file mode 100644
index 0000000..c534980
--- /dev/null
+++ b/test/livestatus/queries/log/class
@@ -0,0 +1,5 @@
+GET log
+Filter: time >= 1348657741
+Filter: class = 1
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/log/localhost_disk b/test/livestatus/queries/log/localhost_disk
new file mode 100644
index 0000000..834047f
--- /dev/null
+++ b/test/livestatus/queries/log/localhost_disk
@@ -0,0 +1,7 @@
+GET log
+Columns: host_name service_description time lineno class type options plugin_output state state_type comment contact_name command_name
+Filter: time >= 1348657741
+Filter: host_name = localhost
+Filter: service_description = disk
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/log/log b/test/livestatus/queries/log/log
new file mode 100644
index 0000000..f69f2f4
--- /dev/null
+++ b/test/livestatus/queries/log/log
@@ -0,0 +1,4 @@
+GET log
+Filter: time >= 1348657741
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/log/minimal b/test/livestatus/queries/log/minimal
new file mode 100644
index 0000000..d224387
--- /dev/null
+++ b/test/livestatus/queries/log/minimal
@@ -0,0 +1,5 @@
+GET log
+Columns: host_name service_description time lineno class type options plugin_output state state_type comment contact_name command_name
+Filter: time >= 1348657741
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/log/trend b/test/livestatus/queries/log/trend
new file mode 100644
index 0000000..bd44d89
--- /dev/null
+++ b/test/livestatus/queries/log/trend
@@ -0,0 +1,26 @@
+GET log
+Columns: time type message
+Filter: host_name = localhost
+Filter: type = HOST ALERT
+Filter: state_type = HARD
+Filter: type = INITIAL HOST STATE
+Filter: state_type = HARD
+Filter: type = CURRENT HOST STATE
+Filter: state_type = HARD
+Filter: type = HOST DOWNTIME ALERT
+Or: 7
+And: 2
+Filter: host_name = localhost
+Filter: type = SERVICE ALERT
+Filter: state_type = HARD
+Filter: type = INITIAL SERVICE STATE
+Filter: state_type = HARD
+Filter: type = CURRENT SERVICE STATE
+Filter: state_type = HARD
+Filter: type = SERVICE DOWNTIME ALERT
+Or: 7
+And: 2
+Filter: class = 2
+Or: 3
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/bygroup b/test/livestatus/queries/services/bygroup
new file mode 100644
index 0000000..668e5f1
--- /dev/null
+++ b/test/livestatus/queries/services/bygroup
@@ -0,0 +1,4 @@
+GET servicesbygroup
+Columns: servicegroup_name host_name service_description
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/byhostgroup b/test/livestatus/queries/services/byhostgroup
new file mode 100644
index 0000000..ddcdbe8
--- /dev/null
+++ b/test/livestatus/queries/services/byhostgroup
@@ -0,0 +1,4 @@
+GET servicesbyhostgroup
+Columns: hostgroup_name host_name service_description
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/check b/test/livestatus/queries/services/check
new file mode 100644
index 0000000..1a8e857
--- /dev/null
+++ b/test/livestatus/queries/services/check
@@ -0,0 +1,4 @@
+GET services
+Columns: description host_name plugin_output check_source
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/command b/test/livestatus/queries/services/command
new file mode 100644
index 0000000..7bbf268
--- /dev/null
+++ b/test/livestatus/queries/services/command
@@ -0,0 +1,4 @@
+GET services
+Columns: check_command check_command_expanded
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/comment b/test/livestatus/queries/services/comment
new file mode 100644
index 0000000..d69ba67
--- /dev/null
+++ b/test/livestatus/queries/services/comment
@@ -0,0 +1,4 @@
+GET services
+Columns: comments comments_with_info comments_with_extra_info
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/contact b/test/livestatus/queries/services/contact
new file mode 100644
index 0000000..1729003
--- /dev/null
+++ b/test/livestatus/queries/services/contact
@@ -0,0 +1,4 @@
+GET services
+Columns: contacts contact_groups
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/customvar b/test/livestatus/queries/services/customvar
new file mode 100644
index 0000000..75c452c
--- /dev/null
+++ b/test/livestatus/queries/services/customvar
@@ -0,0 +1,4 @@
+GET services
+Columns: host_name service_description custom_variables cv_is_json
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/downtime b/test/livestatus/queries/services/downtime
new file mode 100644
index 0000000..243c000
--- /dev/null
+++ b/test/livestatus/queries/services/downtime
@@ -0,0 +1,4 @@
+GET services
+Columns: downtimes downtimes_with_info
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/extra b/test/livestatus/queries/services/extra
new file mode 100644
index 0000000..b58976f
--- /dev/null
+++ b/test/livestatus/queries/services/extra
@@ -0,0 +1,4 @@
+GET services
+Columns: description host_name host_address notes notes_expanded notes_url notes_url_expanded action_url action_url_expanded icon_image icon_image_expanded icon_image_alt
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/group b/test/livestatus/queries/services/group
new file mode 100644
index 0000000..83f4404
--- /dev/null
+++ b/test/livestatus/queries/services/group
@@ -0,0 +1,3 @@
+GET servicegroups
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/legacy b/test/livestatus/queries/services/legacy
new file mode 100644
index 0000000..ebc0b4b
--- /dev/null
+++ b/test/livestatus/queries/services/legacy
@@ -0,0 +1,4 @@
+GET services
+Columns: host_name description notes notes_url action_url icon_image icon_image_alt
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/modattr b/test/livestatus/queries/services/modattr
new file mode 100644
index 0000000..d1facd0
--- /dev/null
+++ b/test/livestatus/queries/services/modattr
@@ -0,0 +1,4 @@
+GET services
+Columns: description modified_attributes modified_attributes_list original_attributes
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/notification b/test/livestatus/queries/services/notification
new file mode 100644
index 0000000..eadcc90
--- /dev/null
+++ b/test/livestatus/queries/services/notification
@@ -0,0 +1,4 @@
+GET services
+Columns: description current_notification_number notification_period notification_interval notifications_enabled no_more_notifications last_notification next_notification
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/services b/test/livestatus/queries/services/services
new file mode 100644
index 0000000..09640fd
--- /dev/null
+++ b/test/livestatus/queries/services/services
@@ -0,0 +1,3 @@
+GET services
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/services/state b/test/livestatus/queries/services/state
new file mode 100644
index 0000000..65bf5f7
--- /dev/null
+++ b/test/livestatus/queries/services/state
@@ -0,0 +1,4 @@
+GET services
+Columns: description host_name last_state_change last_hard_state_change last_time_ok last_time_warning last_time_critical last_time_unknown staleness is_reachable
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/special/services b/test/livestatus/queries/special/services
new file mode 100644
index 0000000..94ddc91
--- /dev/null
+++ b/test/livestatus/queries/special/services
@@ -0,0 +1,5 @@
+GET services
+Separators: 10 32 35 95
+Columns: description custom_variables
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/statehist/duration b/test/livestatus/queries/statehist/duration
new file mode 100644
index 0000000..0de5bfa
--- /dev/null
+++ b/test/livestatus/queries/statehist/duration
@@ -0,0 +1,5 @@
+GET statehist
+Columns: host_name service_description state duration_ok duration_warning duration_critical duration_unknown duration_unmonitored
+Filter: time >= 1348657741
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/statehist/statehist b/test/livestatus/queries/statehist/statehist
new file mode 100644
index 0000000..1ef2382
--- /dev/null
+++ b/test/livestatus/queries/statehist/statehist
@@ -0,0 +1,5 @@
+GET statehist
+Columns: host_name service_description state duration duration_part in_downtime in_host_downtime in_notification_period is_flapping
+Filter: time >= 1348657741
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/statehist/statehist_disk b/test/livestatus/queries/statehist/statehist_disk
new file mode 100644
index 0000000..33f7340
--- /dev/null
+++ b/test/livestatus/queries/statehist/statehist_disk
@@ -0,0 +1,6 @@
+GET statehist
+Columns: host_name service_description state duration duration_part in_downtime in_host_downtime in_notification_period is_flapping
+Filter: service_description = disk
+Filter: time >= 1348657741
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/statehist/sum b/test/livestatus/queries/statehist/sum
new file mode 100644
index 0000000..b244adc
--- /dev/null
+++ b/test/livestatus/queries/statehist/sum
@@ -0,0 +1,9 @@
+GET statehist
+Columns: host_name service_description state duration duration_part
+Filter: host_name = localhost
+Filter: service_description = disk
+Filter: time >= 1348657741
+Stats: sum duration
+Stats: sum duration_part
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/status/checks b/test/livestatus/queries/status/checks
new file mode 100644
index 0000000..32e5957
--- /dev/null
+++ b/test/livestatus/queries/status/checks
@@ -0,0 +1,4 @@
+GET status
+Columns: accept_passive_host_checks accept_passive_service_checks execute_host_checks execute_service_checks
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/status/custom b/test/livestatus/queries/status/custom
new file mode 100644
index 0000000..1c8821d
--- /dev/null
+++ b/test/livestatus/queries/status/custom
@@ -0,0 +1,4 @@
+GET status
+Columns: custom_variables
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/status/livestatus b/test/livestatus/queries/status/livestatus
new file mode 100644
index 0000000..47f163c
--- /dev/null
+++ b/test/livestatus/queries/status/livestatus
@@ -0,0 +1,4 @@
+GET status
+Columns: connections connections_rate external_commands external_commands_rate livestatus_active_connections
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/status/program b/test/livestatus/queries/status/program
new file mode 100644
index 0000000..64d3c17
--- /dev/null
+++ b/test/livestatus/queries/status/program
@@ -0,0 +1,4 @@
+GET status
+Columns: nagios_pid program_start num_hosts num_services program_version
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/status/status b/test/livestatus/queries/status/status
new file mode 100644
index 0000000..bf92485
--- /dev/null
+++ b/test/livestatus/queries/status/status
@@ -0,0 +1,3 @@
+GET status
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/queries/timeperiods/timeperiod b/test/livestatus/queries/timeperiods/timeperiod
new file mode 100644
index 0000000..e5f02ca
--- /dev/null
+++ b/test/livestatus/queries/timeperiods/timeperiod
@@ -0,0 +1,3 @@
+GET timeperiods
+ResponseHeader: fixed16
+
diff --git a/test/livestatus/run_queries b/test/livestatus/run_queries
new file mode 100755
index 0000000..c80aa59
--- /dev/null
+++ b/test/livestatus/run_queries
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+NC=`which nc`
+LOCALSTATEDIR=`icinga2 variable get LocalStateDir`
+LIVESTATUSSOCKET="$LOCALSTATEDIR/run/icinga2/cmd/livestatus"
+LIVESTATUSHOST="127.0.0.1"
+LIVESTATUSPORT="6558"
+LIVESTATUSQUERIES="./queries"
+
+LIVESTATUSTABLE=$1
+
+echo -e "Querying Livestatus socket: $LIVESTATUSSOCKET"
+
+if [ -n "$LIVESTATUSTABLE" ]; then
+ cat "$LIVESTATUSTABLE"
+ (cat "$LIVESTATUSTABLE"; sleep 1) | $NC -U $LIVESTATUSSOCKET
+else
+
+ echo -e "Looking into $LIVESTATUSQUERIES\n"
+ for q in $(find $LIVESTATUSQUERIES -type f)
+ do
+ cat $q
+ (cat $q; sleep 1) | $NC -U $LIVESTATUSSOCKET
+ echo -e "================================\n\n"
+ done
+fi
diff --git a/test/methods-pluginnotificationtask.cpp b/test/methods-pluginnotificationtask.cpp
new file mode 100644
index 0000000..ec582dc
--- /dev/null
+++ b/test/methods-pluginnotificationtask.cpp
@@ -0,0 +1,88 @@
+/* Icinga 2 | (c) 2023 Icinga GmbH | GPLv2+ */
+
+#include "base/array.hpp"
+#include "icinga/checkresult.hpp"
+#include "icinga/host.hpp"
+#include "icinga/notification.hpp"
+#include "icinga/notificationcommand.hpp"
+#include "icinga/service.hpp"
+#include "icinga/user.hpp"
+#include "methods/pluginnotificationtask.hpp"
+#include <BoostTestTargetConfig.h>
+#include <future>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(methods_pluginnotificationtask)
+
+BOOST_AUTO_TEST_CASE(truncate_long_output)
+{
+#ifdef __linux__
+ Host::Ptr h = new Host();
+ CheckResult::Ptr hcr = new CheckResult();
+ CheckResult::Ptr scr = new CheckResult();
+ Service::Ptr s = new Service();
+ User::Ptr u = new User();
+ NotificationCommand::Ptr nc = new NotificationCommand();
+ Notification::Ptr n = new Notification();
+ String placeHolder (1024 * 1024, 'x');
+ std::promise<String> promise;
+ auto future (promise.get_future());
+
+ hcr->SetOutput("H" + placeHolder + "h", true);
+ scr->SetOutput("S" + placeHolder + "s", true);
+
+ h->SetName("example.com", true);
+ h->SetLastCheckResult(hcr, true);
+ h->Register();
+
+ s->SetHostName("example.com", true);
+ s->SetShortName("disk", true);
+ s->SetLastCheckResult(scr, true);
+ s->OnAllConfigLoaded(); // link Host
+
+ nc->SetCommandLine(
+ new Array({
+ "echo",
+ "host_output=$host.output$",
+ "service_output=$service.output$",
+ "notification_comment=$notification.comment$",
+ "output=$output$",
+ "comment=$comment$"
+ }),
+ true
+ );
+
+ nc->SetName("mail", true);
+ nc->Register();
+
+ n->SetFieldByName("host_name", "example.com", false, DebugInfo());
+ n->SetFieldByName("service_name", "disk", false, DebugInfo());
+ n->SetFieldByName("command", "mail", false, DebugInfo());
+ n->OnAllConfigLoaded(); // link Service
+
+ Checkable::ExecuteCommandProcessFinishedHandler = [&promise](const Value&, const ProcessResult& pr) {
+ promise.set_value(pr.Output);
+ };
+
+ PluginNotificationTask::ScriptFunc(n, u, nullptr, NotificationCustom, "jdoe", "C" + placeHolder + "c", nullptr, false);
+ future.wait();
+
+ Checkable::ExecuteCommandProcessFinishedHandler = nullptr;
+ h->Unregister();
+ nc->Unregister();
+
+ auto output (future.get());
+
+ BOOST_CHECK(output.Contains("host_output=Hx"));
+ BOOST_CHECK(!output.Contains("xh"));
+ BOOST_CHECK(output.Contains("x service_output=Sx"));
+ BOOST_CHECK(!output.Contains("xs"));
+ BOOST_CHECK(output.Contains("x notification_comment=Cx"));
+ BOOST_CHECK(!output.Contains("xc"));
+ BOOST_CHECK(output.Contains("x output=Sx"));
+ BOOST_CHECK(output.Contains("x comment=Cx"));
+#endif /* __linux__ */
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/remote-configpackageutility.cpp b/test/remote-configpackageutility.cpp
new file mode 100644
index 0000000..99c2a8b
--- /dev/null
+++ b/test/remote-configpackageutility.cpp
@@ -0,0 +1,25 @@
+/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+#include "remote/configpackageutility.hpp"
+#include <vector>
+#include <string>
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(remote_configpackageutility)
+
+BOOST_AUTO_TEST_CASE(ValidateName)
+{
+ std::vector<std::string> validNames {"foo", "foo-bar", "FooBar", "Foo123", "_Foo-", "123bar"};
+ for (const std::string& n : validNames) {
+ BOOST_CHECK_MESSAGE(ConfigPackageUtility::ValidatePackageName(n), "'" << n << "' should be valid");
+ }
+
+ std::vector<std::string> invalidNames {"", ".", "..", "foo.bar", "foo/../bar", "foo/bar", "foo:bar"};
+ for (const std::string& n : invalidNames) {
+ BOOST_CHECK_MESSAGE(!ConfigPackageUtility::ValidatePackageName(n), "'" << n << "' should not be valid");
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/remote-url.cpp b/test/remote-url.cpp
new file mode 100644
index 0000000..36b7989
--- /dev/null
+++ b/test/remote-url.cpp
@@ -0,0 +1,128 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/array.hpp"
+#include "remote/url.hpp"
+#include <BoostTestTargetConfig.h>
+
+using namespace icinga;
+
+BOOST_AUTO_TEST_SUITE(remote_url)
+
+BOOST_AUTO_TEST_CASE(id_and_path)
+{
+ Url::Ptr url = new Url("http://icinga.com/foo/bar/baz?hurr=durr");
+
+ BOOST_CHECK(url->GetScheme() == "http");
+
+ BOOST_CHECK(url->GetAuthority() == "icinga.com");
+
+ std::vector<String> PathCorrect;
+ PathCorrect.emplace_back("foo");
+ PathCorrect.emplace_back("bar");
+ PathCorrect.emplace_back("baz");
+
+ BOOST_CHECK(url->GetPath() == PathCorrect);
+}
+
+BOOST_AUTO_TEST_CASE(get_and_set)
+{
+ Url::Ptr url = new Url();
+ url->SetScheme("ftp");
+ url->SetUsername("Horst");
+ url->SetPassword("Seehofer");
+ url->SetHost("koenigreich.bayern");
+ url->SetPort("1918");
+ url->SetPath({ "path", "to", "münchen" });
+
+ BOOST_CHECK(url->Format(false, true) == "ftp://Horst:Seehofer@koenigreich.bayern:1918/path/to/m%C3%BCnchen");
+
+ url->SetQuery({
+ {"shout", "hip"},
+ {"shout", "hip"},
+ {"shout", "hurra"},
+ {"sonderzeichen", "äü^ä+#ül-"}
+ });
+ url->AddQueryElement("count", "3");
+
+ auto mn (url->GetQuery());
+
+ BOOST_CHECK(mn.size() == 5);
+
+ BOOST_CHECK(mn[0].first == "shout");
+ BOOST_CHECK(mn[0].second == "hip");
+
+ BOOST_CHECK(mn[1].first == "shout");
+ BOOST_CHECK(mn[1].second == "hip");
+
+ BOOST_CHECK(mn[2].first == "shout");
+ BOOST_CHECK(mn[2].second == "hurra");
+
+ BOOST_CHECK(mn[3].first == "sonderzeichen");
+ BOOST_CHECK(mn[3].second == "äü^ä+#ül-");
+
+ BOOST_CHECK(mn[4].first == "count");
+ BOOST_CHECK(mn[4].second == "3");
+}
+
+BOOST_AUTO_TEST_CASE(parameters)
+{
+ Url::Ptr url = new Url("https://icinga.com/hya/?rair=robert&rain=karl&foo[]=bar");
+
+ auto query (url->GetQuery());
+
+ BOOST_CHECK(query.size() == 3);
+
+ BOOST_CHECK(query[0].first == "rair");
+ BOOST_CHECK(query[0].second == "robert");
+
+ BOOST_CHECK(query[1].first == "rain");
+ BOOST_CHECK(query[1].second == "karl");
+
+ BOOST_CHECK(query[2].first == "foo");
+ BOOST_CHECK(query[2].second == "bar");
+}
+
+BOOST_AUTO_TEST_CASE(format)
+{
+ Url::Ptr url = new Url("http://foo.bar/baz/?hop=top&flop=sop#iLIKEtrains");
+ Url::Ptr url2;
+ BOOST_CHECK(url2 = new Url(url->Format(false, false)));
+
+ url = new Url("//main.args/////////?k[]=one&k[]=two#three");
+ BOOST_CHECK(url2 = new Url(url->Format(false, false)));
+
+ url = new Url("/foo/bar/index.php?blaka");
+ BOOST_CHECK(url2 = new Url(url->Format(false, false)));
+ BOOST_CHECK(url->Format(false, false) == "/foo/bar/index.php?blaka");
+
+ url = new Url("/");
+ BOOST_CHECK(url->Format(false, false) == "/");
+
+ url = new Url("https://nsclient:8443/query/check_cpu?time%5B%5D=1m&time=5m&time%5B%5D=15m");
+ url->SetArrayFormatUseBrackets(false);
+ BOOST_CHECK(url2 = new Url(url->Format(false, false)));
+
+ url = new Url("https://icinga2/query?a[]=1&a[]=2&a[]=3");
+ url->SetArrayFormatUseBrackets(true);
+ BOOST_CHECK(url2 = new Url(url->Format(false, false)));
+}
+
+BOOST_AUTO_TEST_CASE(illegal_legal_strings)
+{
+ Url::Ptr url;
+ BOOST_CHECK(url = new Url("/?foo=barr&foo[]=bazz"));
+ BOOST_CHECK_THROW(url = new Url("/?]=gar"), std::invalid_argument);
+ BOOST_CHECK_THROW(url = new Url("/#?[]"), std::invalid_argument);
+ BOOST_CHECK(url = new Url("/?foo=bar&foo=ba"));
+ BOOST_CHECK_THROW(url = new Url("/?foo=bar&[]=d"), std::invalid_argument);
+ BOOST_CHECK(url = new Url("/?fo=&bar=garOA"));
+ BOOST_CHECK(url = new Url("https://127.0.0.1:5665/demo?type=Service&filter=service.state%3E0"));
+ BOOST_CHECK(url = new Url("/?foo=baz??&\?\?=/?"));
+ BOOST_CHECK(url = new Url("/"));
+ BOOST_CHECK(url = new Url("///////"));
+ BOOST_CHECK(url = new Url("/??[]=?#?=?"));
+ BOOST_CHECK(url = new Url("http://foo/#bar"));
+ BOOST_CHECK(url = new Url("//foo/"));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/test-runner.cpp b/test/test-runner.cpp
new file mode 100644
index 0000000..fac41ea
--- /dev/null
+++ b/test/test-runner.cpp
@@ -0,0 +1,21 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#define BOOST_TEST_MODULE icinga2
+#define BOOST_TEST_NO_MAIN
+#define BOOST_TEST_ALTERNATIVE_INIT_API
+
+#include <BoostTestTargetConfig.h>
+#include <boost/test/unit_test.hpp>
+#include <cstdlib>
+
+int BOOST_TEST_CALL_DECL
+main(int argc, char **argv)
+{
+ std::_Exit(boost::unit_test::unit_test_main(init_unit_test, argc, argv));
+ return EXIT_FAILURE;
+}
+
+#ifdef _WIN32
+#include <boost/test/impl/unit_test_main.ipp>
+#include <boost/test/impl/framework.ipp>
+#endif /* _WIN32 */