From 3c1889fbd0d7b96aae16f3479ac8aae70a7e15f7 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 28 Oct 2015 19:13:53 +0100 Subject: Use Catch for our test suite `make check` is also added to compile and run the tests Catch is fetched with cmake automatically into the build directory when needed --- tests/colors.cpp | 54 ++++++++++++++++++++++ tests/config.cpp | 25 ++++++++++ tests/database.cpp | 30 ++++++++++++ tests/dns.cpp | 83 +++++++++++++++++++++++++++++++++ tests/encoding.cpp | 56 +++++++++++++++++++++++ tests/iid.cpp | 122 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/jid.cpp | 39 ++++++++++++++++ tests/test.cpp | 2 + tests/timed_events.cpp | 62 +++++++++++++++++++++++++ tests/utils.cpp | 72 +++++++++++++++++++++++++++++ tests/uuid.cpp | 13 ++++++ tests/xmpp.cpp | 47 +++++++++++++++++++ 12 files changed, 605 insertions(+) create mode 100644 tests/colors.cpp create mode 100644 tests/config.cpp create mode 100644 tests/database.cpp create mode 100644 tests/dns.cpp create mode 100644 tests/encoding.cpp create mode 100644 tests/iid.cpp create mode 100644 tests/jid.cpp create mode 100644 tests/test.cpp create mode 100644 tests/timed_events.cpp create mode 100644 tests/utils.cpp create mode 100644 tests/uuid.cpp create mode 100644 tests/xmpp.cpp (limited to 'tests') diff --git a/tests/colors.cpp b/tests/colors.cpp new file mode 100644 index 0000000..bf52989 --- /dev/null +++ b/tests/colors.cpp @@ -0,0 +1,54 @@ +#include "catch.hpp" + +#include +#include + +#include + +TEST_CASE("IRC colors parsing") +{ + std::unique_ptr xhtml; + std::string cleaned_up; + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("bold"); + CHECK(xhtml); + CHECK(xhtml->to_string() == "bold"); + + std::tie(cleaned_up, xhtml) = + irc_format_to_xhtmlim("normalboldunder-and-boldbold normal" + "5red,5default-on-red10,2cyan-on-blue"); + CHECK(xhtml); + CHECK(xhtml->to_string() == "normalboldunder-and-boldbold normalreddefault-on-redcyan-on-blue"); + CHECK(cleaned_up == "normalboldunder-and-boldbold normalreddefault-on-redcyan-on-blue"); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("normal"); + CHECK_FALSE(xhtml); + CHECK(cleaned_up == "normal"); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(""); + CHECK(xhtml); + CHECK(!xhtml->has_children()); + CHECK(cleaned_up.empty()); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(",a"); + CHECK(xhtml); + CHECK(!xhtml->has_children()); + CHECK(cleaned_up == "a"); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(","); + CHECK(xhtml); + CHECK(!xhtml->has_children()); + CHECK(cleaned_up.empty()); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("[\x1D13dolphin-emu/dolphin\x1D] 03foo commented on #283 (Add support for the guide button to XInput): 02http://example.com"); + CHECK(xhtml->to_string() == "[dolphin-emu/dolphin] foo commented on #283 (Add support for the guide button to XInput): http://example.com"); + CHECK(cleaned_up == "[dolphin-emu/dolphin] foo commented on #283 (Add support for the guide button to XInput): http://example.com"); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("0e46ab by 03Pierre Dindon [090|091|040] 02http://example.net/Ojrh4P media: avoid pop-in effect when loading thumbnails by specifying an explicit size"); + CHECK(cleaned_up == "0e46ab by Pierre Dindon [0|1|0] http://example.net/Ojrh4P media: avoid pop-in effect when loading thumbnails by specifying an explicit size"); + CHECK(xhtml->to_string() == "0e46ab by Pierre Dindon [0|1|0] http://example.net/Ojrh4P media: avoid pop-in effect when loading thumbnails by specifying an explicit size"); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("test\ncoucou"); + CHECK(cleaned_up == "test\ncoucou"); + CHECK(xhtml->to_string() == "test
coucou"); +} diff --git a/tests/config.cpp b/tests/config.cpp new file mode 100644 index 0000000..1419f0b --- /dev/null +++ b/tests/config.cpp @@ -0,0 +1,25 @@ +#include "catch.hpp" + +#include + +TEST_CASE("Config basic") +{ + Config::filename = "test.cfg"; + Config::file_must_exist = false; + Config::set("coucou", "bonjour", true); + Config::close(); + + bool error = false; + try + { + Config::file_must_exist = true; + CHECK(Config::get("coucou", "") == "bonjour"); + CHECK(Config::get("does not exist", "default") == "default"); + Config::close(); + } + catch (const std::ios::failure& e) + { + error = true; + } + CHECK_FALSE(error); +} diff --git a/tests/database.cpp b/tests/database.cpp new file mode 100644 index 0000000..fd9e873 --- /dev/null +++ b/tests/database.cpp @@ -0,0 +1,30 @@ +#include "catch.hpp" + +#include + +#include +#include + +TEST_CASE("Database") +{ +#ifdef USE_DATABASE + // Remove any potential existing db + ::unlink("./test.db"); + Config::set("db_name", "test.db"); + Database::set_verbose(false); + auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); + o.update(); + auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); + auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com"); + + // b does not yet exist in the db, the object is created but not yet + // inserted + CHECK(1 == Database::count()); + + b.update(); + CHECK(2 == Database::count()); + + CHECK(b.pass == ""); + CHECK(b.pass.value() == ""); +#endif +} diff --git a/tests/dns.cpp b/tests/dns.cpp new file mode 100644 index 0000000..e8cbc7f --- /dev/null +++ b/tests/dns.cpp @@ -0,0 +1,83 @@ +#include "catch.hpp" + +#include +#include +#include + +#include + +TEST_CASE("DNS resolver") +{ + Resolver resolver; + + /** + * If we are using cares, we need to run a poller loop until each + * resolution is finished. Without cares we get the result before + * resolve() returns because it’s blocking. + */ +#ifdef CARES_FOUND + auto p = std::make_shared(); + + const auto loop = [&p]() + { + do + { + DNSHandler::instance.watch_dns_sockets(p); + } + while (p->poll(utils::no_timeout) != -1); + }; +#else + // We don’t need to do anything if we are not using cares. + const auto loop = [](){}; +#endif + + std::string hostname; + std::string port = "6667"; + + bool success = true; + + auto error_cb = [&success, &hostname, &port](const char* msg) + { + INFO("Failed to resolve " << hostname << ":" << port << ": " << msg); + success = false; + }; + auto success_cb = [&success, &hostname, &port](const struct addrinfo* addr) + { + INFO("Successfully resolved " << hostname << ":" << port << ": " << addr_to_string(addr)); + success = true; + }; + + hostname = "example.com"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + CHECK(success); + + hostname = "this.should.fail.because.it.is..misformatted"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + CHECK(!success); + + hostname = "this.should.fail.because.it.is.does.not.exist.invalid"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + CHECK(!success); + + hostname = "localhost6"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + CHECK(success); + + hostname = "localhost"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + CHECK(success); + +#ifdef CARES_FOUND + DNSHandler::instance.destroy(); +#endif +} diff --git a/tests/encoding.cpp b/tests/encoding.cpp new file mode 100644 index 0000000..389cf23 --- /dev/null +++ b/tests/encoding.cpp @@ -0,0 +1,56 @@ +#include "catch.hpp" + +#include + + +TEST_CASE("UTF-8 validation") +{ + const char* valid = "C̡͔͕̩͙̽ͫ̈́ͥ̿̆ͧ̚r̸̩̘͍̻͖̆͆͛͊̉̕͡o͇͈̳̤̱̊̈͢q̻͍̦̮͕ͥͬͬ̽ͭ͌̾ͅǔ͉͕͇͚̙͉̭͉̇̽ȇ͈̮̼͍͔ͣ͊͞͝ͅ ͫ̾ͪ̓ͥ̆̋̔҉̢̦̠͈͔̖̲̯̦ụ̶̯͐̃̋ͮ͆͝n̬̱̭͇̻̱̰̖̤̏͛̏̿̑͟ë́͐҉̸̥̪͕̹̻̙͉̰ ̹̼̱̦̥ͩ͑̈́͑͝ͅt͍̥͈̹̝ͣ̃̔̈̔ͧ̕͝ḙ̸̖̟̙͙ͪ͢ų̯̞̼̲͓̻̞͛̃̀́b̮̰̗̩̰̊̆͗̾̎̆ͯ͌͝.̗̙͎̦ͫ̈́ͥ͌̈̓ͬ"; + CHECK(utils::is_valid_utf8(valid)); + CHECK_FALSE(utils::is_valid_utf8("\xF0\x0F")); + CHECK_FALSE(utils::is_valid_utf8("\xFE\xFE\xFF\xFF")); + + std::string in = "Biboumi ╯°□°)╯︵ ┻━┻"; + INFO(in); + CHECK(utils::is_valid_utf8(in.data())); +} + +TEST_CASE("UTF-8 conversion") +{ + std::string in = "Biboumi ╯°□°)╯︵ ┻━┻"; + REQUIRE(utils::is_valid_utf8(in.data())); + + SECTION("Converting UTF-8 to UTF-8 should return the same string") + { + std::string res = utils::convert_to_utf8(in, "UTF-8"); + CHECK(utils::is_valid_utf8(res.c_str()) == true); + CHECK(res == in); + } + + SECTION("Testing latin-1 conversion") + { + std::string original_utf8("couc¥ou"); + std::string original_latin1("couc\xa5ou"); + + SECTION("Convert proper latin-1 to UTF-8") + { + std::string from_latin1 = utils::convert_to_utf8(original_latin1.c_str(), "ISO-8859-1"); + CHECK(from_latin1 == original_utf8); + } + SECTION("Check the behaviour when the decoding fails (here because we provide a wrong charset)") + { + std::string from_ascii = utils::convert_to_utf8(original_latin1, "US-ASCII"); + CHECK(from_ascii == "couc�ou"); + } + } +} + +TEST_CASE("Remove invalid XML chars") +{ + std::string without_ctrl_char("𤭢€¢$"); + std::string in = "Biboumi ╯°□°)╯︵ ┻━┻"; + INFO(in); + CHECK(utils::remove_invalid_xml_chars(without_ctrl_char) == without_ctrl_char); + CHECK(utils::remove_invalid_xml_chars(in) == in); + CHECK(utils::remove_invalid_xml_chars("\acouco\u0008u\uFFFEt\uFFFFe\r\n♥") == "coucoute\r\n♥"); +} diff --git a/tests/iid.cpp b/tests/iid.cpp new file mode 100644 index 0000000..a90c208 --- /dev/null +++ b/tests/iid.cpp @@ -0,0 +1,122 @@ +#include "catch.hpp" + +#include +#include + +#include + +TEST_CASE("Irc user parsing") +{ + const std::map prefixes{{'!', 'a'}, {'@', 'o'}}; + + IrcUser user1("!nick!~some@host.bla", prefixes); + CHECK(user1.nick == "nick"); + CHECK(user1.host == "~some@host.bla"); + CHECK(user1.modes.size() == 1); + CHECK(user1.modes.find('a') != user1.modes.end()); + + IrcUser user2("coucou!~other@host.bla", prefixes); + CHECK(user2.nick == "coucou"); + CHECK(user2.host == "~other@host.bla"); + CHECK(user2.modes.empty()); + CHECK(user2.modes.find('a') == user2.modes.end()); +} + +/** + * Let Catch know how to display Iid objects + */ +namespace Catch +{ + template<> + struct StringMaker + { + static std::string convert(const Iid& value) + { + return std::to_string(value); + } + }; +} + +TEST_CASE("Iid creation") +{ + Iid iid1("foo!irc.example.org"); + CHECK(std::to_string(iid1) == "foo!irc.example.org"); + CHECK(iid1.get_local() == "foo"); + CHECK(iid1.get_server() == "irc.example.org"); + CHECK(!iid1.is_channel); + CHECK(iid1.is_user); + + Iid iid2("#test%irc.example.org"); + CHECK(std::to_string(iid2) == "#test%irc.example.org"); + CHECK(iid2.get_local() == "#test"); + CHECK(iid2.get_server() == "irc.example.org"); + CHECK(iid2.is_channel); + CHECK(!iid2.is_user); + + Iid iid3("%irc.example.org"); + CHECK(std::to_string(iid3) == "%irc.example.org"); + CHECK(iid3.get_local() == ""); + CHECK(iid3.get_server() == "irc.example.org"); + CHECK(iid3.is_channel); + CHECK(!iid3.is_user); + + Iid iid4("irc.example.org"); + CHECK(std::to_string(iid4) == "irc.example.org"); + CHECK(iid4.get_local() == ""); + CHECK(iid4.get_server() == "irc.example.org"); + CHECK(!iid4.is_channel); + CHECK(!iid4.is_user); + + Iid iid5("nick!"); + CHECK(std::to_string(iid5) == "nick!"); + CHECK(iid5.get_local() == "nick"); + CHECK(iid5.get_server() == ""); + CHECK(!iid5.is_channel); + CHECK(iid5.is_user); + + Iid iid6("##channel%"); + CHECK(std::to_string(iid6) == "##channel%"); + CHECK(iid6.get_local() == "##channel"); + CHECK(iid6.get_server() == ""); + CHECK(iid6.is_channel); + CHECK(!iid6.is_user); +} + +TEST_CASE("Iid creation in fixed_server mode") +{ + Config::set("fixed_irc_server", "fixed.example.com", false); + + Iid iid1("foo!irc.example.org"); + CHECK(std::to_string(iid1) == "foo!"); + CHECK(iid1.get_local() == "foo"); + CHECK(iid1.get_server() == "fixed.example.com"); + CHECK(!iid1.is_channel); + CHECK(iid1.is_user); + + Iid iid2("#test%irc.example.org"); + CHECK(std::to_string(iid2) == "#test%irc.example.org"); + CHECK(iid2.get_local() == "#test%irc.example.org"); + CHECK(iid2.get_server() == "fixed.example.com"); + CHECK(iid2.is_channel); + CHECK(!iid2.is_user); + + // Note that it is impossible to adress the IRC server directly, or to + // use the virtual channel, in that mode + + // Iid iid3("%irc.example.org"); + // Iid iid4("irc.example.org"); + + Iid iid5("nick!"); + CHECK(std::to_string(iid5) == "nick!"); + CHECK(iid5.get_local() == "nick"); + CHECK(iid5.get_server() == "fixed.example.com"); + CHECK(!iid5.is_channel); + CHECK(iid5.is_user); + + Iid iid6("##channel%"); + CHECK(std::to_string(iid6) == "##channel%"); + CHECK(iid6.get_local() == "##channel%"); + CHECK(iid6.get_server() == "fixed.example.com"); + CHECK(iid6.is_channel); + CHECK(!iid6.is_user); +} diff --git a/tests/jid.cpp b/tests/jid.cpp new file mode 100644 index 0000000..9015afd --- /dev/null +++ b/tests/jid.cpp @@ -0,0 +1,39 @@ +#include "catch.hpp" + +#include +#include + +TEST_CASE("Jid") +{ + Jid jid1("♥@ツ.coucou/coucou@coucou/coucou"); + CHECK(jid1.local == "♥"); + CHECK(jid1.domain == "ツ.coucou"); + CHECK(jid1.resource == "coucou@coucou/coucou"); + + // Domain and resource + Jid jid2("ツ.coucou/coucou@coucou/coucou"); + CHECK(jid2.local == ""); + CHECK(jid2.domain == "ツ.coucou"); + CHECK(jid2.resource == "coucou@coucou/coucou"); + + // Jidprep + const std::string badjid("~zigougou™@EpiK-7D9D1FDE.poez.io/Boujour/coucou/slt™"); + const std::string correctjid = jidprep(badjid); +#ifdef LIBIDN_FOUND + CHECK(correctjid == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); + // Check that the cache does not break things when we prep the same string + // multiple times + CHECK(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); + CHECK(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); + + const std::string badjid2("Zigougou@poez.io"); + const std::string correctjid2 = jidprep(badjid2); + CHECK(correctjid2 == "zigougou@poez.io"); + + const std::string crappy("~Bisous@7ea8beb1:c5fd2849:da9a048e:ip"); + const std::string fixed_crappy = jidprep(crappy); + CHECK(fixed_crappy == "~bisous@7ea8beb1-c5fd2849-da9a048e-ip"); +#else // Without libidn, jidprep always returns an empty string + CHECK(jidprep(badjid) == ""); +#endif +} diff --git a/tests/test.cpp b/tests/test.cpp new file mode 100644 index 0000000..0c7c351 --- /dev/null +++ b/tests/test.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" diff --git a/tests/timed_events.cpp b/tests/timed_events.cpp new file mode 100644 index 0000000..3844f3d --- /dev/null +++ b/tests/timed_events.cpp @@ -0,0 +1,62 @@ +#include "catch.hpp" + +#include + +/** + * Let Catch know how to display std::chrono::duration values + */ +namespace Catch +{ + template struct StringMaker> + { + static std::string convert(const std::chrono::duration& value) + { + return std::to_string(std::chrono::duration_cast(value).count()) + "ms"; + } + }; +} + +/** + * TODO, use a mock clock instead of relying on the real time with a sleep: + * it’s unreliable on heavy load. + */ +#include + +TEST_CASE("Test timed event expiration") +{ + SECTION("Check what happens when there is no events") + { + CHECK(TimedEventsManager::instance().get_timeout() == utils::no_timeout); + CHECK(TimedEventsManager::instance().execute_expired_events() == 0); + } + + // Add a single event + TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 50ms, [](){})); + + // The event should not yet be expired + CHECK(TimedEventsManager::instance().get_timeout() > 0ms); + CHECK(TimedEventsManager::instance().execute_expired_events() == 0); + + std::chrono::milliseconds timoute = TimedEventsManager::instance().get_timeout(); + INFO("Sleeping for " << timoute.count() << "ms"); + std::this_thread::sleep_for(timoute); + + // Event is now expired + CHECK(TimedEventsManager::instance().execute_expired_events() == 1); + CHECK(TimedEventsManager::instance().get_timeout() == utils::no_timeout); +} + +TEST_CASE("Test timed event cancellation") +{ + auto now = std::chrono::steady_clock::now(); + TimedEventsManager::instance().add_event(TimedEvent(now + 100ms, [](){ }, "un")); + TimedEventsManager::instance().add_event(TimedEvent(now + 200ms, [](){ }, "deux")); + TimedEventsManager::instance().add_event(TimedEvent(now + 300ms, [](){ }, "deux")); + + CHECK(TimedEventsManager::instance().get_timeout() > 0ms); + CHECK(TimedEventsManager::instance().size() == 3); + CHECK(TimedEventsManager::instance().cancel("un") == 1); + CHECK(TimedEventsManager::instance().size() == 2); + CHECK(TimedEventsManager::instance().cancel("deux") == 2); + CHECK(TimedEventsManager::instance().get_timeout() == utils::no_timeout); +} diff --git a/tests/utils.cpp b/tests/utils.cpp new file mode 100644 index 0000000..6e3c32a --- /dev/null +++ b/tests/utils.cpp @@ -0,0 +1,72 @@ +#include "catch.hpp" + +#include +#include +#include +#include +#include + +TEST_CASE("String split") +{ + std::vector splitted = utils::split("a::a", ':', false); + CHECK(splitted.size() == 2); + splitted = utils::split("a::a", ':', true); + CHECK(splitted.size() == 3); + CHECK(splitted[0] == "a"); + CHECK(splitted[1] == ""); + CHECK(splitted[2] == "a"); + splitted = utils::split("\na", '\n', true); + CHECK(splitted.size() == 2); + CHECK(splitted[0] == ""); + CHECK(splitted[1] == "a"); +} + +TEST_CASE("tolower") +{ + const std::string lowercase = utils::tolower("CoUcOu LeS CoPaiNs ♥"); + CHECK(lowercase == "coucou les copains ♥"); + + const std::string ltr = "coucou"; + CHECK(utils::revstr(ltr) == "uocuoc"); +} + +TEST_CASE("to_bool") +{ + CHECK(to_bool("true")); + CHECK(!to_bool("trou")); + CHECK(to_bool("1")); + CHECK(!to_bool("0")); + CHECK(!to_bool("-1")); + CHECK(!to_bool("false")); +} + +TEST_CASE("xdg_*_path") +{ + ::unsetenv("XDG_CONFIG_HOME"); + ::unsetenv("HOME"); + std::string res; + + SECTION("Without XDG_CONFIG_HOME nor HOME") + { + res = xdg_config_path("coucou.txt"); + CHECK(res == "coucou.txt"); + } + SECTION("With only HOME") + { + ::setenv("HOME", "/home/user", 1); + res = xdg_config_path("coucou.txt"); + CHECK(res == "/home/user/.config/biboumi/coucou.txt"); + } + SECTION("With only XDG_CONFIG_HOME") + { + ::setenv("XDG_CONFIG_HOME", "/some_weird_dir", 1); + res = xdg_config_path("coucou.txt"); + CHECK(res == "/some_weird_dir/biboumi/coucou.txt"); + } + SECTION("With XDG_DATA_HOME") + { + ::setenv("XDG_DATA_HOME", "/datadir", 1); + res = xdg_data_path("bonjour.txt"); + CHECK(res == "/datadir/biboumi/bonjour.txt"); + } +} diff --git a/tests/uuid.cpp b/tests/uuid.cpp new file mode 100644 index 0000000..12c6c32 --- /dev/null +++ b/tests/uuid.cpp @@ -0,0 +1,13 @@ +#include "catch.hpp" + +#include + +TEST_CASE("id generation") +{ + const std::string first_uuid = XmppComponent::next_id(); + const std::string second_uuid = XmppComponent::next_id(); + + CHECK(first_uuid.size() == 36); + CHECK(second_uuid.size() == 36); + CHECK(first_uuid != second_uuid); +} diff --git a/tests/xmpp.cpp b/tests/xmpp.cpp new file mode 100644 index 0000000..46ecd35 --- /dev/null +++ b/tests/xmpp.cpp @@ -0,0 +1,47 @@ +#include "catch.hpp" + +#include + +TEST_CASE("Test basic XML parsing") +{ + XmppParser xml; + + const std::string doc = "innertail"; + + auto check_stanza = [](const Stanza& stanza) + { + CHECK(stanza.get_name() == "stanza"); + CHECK(stanza.get_tag("xmlns") == "stream_ns"); + CHECK(stanza.get_tag("b") == "c"); + CHECK(stanza.get_inner() == "inner"); + CHECK(stanza.get_tail() == ""); + CHECK(stanza.get_child("child1", "stream_ns") != nullptr); + CHECK(stanza.get_child("child2", "stream_ns") == nullptr); + CHECK(stanza.get_child("child2", "child2_ns") != nullptr); + CHECK(stanza.get_child("child2", "child2_ns")->get_tail() == "tail"); + }; + xml.add_stanza_callback([check_stanza](const Stanza& stanza) + { + check_stanza(stanza); + // Do the same checks on a copy of that stanza. + Stanza copy(stanza); + check_stanza(copy); + }); + xml.feed(doc.data(), doc.size(), true); + + const std::string doc2 = "coucou\r\n\a"; + xml.add_stanza_callback([](const Stanza& stanza) + { + CHECK(stanza.get_inner() == "coucou\r\n"); + }); + + xml.feed(doc2.data(), doc.size(), true); +} + +TEST_CASE("XML escape/unescape") +{ + const std::string unescaped = "'coucou'/&\"gaga\""; + CHECK(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); + CHECK(xml_unescape(xml_escape(unescaped)) == unescaped); +} + -- cgit v1.2.3 From 66887c225b63cecea62d17bcfae40cddef38c9d1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 Oct 2015 05:35:46 +0100 Subject: Add a few tests --- tests/config.cpp | 29 +++++++++++++++++++++++++++++ tests/database.cpp | 2 ++ tests/logger.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/utils.cpp | 18 ++++++++++++++++++ tests/xmpp.cpp | 3 ++- 5 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 tests/logger.cpp (limited to 'tests') diff --git a/tests/config.cpp b/tests/config.cpp index 1419f0b..346dea1 100644 --- a/tests/config.cpp +++ b/tests/config.cpp @@ -23,3 +23,32 @@ TEST_CASE("Config basic") } CHECK_FALSE(error); } + +TEST_CASE("Config callbacks") +{ + bool switched = false; + Config::connect([&switched]() + { + switched = !switched; + }); + CHECK_FALSE(switched); + Config::set("un", "deux", true); + CHECK(switched); + Config::set("un", "trois", true); + CHECK_FALSE(switched); + + Config::set("un", "trois", false); + CHECK_FALSE(switched); +} + +TEST_CASE("Config get_int") +{ + auto res = Config::get_int("number", 0); + CHECK(res == 0); + Config::set("number", "88"); + res = Config::get_int("number", 0); + CHECK(res == 88); + Config::set("number", "pouet"); + res = Config::get_int("number", -1); + CHECK(res == 0); +} diff --git a/tests/database.cpp b/tests/database.cpp index fd9e873..c248568 100644 --- a/tests/database.cpp +++ b/tests/database.cpp @@ -26,5 +26,7 @@ TEST_CASE("Database") CHECK(b.pass == ""); CHECK(b.pass.value() == ""); + + Database::close(); #endif } diff --git a/tests/logger.cpp b/tests/logger.cpp new file mode 100644 index 0000000..d4fb055 --- /dev/null +++ b/tests/logger.cpp @@ -0,0 +1,50 @@ +#include "catch.hpp" + +#include +#include + +#include "io_tester.hpp" +#include + +using namespace std::string_literals; + +TEST_CASE("Basic logging") +{ + Logger::instance().reset(); + GIVEN("A logger with log_level 0") + { + Config::set("log_level", "0"); + WHEN("we log some debug text") + { + IoTester out(std::cout); + log_debug("debug"); + THEN("debug logs are written") + CHECK(out.str() == "<7>tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\tdebug\n"); + } + WHEN("we log some errors") + { + IoTester out(std::cout); + log_error("error"); + THEN("error logs are written") + CHECK(out.str() == "<3>tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); + } + } + GIVEN("A logger with log_level 3") + { + Config::set("log_level", "3"); + WHEN("we log some debug text") + { + IoTester out(std::cout); + log_debug("debug"); + THEN("nothing is written") + CHECK(out.str().empty()); + } + WHEN("we log some errors") + { + IoTester out(std::cout); + log_error("error"); + THEN("error logs are still written") + CHECK(out.str() == "<3>tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); + } + } +} diff --git a/tests/utils.cpp b/tests/utils.cpp index 6e3c32a..8691910 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -5,6 +5,7 @@ #include #include #include +#include TEST_CASE("String split") { @@ -70,3 +71,20 @@ TEST_CASE("xdg_*_path") CHECK(res == "/datadir/biboumi/bonjour.txt"); } } + +TEST_CASE("empty if fixed irc server") +{ + GIVEN("A config with fixed_irc_server") + { + Config::set("fixed_irc_server", "irc.localhost"); + THEN("our string is made empty") + CHECK(utils::empty_if_fixed_server("coucou coucou") == ""); + } + GIVEN("A config with NO fixed_irc_server") + { + Config::set("fixed_irc_server", ""); + THEN("our string is returned untouched") + CHECK(utils::empty_if_fixed_server("coucou coucou") == "coucou coucou"); + } + +} diff --git a/tests/xmpp.cpp b/tests/xmpp.cpp index 46ecd35..b6b50ed 100644 --- a/tests/xmpp.cpp +++ b/tests/xmpp.cpp @@ -26,6 +26,8 @@ TEST_CASE("Test basic XML parsing") // Do the same checks on a copy of that stanza. Stanza copy(stanza); check_stanza(copy); + // And do the same checks on moved-constructed stanza + Stanza moved(std::move(copy)); }); xml.feed(doc.data(), doc.size(), true); @@ -44,4 +46,3 @@ TEST_CASE("XML escape/unescape") CHECK(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); CHECK(xml_unescape(xml_escape(unescaped)) == unescaped); } - -- cgit v1.2.3 From 4dd01a29caff8a1712478cb20e9137453367da07 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 Oct 2015 05:35:59 +0100 Subject: Run the database tests with an in-memory sqlite db --- tests/database.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'tests') diff --git a/tests/database.cpp b/tests/database.cpp index c248568..c0f4e9f 100644 --- a/tests/database.cpp +++ b/tests/database.cpp @@ -2,15 +2,12 @@ #include -#include #include TEST_CASE("Database") { #ifdef USE_DATABASE - // Remove any potential existing db - ::unlink("./test.db"); - Config::set("db_name", "test.db"); + Config::set("db_name", ":memory:"); Database::set_verbose(false); auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); o.update(); -- cgit v1.2.3 From 6430479a3a6f15e221f0b9f3e822b44ca37af0f8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 Oct 2015 06:25:47 +0100 Subject: Add a IoTester class --- tests/io_tester.cpp | 30 ++++++++++++++++++++++++++++++ tests/io_tester.hpp | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 tests/io_tester.cpp create mode 100644 tests/io_tester.hpp (limited to 'tests') diff --git a/tests/io_tester.cpp b/tests/io_tester.cpp new file mode 100644 index 0000000..19c97c9 --- /dev/null +++ b/tests/io_tester.cpp @@ -0,0 +1,30 @@ +#include "io_tester.hpp" +#include "catch.hpp" +#include + +/** + * Directly test this class here + */ +TEST_CASE() +{ + { + IoTester out(std::cout); + std::cout << "test"; + CHECK(out.str() == "test"); + } + { + IoTester out(std::cout); + CHECK(out.str().empty()); + } +} + +TEST_CASE() +{ + { + IoTester is(std::cin); + is.set_string("coucou"); + std::string res; + std::cin >> res; + CHECK(res == "coucou"); + } +} diff --git a/tests/io_tester.hpp b/tests/io_tester.hpp new file mode 100644 index 0000000..8afa6f6 --- /dev/null +++ b/tests/io_tester.hpp @@ -0,0 +1,47 @@ +#ifndef BIBOUMI_IO_TESTER_HPP +#define BIBOUMI_IO_TESTER_HPP + +#include +#include + +/** + * Redirects a stream into a streambuf until the object is destroyed. + */ +template +class IoTester +{ +public: + IoTester(StreamType& ios): + stream{}, + ios(ios), + old_buf(ios.rdbuf()) + { + // Redirect the given os into our stringstream’s buf + this->ios.rdbuf(this->stream.rdbuf()); + } + ~IoTester() + { + this->ios.rdbuf(this->old_buf); + } + IoTester& operator=(const IoTester&) = delete; + IoTester& operator=(IoTester&&) = delete; + IoTester(const IoTester&) = delete; + IoTester(IoTester&&) = delete; + + std::string str() const + { + return this->stream.str(); + } + + void set_string(const std::string& s) + { + this->stream.str(s); + } + +private: + std::stringstream stream; + StreamType& ios; + std::streambuf* const old_buf; +}; + +#endif //BIBOUMI_IO_TESTER_HPP -- cgit v1.2.3 From 7e07a17420117758ca319b5beab6440ff1d634f7 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 Oct 2015 15:41:05 +0100 Subject: Fix the logger test when built without systemd --- tests/logger.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'tests') diff --git a/tests/logger.cpp b/tests/logger.cpp index d4fb055..2a99374 100644 --- a/tests/logger.cpp +++ b/tests/logger.cpp @@ -10,6 +10,13 @@ using namespace std::string_literals; TEST_CASE("Basic logging") { +#ifdef SYSTEMD_FOUND + const std::string debug_header = "<7>"; + const std::string error_header = "<3>"; +#else + const std::string debug_header = "[DEBUG]: "; + const std::string error_header = "[ERROR]: "; +#endif Logger::instance().reset(); GIVEN("A logger with log_level 0") { @@ -19,14 +26,14 @@ TEST_CASE("Basic logging") IoTester out(std::cout); log_debug("debug"); THEN("debug logs are written") - CHECK(out.str() == "<7>tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\tdebug\n"); + CHECK(out.str() == debug_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\tdebug\n"); } WHEN("we log some errors") { IoTester out(std::cout); log_error("error"); THEN("error logs are written") - CHECK(out.str() == "<3>tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); + CHECK(out.str() == error_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); } } GIVEN("A logger with log_level 3") @@ -44,7 +51,7 @@ TEST_CASE("Basic logging") IoTester out(std::cout); log_error("error"); THEN("error logs are still written") - CHECK(out.str() == "<3>tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); + CHECK(out.str() == error_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); } } } -- cgit v1.2.3 From 421c960df501b40e836a783400ab00dc60c3fdae Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 24 Dec 2015 21:39:53 +0100 Subject: Add a ChannelOptions table in the DB And a way to retrieve its values, defaulting on the ServerOptions for unset values. --- tests/database.cpp | 84 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 73 insertions(+), 11 deletions(-) (limited to 'tests') diff --git a/tests/database.cpp b/tests/database.cpp index c0f4e9f..5d557fd 100644 --- a/tests/database.cpp +++ b/tests/database.cpp @@ -9,20 +9,82 @@ TEST_CASE("Database") #ifdef USE_DATABASE Config::set("db_name", ":memory:"); Database::set_verbose(false); - auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); - o.update(); - auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); - auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com"); - // b does not yet exist in the db, the object is created but not yet - // inserted - CHECK(1 == Database::count()); + SECTION("Basic retrieve and update") + { + auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); + o.update(); + auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); + auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com"); - b.update(); - CHECK(2 == Database::count()); + // b does not yet exist in the db, the object is created but not yet + // inserted + CHECK(1 == Database::count()); - CHECK(b.pass == ""); - CHECK(b.pass.value() == ""); + b.update(); + CHECK(2 == Database::count()); + + CHECK(b.pass == ""); + CHECK(b.pass.value() == ""); + } + + SECTION("channel options") + { + Config::set("db_name", ":memory:"); + auto o = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo"); + + CHECK(o.encodingIn == ""); + o.encodingIn = "ISO-8859-1"; + o.update(); + auto b = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo"); + CHECK(o.encodingIn == "ISO-8859-1"); + } + + SECTION("Channel options with server default") + { + const std::string owner{"zouzou@example.com"}; + const std::string server{"irc.example.com"}; + const std::string chan1{"#foo"}; + + auto c = Database::get_irc_channel_options(owner, server, chan1); + auto s = Database::get_irc_server_options(owner, server); + + GIVEN("An option defined for the channel but not the server") + { + c.encodingIn = "channelEncoding"; + c.update(); + WHEN("we fetch that option") + { + auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1); + THEN("we get the channel option") + CHECK(r.encodingIn == "channelEncoding"); + } + } + GIVEN("An option defined for the server but not the channel") + { + s.encodingIn = "serverEncoding"; + s.update(); + WHEN("we fetch that option") + { + auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1); + THEN("we get the server option") + CHECK(r.encodingIn == "serverEncoding"); + } + } + GIVEN("An option defined for both the server and the channel") + { + s.encodingIn = "serverEncoding"; + s.update(); + c.encodingIn = "channelEncoding"; + c.update(); + WHEN("we fetch that option") + { + auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1); + THEN("we get the channel option") + CHECK(r.encodingIn == "channelEncoding"); + } + } + } Database::close(); #endif -- cgit v1.2.3 From 79cdf170d2ab6c5378cfbf61d5c8888a4c666190 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 28 Dec 2015 21:25:48 +0100 Subject: Use the configured encoding value when decoding received messages --- tests/database.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'tests') diff --git a/tests/database.cpp b/tests/database.cpp index 5d557fd..b059d0d 100644 --- a/tests/database.cpp +++ b/tests/database.cpp @@ -83,6 +83,12 @@ TEST_CASE("Database") THEN("we get the channel option") CHECK(r.encodingIn == "channelEncoding"); } + WHEN("we fetch that option, with no channel specified") + { + auto r = Database::get_irc_channel_options_with_server_default(owner, server, ""); + THEN("we get the server option") + CHECK(r.encodingIn == "serverEncoding"); + } } } -- cgit v1.2.3 From a38b17692e0297cbd5d719f059bd0a1b6ef39fe4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 29 Dec 2015 11:19:39 +0100 Subject: Support multi-prefix See http://ircv3.net/specs/extensions/multi-prefix-3.1.html ref #3103 --- tests/iid.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/iid.cpp b/tests/iid.cpp index a90c208..74d010d 100644 --- a/tests/iid.cpp +++ b/tests/iid.cpp @@ -8,7 +8,6 @@ TEST_CASE("Irc user parsing") { const std::map prefixes{{'!', 'a'}, {'@', 'o'}}; - IrcUser user1("!nick!~some@host.bla", prefixes); CHECK(user1.nick == "nick"); CHECK(user1.host == "~some@host.bla"); @@ -22,6 +21,15 @@ TEST_CASE("Irc user parsing") CHECK(user2.modes.find('a') == user2.modes.end()); } +TEST_CASE("multi-prefix") +{ + const std::map prefixes{{'!', 'a'}, {'@', 'o'}, {'~', 'f'}}; + IrcUser user("!@~nick", prefixes); + CHECK(user.nick == "nick"); + CHECK(user.modes.size() == 3); + CHECK(user.modes.find('f') != user.modes.end()); +} + /** * Let Catch know how to display Iid objects */ -- cgit v1.2.3 From 0c85ed128a650aa4dde7d919b4165162225a818f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 15 Jan 2016 12:14:49 +0100 Subject: =?UTF-8?q?Remove=20the=20localhost6=20test,=20that=E2=80=99s=20no?= =?UTF-8?q?t=20portable=20at=20all?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/dns.cpp | 6 ------ 1 file changed, 6 deletions(-) (limited to 'tests') diff --git a/tests/dns.cpp b/tests/dns.cpp index e8cbc7f..4ec1b96 100644 --- a/tests/dns.cpp +++ b/tests/dns.cpp @@ -65,12 +65,6 @@ TEST_CASE("DNS resolver") loop(); CHECK(!success); - hostname = "localhost6"; - resolver.resolve(hostname, port, - success_cb, error_cb); - loop(); - CHECK(success); - hostname = "localhost"; resolver.resolve(hostname, port, success_cb, error_cb); -- cgit v1.2.3 From 218260362f0ccb4fd5b51765d4bd331389f39baa Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 10 Feb 2016 20:22:51 +0100 Subject: Remove unused xml_unescape() function --- tests/xmpp.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/xmpp.cpp b/tests/xmpp.cpp index b6b50ed..6aab8c4 100644 --- a/tests/xmpp.cpp +++ b/tests/xmpp.cpp @@ -40,9 +40,8 @@ TEST_CASE("Test basic XML parsing") xml.feed(doc2.data(), doc.size(), true); } -TEST_CASE("XML escape/unescape") +TEST_CASE("XML escape") { const std::string unescaped = "'coucou'/&\"gaga\""; CHECK(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); - CHECK(xml_unescape(xml_escape(unescaped)) == unescaped); } -- cgit v1.2.3 From 51a34f83f9cae36f65b021e379e411cacf84c054 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 23 Feb 2016 19:20:00 +0100 Subject: Add a basic integration test in python --- tests/end_to_end/__main__.py | 217 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 tests/end_to_end/__main__.py (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py new file mode 100644 index 0000000..96bbbf4 --- /dev/null +++ b/tests/end_to_end/__main__.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 + +import slixmpp +import asyncio +import logging +import signal +import atexit +import sys +from functools import partial + + +class MatchAll(slixmpp.xmlstream.matcher.base.MatcherBase): + """match everything""" + + def match(self, xml): + return True + + +class XMPPComponent(slixmpp.BaseXMPP): + """ + XMPPComponent sending a “scenario” of stanzas, checking that the responses + match the expected results. + """ + + def __init__(self, scenario, biboumi): + super().__init__(jid="biboumi.localhost", default_ns="jabber:component:accept") + self.is_component = True + self.stream_header = '' % ( + 'xmlns="jabber:component:accept"', + 'xmlns:stream="%s"' % self.stream_ns, + self.boundjid, self.get_id()) + self.stream_footer = "" + + self.register_handler(slixmpp.Callback('Match All', + MatchAll(None), + self.handle_incoming_stanza)) + + self.add_event_handler("session_end", self.on_end_session) + + asyncio.async(self.accept_routine()) + + self.scenario = scenario + self.biboumi = biboumi + self.expected_xpath = None + self.failed = False + self.accepting_server = None + + def error(self, message): + print("Failure: %s" % (message,)) + self.scenario.steps = [] + self.failed = True + + def on_end_session(self, event): + self.loop.stop() + + def handle_incoming_stanza(self, stanza): + if self.expected_xpath: + matched = slixmpp.xmlstream.matcher.xpath.MatchXPath(self.expected_xpath).match(stanza) + if not matched: + self.error("Received stanza “%s” did not match expected xpath “%s”" % (stanza, self.expected_xpath)) + self.expected_xpath = None + self.run_scenario() + + def run_scenario(self): + if scenario.steps: + step = scenario.steps.pop(0) + step(self, self.biboumi) + else: + self.biboumi.stop() + + @asyncio.coroutine + def accept_routine(self): + self.accepting_server = yield from self.loop.create_server(lambda: self, + "127.0.0.1", "8811", reuse_address=True) + + +class Scenario: + """Defines a list of actions that are executed in sequence, until one of + them throws an exception, or until the end. An action can be something + like “send a stanza”, “receive the next stanza and check that it matches + the given XPath”, “send a signal”, “wait for the end of the process”, + etc + """ + + def __init__(self, name, steps): + """ + Steps is a list of 2-tuple: + [(action, answer), (action, answer)] + """ + self.name = name + self.steps = steps + + +class BiboumiRunner: + def __init__(self, name, with_valgrind): + self.name = name + self.fd = open("biboumi_%s_output.txt" % (name,), "w") + if with_valgrind: + self.create = asyncio.create_subprocess_exec("valgrind", "--leak-check=full", "--show-leak-kinds=all", "--errors-for-leak-kinds=all", "--error-exitcode=16", "./biboumi", "test.conf", stdin=None, stdout=self.fd, + stderr=self.fd, loop=None, limit=None) + else: + self.create = asyncio.create_subprocess_exec("./biboumi", "test.conf", stdin=None, stdout=self.fd, + stderr=self.fd, loop=None, limit=None) + self.process = None + + self.signal_sent = False + + @asyncio.coroutine + def start(self): + self.process = yield from self.create + + @asyncio.coroutine + def wait(self): + code = yield from self.process.wait() + return code + + def stop(self): + if not self.signal_sent: + self.signal_sent = True + if self.process: + self.process.send_signal(signal.SIGINT) + + +def send_stanza(stanza, xmpp, biboumi): + xmpp.send_raw(stanza) + asyncio.get_event_loop().call_soon(xmpp.run_scenario) + + +def expect_stanza(xpath, xmpp, biboumi): + xmpp.expected_xpath = xpath + + +class BiboumiTest: + """ + Spawns a biboumi process and a fake XMPP Component that will run a + Scenario. It redirects the outputs of the subprocess into separated + files, and detects any failure in the running of the scenario. + """ + + def __init__(self, scenario, expected_code=0): + self.scenario = scenario + self.expected_code = 0 + + def run(self, with_valgrind=True): + print("Running scenario: %s%s" % (self.scenario.name, " (with valgrind)" if with_valgrind else '')) + # Redirect the slixmpp logging into a specific file + output_filename = "slixmpp_%s_output.txt" % (self.scenario.name,) + with open(output_filename, "w"): + pass + logging.basicConfig(level=logging.DEBUG, + format='%(levelname)-8s %(message)s', + filename=output_filename) + + # Start the XMPP component and biboumi + biboumi = BiboumiRunner(scenario.name, with_valgrind) + xmpp = XMPPComponent(self.scenario, biboumi) + asyncio.get_event_loop().run_until_complete(biboumi.start()) + + asyncio.get_event_loop().call_soon(xmpp.run_scenario) + + xmpp.process() + + code = asyncio.get_event_loop().run_until_complete(biboumi.wait()) + failed = False + if not xmpp.failed: + if code != self.expected_code: + xmpp.error("Wrong return code from biboumi's process: %d" % (code,)) + failed = True + else: + print("Success!") + else: + failed = True + + if xmpp.server: + xmpp.accepting_server.close() + + return not failed + + +if __name__ == '__main__': + + atexit.register(asyncio.get_event_loop().close) + + # Start the test component, accepting connections on the configured + # port. + scenarios = ( + Scenario("basic_handshake_success", + [ + partial(expect_stanza, "{jabber:component:accept}handshake"), + partial(send_stanza, ""), + ]), + Scenario("channel_join", + [ + partial(expect_stanza, "{jabber:component:accept}handshake"), + partial(send_stanza, ""), + partial(send_stanza, ""), + partial(expect_stanza, "{jabber:component:accept}message/body"), + ]), + ) + + failures = 0 + + print("Running %s checks for biboumi." % (len(scenarios))) + + for scenario in scenarios: + test = BiboumiTest(scenario) + if not test.run(False): + print("You can check the files slixmpp_%s_output.txt and biboumi_%s_output.txt to help you debug." % + (scenario.name, scenario.name)) + failures += 1 + + if failures: + print("%d test%s failed, please fix %s." % (failures, 's' if failures > 1 else '', + 'them' if failures > 1 else 'it')) + sys.exit(1) + else: + print("All tests passed successfully") -- cgit v1.2.3 From 1ee5f8e01a932b73628ed3f89e8c77c5fa25f1b0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 23 Feb 2016 19:40:17 +0100 Subject: end_to_end creates a config file before running biboumi --- tests/end_to_end/__main__.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 96bbbf4..8e9c46e 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -151,6 +151,9 @@ class BiboumiTest: format='%(levelname)-8s %(message)s', filename=output_filename) + with open("test.conf", "w") as fd: + fd.write(confs['basic']) + # Start the XMPP component and biboumi biboumi = BiboumiRunner(scenario.name, with_valgrind) xmpp = XMPPComponent(self.scenario, biboumi) @@ -176,6 +179,11 @@ class BiboumiTest: return not failed +confs = {'basic': +"""hostname=biboumi.localhost +password=coucou +db_name=biboumi.sqlite +port=8811"""} if __name__ == '__main__': -- cgit v1.2.3 From 55d1d817719646d6be3cac200e4ff8b7b113e136 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 29 Feb 2016 17:59:47 +0100 Subject: Provide a better way to check stanzas at each step of the end_to_end test --- tests/end_to_end/__main__.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 8e9c46e..dbc7c66 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -16,6 +16,10 @@ class MatchAll(slixmpp.xmlstream.matcher.base.MatcherBase): return True +class StanzaError(Exception): + pass + + class XMPPComponent(slixmpp.BaseXMPP): """ XMPPComponent sending a “scenario” of stanzas, checking that the responses @@ -41,7 +45,9 @@ class XMPPComponent(slixmpp.BaseXMPP): self.scenario = scenario self.biboumi = biboumi - self.expected_xpath = None + # A callable, taking a stanza as argument and raising a StanzaError + # exception if the test should fail. + self.stanza_checker = None self.failed = False self.accepting_server = None @@ -54,11 +60,12 @@ class XMPPComponent(slixmpp.BaseXMPP): self.loop.stop() def handle_incoming_stanza(self, stanza): - if self.expected_xpath: - matched = slixmpp.xmlstream.matcher.xpath.MatchXPath(self.expected_xpath).match(stanza) - if not matched: - self.error("Received stanza “%s” did not match expected xpath “%s”" % (stanza, self.expected_xpath)) - self.expected_xpath = None + if self.stanza_checker: + try: + self.stanza_checker(stanza) + except StanzaError as e: + self.error(e) + self.stanza_checker = None self.run_scenario() def run_scenario(self): @@ -73,6 +80,10 @@ class XMPPComponent(slixmpp.BaseXMPP): self.accepting_server = yield from self.loop.create_server(lambda: self, "127.0.0.1", "8811", reuse_address=True) +def check_xpath(xpath, stanza): + matched = slixmpp.xmlstream.matcher.xpath.MatchXPath(xpath).match(stanza) + if not matched: + raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, self.expected_xpath)) class Scenario: """Defines a list of actions that are executed in sequence, until one of @@ -127,7 +138,7 @@ def send_stanza(stanza, xmpp, biboumi): def expect_stanza(xpath, xmpp, biboumi): - xmpp.expected_xpath = xpath + xmpp.stanza_checker = partial(check_xpath, xpath) class BiboumiTest: -- cgit v1.2.3 From d57b8bb8c3edc4271a9be3d052a220db09250a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 17 Mar 2016 19:18:24 +0100 Subject: Trivial formatting --- tests/end_to_end/__main__.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index dbc7c66..7b01a39 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -7,9 +7,10 @@ import signal import atexit import sys from functools import partial +from slixmpp.xmlstream.matcher.base import MatcherBase -class MatchAll(slixmpp.xmlstream.matcher.base.MatcherBase): +class MatchAll(MatcherBase): """match everything""" def match(self, xml): @@ -80,11 +81,13 @@ class XMPPComponent(slixmpp.BaseXMPP): self.accepting_server = yield from self.loop.create_server(lambda: self, "127.0.0.1", "8811", reuse_address=True) + def check_xpath(xpath, stanza): matched = slixmpp.xmlstream.matcher.xpath.MatchXPath(xpath).match(stanza) if not matched: raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, self.expected_xpath)) + class Scenario: """Defines a list of actions that are executed in sequence, until one of them throws an exception, or until the end. An action can be something @@ -107,7 +110,9 @@ class BiboumiRunner: self.name = name self.fd = open("biboumi_%s_output.txt" % (name,), "w") if with_valgrind: - self.create = asyncio.create_subprocess_exec("valgrind", "--leak-check=full", "--show-leak-kinds=all", "--errors-for-leak-kinds=all", "--error-exitcode=16", "./biboumi", "test.conf", stdin=None, stdout=self.fd, + self.create = asyncio.create_subprocess_exec("valgrind", "--leak-check=full", "--show-leak-kinds=all", + "--errors-for-leak-kinds=all", "--error-exitcode=16", + "./biboumi", "test.conf", stdin=None, stdout=self.fd, stderr=self.fd, loop=None, limit=None) else: self.create = asyncio.create_subprocess_exec("./biboumi", "test.conf", stdin=None, stdout=self.fd, @@ -190,6 +195,7 @@ class BiboumiTest: return not failed + confs = {'basic': """hostname=biboumi.localhost password=coucou @@ -212,7 +218,8 @@ if __name__ == '__main__': [ partial(expect_stanza, "{jabber:component:accept}handshake"), partial(send_stanza, ""), - partial(send_stanza, ""), + partial(send_stanza, + ""), partial(expect_stanza, "{jabber:component:accept}message/body"), ]), ) -- cgit v1.2.3 From 606700f4c5c2e76762b6c5d1ba360e99932b3309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 11 Apr 2016 17:18:07 +0200 Subject: Improve e2e test, start mammond ourself, etc --- tests/end_to_end/__main__.py | 182 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 152 insertions(+), 30 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 7b01a39..da9e80c 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1,11 +1,14 @@ #!/usr/bin/env python3 +import collections import slixmpp import asyncio import logging import signal import atexit +import lxml.etree import sys +import io from functools import partial from slixmpp.xmlstream.matcher.base import MatcherBase @@ -81,11 +84,16 @@ class XMPPComponent(slixmpp.BaseXMPP): self.accepting_server = yield from self.loop.create_server(lambda: self, "127.0.0.1", "8811", reuse_address=True) + def check_stanza_against_all_expected_xpaths(self): + pass -def check_xpath(xpath, stanza): - matched = slixmpp.xmlstream.matcher.xpath.MatchXPath(xpath).match(stanza) - if not matched: - raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, self.expected_xpath)) +def check_xpath(xpaths, stanza): + for xpath in xpaths: + tree = lxml.etree.parse(io.StringIO(str(stanza))) + matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions', + 'muc_user': 'http://jabber.org/protocol/muc#user'}) + if not matched: + raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath)) class Scenario: @@ -102,23 +110,17 @@ class Scenario: [(action, answer), (action, answer)] """ self.name = name - self.steps = steps - + self.steps = [] + for elem in steps: + if isinstance(elem, collections.Iterable): + for step in elem: + self.steps.append(step) + else: + self.steps.append(elem) -class BiboumiRunner: - def __init__(self, name, with_valgrind): - self.name = name - self.fd = open("biboumi_%s_output.txt" % (name,), "w") - if with_valgrind: - self.create = asyncio.create_subprocess_exec("valgrind", "--leak-check=full", "--show-leak-kinds=all", - "--errors-for-leak-kinds=all", "--error-exitcode=16", - "./biboumi", "test.conf", stdin=None, stdout=self.fd, - stderr=self.fd, loop=None, limit=None) - else: - self.create = asyncio.create_subprocess_exec("./biboumi", "test.conf", stdin=None, stdout=self.fd, - stderr=self.fd, loop=None, limit=None) +class ProcessRunner: + def __init__(self): self.process = None - self.signal_sent = False @asyncio.coroutine @@ -136,15 +138,42 @@ class BiboumiRunner: if self.process: self.process.send_signal(signal.SIGINT) + def __del__(self): + self.stop() + +class BiboumiRunner(ProcessRunner): + def __init__(self, name, with_valgrind): + super().__init__() + self.name = name + self.fd = open("biboumi_%s_output.txt" % (name,), "w") + if with_valgrind: + self.create = asyncio.create_subprocess_exec("valgrind", "--leak-check=full", "--show-leak-kinds=all", + "--errors-for-leak-kinds=all", "--error-exitcode=16", + "./biboumi", "test.conf", stdin=None, stdout=self.fd, + stderr=self.fd, loop=None, limit=None) + else: + self.create = asyncio.create_subprocess_exec("./biboumi", "test.conf", stdin=None, stdout=self.fd, + stderr=self.fd, loop=None, limit=None) + +class IrcServerRunner(ProcessRunner): + def __init__(self): + super().__init__() + self.create = asyncio.create_subprocess_exec("mammond", "--debug", "--nofork", + "--config", "../tests/end_to_end/mammond.conf", + stderr=asyncio.subprocess.PIPE) def send_stanza(stanza, xmpp, biboumi): - xmpp.send_raw(stanza) + xmpp.send_raw(stanza.format_map(common_replacements)) asyncio.get_event_loop().call_soon(xmpp.run_scenario) -def expect_stanza(xpath, xmpp, biboumi): - xmpp.stanza_checker = partial(check_xpath, xpath) - +def expect_stanza(xpaths, xmpp, biboumi): + if isinstance(xpaths, str): + xmpp.stanza_checker = partial(check_xpath, [xpaths.format_map(common_replacements)]) + elif isinstance(xpaths, tuple): + xmpp.stanza_checker = partial(check_xpath, [xpath.format_map(common_replacements) for xpath in xpaths]) + else: + print("Warning, from argument type passed to expect_stanza: %s" % (type(xpaths))) class BiboumiTest: """ @@ -202,6 +231,59 @@ password=coucou db_name=biboumi.sqlite port=8811"""} +common_replacements = { + 'irc_server_one': 'irc.localhost@biboumi.localhost', + 'irc_host_one': 'irc.localhost', + 'resource_one': 'resource1', + 'nick_one': 'Nick', + 'jid_one': 'first@example.com', + 'jid_two': 'second@example.com', + 'nick_two': 'Bobby', +} + + +def handshake_sequence(): + return (partial(expect_stanza, "//handshake"), + partial(send_stanza, "")) + + +def connection_sequence(irc_host, jid): + jid = jid.format_map(common_replacements) + xpath = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[text()='%s']" + xpath_re = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[re:test(text(), '%s')]" + return ( + partial(expect_stanza, + xpath % ('Connecting to %s:6697 (encrypted)' % irc_host)), + partial(expect_stanza, + xpath % ('Connection failed: Connection refused')), + partial(expect_stanza, + xpath % ('Connecting to %s:6670 (encrypted)' % irc_host)), + partial(expect_stanza, + xpath % ('Connection failed: Connection refused')), + partial(expect_stanza, + xpath % ('Connecting to %s:6667 (not encrypted)' % irc_host)), + partial(expect_stanza, + xpath % ('Connected to IRC server.')), + partial(expect_stanza, + xpath % ('%s: *** Looking up your hostname...' % irc_host)), + partial(expect_stanza, + xpath % ('%s: *** Checking Ident' % irc_host)), + # These three messages can be received in any order + partial(expect_stanza, + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|NAK multi-prefix |\*\*\* No Ident response)$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|NAK multi-prefix |\*\*\* No Ident response)$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|NAK multi-prefix |\*\*\* No Ident response)$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: Your host is .*$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: This server was started at .*$' % irc_host)), + partial(expect_stanza, + xpath % ("- Default MOTD\n")), + ) + + if __name__ == '__main__': atexit.register(asyncio.get_event_loop().close) @@ -211,21 +293,57 @@ if __name__ == '__main__': scenarios = ( Scenario("basic_handshake_success", [ - partial(expect_stanza, "{jabber:component:accept}handshake"), - partial(send_stanza, ""), + handshake_sequence() + ]), + Scenario("irc_server_connection", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + ]), + Scenario("simple_channel_join", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost.localdomain'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), ]), - Scenario("channel_join", + Scenario("channel_join_with_two_users", [ - partial(expect_stanza, "{jabber:component:accept}handshake"), - partial(send_stanza, ""), + handshake_sequence(), + # First user joins partial(send_stanza, - ""), - partial(expect_stanza, "{jabber:component:accept}message/body"), + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost.localdomain'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # Second user joins + partial(send_stanza, + ""), + # connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), ]), ) failures = 0 + irc = IrcServerRunner() + print("Starting mammond server…") + asyncio.get_event_loop().run_until_complete(irc.start()) + while True: + res = asyncio.get_event_loop().run_until_complete(irc.process.stderr.readline()) + if b"init finished..." in res: + break + print("mammond server started.") print("Running %s checks for biboumi." % (len(scenarios))) for scenario in scenarios: @@ -235,6 +353,10 @@ if __name__ == '__main__': (scenario.name, scenario.name)) failures += 1 + print("Waiting for mammond to exit…") + irc.stop() + code = asyncio.get_event_loop().run_until_complete(irc.wait()) + if failures: print("%d test%s failed, please fix %s." % (failures, 's' if failures > 1 else '', 'them' if failures > 1 else 'it')) -- cgit v1.2.3 From 065f52a0b32d5355da58e200a7e9a301693dcb55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 11 Apr 2016 17:29:54 +0200 Subject: Add mammond conf --- tests/end_to_end/mammond.conf | 269 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 tests/end_to_end/mammond.conf (limited to 'tests') diff --git a/tests/end_to_end/mammond.conf b/tests/end_to_end/mammond.conf new file mode 100644 index 0000000..a9c7d43 --- /dev/null +++ b/tests/end_to_end/mammond.conf @@ -0,0 +1,269 @@ +# The server object defines the server information parameters. +server: + # name - the server name + name: "irc.localhost" + + # description - the description of the server on the network + description: "Test server for biboumi" + + # network - the NETWORK= name in 005 for rfc1459 clients + network: "irc.localhost" + + # recvq_len - the maximum number of lines that can be in a client's recvq + recvq_len: 20 + + # motd - the motd content (will later be file) + motd: + - "Default MOTD" + + +# The clients object defines client parameters +clients: + # ping_frequency - client ping frequency + ping_frequency: + minutes: 1 + + # ping_timeout - ping timeout length + ping_timeout: + minutes: 2 + + +# The data object defines the data store parameters +data: + + ## JSON should only be considered for testing + # format - data store type + format: "json" + + # filename - data store filename + filename: ".mammon.data.json" + + # save_frequency - save the database every this amount of time + save_frequency: + minutes: 5 + + +# The listeners object is a list of listeners. +listeners: +- {"host": "0.0.0.0", "port": 6667, "ssl": false, "certfile": "~/workspace/biboumi/cert.pem", "keyfile": "~/workspace/biboumi/key.pem", } + + +# The logs section is a list of logs. +logs: +- { + # path - the path of the logfile + "path": "mammond.log", + + # level - the log level of the file + "level": "debug" + } + + +# Limits define maximum lengths for various commands and objects +# to remove a limit, simply comment it out +limits: + # user - maximum length of usernames + user: 10 + + # nick - maximum length of nicknames + nick: 50 + + # channel - maximum length of channel names + channel: 200 + + # topic - maximum length of channel topics + topic: 400 + + # line - maximum length of lines in and out + line: 2048 + + +# The register object defines registration information +register: + + # verify_timeout - length of time a user has to verify their newly-created + # account before it can be re-registered + verify_timeout: + days: 5 + + # enabled_callbacks - callbacks that we allow + enabled_callbacks: + # - mailto + # - none # no verification required, will instantly register successfully + + # callbacks - types and details for various callback methods + callbacks: + + # mailto - email using sendmail + mailto: + # from - address our messages get sent from + from: mammon@example.com + + # sendmail - location of the sendmail binary + sendmail: /usr/sbin/sendmail + + # verify_message_subject - subject of the verify message + verify_message_subject: "{network_name} Account Registration" + + # verify_message - message sent to users to verify their account + verify_message: | + Hi, + + You have requested to register the account {account}. + + Your verification code is {auth_code} + + Please type "/quote REG VERIFY {account} {auth_code}" to complete registration + + Thank you, + {network_name} +# Roles define the capabilities an oper may have, as well as role-specific +# metadata. + + # mammon capability names: + # oper:local_kill allows /KILLing local users + # oper:global_kill allows /KILLing local and remote users + # oper:routing allows remote SQUIT and CONNECT + # oper:kline allows KLINE and DLINE + # oper:unkline allows UNKLINE and UNDLINE + # oper:remote_ban allows remote klines + # oper:rehash allows REHASH of server config + # oper:die allows DIE and RESTART + +roles: + # name - the name of the privilege set + "local_op": + # capabilities - a list of qualified capability names + capabilities: + - oper:local_kill + - oper:kline + - oper:unkline + - metadata:set_local + + # metakeys_get - metadata keys this role can view + metakeys_get: + # - spammer_probability + + # title - metadata identifying the specific role + title: "IRC Operator" + + # name - the name of the privilege set + "global_op": + # extends - inherets this role's capabilities + extends: "local_op" + + # capabilities - a list of qualified capability names + capabilities: + - oper:global_kill + - oper:remote_ban + - metadata:set_global + + # title - metadata identifying the specific role + title: "IRC Operator" + + # name - the name of the privilege set + "network_admin": + # capabilities - a list of qualified capability names + capabilities: + - oper:global_kill + - oper:routing + + # title - metadata identifying the specific role + title: "Network Administrator" + + # name - the name of the privilege set + "server_admin": + # extends - inherets this role's capabilities + extends: "local_op" + + # capabilities - a list of qualified capability names + capabilities: + - oper:rehash + - oper:die + + # title - metadata identifying the specific role + title: "Server Administrator" + + # example metadata-specific roles + # name - the name of the privilege set + "spam_detection_bot": + # capabilities - a list of qualified capability names + capabilities: + - metadata:set_global + + # metakeys_access - metadata keys this role can view and set + metakeys_access: + - spammer_probability + + # title - metadata identifying the specific role + title: "Spam Detection Bot" + + +# Metadata defines the metadata users are allowed to set for themselves +metadata: + # limit - max number of metadata each target is allowed to have + # comment out to remove limit + limit: 20 + + # whitelist - if defined, a list of lowercase keys that are checked when users set data + whitelist: + - avatar + - info + - source + - url + - version + + # restricted_keys - keys that unprived users cannot see / edit, and require a + # special entry in a ROLE to see / edit + restricted_keys: + - away + # - spammer_probability + + +# Monitor defines the monitoring users are allowed to do on other users +monitor: + # limit - max number of 'monitors' each target is allowed to have + # comment out to remove limit + limit: 20 + + +# Operator credentials allow a user to transition from a typical user role +# to a privileged role. +opers: + # name - the name of the operator + "nobody": + # password - the plaintext oper password + password: "nothing" + + # hostmask - if defined, the hostmask the oper must connect from + hostmask: "*@localhost" + + # role - the role that the credentials allow transition to + role: "local_op" + + # name - the name of the operator + "somebody": + # password - the hashed oper password + # created by: mammond --mkpasswd + password: "$6$rounds=100000$KkEHFBhWHV3BHCCS$YuOdlikJHdeIv2YpwvyLAtYCslDlsnUUnrfeKJiBh4SeVhkSU6pQqHWWDjr6lnalkkf1KLDD1wgSQH5AObILQ1" + + # hash - the hashing algorithm to use + hash: "sha512_crypt" + + # hostmask - if defined, the hostmask the oper must connect from + hostmask: "somebody!*@localhost" + + # role - the role that the credentials allow transition to + role: "local_op" + + +# The extensions section is a list of extension modules to load. +extensions: +- mammon.ext.rfc1459.42 +- mammon.ext.rfc1459.ident +- mammon.ext.ircv3.account_notify +- mammon.ext.ircv3.server_time +- mammon.ext.ircv3.echo_message +- mammon.ext.ircv3.register +- mammon.ext.ircv3.sasl +- mammon.ext.misc.nopost \ No newline at end of file -- cgit v1.2.3 From e0ee881f10ddc29d2e633df62c4dc1879a39b342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 11 Apr 2016 17:33:54 +0200 Subject: Remove localdomain --- tests/end_to_end/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index da9e80c..3d907ca 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -309,7 +309,7 @@ if __name__ == '__main__': ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost.localdomain'][@role='participant']", + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", "/presence/muc_user:x/muc_user:status[@code='110']") ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), @@ -322,7 +322,7 @@ if __name__ == '__main__': ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost.localdomain'][@role='participant']", + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']", "/presence/muc_user:x/muc_user:status[@code='110']") ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), -- cgit v1.2.3 From 45f442b67d0331e82e25d8cafe3b6f2c87d1d611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 17 Apr 2016 17:01:28 +0200 Subject: More tests --- tests/end_to_end/__main__.py | 65 +++++++++++++++++++++++++++++++++++++++---- tests/end_to_end/mammond.conf | 2 +- 2 files changed, 60 insertions(+), 7 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 3d907ca..f498585 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -87,6 +87,7 @@ class XMPPComponent(slixmpp.BaseXMPP): def check_stanza_against_all_expected_xpaths(self): pass + def check_xpath(xpaths, stanza): for xpath in xpaths: tree = lxml.etree.parse(io.StringIO(str(stanza))) @@ -118,6 +119,7 @@ class Scenario: else: self.steps.append(elem) + class ProcessRunner: def __init__(self): self.process = None @@ -155,13 +157,14 @@ class BiboumiRunner(ProcessRunner): self.create = asyncio.create_subprocess_exec("./biboumi", "test.conf", stdin=None, stdout=self.fd, stderr=self.fd, loop=None, limit=None) + class IrcServerRunner(ProcessRunner): def __init__(self): super().__init__() - self.create = asyncio.create_subprocess_exec("mammond", "--debug", "--nofork", - "--config", "../tests/end_to_end/mammond.conf", + self.create = asyncio.create_subprocess_exec("/home/louiz/sources/charybdis/ircd/charybdis", "-foreground", stderr=asyncio.subprocess.PIPE) + def send_stanza(stanza, xmpp, biboumi): xmpp.send_raw(stanza.format_map(common_replacements)) asyncio.get_event_loop().call_soon(xmpp.run_scenario) @@ -175,6 +178,7 @@ def expect_stanza(xpaths, xmpp, biboumi): else: print("Warning, from argument type passed to expect_stanza: %s" % (type(xpaths))) + class BiboumiTest: """ Spawns a biboumi process and a fake XMPP Component that will run a @@ -330,20 +334,69 @@ if __name__ == '__main__': # Second user joins partial(send_stanza, ""), + connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + # Our presence, sent to the other user + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), + # The other user presence + partial(expect_stanza, + ("/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']") + ), + # Our own presence + partial(expect_stanza, + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + ]), + Scenario("channel_custom_topic", + [ + handshake_sequence(), + # First user joins + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # First user sets the topic + partial(send_stanza, + "TOPIC\nTEST"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC\nTEST']"), + + # # Second user joins + # partial(send_stanza, + # ""), # connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + # # Our presence, sent to the other user + # partial(expect_stanza, + # ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), + # # The other user presence + # partial(expect_stanza, + # ("/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']") + # ), + # # Our own presence + # partial(expect_stanza, + # ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", + # "/presence/muc_user:x/muc_user:status[@code='110']") + # ), + # partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), ]), ) failures = 0 irc = IrcServerRunner() - print("Starting mammond server…") + print("Starting irc server…") asyncio.get_event_loop().run_until_complete(irc.start()) while True: res = asyncio.get_event_loop().run_until_complete(irc.process.stderr.readline()) - if b"init finished..." in res: + if b"now running in foreground mode" in res: break - print("mammond server started.") + print("irc server started.") print("Running %s checks for biboumi." % (len(scenarios))) for scenario in scenarios: @@ -353,7 +406,7 @@ if __name__ == '__main__': (scenario.name, scenario.name)) failures += 1 - print("Waiting for mammond to exit…") + print("Waiting for irc server to exit…") irc.stop() code = asyncio.get_event_loop().run_until_complete(irc.wait()) diff --git a/tests/end_to_end/mammond.conf b/tests/end_to_end/mammond.conf index a9c7d43..99db2bc 100644 --- a/tests/end_to_end/mammond.conf +++ b/tests/end_to_end/mammond.conf @@ -233,7 +233,7 @@ opers: # name - the name of the operator "nobody": # password - the plaintext oper password - password: "nothing" + # password: "nothing" # hostmask - if defined, the hostmask the oper must connect from hostmask: "*@localhost" -- cgit v1.2.3 From bd625aa59bcd5194dd5ee5eea03e7f465d555ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 19 Apr 2016 02:36:45 +0200 Subject: e2etests: we are able to receive optional stanzas --- tests/end_to_end/__main__.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index f498585..c45e8b9 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -21,6 +21,17 @@ class MatchAll(MatcherBase): class StanzaError(Exception): + """ + Raised when a step fails. + """ + pass + + +class SkipStepError(Exception): + """ + Raised by a step when it needs to be skiped, by running + the next available step immediately. + """ pass @@ -69,6 +80,10 @@ class XMPPComponent(slixmpp.BaseXMPP): self.stanza_checker(stanza) except StanzaError as e: self.error(e) + except SkipStepError: + # Run the next step and then re-handle this same stanza + self.run_scenario() + return self.handle_incoming_stanza(stanza) self.stanza_checker = None self.run_scenario() @@ -97,6 +112,13 @@ def check_xpath(xpaths, stanza): raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath)) +def check_xpath_optional(xpaths, stanza): + try: + check_xpath(xpaths, stanza) + except StanzaError: + raise SkipStepError() + + class Scenario: """Defines a list of actions that are executed in sequence, until one of them throws an exception, or until the end. An action can be something @@ -170,11 +192,12 @@ def send_stanza(stanza, xmpp, biboumi): asyncio.get_event_loop().call_soon(xmpp.run_scenario) -def expect_stanza(xpaths, xmpp, biboumi): +def expect_stanza(xpaths, xmpp, biboumi, optional=False): + check_func = check_xpath if not optional else check_xpath_optional if isinstance(xpaths, str): - xmpp.stanza_checker = partial(check_xpath, [xpaths.format_map(common_replacements)]) + xmpp.stanza_checker = partial(check_func, [xpaths.format_map(common_replacements)]) elif isinstance(xpaths, tuple): - xmpp.stanza_checker = partial(check_xpath, [xpath.format_map(common_replacements) for xpath in xpaths]) + xmpp.stanza_checker = partial(check_func, [xpath.format_map(common_replacements) for xpath in xpaths]) else: print("Warning, from argument type passed to expect_stanza: %s" % (type(xpaths))) -- cgit v1.2.3 From 64f341ee80b0d53f0a6e128a1ccc38205361c3bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 19 Apr 2016 02:39:27 +0200 Subject: e2etests: Use charybdis and update the scenarios accordingly --- tests/end_to_end/__main__.py | 96 +++++++++------ tests/end_to_end/mammond.conf | 269 ------------------------------------------ 2 files changed, 58 insertions(+), 307 deletions(-) delete mode 100644 tests/end_to_end/mammond.conf (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index c45e8b9..b027bd9 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -165,6 +165,7 @@ class ProcessRunner: def __del__(self): self.stop() + class BiboumiRunner(ProcessRunner): def __init__(self, name, with_valgrind): super().__init__() @@ -183,7 +184,7 @@ class BiboumiRunner(ProcessRunner): class IrcServerRunner(ProcessRunner): def __init__(self): super().__init__() - self.create = asyncio.create_subprocess_exec("/home/louiz/sources/charybdis/ircd/charybdis", "-foreground", + self.create = asyncio.create_subprocess_exec("charybdis", "-foreground", stderr=asyncio.subprocess.PIPE) @@ -271,7 +272,7 @@ common_replacements = { def handshake_sequence(): return (partial(expect_stanza, "//handshake"), - partial(send_stanza, "")) + partial(send_stanza, "")) def connection_sequence(irc_host, jid): @@ -282,32 +283,47 @@ def connection_sequence(irc_host, jid): partial(expect_stanza, xpath % ('Connecting to %s:6697 (encrypted)' % irc_host)), partial(expect_stanza, - xpath % ('Connection failed: Connection refused')), + xpath % 'Connection failed: Connection refused'), partial(expect_stanza, xpath % ('Connecting to %s:6670 (encrypted)' % irc_host)), partial(expect_stanza, - xpath % ('Connection failed: Connection refused')), + xpath % 'Connection failed: Connection refused'), partial(expect_stanza, xpath % ('Connecting to %s:6667 (not encrypted)' % irc_host)), partial(expect_stanza, - xpath % ('Connected to IRC server.')), + xpath % 'Connected to IRC server.'), + # These two messages can be receive in any order partial(expect_stanza, - xpath % ('%s: *** Looking up your hostname...' % irc_host)), + xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)), partial(expect_stanza, - xpath % ('%s: *** Checking Ident' % irc_host)), + xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)), # These three messages can be received in any order partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|NAK multi-prefix |\*\*\* No Ident response)$' % irc_host)), + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|NAK multi-prefix |\*\*\* No Ident response)$' % irc_host)), + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|NAK multi-prefix |\*\*\* No Ident response)$' % irc_host)), + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), partial(expect_stanza, xpath_re % (r'^%s: Your host is .*$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: This server was started at .*$' % irc_host)), + xpath_re % (r'^%s: This server was created .*$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: There are \d+ users and \d+ invisible on \d+ servers$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: \d+ channels formed$' % irc_host), optional=True), + partial(expect_stanza, + xpath_re % (r'^%s: I have \d+ clients and \d+ servers$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: \d+ \d+ Current local users \d+, max \d+$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: \d+ \d+ Current global users \d+, max \d+$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: Highest connection count: \d+ \(\d+ clients\) \(\d+ connections received\)$' % irc_host)), + partial(expect_stanza, + xpath % "- This is charybdis MOTD you might replace it, but if not your friends will\n- laugh at you.\n"), partial(expect_stanza, - xpath % ("- Default MOTD\n")), + xpath_re % r'^User mode for \w+ is \[\+i\]$'), ) @@ -336,7 +352,9 @@ if __name__ == '__main__': ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", "/presence/muc_user:x/muc_user:status[@code='110']") ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), @@ -349,7 +367,9 @@ if __name__ == '__main__': ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']", + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", "/presence/muc_user:x/muc_user:status[@code='110']") ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), @@ -363,8 +383,7 @@ if __name__ == '__main__': ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), # The other user presence partial(expect_stanza, - ("/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']") - ), + "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']"), # Our own presence partial(expect_stanza, ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", @@ -380,33 +399,34 @@ if __name__ == '__main__': ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']", - "/presence/muc_user:x/muc_user:status[@code='110']") + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), # First user sets the topic partial(send_stanza, - "TOPIC\nTEST"), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC\nTEST']"), - - # # Second user joins - # partial(send_stanza, - # ""), - # connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - # # Our presence, sent to the other user - # partial(expect_stanza, - # ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), - # # The other user presence - # partial(expect_stanza, - # ("/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']") - # ), - # # Our own presence - # partial(expect_stanza, - # ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", - # "/presence/muc_user:x/muc_user:status[@code='110']") - # ), - # partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + "TOPIC TEST"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC TEST']"), + + # Second user joins + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + # Our presence, sent to the other user + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), + # The other user presence + partial(expect_stanza, + "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']"), + # Our own presence + partial(expect_stanza, + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[text()='TOPIC TEST']"), ]), ) diff --git a/tests/end_to_end/mammond.conf b/tests/end_to_end/mammond.conf deleted file mode 100644 index 99db2bc..0000000 --- a/tests/end_to_end/mammond.conf +++ /dev/null @@ -1,269 +0,0 @@ -# The server object defines the server information parameters. -server: - # name - the server name - name: "irc.localhost" - - # description - the description of the server on the network - description: "Test server for biboumi" - - # network - the NETWORK= name in 005 for rfc1459 clients - network: "irc.localhost" - - # recvq_len - the maximum number of lines that can be in a client's recvq - recvq_len: 20 - - # motd - the motd content (will later be file) - motd: - - "Default MOTD" - - -# The clients object defines client parameters -clients: - # ping_frequency - client ping frequency - ping_frequency: - minutes: 1 - - # ping_timeout - ping timeout length - ping_timeout: - minutes: 2 - - -# The data object defines the data store parameters -data: - - ## JSON should only be considered for testing - # format - data store type - format: "json" - - # filename - data store filename - filename: ".mammon.data.json" - - # save_frequency - save the database every this amount of time - save_frequency: - minutes: 5 - - -# The listeners object is a list of listeners. -listeners: -- {"host": "0.0.0.0", "port": 6667, "ssl": false, "certfile": "~/workspace/biboumi/cert.pem", "keyfile": "~/workspace/biboumi/key.pem", } - - -# The logs section is a list of logs. -logs: -- { - # path - the path of the logfile - "path": "mammond.log", - - # level - the log level of the file - "level": "debug" - } - - -# Limits define maximum lengths for various commands and objects -# to remove a limit, simply comment it out -limits: - # user - maximum length of usernames - user: 10 - - # nick - maximum length of nicknames - nick: 50 - - # channel - maximum length of channel names - channel: 200 - - # topic - maximum length of channel topics - topic: 400 - - # line - maximum length of lines in and out - line: 2048 - - -# The register object defines registration information -register: - - # verify_timeout - length of time a user has to verify their newly-created - # account before it can be re-registered - verify_timeout: - days: 5 - - # enabled_callbacks - callbacks that we allow - enabled_callbacks: - # - mailto - # - none # no verification required, will instantly register successfully - - # callbacks - types and details for various callback methods - callbacks: - - # mailto - email using sendmail - mailto: - # from - address our messages get sent from - from: mammon@example.com - - # sendmail - location of the sendmail binary - sendmail: /usr/sbin/sendmail - - # verify_message_subject - subject of the verify message - verify_message_subject: "{network_name} Account Registration" - - # verify_message - message sent to users to verify their account - verify_message: | - Hi, - - You have requested to register the account {account}. - - Your verification code is {auth_code} - - Please type "/quote REG VERIFY {account} {auth_code}" to complete registration - - Thank you, - {network_name} -# Roles define the capabilities an oper may have, as well as role-specific -# metadata. - - # mammon capability names: - # oper:local_kill allows /KILLing local users - # oper:global_kill allows /KILLing local and remote users - # oper:routing allows remote SQUIT and CONNECT - # oper:kline allows KLINE and DLINE - # oper:unkline allows UNKLINE and UNDLINE - # oper:remote_ban allows remote klines - # oper:rehash allows REHASH of server config - # oper:die allows DIE and RESTART - -roles: - # name - the name of the privilege set - "local_op": - # capabilities - a list of qualified capability names - capabilities: - - oper:local_kill - - oper:kline - - oper:unkline - - metadata:set_local - - # metakeys_get - metadata keys this role can view - metakeys_get: - # - spammer_probability - - # title - metadata identifying the specific role - title: "IRC Operator" - - # name - the name of the privilege set - "global_op": - # extends - inherets this role's capabilities - extends: "local_op" - - # capabilities - a list of qualified capability names - capabilities: - - oper:global_kill - - oper:remote_ban - - metadata:set_global - - # title - metadata identifying the specific role - title: "IRC Operator" - - # name - the name of the privilege set - "network_admin": - # capabilities - a list of qualified capability names - capabilities: - - oper:global_kill - - oper:routing - - # title - metadata identifying the specific role - title: "Network Administrator" - - # name - the name of the privilege set - "server_admin": - # extends - inherets this role's capabilities - extends: "local_op" - - # capabilities - a list of qualified capability names - capabilities: - - oper:rehash - - oper:die - - # title - metadata identifying the specific role - title: "Server Administrator" - - # example metadata-specific roles - # name - the name of the privilege set - "spam_detection_bot": - # capabilities - a list of qualified capability names - capabilities: - - metadata:set_global - - # metakeys_access - metadata keys this role can view and set - metakeys_access: - - spammer_probability - - # title - metadata identifying the specific role - title: "Spam Detection Bot" - - -# Metadata defines the metadata users are allowed to set for themselves -metadata: - # limit - max number of metadata each target is allowed to have - # comment out to remove limit - limit: 20 - - # whitelist - if defined, a list of lowercase keys that are checked when users set data - whitelist: - - avatar - - info - - source - - url - - version - - # restricted_keys - keys that unprived users cannot see / edit, and require a - # special entry in a ROLE to see / edit - restricted_keys: - - away - # - spammer_probability - - -# Monitor defines the monitoring users are allowed to do on other users -monitor: - # limit - max number of 'monitors' each target is allowed to have - # comment out to remove limit - limit: 20 - - -# Operator credentials allow a user to transition from a typical user role -# to a privileged role. -opers: - # name - the name of the operator - "nobody": - # password - the plaintext oper password - # password: "nothing" - - # hostmask - if defined, the hostmask the oper must connect from - hostmask: "*@localhost" - - # role - the role that the credentials allow transition to - role: "local_op" - - # name - the name of the operator - "somebody": - # password - the hashed oper password - # created by: mammond --mkpasswd - password: "$6$rounds=100000$KkEHFBhWHV3BHCCS$YuOdlikJHdeIv2YpwvyLAtYCslDlsnUUnrfeKJiBh4SeVhkSU6pQqHWWDjr6lnalkkf1KLDD1wgSQH5AObILQ1" - - # hash - the hashing algorithm to use - hash: "sha512_crypt" - - # hostmask - if defined, the hostmask the oper must connect from - hostmask: "somebody!*@localhost" - - # role - the role that the credentials allow transition to - role: "local_op" - - -# The extensions section is a list of extension modules to load. -extensions: -- mammon.ext.rfc1459.42 -- mammon.ext.rfc1459.ident -- mammon.ext.ircv3.account_notify -- mammon.ext.ircv3.server_time -- mammon.ext.ircv3.echo_message -- mammon.ext.ircv3.register -- mammon.ext.ircv3.sasl -- mammon.ext.misc.nopost \ No newline at end of file -- cgit v1.2.3 From 04d28f968b227067e77e365d317fc251d3c965f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 19 Apr 2016 02:43:26 +0200 Subject: Forward the topic authors, handle the author from 333 messages fix #2 --- tests/end_to_end/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index b027bd9..f9d0447 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -409,7 +409,7 @@ if __name__ == '__main__': # First user sets the topic partial(send_stanza, "TOPIC TEST"), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC TEST']"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC TEST']"), # Second user joins partial(send_stanza, @@ -426,7 +426,7 @@ if __name__ == '__main__': ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", "/presence/muc_user:x/muc_user:status[@code='110']") ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[text()='TOPIC TEST']"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/subject[text()='TOPIC TEST']"), ]), ) -- cgit v1.2.3 From 04facc515ce2bc32249fa4cf2cdadb75a8bcb3ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 20 Apr 2016 22:57:27 +0200 Subject: Provide a conf for charybdis --- tests/end_to_end/__main__.py | 2 +- tests/end_to_end/ircd.conf | 510 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 tests/end_to_end/ircd.conf (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index f9d0447..58165d9 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -184,7 +184,7 @@ class BiboumiRunner(ProcessRunner): class IrcServerRunner(ProcessRunner): def __init__(self): super().__init__() - self.create = asyncio.create_subprocess_exec("charybdis", "-foreground", + self.create = asyncio.create_subprocess_exec("charybdis", "-foreground", "-configfile", "../tests/end_to_end/ircd.conf", stderr=asyncio.subprocess.PIPE) diff --git a/tests/end_to_end/ircd.conf b/tests/end_to_end/ircd.conf new file mode 100644 index 0000000..7edb3a8 --- /dev/null +++ b/tests/end_to_end/ircd.conf @@ -0,0 +1,510 @@ +/* doc/ircd.conf.example - brief example configuration file + * + * Copyright (C) 2000-2002 Hybrid Development Team + * Copyright (C) 2002-2005 ircd-ratbox development team + * Copyright (C) 2005-2006 charybdis development team + * + * See reference.conf for more information. + */ + +/* Extensions */ +#loadmodule "extensions/chm_operonly_compat"; +#loadmodule "extensions/chm_quietunreg_compat"; +#loadmodule "extensions/chm_sslonly_compat"; +#loadmodule "extensions/chm_operpeace"; +#loadmodule "extensions/createauthonly"; +#loadmodule "extensions/extb_account"; +#loadmodule "extensions/extb_canjoin"; +#loadmodule "extensions/extb_channel"; +#loadmodule "extensions/extb_combi"; +#loadmodule "extensions/extb_extgecos"; +#loadmodule "extensions/extb_hostmask"; +#loadmodule "extensions/extb_oper"; +#loadmodule "extensions/extb_realname"; +#loadmodule "extensions/extb_server"; +#loadmodule "extensions/extb_ssl"; +#loadmodule "extensions/extb_usermode"; +#loadmodule "extensions/hurt"; +#loadmodule "extensions/m_extendchans"; +#loadmodule "extensions/m_findforwards"; +#loadmodule "extensions/m_identify"; +#loadmodule "extensions/m_locops"; +#loadmodule "extensions/no_oper_invis"; +#loadmodule "extensions/sno_farconnect"; +#loadmodule "extensions/sno_globalkline"; +#loadmodule "extensions/sno_globalnickchange"; +#loadmodule "extensions/sno_globaloper"; +#loadmodule "extensions/sno_whois"; +#loadmodule "extensions/override"; +#loadmodule "extensions/no_kill_services"; + +/* + * IP cloaking extensions: use ip_cloaking_4.0 + * if you're linking 3.2 and later, otherwise use + * ip_cloaking, for compatibility with older 3.x + * releases. + */ + +#loadmodule "extensions/ip_cloaking_4.0"; +#loadmodule "extensions/ip_cloaking"; + +serverinfo { + name = "irc.localhost"; + sid = "42X"; + description = "charybdis test server"; + network_name = "StaticBox"; + + /* On multi-homed hosts you may need the following. These define + * the addresses we connect from to other servers. */ + /* for IPv4 */ + #vhost = "192.0.2.6"; + /* for IPv6 */ + #vhost6 = "2001:db8:2::6"; + + /* ssl_private_key: our ssl private key */ + ssl_private_key = "etc/ssl.key"; + + /* ssl_cert: certificate for our ssl server */ + ssl_cert = "etc/ssl.pem"; + + /* ssl_dh_params: DH parameters, generate with openssl dhparam -out dh.pem 2048 + * In general, the DH parameters size should be the same as your key's size. + * However it has been reported that some clients have broken TLS implementations which may + * choke on keysizes larger than 2048-bit, so we would recommend using 2048-bit DH parameters + * for now if your keys are larger than 2048-bit. + */ + ssl_dh_params = "etc/dh.pem"; + + /* ssld_count: number of ssld processes you want to start, if you + * have a really busy server, using N-1 where N is the number of + * cpu/cpu cores you have might be useful. A number greater than one + * can also be useful in case of bugs in ssld and because ssld needs + * two file descriptors per SSL connection. + */ + ssld_count = 1; + + /* default max clients: the default maximum number of clients + * allowed to connect. This can be changed once ircd has started by + * issuing: + * /quote set maxclients + */ + default_max_clients = 1024; + + /* nicklen: enforced nickname length (for this server only; must not + * be longer than the maximum length set while building). + */ + nicklen = 30; +}; + +admin { + name = "Lazy admin (lazya)"; + description = "StaticBox client server"; + email = "nobody@127.0.0.1"; +}; + +log { + fname_userlog = "logs/userlog"; + #fname_fuserlog = "logs/fuserlog"; + fname_operlog = "logs/operlog"; + #fname_foperlog = "logs/foperlog"; + fname_serverlog = "logs/serverlog"; + #fname_klinelog = "logs/klinelog"; + fname_killlog = "logs/killlog"; + fname_operspylog = "logs/operspylog"; + #fname_ioerrorlog = "logs/ioerror"; +}; + +/* class {} blocks MUST be specified before anything that uses them. That + * means they must be defined before auth {} and before connect {}. + */ +class "users" { + ping_time = 2 minutes; + number_per_ident = 10; + number_per_ip = 10; + number_per_ip_global = 50; + cidr_ipv4_bitlen = 24; + cidr_ipv6_bitlen = 64; + number_per_cidr = 200; + max_number = 3000; + sendq = 400 kbytes; +}; + +class "opers" { + ping_time = 5 minutes; + number_per_ip = 10; + max_number = 1000; + sendq = 1 megabyte; +}; + +class "server" { + ping_time = 5 minutes; + connectfreq = 5 minutes; + max_number = 1; + sendq = 4 megabytes; +}; + +listen { + /* defer_accept: wait for clients to send IRC handshake data before + * accepting them. if you intend to use software which depends on the + * server replying first, such as BOPM, you should disable this feature. + * otherwise, you probably want to leave it on. + */ + defer_accept = yes; + + /* If you want to listen on a specific IP only, specify host. + * host definitions apply only to the following port line. + */ + #host = "192.0.2.6"; + port = 5000, 6665 .. 6669; + # sslport = 6697; + + /* Listen on IPv6 (if you used host= above). */ + #host = "2001:db8:2::6"; + #port = 5000, 6665 .. 6669; + #sslport = 9999; +}; + +/* auth {}: allow users to connect to the ircd (OLD I:) + * auth {} blocks MUST be specified in order of precedence. The first one + * that matches a user will be used. So place spoofs first, then specials, + * then general access, then restricted. + */ +auth { + /* user: the user@host allowed to connect. Multiple IPv4/IPv6 user + * lines are permitted per auth block. This is matched against the + * hostname and IP address (using :: shortening for IPv6 and + * prepending a 0 if it starts with a colon) and can also use CIDR + * masks. + */ + user = "*@198.51.100.0/24"; + user = "*test@2001:db8:1:*"; + + /* password: an optional password that is required to use this block. + * By default this is not encrypted, specify the flag "encrypted" in + * flags = ...; below if it is. + */ + password = "letmein"; + + /* spoof: fake the users user@host to be be this. You may either + * specify a host or a user@host to spoof to. This is free-form, + * just do everyone a favour and dont abuse it. (OLD I: = flag) + */ + spoof = "I.still.hate.packets"; + + /* Possible flags in auth: + * + * encrypted | password is encrypted with mkpasswd + * spoof_notice | give a notice when spoofing hosts + * exceed_limit (old > flag) | allow user to exceed class user limits + * kline_exempt (old ^ flag) | exempt this user from k/g/xlines, + * | dnsbls, and proxies + * proxy_exempt | exempt this user from proxies + * dnsbl_exempt | exempt this user from dnsbls + * spambot_exempt | exempt this user from spambot checks + * shide_exempt | exempt this user from serverhiding + * jupe_exempt | exempt this user from generating + * warnings joining juped channels + * resv_exempt | exempt this user from resvs + * flood_exempt | exempt this user from flood limits + * USE WITH CAUTION. + * no_tilde (old - flag) | don't prefix ~ to username if no ident + * need_ident (old + flag) | require ident for user in this class + * need_ssl | require SSL/TLS for user in this class + * need_sasl | require SASL id for user in this class + */ + flags = kline_exempt, exceed_limit; + + /* class: the class the user is placed in */ + class = "opers"; +}; + +auth { + user = "*@*"; + class = "users"; +}; + +/* privset {} blocks MUST be specified before anything that uses them. That + * means they must be defined before operator {}. + */ +privset "local_op" { + privs = oper:local_kill, oper:operwall; +}; + +privset "server_bot" { + extends = "local_op"; + privs = oper:kline, oper:remoteban, snomask:nick_changes; +}; + +privset "global_op" { + extends = "local_op"; + privs = oper:global_kill, oper:routing, oper:kline, oper:unkline, oper:xline, + oper:resv, oper:mass_notice, oper:remoteban; +}; + +privset "admin" { + extends = "global_op"; + privs = oper:admin, oper:die, oper:rehash, oper:spy, oper:grant; +}; + +operator "god" { + /* name: the name of the oper must go above */ + + /* user: the user@host required for this operator. CIDR *is* + * supported now. auth{} spoofs work here, other spoofs do not. + * multiple user="" lines are supported. + */ + user = "*god@127.0.0.1"; + + /* password: the password required to oper. Unless ~encrypted is + * contained in flags = ...; this will need to be encrypted using + * mkpasswd, MD5 is supported + */ + password = "etcnjl8juSU1E"; + + /* rsa key: the public key for this oper when using Challenge. + * A password should not be defined when this is used, see + * doc/challenge.txt for more information. + */ + #rsa_public_key_file = "/usr/local/ircd/etc/oper.pub"; + + /* umodes: the specific umodes this oper gets when they oper. + * If this is specified an oper will not be given oper_umodes + * These are described above oper_only_umodes in general {}; + */ + #umodes = locops, servnotice, operwall, wallop; + + /* fingerprint: if specified, the oper's client certificate + * fingerprint will be checked against the specified fingerprint + * below. + */ + #fingerprint = "c77106576abf7f9f90cca0f63874a60f2e40a64b"; + + /* snomask: specific server notice mask on oper up. + * If this is specified an oper will not be given oper_snomask. + */ + snomask = "+Zbfkrsuy"; + + /* flags: misc options for the operator. You may prefix an option + * with ~ to disable it, e.g. ~encrypted. + * + * Default flags are encrypted. + * + * Available options: + * + * encrypted: the password above is encrypted [DEFAULT] + * need_ssl: must be using SSL/TLS to oper up + */ + flags = encrypted; + + /* privset: privileges set to grant */ + privset = "admin"; +}; + +connect "irc.uplink.com" { + host = "203.0.113.3"; + send_password = "password"; + accept_password = "anotherpassword"; + port = 6666; + hub_mask = "*"; + class = "server"; + flags = compressed, topicburst; + + #fingerprint = "c77106576abf7f9f90cca0f63874a60f2e40a64b"; + + /* If the connection is IPv6, uncomment below. + * Use 0::1, not ::1, for IPv6 localhost. */ + #aftype = ipv6; +}; + +connect "ssl.uplink.com" { + host = "203.0.113.129"; + send_password = "password"; + accept_password = "anotherpassword"; + port = 9999; + hub_mask = "*"; + class = "server"; + flags = ssl, topicburst; +}; + +service { + name = "services.int"; +}; + +cluster { + name = "*"; + flags = kline, tkline, unkline, xline, txline, unxline, resv, tresv, unresv; +}; + +shared { + oper = "*@*", "*"; + flags = all, rehash; +}; + +/* exempt {}: IPs that are exempt from Dlines and rejectcache. (OLD d:) */ +exempt { + ip = "127.0.0.1"; +}; + +channel { + use_invex = yes; + use_except = yes; + use_forward = yes; + use_knock = yes; + knock_delay = 5 minutes; + knock_delay_channel = 1 minute; + max_chans_per_user = 15; + max_chans_per_user_large = 60; + max_bans = 100; + max_bans_large = 500; + default_split_user_count = 0; + default_split_server_count = 0; + no_create_on_split = no; + no_join_on_split = no; + burst_topicwho = yes; + kick_on_split_riding = no; + only_ascii_channels = no; + resv_forcepart = yes; + channel_target_change = yes; + disable_local_channels = no; + autochanmodes = "+nt"; + displayed_usercount = 3; + strip_topic_colors = no; +}; + +serverhide { + flatten_links = yes; + links_delay = 5 minutes; + hidden = no; + disable_hidden = no; +}; + +alias "NickServ" { + target = "NickServ"; +}; + +alias "ChanServ" { + target = "ChanServ"; +}; + +alias "OperServ" { + target = "OperServ"; +}; + +alias "MemoServ" { + target = "MemoServ"; +}; + +alias "NS" { + target = "NickServ"; +}; + +alias "CS" { + target = "ChanServ"; +}; + +alias "OS" { + target = "OperServ"; +}; + +alias "MS" { + target = "MemoServ"; +}; + +general { + hide_error_messages = opers; + hide_spoof_ips = yes; + + /* + * default_umodes: umodes to enable on connect. + * If you have enabled the new ip_cloaking_4.0 module, and you want + * to make use of it, add +x to this option, i.e.: + * default_umodes = "+ix"; + * + * If you have enabled the old ip_cloaking module, and you want + * to make use of it, add +h to this option, i.e.: + * default_umodes = "+ih"; + */ + default_umodes = "+i"; + + default_operstring = "is an IRC Operator"; + default_adminstring = "is a Server Administrator"; + servicestring = "is a Network Service"; + + /* + * Nick of the network's SASL agent. Used to check whether services are here, + * SASL credentials are only sent to its server. Needs to be a service. + * + * Defaults to SaslServ if unspecified. + */ + sasl_service = "SaslServ"; + disable_fake_channels = no; + tkline_expire_notices = no; + default_floodcount = 10; + failed_oper_notice = yes; + dots_in_ident=2; + min_nonwildcard = 4; + min_nonwildcard_simple = 3; + max_accept = 100; + max_monitor = 100; + anti_nick_flood = yes; + max_nick_time = 20 seconds; + max_nick_changes = 5; + anti_spam_exit_message_time = 5 minutes; + ts_warn_delta = 30 seconds; + ts_max_delta = 5 minutes; + client_exit = yes; + collision_fnc = yes; + resv_fnc = yes; + global_snotices = yes; + dline_with_reason = yes; + kline_delay = 0 seconds; + kline_with_reason = yes; + kline_reason = "K-Lined"; + identify_service = "NickServ@services.int"; + identify_command = "IDENTIFY"; + non_redundant_klines = yes; + warn_no_nline = yes; + use_propagated_bans = yes; + stats_e_disabled = yes; + stats_c_oper_only=no; + stats_h_oper_only=no; + stats_y_oper_only=no; + stats_o_oper_only=yes; + stats_P_oper_only=no; + stats_i_oper_only=masked; + stats_k_oper_only=masked; + map_oper_only = no; + operspy_admin_only = no; + operspy_dont_care_user_info = no; + caller_id_wait = 1 minute; + pace_wait_simple = 1 second; + pace_wait = 10 seconds; + short_motd = no; + ping_cookie = no; + connect_timeout = 30 seconds; + default_ident_timeout = 5; + disable_auth = no; + no_oper_flood = yes; + max_targets = 4; + client_flood_max_lines = 20; + use_whois_actually = no; + oper_only_umodes = operwall, locops, servnotice; + oper_umodes = locops, servnotice, operwall, wallop; + oper_snomask = "+s"; + burst_away = yes; + nick_delay = 0 seconds; # 15 minutes if you want to enable this + reject_ban_time = 1 minute; + reject_after_count = 3; + reject_duration = 5 minutes; + throttle_duration = 60; + throttle_count = 4; + max_ratelimit_tokens = 30; + away_interval = 30; + certfp_method = sha1; + hide_opers_in_whois = no; +}; + +modules { + path = "modules"; + path = "modules/autoload"; +}; -- cgit v1.2.3 From 71b3f8f50b2831142ec50c725f07333ab948c169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 21 Apr 2016 10:14:17 +0200 Subject: Correct charybdis config file path --- tests/end_to_end/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 58165d9..2efbf24 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -9,6 +9,7 @@ import atexit import lxml.etree import sys import io +import os from functools import partial from slixmpp.xmlstream.matcher.base import MatcherBase @@ -184,7 +185,7 @@ class BiboumiRunner(ProcessRunner): class IrcServerRunner(ProcessRunner): def __init__(self): super().__init__() - self.create = asyncio.create_subprocess_exec("charybdis", "-foreground", "-configfile", "../tests/end_to_end/ircd.conf", + self.create = asyncio.create_subprocess_exec("charybdis", "-foreground", "-configfile", os.getcwd() + "/../tests/end_to_end/ircd.conf", stderr=asyncio.subprocess.PIPE) -- cgit v1.2.3 From 3ddca86851786b5d2bb24a29ba0f73567e0c2bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 21 Apr 2016 17:48:39 +0200 Subject: Debug and exit if IRC server startup fails --- tests/end_to_end/__main__.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 2efbf24..0eba231 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -438,6 +438,10 @@ if __name__ == '__main__': asyncio.get_event_loop().run_until_complete(irc.start()) while True: res = asyncio.get_event_loop().run_until_complete(irc.process.stderr.readline()) + print(res) + if not res: + print("IRC server failed to start, exiting") + sys.exit(1) if b"now running in foreground mode" in res: break print("irc server started.") -- cgit v1.2.3 From 327c821f4630b283e08a0b0875a6e7073af2423d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 10:23:25 +0200 Subject: Write irc output into a file --- tests/end_to_end/__main__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 0eba231..1043a95 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -433,14 +433,15 @@ if __name__ == '__main__': failures = 0 + irc_output = open("irc_output.txt", "w") irc = IrcServerRunner() print("Starting irc server…") asyncio.get_event_loop().run_until_complete(irc.start()) while True: res = asyncio.get_event_loop().run_until_complete(irc.process.stderr.readline()) - print(res) + irc_output.write(res.decode()) if not res: - print("IRC server failed to start, exiting") + print("IRC server failed to start, see irc_output.txt for more details. Exiting…") sys.exit(1) if b"now running in foreground mode" in res: break -- cgit v1.2.3 From ea35a12d5b20c50a1405a6eed5149f44aee59a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 11:57:32 +0200 Subject: Each e2e scenario can provide its own config name --- tests/end_to_end/__main__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 1043a95..c0c8542 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -128,13 +128,14 @@ class Scenario: etc """ - def __init__(self, name, steps): + def __init__(self, name, steps, conf="basic"): """ Steps is a list of 2-tuple: [(action, answer), (action, answer)] """ self.name = name self.steps = [] + self.conf = conf for elem in steps: if isinstance(elem, collections.Iterable): for step in elem: @@ -226,7 +227,7 @@ class BiboumiTest: filename=output_filename) with open("test.conf", "w") as fd: - fd.write(confs['basic']) + fd.write(confs[scenario.conf]) # Start the XMPP component and biboumi biboumi = BiboumiRunner(scenario.name, with_valgrind) @@ -254,7 +255,8 @@ class BiboumiTest: return not failed -confs = {'basic': +confs = { +'basic': """hostname=biboumi.localhost password=coucou db_name=biboumi.sqlite -- cgit v1.2.3 From 439ea262237f6c33342bf62c05de7f305a63ff2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 11:55:08 +0200 Subject: Add a basic fixed_server e2e scenario --- tests/end_to_end/__main__.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index c0c8542..ef8c7aa 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -260,11 +260,20 @@ confs = { """hostname=biboumi.localhost password=coucou db_name=biboumi.sqlite -port=8811"""} +port=8811""", + +'fixed_server': +"""hostname=biboumi.localhost +password=coucou +db_name=biboumi.sqlite +port=8811 +fixed_irc_server=irc.localhost +"""} common_replacements = { 'irc_server_one': 'irc.localhost@biboumi.localhost', 'irc_host_one': 'irc.localhost', + 'biboumi_host': 'biboumi.localhost', 'resource_one': 'resource1', 'nick_one': 'Nick', 'jid_one': 'first@example.com', @@ -431,6 +440,21 @@ if __name__ == '__main__': ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/subject[text()='TOPIC TEST']"), ]), + Scenario("channel_basic_join_on_fixed_irc_server", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + "/message/body[text()='Mode #zgeg [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#zgeg@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#zgeg@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), + ], conf='fixed_server' + ), ) failures = 0 -- cgit v1.2.3 From 3fe55ab0b3935339e77c86c55cd81434c29edec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 11:55:30 +0200 Subject: Remove an unused variable --- tests/end_to_end/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index ef8c7aa..3811d2b 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -483,7 +483,7 @@ if __name__ == '__main__': print("Waiting for irc server to exit…") irc.stop() - code = asyncio.get_event_loop().run_until_complete(irc.wait()) + asyncio.get_event_loop().run_until_complete(irc.wait()) if failures: print("%d test%s failed, please fix %s." % (failures, 's' if failures > 1 else '', -- cgit v1.2.3 From 376b28172b94d3950d4a20d447a9b9e49dad9ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 15:02:28 +0200 Subject: tests: Split the connection sequence into two We can now insert steps between the two parts --- tests/end_to_end/__main__.py | 46 ++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 17 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 3811d2b..d1fbb29 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -287,51 +287,59 @@ def handshake_sequence(): partial(send_stanza, "")) -def connection_sequence(irc_host, jid): +def connection_begin_sequence(irc_host, jid): jid = jid.format_map(common_replacements) xpath = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[text()='%s']" xpath_re = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[re:test(text(), '%s')]" return ( partial(expect_stanza, - xpath % ('Connecting to %s:6697 (encrypted)' % irc_host)), + xpath % ('Connecting to %s:6697 (encrypted)' % irc_host)), partial(expect_stanza, xpath % 'Connection failed: Connection refused'), partial(expect_stanza, - xpath % ('Connecting to %s:6670 (encrypted)' % irc_host)), + xpath % ('Connecting to %s:6670 (encrypted)' % irc_host)), partial(expect_stanza, xpath % 'Connection failed: Connection refused'), partial(expect_stanza, - xpath % ('Connecting to %s:6667 (not encrypted)' % irc_host)), + xpath % ('Connecting to %s:6667 (not encrypted)' % irc_host)), partial(expect_stanza, xpath % 'Connected to IRC server.'), # These two messages can be receive in any order partial(expect_stanza, - xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)), + xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)), + xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)), # These three messages can be received in any order partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), + ) + + +def connection_end_sequence(irc_host, jid): + jid = jid.format_map(common_replacements) + xpath = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[text()='%s']" + xpath_re = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[re:test(text(), '%s')]" + return ( partial(expect_stanza, - xpath_re % (r'^%s: Your host is .*$' % irc_host)), + xpath_re % (r'^%s: Your host is .*$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: This server was created .*$' % irc_host)), + xpath_re % (r'^%s: This server was created .*$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: There are \d+ users and \d+ invisible on \d+ servers$' % irc_host)), + xpath_re % (r'^%s: There are \d+ users and \d+ invisible on \d+ servers$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: \d+ channels formed$' % irc_host), optional=True), + xpath_re % (r'^%s: \d+ channels formed$' % irc_host), optional=True), partial(expect_stanza, - xpath_re % (r'^%s: I have \d+ clients and \d+ servers$' % irc_host)), + xpath_re % (r'^%s: I have \d+ clients and \d+ servers$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: \d+ \d+ Current local users \d+, max \d+$' % irc_host)), + xpath_re % (r'^%s: \d+ \d+ Current local users \d+, max \d+$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: \d+ \d+ Current global users \d+, max \d+$' % irc_host)), + xpath_re % (r'^%s: \d+ \d+ Current global users \d+, max \d+$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: Highest connection count: \d+ \(\d+ clients\) \(\d+ connections received\)$' % irc_host)), + xpath_re % (r'^%s: Highest connection count: \d+ \(\d+ clients\) \(\d+ connections received\)$' % irc_host)), partial(expect_stanza, xpath % "- This is charybdis MOTD you might replace it, but if not your friends will\n- laugh at you.\n"), partial(expect_stanza, @@ -339,6 +347,10 @@ def connection_sequence(irc_host, jid): ) +def connection_sequence(irc_host, jid): + return connection_begin_sequence(irc_host, jid) + connection_end_sequence(irc_host, jid) + + if __name__ == '__main__': atexit.register(asyncio.get_event_loop().close) -- cgit v1.2.3 From 254249dd7848020fea48dab5dd3405ae56c85b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 15:03:29 +0200 Subject: Test the the virtual channel --- tests/end_to_end/__main__.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index d1fbb29..4c07661 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -383,6 +383,22 @@ if __name__ == '__main__': ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), ]), + Scenario("virtual_channel_join", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_begin_sequence("irc.localhost", '{jid_one}/{resource_one}'), + # partial(expect_stanza, + # "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='%{irc_server_one}'][@type='groupchat']/subject[re:test(text(), '^This is a virtual channel.*$')]"), + # partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + connection_end_sequence("irc.localhost", '{jid_one}/{resource_one}'), + ]), Scenario("channel_join_with_two_users", [ handshake_sequence(), -- cgit v1.2.3 From b98402c972e1d27656af2303754d233a23868fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 15:06:45 +0200 Subject: Remove a forgotten comment [ci skip] --- tests/end_to_end/__main__.py | 1 - 1 file changed, 1 deletion(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 4c07661..3b2e9ee 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -396,7 +396,6 @@ if __name__ == '__main__': "/presence/muc_user:x/muc_user:status[@code='110']") ), partial(expect_stanza, "/message[@from='%{irc_server_one}'][@type='groupchat']/subject[re:test(text(), '^This is a virtual channel.*$')]"), - # partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), connection_end_sequence("irc.localhost", '{jid_one}/{resource_one}'), ]), Scenario("channel_join_with_two_users", -- cgit v1.2.3 From d452c2ff1897d5f5c519d6a821598864d8207933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 25 Apr 2016 10:28:53 +0200 Subject: Add e2e tests for the ad-hoc listing on the server jid --- tests/end_to_end/__main__.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 3b2e9ee..4f8e35b 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -108,7 +108,8 @@ def check_xpath(xpaths, stanza): for xpath in xpaths: tree = lxml.etree.parse(io.StringIO(str(stanza))) matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions', - 'muc_user': 'http://jabber.org/protocol/muc#user'}) + 'muc_user': 'http://jabber.org/protocol/muc#user', + 'disco_items': 'http://jabber.org/protocol/disco#items'}) if not matched: raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath)) @@ -260,7 +261,8 @@ confs = { """hostname=biboumi.localhost password=coucou db_name=biboumi.sqlite -port=8811""", +port=8811 +admin=admin@example.com""", 'fixed_server': """hostname=biboumi.localhost @@ -268,6 +270,7 @@ password=coucou db_name=biboumi.sqlite port=8811 fixed_irc_server=irc.localhost +admin=admin@example.com """} common_replacements = { @@ -278,6 +281,7 @@ common_replacements = { 'nick_one': 'Nick', 'jid_one': 'first@example.com', 'jid_two': 'second@example.com', + 'jid_admin': 'admin@example.com', 'nick_two': 'Bobby', } @@ -482,6 +486,34 @@ if __name__ == '__main__': partial(expect_stanza, "/message[@from='#zgeg@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), ], conf='fixed_server' ), + Scenario("list_adhoc", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[3]")), + ]), + Scenario("list_admin_adhoc", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[5]")), + ]), + Scenario("list_adhoc_fixed_server", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[3]")), + ], conf='fixed_server'), + Scenario("list_admin_adhoc_fixed_server", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[5]")), + ], conf='fixed_server'), ) failures = 0 -- cgit v1.2.3 From 82e0cf99b0dd0ba48f31060656d03a9586b939c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 25 Apr 2016 10:29:55 +0200 Subject: Trivial cleanup --- tests/end_to_end/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 4f8e35b..786c4e9 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -240,6 +240,8 @@ class BiboumiTest: xmpp.process() code = asyncio.get_event_loop().run_until_complete(biboumi.wait()) + xmpp.biboumi = None + scenario.steps.clear() failed = False if not xmpp.failed: if code != self.expected_code: @@ -393,8 +395,6 @@ if __name__ == '__main__': partial(send_stanza, ""), connection_begin_sequence("irc.localhost", '{jid_one}/{resource_one}'), - # partial(expect_stanza, - # "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), partial(expect_stanza, ("/presence[@to='{jid_one}/{resource_one}'][@from='%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", "/presence/muc_user:x/muc_user:status[@code='110']") -- cgit v1.2.3 From 1e56c59e8241dbfc6a2526c371cc2e894f9d0f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 25 Apr 2016 11:29:52 +0200 Subject: Include the Configure ad-hoc command on biboumi's JID for fixed_irc_server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because a jid like “freenode.example.org” is both the JID for the configured IRC server, and biboumi’s JID. fix #3175 --- tests/end_to_end/__main__.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 786c4e9..fbfd3ce 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -514,6 +514,31 @@ if __name__ == '__main__': partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", "/iq/disco_items:query/disco_items:item[5]")), ], conf='fixed_server'), + + + Scenario("list_adhoc_irc", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[1]")), + ]), + Scenario("list_adhoc_irc_fixed_server", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[4]")), + ], conf='fixed_server'), + Scenario("list_admin_adhoc_irc_fixed_server", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[6]")), + ], conf='fixed_server'), + + ) failures = 0 -- cgit v1.2.3 From 98517e9a7b7873c15a5bde081a29be92c3dcfac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 27 Apr 2016 10:22:43 +0200 Subject: Change the name of the DB in the e2e tests --- tests/end_to_end/__main__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index fbfd3ce..147e132 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -262,14 +262,14 @@ confs = { 'basic': """hostname=biboumi.localhost password=coucou -db_name=biboumi.sqlite +db_name=e2e_test.sqlite port=8811 admin=admin@example.com""", 'fixed_server': """hostname=biboumi.localhost password=coucou -db_name=biboumi.sqlite +db_name=e2e_test.sqlite port=8811 fixed_irc_server=irc.localhost admin=admin@example.com @@ -537,8 +537,6 @@ if __name__ == '__main__': partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", "/iq/disco_items:query/disco_items:item[6]")), ], conf='fixed_server'), - - ) failures = 0 -- cgit v1.2.3 From 0b1bf92479f7086f04404538b6813106ce7733bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 13 May 2016 15:14:29 +0200 Subject: e2e: possibility to extract a value from a stanza and reuse it in send_stanza --- tests/end_to_end/__main__.py | 48 +++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 12 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 147e132..815093e 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -67,6 +67,8 @@ class XMPPComponent(slixmpp.BaseXMPP): self.failed = False self.accepting_server = None + self.saved_values = {} + def error(self, message): print("Failure: %s" % (message,)) self.scenario.steps = [] @@ -104,19 +106,27 @@ class XMPPComponent(slixmpp.BaseXMPP): pass -def check_xpath(xpaths, stanza): - for xpath in xpaths: - tree = lxml.etree.parse(io.StringIO(str(stanza))) - matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions', - 'muc_user': 'http://jabber.org/protocol/muc#user', - 'disco_items': 'http://jabber.org/protocol/disco#items'}) +def match(stanza, xpath): + tree = lxml.etree.parse(io.StringIO(str(stanza))) + matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions', + 'muc_user': 'http://jabber.org/protocol/muc#user', + 'disco_items': 'http://jabber.org/protocol/disco#items', + 'dataform': 'jabber:x:data'}) + return matched + + +def check_xpath(xpaths, xmpp, after, stanza): + for i, xpath in enumerate(xpaths): + matched = match(stanza, xpath) if not matched: raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath)) + if after: + after(stanza, xmpp) -def check_xpath_optional(xpaths, stanza): +def check_xpath_optional(xpaths, xmpp, after, stanza): try: - check_xpath(xpaths, stanza) + check_xpath(xpaths, xmpp, after, stanza) except StanzaError: raise SkipStepError() @@ -149,6 +159,7 @@ class ProcessRunner: def __init__(self): self.process = None self.signal_sent = False + self.create = None @asyncio.coroutine def start(self): @@ -192,16 +203,18 @@ class IrcServerRunner(ProcessRunner): def send_stanza(stanza, xmpp, biboumi): - xmpp.send_raw(stanza.format_map(common_replacements)) + replacements = common_replacements + replacements.update(xmpp.saved_values) + xmpp.send_raw(stanza.format_map(replacements)) asyncio.get_event_loop().call_soon(xmpp.run_scenario) -def expect_stanza(xpaths, xmpp, biboumi, optional=False): +def expect_stanza(xpaths, xmpp, biboumi, optional=False, after=None): check_func = check_xpath if not optional else check_xpath_optional if isinstance(xpaths, str): - xmpp.stanza_checker = partial(check_func, [xpaths.format_map(common_replacements)]) + xmpp.stanza_checker = partial(check_func, [xpaths.format_map(common_replacements)], xmpp, after) elif isinstance(xpaths, tuple): - xmpp.stanza_checker = partial(check_func, [xpath.format_map(common_replacements) for xpath in xpaths]) + xmpp.stanza_checker = partial(check_func, [xpath.format_map(common_replacements) for xpath in xpaths], xmpp, after) else: print("Warning, from argument type passed to expect_stanza: %s" % (type(xpaths))) @@ -252,6 +265,8 @@ class BiboumiTest: else: failed = True + xmpp.saved_values.clear() + if xmpp.server: xmpp.accepting_server.close() @@ -357,6 +372,15 @@ def connection_sequence(irc_host, jid): return connection_begin_sequence(irc_host, jid) + connection_end_sequence(irc_host, jid) +def extract_attribute(xpath, name, stanza): + matched = match(stanza, xpath) + return matched[0].get(name) + + +def save_value(name, func, stanza, xmpp): + xmpp.saved_values[name] = func(stanza) + + if __name__ == '__main__': atexit.register(asyncio.get_event_loop().close) -- cgit v1.2.3 From 8f80b9c53011561f8eee9875504e526c174317a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 13 May 2016 15:15:10 +0200 Subject: Test the execution of the hello command --- tests/end_to_end/__main__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 815093e..706d177 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -111,6 +111,7 @@ def match(stanza, xpath): matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions', 'muc_user': 'http://jabber.org/protocol/muc#user', 'disco_items': 'http://jabber.org/protocol/disco#items', + 'commands': 'http://jabber.org/protocol/commands', 'dataform': 'jabber:x:data'}) return matched @@ -561,6 +562,23 @@ if __name__ == '__main__': partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", "/iq/disco_items:query/disco_items:item[6]")), ], conf='fixed_server'), + + Scenario("execute_hello_adhoc_command", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='hello'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure your name.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Please provide your name.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single']/dataform:required", + "/iq/commands:command/commands:actions/commands:next", + ), + after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='hello']", "sessionid")) + + ), + partial(send_stanza, "COUCOU"), + partial(expect_stanza, "/iq[@type='result']/commands:command[@node='hello'][@status='completed']/commands:note[@type='info'][text()='Hello COUCOU!']") + ]), ) failures = 0 -- cgit v1.2.3 From 367de4826c3c7298a88e80c92e325081a3c3d794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 18 May 2016 22:53:17 +0200 Subject: Associate a bridge with a bare JID instead of a full JID ref #2556 --- tests/end_to_end/__main__.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 706d177..630f59a 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -296,6 +296,7 @@ common_replacements = { 'irc_host_one': 'irc.localhost', 'biboumi_host': 'biboumi.localhost', 'resource_one': 'resource1', + 'resource_two': 'resource2', 'nick_one': 'Nick', 'jid_one': 'first@example.com', 'jid_two': 'second@example.com', @@ -579,6 +580,31 @@ if __name__ == '__main__': partial(send_stanza, "COUCOU"), partial(expect_stanza, "/iq[@type='result']/commands:command[@node='hello'][@status='completed']/commands:note[@type='info'][text()='Hello COUCOU!']") ]), + Scenario("simple_multisessionnick", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # The other resources joins the same room, with the same nick + partial(send_stanza, + ""), + # We receive our own join + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + ]), + ) failures = 0 -- cgit v1.2.3 From 199f010f523b1613afcc760f2b9aedc1888ab7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 8 Jun 2016 11:49:44 +0200 Subject: Add a basic e2e test for channel and private messages --- tests/end_to_end/__main__.py | 53 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 630f59a..186ada1 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -302,6 +302,7 @@ common_replacements = { 'jid_two': 'second@example.com', 'jid_admin': 'admin@example.com', 'nick_two': 'Bobby', + 'lower_nick_one': 'nick', } @@ -592,7 +593,7 @@ if __name__ == '__main__': ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", "/presence/muc_user:x/muc_user:status[@code='110']") ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[not(text())]"), # The other resources joins the same room, with the same nick partial(send_stanza, @@ -602,9 +603,57 @@ if __name__ == '__main__': ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", "/presence/muc_user:x/muc_user:status[@code='110']") ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), ]), + Scenario("channel_messages", + [ + handshake_sequence(), + # First user joins + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # Second user joins + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + # Our presence, sent to the other user + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), + # The other user presence + partial(expect_stanza, + "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']"), + # Our own presence + partial(expect_stanza, + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + # Send a channel message + partial(send_stanza, "coucou"), + # Receive the message, forwarded to the two users + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + + # Send a private message, to a in-room JID + partial(send_stanza, "coucou in private"), + # Message is received with a server-wide JID + partial(expect_stanza, "/message[@from='{lower_nick_one}!{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='coucou in private']"), + + # Respond to the message, to the server-wide JID + partial(send_stanza, "yes"), + # The response is received from the in-room JID + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='yes']"), + ] + ) ) failures = 0 -- cgit v1.2.3 From 7540f6793342aeb1e9cec53208b347ca8a346c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 9 Jun 2016 16:32:23 +0200 Subject: Add some e2e tests to check that private messages come from the right JIDs --- tests/end_to_end/__main__.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 186ada1..1408ea9 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -610,7 +610,7 @@ if __name__ == '__main__': handshake_sequence(), # First user joins partial(send_stanza, - ""), + ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), partial(expect_stanza, "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), @@ -652,6 +652,27 @@ if __name__ == '__main__': partial(send_stanza, "yes"), # The response is received from the in-room JID partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='yes']"), + + ## Do the exact same thing, from a different chan, + # to check if the response comes from the right JID + + # Join the virtual channel + partial(send_stanza, + ""), + partial(expect_stanza, + "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, "/message[@from='%{irc_server_one}'][@type='groupchat']/subject"), + + + # Send a private message, to a in-room JID + partial(send_stanza, "re in private"), + # Message is received with a server-wide JID + partial(expect_stanza, "/message[@from='{lower_nick_one}!{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='re in private']"), + + # Respond to the message, to the server-wide JID + partial(send_stanza, "re"), + # The response is received from the in-room JID + partial(expect_stanza, "/message[@from='%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='re']"), ] ) ) -- cgit v1.2.3 From 272c0e4995f2fe94fb2366c15453fdada341861a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 10 Jun 2016 10:00:48 +0200 Subject: Reset the preferred private JID when all resources leave a room MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For example if we are talking in private with nick Joe from room #foo, and then we leave that room, we start receiving Joe’s message from the server-wide JID e2e tests included!!! --- tests/end_to_end/__main__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 1408ea9..0e2ca64 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -303,6 +303,7 @@ common_replacements = { 'jid_admin': 'admin@example.com', 'nick_two': 'Bobby', 'lower_nick_one': 'nick', + 'lower_nick_two': 'bobby', } @@ -673,6 +674,17 @@ if __name__ == '__main__': partial(send_stanza, "re"), # The response is received from the in-room JID partial(expect_stanza, "/message[@from='%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='re']"), + + # Now we leave the room, to check if the subsequent private messages are still received properly + partial(send_stanza, + ""), + partial(expect_stanza, + "/presence[@type='unavailable']/muc_user:x/muc_user:status[@code='110']"), + + # The private messages from this nick should now come (again) from the server-wide JID + partial(send_stanza, "hihihoho"), + partial(expect_stanza, + "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_one}']"), ] ) ) -- cgit v1.2.3 From 5a2e61161792cf51209f240e40e28036195f35be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 13 Jun 2016 19:59:17 +0200 Subject: Show off, with some variadic templates, for the logger module --- tests/logger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/logger.cpp b/tests/logger.cpp index 2a99374..7ae4f03 100644 --- a/tests/logger.cpp +++ b/tests/logger.cpp @@ -24,7 +24,7 @@ TEST_CASE("Basic logging") WHEN("we log some debug text") { IoTester out(std::cout); - log_debug("debug"); + log_debug("deb", "ug"); THEN("debug logs are written") CHECK(out.str() == debug_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\tdebug\n"); } -- cgit v1.2.3 From 550ab46f02511614d7ce7b46a4b4f63beae1aebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 13 Jun 2016 20:45:12 +0200 Subject: Make the logger tests more useful --- tests/logger.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'tests') diff --git a/tests/logger.cpp b/tests/logger.cpp index 7ae4f03..1d59a22 100644 --- a/tests/logger.cpp +++ b/tests/logger.cpp @@ -31,9 +31,9 @@ TEST_CASE("Basic logging") WHEN("we log some errors") { IoTester out(std::cout); - log_error("error"); + log_error("err", 12, "or"); THEN("error logs are written") - CHECK(out.str() == error_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); + CHECK(out.str() == error_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terr12or\n"); } } GIVEN("A logger with log_level 3") @@ -42,16 +42,16 @@ TEST_CASE("Basic logging") WHEN("we log some debug text") { IoTester out(std::cout); - log_debug("debug"); + log_debug(123, "debug"); THEN("nothing is written") CHECK(out.str().empty()); } WHEN("we log some errors") { IoTester out(std::cout); - log_error("error"); + log_error(123, " errors"); THEN("error logs are still written") - CHECK(out.str() == error_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); + CHECK(out.str() == error_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\t123 errors\n"); } } } -- cgit v1.2.3 From 46ff73662cc94220c5ee962b591c8ee327de6f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 14 Jun 2016 03:02:36 +0200 Subject: Clean the Config module, use static things instead of a stupid singleton --- tests/config.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'tests') diff --git a/tests/config.cpp b/tests/config.cpp index 346dea1..ddea151 100644 --- a/tests/config.cpp +++ b/tests/config.cpp @@ -4,18 +4,18 @@ TEST_CASE("Config basic") { - Config::filename = "test.cfg"; - Config::file_must_exist = false; + // Write a value in the config file + Config::read_conf("test.cfg"); Config::set("coucou", "bonjour", true); - Config::close(); + Config::clear(); bool error = false; try { - Config::file_must_exist = true; + CHECK(Config::read_conf()); CHECK(Config::get("coucou", "") == "bonjour"); CHECK(Config::get("does not exist", "default") == "default"); - Config::close(); + Config::clear(); } catch (const std::ios::failure& e) { -- cgit v1.2.3 From 80d0c19c5a8d548a8c6019033bf574ff2be4c0ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 14 Jun 2016 20:14:36 +0200 Subject: Refactor, test and improve the way we cut too-long messages for IRC --- tests/utils.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'tests') diff --git a/tests/utils.cpp b/tests/utils.cpp index 8691910..54e743f 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -88,3 +88,9 @@ TEST_CASE("empty if fixed irc server") } } + +TEST_CASE("string cut") +{ + CHECK(cut("coucou", 2).size() == 3); + CHECK(cut("bonjour les copains", 6).size() == 4); +} \ No newline at end of file -- cgit v1.2.3 From 4b1c580bb9bc03d656e59d702c72c3e793a1bbe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 15 Jun 2016 12:19:19 +0200 Subject: cut messages at 512 bytes, taking into account the UTF-8 codepoints ref #3067 --- tests/utils.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/utils.cpp b/tests/utils.cpp index 54e743f..01d070e 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -93,4 +93,10 @@ TEST_CASE("string cut") { CHECK(cut("coucou", 2).size() == 3); CHECK(cut("bonjour les copains", 6).size() == 4); -} \ No newline at end of file + CHECK(cut("««««", 2).size() == 4); + CHECK(cut("a««««", 2).size() == 5); + const auto res = cut("rhello, ♥", 10); + CHECK(res.size() == 2); + CHECK(res[0] == "rhello, "); + CHECK(res[1] == "♥"); +} -- cgit v1.2.3 From 4e959a3869c4a69b9d59de69694644c37380ff11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 23 Jun 2016 22:50:05 +0200 Subject: Add a simple e2e test that joins a channel with xep0106-encoded name --- tests/end_to_end/__main__.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 0e2ca64..7859202 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -686,7 +686,22 @@ if __name__ == '__main__': partial(expect_stanza, "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_one}']"), ] - ) + ), + Scenario("encoded_channel_join", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + "/message/body[text()='Mode #biboumi@louiz.org:80 [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + ]), + ) failures = 0 -- cgit v1.2.3 From 7ae29890c3fd498d8d4fecd64cfc0da703b77a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 24 Jun 2016 11:25:04 +0200 Subject: Add a e2e for self-ping, with a single channel resource --- tests/end_to_end/__main__.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 7859202..ebd2a73 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -701,7 +701,32 @@ if __name__ == '__main__': ), partial(expect_stanza, "/message[@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), ]), + Scenario("self_ping_on_real_channel", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + # Send a ping to ourself + partial(send_stanza, + ""), + # We receive our own ping request, + partial(expect_stanza, + "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}'][@id='gnip_tsrif']"), + # Respond to the request + partial(send_stanza, + ""), + partial(expect_stanza, + "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), + ]), ) failures = 0 -- cgit v1.2.3 From 2fe8878169b094a12d74d69ea97314d172647ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 24 Jun 2016 11:43:13 +0200 Subject: Add a e2e test that checks self-ping with multiple resources behind one nick --- tests/end_to_end/__main__.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index ebd2a73..06aa1ce 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -726,6 +726,40 @@ if __name__ == '__main__': ""), partial(expect_stanza, "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), + + # Now join the same room, from the same bare JID, behind the same nick + partial(send_stanza, + ""), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), + + # And re-send a self ping + partial(send_stanza, + ""), + # We receive our own ping request. Note that we don't know the to value, it could be one of our two resources. + partial(expect_stanza, + "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to][@id='gnip_dnoces']", + after = partial(save_value, "to", partial(extract_attribute, "/iq", "to"))), + # Respond to the request, using the extracted 'to' value as our 'from' + partial(send_stanza, + ""), + partial(expect_stanza, + "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_ping']"), + + ## And re-do exactly the same thing, just change the resource initiating the self ping + partial(send_stanza, + ""), + partial(expect_stanza, + "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to][@id='gnip_driht']", + after = partial(save_value, "to", partial(extract_attribute, "/iq", "to"))), + partial(send_stanza, + ""), + partial(expect_stanza, + "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='third_ping']"), + ]), ) -- cgit v1.2.3 From 79002aa5c9ada3a09d5f09cde0cac4c456adaa47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 29 Jun 2016 19:52:43 +0200 Subject: Run e2e through with valgrind if BIBOUMI_E2E_VALGRIND is set in the env --- tests/end_to_end/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 06aa1ce..e6ed911 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -782,7 +782,7 @@ if __name__ == '__main__': for scenario in scenarios: test = BiboumiTest(scenario) - if not test.run(False): + if not test.run(os.getenv("E2E_BIBOUMI_VALGRIND") is not None): print("You can check the files slixmpp_%s_output.txt and biboumi_%s_output.txt to help you debug." % (scenario.name, scenario.name)) failures += 1 -- cgit v1.2.3 From 8cef7303187297bc98d8a9ddceef4674a9297e9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 2 Jul 2016 15:08:40 +0200 Subject: Add a valgrind suppression file --- tests/end_to_end/__main__.py | 2 +- tests/end_to_end/biboumi.supp | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tests/end_to_end/biboumi.supp (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index e6ed911..0223e47 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -187,7 +187,7 @@ class BiboumiRunner(ProcessRunner): self.name = name self.fd = open("biboumi_%s_output.txt" % (name,), "w") if with_valgrind: - self.create = asyncio.create_subprocess_exec("valgrind", "--leak-check=full", "--show-leak-kinds=all", + self.create = asyncio.create_subprocess_exec("valgrind", "--suppressions=" + (os.environ.get("E2E_BIBOUMI_SUPP_DIR") or "") + "biboumi.supp", "--leak-check=full", "--show-leak-kinds=all", "--errors-for-leak-kinds=all", "--error-exitcode=16", "./biboumi", "test.conf", stdin=None, stdout=self.fd, stderr=self.fd, loop=None, limit=None) diff --git a/tests/end_to_end/biboumi.supp b/tests/end_to_end/biboumi.supp new file mode 100644 index 0000000..d153665 --- /dev/null +++ b/tests/end_to_end/biboumi.supp @@ -0,0 +1,10 @@ +{ + stdlibc++ thingy + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:call_init.part.0 + fun:_dl_init + ... +} -- cgit v1.2.3 From 0ce75ab52111ba27ca99961057b36b68f0a135a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 3 Jul 2016 15:43:11 +0200 Subject: Properly remove the resource from the server when we leave the last channel --- tests/end_to_end/__main__.py | 46 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 0223e47..d1d653d 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -229,7 +229,7 @@ class BiboumiTest: def __init__(self, scenario, expected_code=0): self.scenario = scenario - self.expected_code = 0 + self.expected_code = expected_code def run(self, with_valgrind=True): print("Running scenario: %s%s" % (self.scenario.name, " (with valgrind)" if with_valgrind else '')) @@ -355,6 +355,8 @@ def connection_end_sequence(irc_host, jid): xpath_re % (r'^%s: This server was created .*$' % irc_host)), partial(expect_stanza, xpath_re % (r'^%s: There are \d+ users and \d+ invisible on \d+ servers$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: \d+ unknown connection\(s\)$' % irc_host), optional=True), partial(expect_stanza, xpath_re % (r'^%s: \d+ channels formed$' % irc_host), optional=True), partial(expect_stanza, @@ -582,7 +584,7 @@ if __name__ == '__main__': partial(send_stanza, "COUCOU"), partial(expect_stanza, "/iq[@type='result']/commands:command[@node='hello'][@status='completed']/commands:note[@type='info'][text()='Hello COUCOU!']") ]), - Scenario("simple_multisessionnick", + Scenario("multisessionnick", [ handshake_sequence(), partial(send_stanza, @@ -605,6 +607,46 @@ if __name__ == '__main__': "/presence/muc_user:x/muc_user:status[@code='110']") ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), + + # A different user joins the same room + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']",)), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']",)), + + partial(expect_stanza, + "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # That second user sends a private message to the first one + partial(send_stanza, "RELLO"), + # Message is received with a server-wide JID, by the two resources behind nick_one + partial(expect_stanza, "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='RELLO']"), + partial(expect_stanza, "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='RELLO']"), + + # One resource leaves the server entirely. + partial(send_stanza, ""), + # The leave is forwarded only to us + partial(expect_stanza, + ("/presence[@type='unavailable']/muc_user:x/muc_user:status[@code='110']", + "/presence/status[text()='Biboumi note: 1 resources are still in this channel.']") + ), + # The second user sends two new private messages to the first user + partial(send_stanza, "first"), + partial(send_stanza, "second"), + # The first user receives the two messages, on the connected resource, once each + partial(expect_stanza, "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='first']"), + partial(expect_stanza, "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='second']"), + + ]), Scenario("channel_messages", [ -- cgit v1.2.3 From 81f8f45b371d1a0ef72c2768fbd1f9188fe83616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 4 Jul 2016 17:53:53 +0200 Subject: Replace all include guards by #pragma once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s $CURRENT_YEAR --- tests/io_tester.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'tests') diff --git a/tests/io_tester.hpp b/tests/io_tester.hpp index 8afa6f6..b9cdaa7 100644 --- a/tests/io_tester.hpp +++ b/tests/io_tester.hpp @@ -1,5 +1,4 @@ -#ifndef BIBOUMI_IO_TESTER_HPP -#define BIBOUMI_IO_TESTER_HPP +#pragma once #include #include @@ -44,4 +43,3 @@ private: std::streambuf* const old_buf; }; -#endif //BIBOUMI_IO_TESTER_HPP -- cgit v1.2.3 From 4c1b9abe7e230a39b119bdc45ebcd5e677fad488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 12 Jul 2016 00:31:57 +0200 Subject: Properly catch and handle database errors Do not use a singleton for the database. fix #3203 --- tests/database.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/database.cpp b/tests/database.cpp index b059d0d..4e2be14 100644 --- a/tests/database.cpp +++ b/tests/database.cpp @@ -7,7 +7,7 @@ TEST_CASE("Database") { #ifdef USE_DATABASE - Config::set("db_name", ":memory:"); + Database::open(":memory:"); Database::set_verbose(false); SECTION("Basic retrieve and update") -- cgit v1.2.3 From c7e4fc1386b7992c33ea912bf3bfcfcff5d85758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 25 Jul 2016 14:59:25 +0200 Subject: Test the resolving of multiple hostnames at the same time --- tests/dns.cpp | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) (limited to 'tests') diff --git a/tests/dns.cpp b/tests/dns.cpp index 4ec1b96..c3eda7b 100644 --- a/tests/dns.cpp +++ b/tests/dns.cpp @@ -9,6 +9,8 @@ TEST_CASE("DNS resolver") { Resolver resolver; + Resolver resolver2; + Resolver resolver3; /** * If we are using cares, we need to run a poller loop until each @@ -36,38 +38,50 @@ TEST_CASE("DNS resolver") bool success = true; - auto error_cb = [&success, &hostname, &port](const char* msg) - { - INFO("Failed to resolve " << hostname << ":" << port << ": " << msg); - success = false; - }; - auto success_cb = [&success, &hostname, &port](const struct addrinfo* addr) - { - INFO("Successfully resolved " << hostname << ":" << port << ": " << addr_to_string(addr)); - success = true; - }; + const auto error_cb = [&success](const std::string& hostname) + { + return [&success, hostname](const char *msg) + { + INFO("Failed to resolve " << hostname << ":" << msg); + success = false; + }; + }; + const auto success_cb = [&success](const std::string& hostname) + { + return [&success, hostname](const struct addrinfo *addr) + { + INFO("Successfully resolved " << hostname << ": " << addr_to_string(addr)); + success = true; + }; + }; hostname = "example.com"; resolver.resolve(hostname, port, - success_cb, error_cb); + success_cb(hostname), error_cb(hostname)); + hostname = "poez.io"; + resolver2.resolve(hostname, port, + success_cb(hostname), error_cb(hostname)); + hostname = "louiz.org"; + resolver3.resolve(hostname, port, + success_cb(hostname), error_cb(hostname)); loop(); CHECK(success); hostname = "this.should.fail.because.it.is..misformatted"; resolver.resolve(hostname, port, - success_cb, error_cb); + success_cb(hostname), error_cb(hostname)); loop(); CHECK(!success); hostname = "this.should.fail.because.it.is.does.not.exist.invalid"; resolver.resolve(hostname, port, - success_cb, error_cb); + success_cb(hostname), error_cb(hostname)); loop(); CHECK(!success); hostname = "localhost"; resolver.resolve(hostname, port, - success_cb, error_cb); + success_cb(hostname), error_cb(hostname)); loop(); CHECK(success); -- cgit v1.2.3 From 0d1e0629e7efe07bacce6a22e45ddfd7652eb505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 28 Jul 2016 10:47:44 +0200 Subject: Fix the timeout test, now that we don't wait 1ms too much everytime --- tests/timed_events.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/timed_events.cpp b/tests/timed_events.cpp index 3844f3d..d63abef 100644 --- a/tests/timed_events.cpp +++ b/tests/timed_events.cpp @@ -39,7 +39,7 @@ TEST_CASE("Test timed event expiration") std::chrono::milliseconds timoute = TimedEventsManager::instance().get_timeout(); INFO("Sleeping for " << timoute.count() << "ms"); - std::this_thread::sleep_for(timoute); + std::this_thread::sleep_for(timoute + 1ms); // Event is now expired CHECK(TimedEventsManager::instance().execute_expired_events() == 1); -- cgit v1.2.3 From 47902953131a6fc6fc3ab24d15f5fd163c2f7add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 31 Jul 2016 17:55:47 +0200 Subject: test kick --- tests/end_to_end/__main__.py | 61 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index d1d653d..2442d03 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -220,6 +220,11 @@ def expect_stanza(xpaths, xmpp, biboumi, optional=False, after=None): print("Warning, from argument type passed to expect_stanza: %s" % (type(xpaths))) +def log_message(message, xmpp, biboumi): + print("%s" % (message,)) + asyncio.get_event_loop().call_soon(xmpp.run_scenario) + + class BiboumiTest: """ Spawns a biboumi process and a fake XMPP Component that will run a @@ -252,7 +257,6 @@ class BiboumiTest: asyncio.get_event_loop().call_soon(xmpp.run_scenario) xmpp.process() - code = asyncio.get_event_loop().run_until_complete(biboumi.wait()) xmpp.biboumi = None scenario.steps.clear() @@ -436,6 +440,8 @@ if __name__ == '__main__': [ handshake_sequence(), # First user joins + partial(log_message, + "First user joins"), partial(send_stanza, ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), @@ -448,16 +454,24 @@ if __name__ == '__main__': partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), # Second user joins + partial(log_message, + "Second user joins"), partial(send_stanza, ""), connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), # Our presence, sent to the other user + partial(log_message, + "Our presence sent to the other user"), partial(expect_stanza, ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), # The other user presence + partial(log_message, + "The other user presence"), partial(expect_stanza, "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']"), # Our own presence + partial(log_message, + "Our own presence"), partial(expect_stanza, ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", "/presence/muc_user:x/muc_user:status[@code='110']") @@ -790,7 +804,6 @@ if __name__ == '__main__': ""), partial(expect_stanza, "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_ping']"), - ## And re-do exactly the same thing, just change the resource initiating the self ping partial(send_stanza, ""), @@ -803,6 +816,50 @@ if __name__ == '__main__': "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='third_ping']"), ]), + Scenario("simple_kick", + [ + handshake_sequence(), + # First user joins + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, "/message[@type='groupchat']/subject"), + + # Second user joins + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + partial(expect_stanza, + "/presence/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",), + + partial(expect_stanza, + "/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"), + partial(expect_stanza, + "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, "/message/subject"), + + # Moderator kicks participant + partial(log_message, "Moderator kicks participant"), + partial(send_stanza, + "reported"), + partial(log_message, "Presence is sent to everyone"), + partial(expect_stanza, + ("/presence[@type='unavailable'][@to='{jid_second}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + "/presence/muc_user:x/muc_user:status[@code='110']" + )), + partial(expect_stanza, + ("/presence[@type='unavailable']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + ), + ), + partial(expect_stanza, + "/iq[@id='kick1'][@type='result']"), + ]), ) failures = 0 -- cgit v1.2.3 From 3fbe01bb30a38111ce058ff78b517234280c0a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 2 Aug 2016 11:11:27 +0200 Subject: Test the kick on a multisession nick --- tests/end_to_end/__main__.py | 60 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 2442d03..d7e68b5 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -860,6 +860,66 @@ if __name__ == '__main__': partial(expect_stanza, "/iq[@id='kick1'][@type='result']"), ]), + Scenario("multisession_kick", + [ + handshake_sequence(), + # First user joins + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, "/message[@type='groupchat']/subject"), + + # Second user joins, from two resources + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + partial(expect_stanza, + "/presence/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",), + + partial(expect_stanza, + "/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"), + partial(expect_stanza, + "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, "/message/subject"), + + partial(send_stanza, + ""), + partial(expect_stanza, + "/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_two}/{resource_two}']/subject[not(text())]"), + + # Moderator kicks participant + partial(log_message, "Moderator kicks participant"), + partial(send_stanza, + "reported"), + partial(log_message, "Unavailable presence is sent to the two resources"), + partial(expect_stanza, + ("/presence[@type='unavailable'][@to='{jid_second}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + "/presence/muc_user:x/muc_user:status[@code='110']" + )), + partial(expect_stanza, + ("/presence[@type='unavailable'][@to='{jid_second}/{resource_two}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + "/presence/muc_user:x/muc_user:status[@code='110']" + )), + partial(expect_stanza, + ("/presence[@type='unavailable']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + ), + ), + partial(expect_stanza, + "/iq[@id='kick1'][@type='result']"), + ]), ) failures = 0 -- cgit v1.2.3 From ec0e87a965fbdbd4682f1f3bcdbea2ff7440891e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 3 Aug 2016 14:28:29 +0200 Subject: Test the version request, in many ways --- tests/end_to_end/__main__.py | 73 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index d7e68b5..4348197 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -112,7 +112,8 @@ def match(stanza, xpath): 'muc_user': 'http://jabber.org/protocol/muc#user', 'disco_items': 'http://jabber.org/protocol/disco#items', 'commands': 'http://jabber.org/protocol/commands', - 'dataform': 'jabber:x:data'}) + 'dataform': 'jabber:x:data', + 'version': 'jabber:iq:version'}) return matched @@ -122,7 +123,11 @@ def check_xpath(xpaths, xmpp, after, stanza): if not matched: raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath)) if after: - after(stanza, xmpp) + if isinstance(after, collections.Iterable): + for af in after: + af(stanza, xmpp) + else: + after(stanza, xmpp) def check_xpath_optional(xpaths, xmpp, after, stanza): @@ -920,6 +925,70 @@ if __name__ == '__main__': partial(expect_stanza, "/iq[@id='kick1'][@type='result']"), ]), + Scenario("self_version", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # Send a version request to ourself + partial(send_stanza, + ""), + # We receive our own request, + partial(expect_stanza, + "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}']", + after = partial(save_value, "id", partial(extract_attribute, "/iq", 'id'))), + # Respond to the request + partial(send_stanza, + "e2e test1.0Fedora"), + partial(expect_stanza, + "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_version']/version:query/version:name[text()='e2e test (through the biboumi gateway) 1.0 Fedora']"), + + # Now join the same room, from the same bare JID, behind the same nick + partial(send_stanza, + ""), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), + + # And re-send a self ping + partial(send_stanza, + ""), + # We receive our own request. Note that we don't know the to value, it could be one of our two resources. + partial(expect_stanza, + "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to]", + after = (partial(save_value, "to", partial(extract_attribute, "/iq", "to")), + partial(save_value, "id", partial(extract_attribute, "/iq", "id")))), + # Respond to the request, using the extracted 'to' value as our 'from' + partial(send_stanza, + "e2e test1.0Fedora"), + partial(expect_stanza, + "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='second_version']"), + + # And do exactly the same thing, but initiated by the other resource + partial(send_stanza, + ""), + # We receive our own request. Note that we don't know the to value, it could be one of our two resources. + partial(expect_stanza, + "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to]", + after = (partial(save_value, "to", partial(extract_attribute, "/iq", "to")), + partial(save_value, "id", partial(extract_attribute, "/iq", "id")))), + # Respond to the request, using the extracted 'to' value as our 'from' + partial(send_stanza, + "e2e test1.0Fedora"), + partial(expect_stanza, + "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_version']"), + ]), ) failures = 0 -- cgit v1.2.3