diff options
-rw-r--r-- | CMakeLists.txt | 22 | ||||
-rwxr-xr-x | scripts/build_and_run_tests.sh | 2 | ||||
-rw-r--r-- | src/test.cpp | 551 | ||||
-rw-r--r-- | tests/colors.cpp | 54 | ||||
-rw-r--r-- | tests/config.cpp | 25 | ||||
-rw-r--r-- | tests/database.cpp | 30 | ||||
-rw-r--r-- | tests/dns.cpp | 83 | ||||
-rw-r--r-- | tests/encoding.cpp | 56 | ||||
-rw-r--r-- | tests/iid.cpp | 122 | ||||
-rw-r--r-- | tests/jid.cpp | 39 | ||||
-rw-r--r-- | tests/test.cpp | 2 | ||||
-rw-r--r-- | tests/timed_events.cpp | 62 | ||||
-rw-r--r-- | tests/utils.cpp | 72 | ||||
-rw-r--r-- | tests/uuid.cpp | 13 | ||||
-rw-r--r-- | tests/xmpp.cpp | 47 |
15 files changed, 625 insertions, 555 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index b16b906..960b5d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,12 +145,13 @@ if(SYSTEMD_FOUND) target_link_libraries(xmpp ${SYSTEMD_LIBRARIES}) endif() - # ## Tests # +file(GLOB source_tests + tests/*.cpp) add_executable(test_suite EXCLUDE_FROM_ALL - src/test.cpp) + ${source_tests}) target_link_libraries(test_suite xmpplib xmpp @@ -160,11 +161,26 @@ target_link_libraries(test_suite config logger network) - if(USE_DATABASE) target_link_libraries(test_suite database) endif() +include(ExternalProject) +ExternalProject_Add(catch + GIT_REPOSITORY "https://github.com/philsquared/Catch.git" + PREFIX "external" + UPDATE_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" +) +ExternalProject_Get_Property(catch SOURCE_DIR) +target_include_directories(test_suite + PUBLIC "${SOURCE_DIR}/include/" +) +add_dependencies(test_suite catch) +add_custom_target(check COMMAND "test_suite" + DEPENDS test_suite) # ## Install target diff --git a/scripts/build_and_run_tests.sh b/scripts/build_and_run_tests.sh index 69a27fd..738ef52 100755 --- a/scripts/build_and_run_tests.sh +++ b/scripts/build_and_run_tests.sh @@ -4,4 +4,4 @@ set -e -x cmake .. $@ make -j$(nproc) biboumi test_suite -./test_suite +make -j$(nproc) check diff --git a/src/test.cpp b/src/test.cpp deleted file mode 100644 index 14ba929..0000000 --- a/src/test.cpp +++ /dev/null @@ -1,551 +0,0 @@ -/** - * Just a very simple test suite, by hand, using assert() - */ - -#include <xmpp/xmpp_component.hpp> -#include <network/dns_handler.hpp> -#include <utils/timed_events.hpp> -#include <database/database.hpp> -#include <network/resolver.hpp> -#include <utils/encoding.hpp> -#include <network/poller.hpp> -#include <logger/logger.hpp> -#include <config/config.hpp> -#include <bridge/colors.hpp> -#include <utils/tolower.hpp> -#include <utils/revstr.hpp> -#include <irc/irc_user.hpp> -#include <utils/string.hpp> -#include <utils/split.hpp> -#include <utils/xdg.hpp> -#include <xmpp/jid.hpp> -#include <irc/iid.hpp> -#include <unistd.h> - -#include <thread> - -#undef NDEBUG -#include <assert.h> - -using namespace std::chrono_literals; - -static const std::string color("[35m"); -static const std::string success_color("[32m"); -static const std::string reset("[m"); - -int main() -{ - - /** - * Config - */ - std::cout << color << "Testing config…" << reset << std::endl; - 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; - assert(Config::get("coucou", "") == "bonjour"); - assert(Config::get("does not exist", "default") == "default"); - Config::close(); - } - catch (const std::ios::failure& e) - { - error = true; - } - assert(error == false); - - Config::set("log_level", "2"); - Config::set("log_file", ""); - - std::cout << color << "Testing logging…" << reset << std::endl; - log_debug("If you see this, the test FAILED."); - log_info("If you see this, the test FAILED."); - log_warning("You must see this message. And the next one too."); - log_error("It’s not an error, don’t worry, the test passed."); - - - /** - * Timed events - */ - std::cout << color << "Testing timed events…" << reset << std::endl; - // No event. - assert(TimedEventsManager::instance().get_timeout() == utils::no_timeout); - assert(TimedEventsManager::instance().execute_expired_events() == 0); - - // Add a single event - TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 50ms, [](){ std::cout << "Timeout expired" << std::endl; })); - // The event should not yet be expired - assert(TimedEventsManager::instance().get_timeout() > 0ms); - assert(TimedEventsManager::instance().execute_expired_events() == 0); - std::chrono::milliseconds timoute = TimedEventsManager::instance().get_timeout(); - std::cout << "Sleeping for " << timoute.count() << "ms" << std::endl; - std::this_thread::sleep_for(timoute); - - // Event is now expired - assert(TimedEventsManager::instance().execute_expired_events() == 1); - assert(TimedEventsManager::instance().get_timeout() == utils::no_timeout); - - // Test canceling events - TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 100ms, [](){ }, "un")); - TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 200ms, [](){ }, "deux")); - TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 300ms, [](){ }, "deux")); - assert(TimedEventsManager::instance().get_timeout() > 0ms); - assert(TimedEventsManager::instance().size() == 3); - assert(TimedEventsManager::instance().cancel("un") == 1); - assert(TimedEventsManager::instance().size() == 2); - assert(TimedEventsManager::instance().cancel("deux") == 2); - assert(TimedEventsManager::instance().get_timeout() == utils::no_timeout); - - /** - * Encoding - */ - std::cout << color << "Testing encoding…" << reset << std::endl; - const char* valid = "C̡͔͕̩͙̽ͫ̈́ͥ̿̆ͧ̚r̸̩̘͍̻͖̆͆͛͊̉̕͡o͇͈̳̤̱̊̈͢q̻͍̦̮͕ͥͬͬ̽ͭ͌̾ͅǔ͉͕͇͚̙͉̭͉̇̽ȇ͈̮̼͍͔ͣ͊͞͝ͅ ͫ̾ͪ̓ͥ̆̋̔҉̢̦̠͈͔̖̲̯̦ụ̶̯͐̃̋ͮ͆͝n̬̱̭͇̻̱̰̖̤̏͛̏̿̑͟ë́͐҉̸̥̪͕̹̻̙͉̰ ̹̼̱̦̥ͩ͑̈́͑͝ͅt͍̥͈̹̝ͣ̃̔̈̔ͧ̕͝ḙ̸̖̟̙͙ͪ͢ų̯̞̼̲͓̻̞͛̃̀́b̮̰̗̩̰̊̆͗̾̎̆ͯ͌͝.̗̙͎̦ͫ̈́ͥ͌̈̓ͬ"; - assert(utils::is_valid_utf8(valid)); - const char* invalid = "\xF0\x0F"; - assert(utils::is_valid_utf8(invalid) == false); - const char* invalid2 = "\xFE\xFE\xFF\xFF"; - assert(utils::is_valid_utf8(invalid2) == false); - - std::string in = "Biboumi ╯°□°)╯︵ ┻━┻"; - std::cout << in << std::endl; - assert(utils::is_valid_utf8(in.c_str()) == true); - std::string res = utils::convert_to_utf8(in, "UTF-8"); - assert(utils::is_valid_utf8(res.c_str()) == true && res == in); - - std::string original_utf8("couc¥ou"); - std::string original_latin1("couc\xa5ou"); - - // When converting back to utf-8 - std::string from_latin1 = utils::convert_to_utf8(original_latin1.c_str(), "ISO-8859-1"); - assert(from_latin1 == original_utf8); - - // 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"); - assert(from_ascii == "couc�ou"); - std::cout << from_ascii << std::endl; - - std::string without_ctrl_char("𤭢€¢$"); - assert(utils::remove_invalid_xml_chars(without_ctrl_char) == without_ctrl_char); - assert(utils::remove_invalid_xml_chars(in) == in); - assert(utils::remove_invalid_xml_chars("\acouco\u0008u\uFFFEt\uFFFFe\r\n♥") == "coucoute\r\n♥"); - - /** - * Id generation - */ - std::cout << color << "Testing id generation…" << reset << std::endl; - const std::string first_uuid = XmppComponent::next_id(); - const std::string second_uuid = XmppComponent::next_id(); - std::cout << first_uuid << std::endl; - std::cout << second_uuid << std::endl; - assert(first_uuid.size() == 36); - assert(second_uuid.size() == 36); - assert(first_uuid != second_uuid); - - /** - * Utils - */ - std::cout << color << "Testing utils…" << reset << std::endl; - std::vector<std::string> splitted = utils::split("a::a", ':', false); - assert(splitted.size() == 2); - splitted = utils::split("a::a", ':', true); - assert(splitted.size() == 3); - assert(splitted[0] == "a"); - assert(splitted[1] == ""); - assert(splitted[2] == "a"); - splitted = utils::split("\na", '\n', true); - assert(splitted.size() == 2); - assert(splitted[0] == ""); - assert(splitted[1] == "a"); - - const std::string lowercase = utils::tolower("CoUcOu LeS CoPaiNs ♥"); - std::cout << lowercase << std::endl; - assert(lowercase == "coucou les copains ♥"); - - const std::string ltr = "coucou"; - assert(utils::revstr(ltr) == "uocuoc"); - - assert(to_bool("true")); - assert(!to_bool("trou")); - assert(to_bool("1")); - assert(!to_bool("0")); - assert(!to_bool("-1")); - assert(!to_bool("false")); - - /** - * XML parsing - */ - std::cout << color << "Testing XML parsing…" << reset << std::endl; - XmppParser xml; - const std::string doc = "<stream xmlns='stream_ns'><stanza b='c'>inner<child1><grandchild/></child1><child2 xmlns='child2_ns'/>tail</stanza></stream>"; - auto check_stanza = [](const Stanza& stanza) - { - assert(stanza.get_name() == "stanza"); - assert(stanza.get_tag("xmlns") == "stream_ns"); - assert(stanza.get_tag("b") == "c"); - assert(stanza.get_inner() == "inner"); - assert(stanza.get_tail() == ""); - assert(stanza.get_child("child1", "stream_ns") != nullptr); - assert(stanza.get_child("child2", "stream_ns") == nullptr); - assert(stanza.get_child("child2", "child2_ns") != nullptr); - assert(stanza.get_child("child2", "child2_ns")->get_tail() == "tail"); - }; - xml.add_stanza_callback([check_stanza](const Stanza& stanza) - { - std::cout << stanza.to_string() << std::endl; - 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 = "<stream xmlns='s'><stanza>coucou\r\n\a</stanza></stream>"; - xml.add_stanza_callback([](const Stanza& stanza) - { - std::cout << stanza.to_string() << std::endl; - assert(stanza.get_inner() == "coucou\r\n"); - }); - xml.feed(doc2.data(), doc.size(), true); - - /** - * XML escape/escape - */ - std::cout << color << "Testing XML escaping…" << reset << std::endl; - const std::string unescaped = "'coucou'<cc>/&\"gaga\""; - assert(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); - assert(xml_unescape(xml_escape(unescaped)) == unescaped); - - /** - * Irc user parsing - */ - const std::map<char, char> prefixes{{'!', 'a'}, {'@', 'o'}}; - - IrcUser user1("!nick!~some@host.bla", prefixes); - assert(user1.nick == "nick"); - assert(user1.host == "~some@host.bla"); - assert(user1.modes.size() == 1); - assert(user1.modes.find('a') != user1.modes.end()); - IrcUser user2("coucou!~other@host.bla", prefixes); - assert(user2.nick == "coucou"); - assert(user2.host == "~other@host.bla"); - assert(user2.modes.empty()); - assert(user2.modes.find('a') == user2.modes.end()); - - /** - * Colors conversion - */ - std::cout << color << "Testing IRC colors conversion…" << reset << std::endl; - std::unique_ptr<XmlNode> xhtml; - std::string cleaned_up; - - std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("bold"); - std::cout << xhtml->to_string() << std::endl; - assert(xhtml && xhtml->to_string() == "<body xmlns='http://www.w3.org/1999/xhtml'><span style='font-weight:bold;'>bold</span></body>"); - - std::tie(cleaned_up, xhtml) = - irc_format_to_xhtmlim("normalboldunder-and-boldbold normal" - "5red,5default-on-red10,2cyan-on-blue"); - assert(xhtml); - assert(xhtml->to_string() == "<body xmlns='http://www.w3.org/1999/xhtml'>normal<span style='font-weight:bold;'>bold</span><span style='font-weight:bold;text-decoration:underline;'>under-and-bold</span><span style='font-weight:bold;'>bold</span> normal<span style='color:red;'>red</span><span style='background-color:red;'>default-on-red</span><span style='color:cyan;background-color:blue;'>cyan-on-blue</span></body>"); - assert(cleaned_up == "normalboldunder-and-boldbold normalreddefault-on-redcyan-on-blue"); - - std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("normal"); - assert(!xhtml && cleaned_up == "normal"); - - std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(""); - assert(xhtml && !xhtml->has_children() && cleaned_up.empty()); - - std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(",a"); - assert(xhtml && !xhtml->has_children() && cleaned_up == "a"); - - std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(","); - assert(xhtml && !xhtml->has_children() && 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"); - assert(xhtml->to_string() == "<body xmlns='http://www.w3.org/1999/xhtml'>[<span style='font-style:italic;'/><span style='font-style:italic;color:lightmagenta;'>dolphin-emu/dolphin</span><span style='color:lightmagenta;'>] </span><span style='color:green;'>foo</span> commented on #283 (Add support for the guide button to XInput): <span style='text-decoration:underline;'/><span style='text-decoration:underline;color:blue;'>http://example.com</span><span style='text-decoration:underline;'/></body>"); - assert(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"); - assert(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"); - assert(xhtml->to_string() == "<body xmlns='http://www.w3.org/1999/xhtml'>0e46ab by <span style='color:green;'>Pierre Dindon</span> [<span style='color:lightgreen;'>0</span>|<span style='color:lightgreen;'>1</span>|<span style='color:indianred;'>0</span>] <span style='text-decoration:underline;'/><span style='text-decoration:underline;color:blue;'>http://example.net/Ojrh4P</span><span style='text-decoration:underline;'/> media: avoid pop-in effect when loading thumbnails by specifying an explicit size</body>"); - - std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("test\ncoucou"); - assert(cleaned_up == "test\ncoucou"); - assert(xhtml->to_string() == "<body xmlns='http://www.w3.org/1999/xhtml'>test<br/>coucou</body>"); - - /** - * JID parsing - */ - std::cout << color << "Testing JID parsing…" << reset << std::endl; - // Full JID - Jid jid1("♥@ツ.coucou/coucou@coucou/coucou"); - std::cout << jid1.local << "@" << jid1.domain << "/" << jid1.resource << std::endl; - assert(jid1.local == "♥"); - assert(jid1.domain == "ツ.coucou"); - assert(jid1.resource == "coucou@coucou/coucou"); - - // Domain and resource - Jid jid2("ツ.coucou/coucou@coucou/coucou"); - std::cout << jid2.local << "@" << jid2.domain << "/" << jid2.resource << std::endl; - assert(jid2.local == ""); - assert(jid2.domain == "ツ.coucou"); - assert(jid2.resource == "coucou@coucou/coucou"); - - // Jidprep - const std::string badjid("~zigougou™@EpiK-7D9D1FDE.poez.io/Boujour/coucou/slt™"); - const std::string correctjid = jidprep(badjid); - std::cout << correctjid << std::endl; -#ifdef LIBIDN_FOUND - assert(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 - assert(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); - assert(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); - - const std::string badjid2("Zigougou@poez.io"); - const std::string correctjid2 = jidprep(badjid2); - std::cout << correctjid2 << std::endl; - assert(correctjid2 == "zigougou@poez.io"); - - const std::string crappy("~Bisous@7ea8beb1:c5fd2849:da9a048e:ip"); - const std::string fixed_crappy = jidprep(crappy); - std::cout << fixed_crappy << std::endl; - assert(fixed_crappy == "~bisous@7ea8beb1-c5fd2849-da9a048e-ip"); -#else // Without libidn, jidprep always returns an empty string - assert(jidprep(badjid) == ""); -#endif - - /** - * IID parsing - */ - { - std::cout << color << "Testing IID parsing…" << reset << std::endl; - Iid iid1("foo!irc.example.org"); - std::cout << std::to_string(iid1) << std::endl; - assert(std::to_string(iid1) == "foo!irc.example.org"); - assert(iid1.get_local() == "foo"); - assert(iid1.get_server() == "irc.example.org"); - assert(!iid1.is_channel); - assert(iid1.is_user); - - Iid iid2("#test%irc.example.org"); - std::cout << std::to_string(iid2) << std::endl; - assert(std::to_string(iid2) == "#test%irc.example.org"); - assert(iid2.get_local() == "#test"); - assert(iid2.get_server() == "irc.example.org"); - assert(iid2.is_channel); - assert(!iid2.is_user); - - Iid iid3("%irc.example.org"); - std::cout << std::to_string(iid3) << std::endl; - assert(std::to_string(iid3) == "%irc.example.org"); - assert(iid3.get_local() == ""); - assert(iid3.get_server() == "irc.example.org"); - assert(iid3.is_channel); - assert(!iid3.is_user); - - Iid iid4("irc.example.org"); - std::cout << std::to_string(iid4) << std::endl; - assert(std::to_string(iid4) == "irc.example.org"); - assert(iid4.get_local() == ""); - assert(iid4.get_server() == "irc.example.org"); - assert(!iid4.is_channel); - assert(!iid4.is_user); - - Iid iid5("nick!"); - std::cout << std::to_string(iid5) << std::endl; - assert(std::to_string(iid5) == "nick!"); - assert(iid5.get_local() == "nick"); - assert(iid5.get_server() == ""); - assert(!iid5.is_channel); - assert(iid5.is_user); - - Iid iid6("##channel%"); - std::cout << std::to_string(iid6) << std::endl; - assert(std::to_string(iid6) == "##channel%"); - assert(iid6.get_local() == "##channel"); - assert(iid6.get_server() == ""); - assert(iid6.is_channel); - assert(!iid6.is_user); - } - - { - std::cout << color << "Testing IID parsing with a fixed server configured…" << reset << std::endl; - // Now do the same tests, but with a configured fixed_irc_server - Config::set("fixed_irc_server", "fixed.example.com", false); - - Iid iid1("foo!irc.example.org"); - std::cout << std::to_string(iid1) << std::endl; - assert(std::to_string(iid1) == "foo!"); - assert(iid1.get_local() == "foo"); - assert(iid1.get_server() == "fixed.example.com"); - assert(!iid1.is_channel); - assert(iid1.is_user); - - Iid iid2("#test%irc.example.org"); - std::cout << std::to_string(iid2) << std::endl; - assert(std::to_string(iid2) == "#test%irc.example.org"); - assert(iid2.get_local() == "#test%irc.example.org"); - assert(iid2.get_server() == "fixed.example.com"); - assert(iid2.is_channel); - assert(!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!"); - std::cout << std::to_string(iid5) << std::endl; - assert(std::to_string(iid5) == "nick!"); - assert(iid5.get_local() == "nick"); - assert(iid5.get_server() == "fixed.example.com"); - assert(!iid5.is_channel); - assert(iid5.is_user); - - Iid iid6("##channel%"); - std::cout << std::to_string(iid6) << std::endl; - assert(std::to_string(iid6) == "##channel%"); - assert(iid6.get_local() == "##channel%"); - assert(iid6.get_server() == "fixed.example.com"); - assert(iid6.is_channel); - assert(!iid6.is_user); - } -#ifdef USE_DATABASE - { - std::cout << color << "Testing the Database…" << reset << std::endl; - // Remove any potential existing db - unlink("./test.db"); - Config::set("db_name", "test.db"); - Database::set_verbose(true); - 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 - assert(1 == Database::count<db::IrcServerOptions>()); - - b.update(); - assert(2 == Database::count<db::IrcServerOptions>()); - - assert(b.pass == ""); - assert(b.pass.value() == ""); - } -#endif - { - std::cout << color << "Testing the xdg_path function…" << reset << std::endl; - std::string res; - - ::unsetenv("XDG_CONFIG_HOME"); - ::unsetenv("HOME"); - res = xdg_config_path("coucou.txt"); - std::cout << res << std::endl; - assert(res == "coucou.txt"); - - ::setenv("HOME", "/home/user", 1); - res = xdg_config_path("coucou.txt"); - std::cout << res << std::endl; - assert(res == "/home/user/.config/biboumi/coucou.txt"); - - ::setenv("XDG_CONFIG_HOME", "/some_weird_dir", 1); - res = xdg_config_path("coucou.txt"); - std::cout << res << std::endl; - assert(res == "/some_weird_dir/biboumi/coucou.txt"); - - ::setenv("XDG_DATA_HOME", "/datadir", 1); - res = xdg_data_path("bonjour.txt"); - std::cout << res << std::endl; - assert(res == "/datadir/biboumi/bonjour.txt"); - } - - - { - std::cout << color << "Testing the DNS resolver…" << reset << std::endl; - - 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<Poller>(); - - 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) - { - std::cout << "Failed to resolve " << hostname << ":" << port << ": " << msg << std::endl; - success = false; - }; - auto success_cb = [&success, &hostname, &port](const struct addrinfo* addr) - { - std::cout << "Successfully resolved " << hostname << ":" << port << ": " << addr_to_string(addr) << std::endl; - success = true; - }; - - hostname = "example.com"; - resolver.resolve(hostname, port, - success_cb, error_cb); - loop(); - assert(success); - - hostname = "this.should.fail.because.it.is..misformatted"; - resolver.resolve(hostname, port, - success_cb, error_cb); - loop(); - assert(!success); - - hostname = "this.should.fail.because.it.is.does.not.exist.invalid"; - resolver.resolve(hostname, port, - success_cb, error_cb); - loop(); - assert(!success); - - hostname = "localhost6"; - resolver.resolve(hostname, port, - success_cb, error_cb); - loop(); - assert(success); - - hostname = "localhost"; - resolver.resolve(hostname, port, - success_cb, error_cb); - loop(); - assert(success); - -#ifdef CARES_FOUND - DNSHandler::instance.destroy(); -#endif - } - - std::cout << success_color << "All test passed successfully!" << reset << std::endl; - return 0; -} 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 <bridge/colors.hpp> +#include <xmpp/xmpp_stanza.hpp> + +#include <memory> + +TEST_CASE("IRC colors parsing") +{ + std::unique_ptr<XmlNode> xhtml; + std::string cleaned_up; + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("bold"); + CHECK(xhtml); + CHECK(xhtml->to_string() == "<body xmlns='http://www.w3.org/1999/xhtml'><span style='font-weight:bold;'>bold</span></body>"); + + 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() == "<body xmlns='http://www.w3.org/1999/xhtml'>normal<span style='font-weight:bold;'>bold</span><span style='font-weight:bold;text-decoration:underline;'>under-and-bold</span><span style='font-weight:bold;'>bold</span> normal<span style='color:red;'>red</span><span style='background-color:red;'>default-on-red</span><span style='color:cyan;background-color:blue;'>cyan-on-blue</span></body>"); + 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() == "<body xmlns='http://www.w3.org/1999/xhtml'>[<span style='font-style:italic;'/><span style='font-style:italic;color:lightmagenta;'>dolphin-emu/dolphin</span><span style='color:lightmagenta;'>] </span><span style='color:green;'>foo</span> commented on #283 (Add support for the guide button to XInput): <span style='text-decoration:underline;'/><span style='text-decoration:underline;color:blue;'>http://example.com</span><span style='text-decoration:underline;'/></body>"); + 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() == "<body xmlns='http://www.w3.org/1999/xhtml'>0e46ab by <span style='color:green;'>Pierre Dindon</span> [<span style='color:lightgreen;'>0</span>|<span style='color:lightgreen;'>1</span>|<span style='color:indianred;'>0</span>] <span style='text-decoration:underline;'/><span style='text-decoration:underline;color:blue;'>http://example.net/Ojrh4P</span><span style='text-decoration:underline;'/> media: avoid pop-in effect when loading thumbnails by specifying an explicit size</body>"); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("test\ncoucou"); + CHECK(cleaned_up == "test\ncoucou"); + CHECK(xhtml->to_string() == "<body xmlns='http://www.w3.org/1999/xhtml'>test<br/>coucou</body>"); +} 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 <config/config.hpp> + +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 <database/database.hpp> + +#include <unistd.h> +#include <config/config.hpp> + +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<db::IrcServerOptions>()); + + b.update(); + CHECK(2 == Database::count<db::IrcServerOptions>()); + + 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 <network/dns_handler.hpp> +#include <network/resolver.hpp> +#include <network/poller.hpp> + +#include <utils/timed_events.hpp> + +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<Poller>(); + + 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 <utils/encoding.hpp> + + +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 <irc/iid.hpp> +#include <irc/irc_user.hpp> + +#include <config/config.hpp> + +TEST_CASE("Irc user parsing") +{ + const std::map<char, char> 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<Iid> + { + 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 <xmpp/jid.hpp> +#include <louloulibs.h> + +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 <utils/timed_events.hpp> + +/** + * Let Catch know how to display std::chrono::duration values + */ +namespace Catch +{ + template<typename Rep, typename Period> struct StringMaker<std::chrono::duration<Rep, Period>> + { + static std::string convert(const std::chrono::duration<Rep, Period>& value) + { + return std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(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 <thread> + +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 <utils/tolower.hpp> +#include <utils/revstr.hpp> +#include <utils/string.hpp> +#include <utils/split.hpp> +#include <utils/xdg.hpp> + +TEST_CASE("String split") +{ + std::vector<std::string> 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 <xmpp/xmpp_component.hpp> + +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 <xmpp/xmpp_parser.hpp> + +TEST_CASE("Test basic XML parsing") +{ + XmppParser xml; + + const std::string doc = "<stream xmlns='stream_ns'><stanza b='c'>inner<child1><grandchild/></child1><child2 xmlns='child2_ns'/>tail</stanza></stream>"; + + 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 = "<stream xmlns='s'><stanza>coucou\r\n\a</stanza></stream>"; + 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'<cc>/&\"gaga\""; + CHECK(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); + CHECK(xml_unescape(xml_escape(unescaped)) == unescaped); +} + |