diff options
Diffstat (limited to 'test')
118 files changed, 6035 insertions, 0 deletions
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..8b800d9 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,212 @@ +# 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-ops.cpp + icinga-checkresult.cpp + icinga-dependencies.cpp + icinga-legacytimeperiod.cpp + icinga-macros.cpp + icinga-notification.cpp + icinga-perfdata.cpp + remote-configpackageutility.cpp + remote-url.cpp + ${base_OBJS} + $<TARGET_OBJECTS:config> + $<TARGET_OBJECTS:remote> + $<TARGET_OBJECTS:icinga> +) + +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_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_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_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/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 + 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..d3a4a00 --- /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 = new Timer(); + BOOST_CHECK(timer); +} + +BOOST_AUTO_TEST_CASE(interval) +{ + Timer::Ptr timer = new Timer(); + 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 = new Timer(); + 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 = new Timer(); + 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..c66cef4 --- /dev/null +++ b/test/base-tlsutility.cpp @@ -0,0 +1,38 @@ +/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +#include "base/tlsutility.hpp" +#include <BoostTestTargetConfig.h> +#include <utility> +#include <vector> + +using namespace icinga; + +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_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..950290e --- /dev/null +++ b/test/base-value.cpp @@ -0,0 +1,52 @@ +/* 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(v != 3); +} + +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..b31f540 --- /dev/null +++ b/test/icinga-dependencies.cpp @@ -0,0 +1,89 @@ +/* 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 reachable. + */ + parentHost1->SetStateRaw(ServiceCritical); // parent Host 1 DOWN + parentHost2->SetStateRaw(ServiceOK); // parent Host 2 UP + + BOOST_CHECK(childHost->IsReachable() == true); + + /* parentHost1 is DOWN, parentHost2 is 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(©); +} + +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..5cf3f49 --- /dev/null +++ b/test/icinga-notification.cpp @@ -0,0 +1,105 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "icinga/notification.hpp" +#include <BoostTestTargetConfig.h> +#include <iostream> + +using namespace icinga; + +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_SUITE_END() diff --git a/test/icinga-perfdata.cpp b/test/icinga-perfdata.cpp new file mode 100644 index 0000000..f763ac0 --- /dev/null +++ b/test/icinga-perfdata.cpp @@ -0,0 +1,392 @@ +/* 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(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/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 */ |