diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e669365..deab88e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,15 +96,15 @@ jobs: - name: Build tests run: | - cmake -S test\\unit -B test\\unit\\build -A ${{ matrix.architecture }} ^ + cmake -S test -B test\\build -A ${{ matrix.architecture }} ^ -DCMAKE_CXX_FLAGS="${{ env.CXXFLAGS }}" -DCMAKE_EXE_LINKER_FLAGS="${{ env.LDFLAGS }}" ^ -DCMAKE_CXX_STANDARD="${{ matrix.cxxstd }}" -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" ^ -DBoost_INCLUDE_DIR="${{ github.workspace }}\\boost_${{ env.BOOST_DIR_VER_NAME }}" - cmake --build test\\unit\\build -j 4 + cmake --build test\\build -j 4 - name: Run tests run: | - .\\test\\unit\\build\\${{ matrix.build-type }}\\mqtt-test.exe --log_level=test_suite + .\\test\\build\\${{ matrix.build-type }}\\mqtt-test.exe --log_level=test_suite posix: name: "${{ matrix.toolset }} std=c++${{ matrix.cxxstd }} ${{ matrix.build-type }}" @@ -193,11 +193,11 @@ jobs: - name: Build tests run: | - cmake -S test/unit -B test/unit/build \ + cmake -S test -B test/build \ -DCMAKE_CXX_COMPILER="${{ matrix.compiler }}" -DCMAKE_CXX_FLAGS="${{ env.CXXFLAGS }}" \ -DCMAKE_CXX_STANDARD="${{ matrix.cxxstd }}" -DCMAKE_EXE_LINKER_FLAGS="${{ env.LDFLAGS }}" -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ -DBoost_INCLUDE_DIR="/usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}" - cmake --build test/unit/build -j 4 + cmake --build test/build -j 4 - name: Run tests - run: ./test/unit/build/mqtt-test --log_level=test_suite + run: ./test/build/mqtt-test --log_level=test_suite diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 62ee522..13a3809 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -60,17 +60,17 @@ jobs: - name: Build tests run: | - cmake -S test/unit -B test/unit/build -DCMAKE_CXX_COMPILER="${{ matrix.compiler }}" -DCMAKE_CXX_FLAGS="${{ matrix.cxxflags }}" \ + cmake -S test -B test/build -DCMAKE_CXX_COMPILER="${{ matrix.compiler }}" -DCMAKE_CXX_FLAGS="${{ matrix.cxxflags }}" \ -DCMAKE_EXE_LINKER_FLAGS="${{ matrix.ldflags }}" -DCMAKE_BUILD_TYPE="Coverage" \ -DBoost_INCLUDE_DIR="/usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}" - cmake --build test/unit/build -j 4 + cmake --build test/build -j 4 - name: Run tests - run: ./test/unit/build/mqtt-test --log_level=test_suite + run: ./test/build/mqtt-test --log_level=test_suite - name: Generate Coverage Report run: | - lcov --capture --output-file coverage.info --directory test/unit/build + lcov --capture --output-file coverage.info --directory test/build lcov --remove coverage.info '/usr/include/*' --output-file coverage.info lcov --remove coverage.info '**/test/*' --output-file coverage.info lcov --remove coverage.info '**/boost/*' --output-file coverage.info diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..ca846ea --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.15) + +project(async-mqtt5-tests CXX) + +include(../cmake/project-is-top-level.cmake) + +if(PROJECT_IS_TOP_LEVEL) + find_package(async-mqtt5 REQUIRED) + enable_testing() +endif() + +file(GLOB integration_tests "integration/*.cpp") +file(GLOB unit_tests "unit/*.cpp") + +add_executable(mqtt-test src/run_tests.cpp ${integration_tests} ${unit_tests}) + +target_include_directories(mqtt-test PRIVATE include) +target_compile_definitions(mqtt-test PRIVATE BOOST_TEST_NO_MAIN=1) + +find_package(OpenSSL REQUIRED) +target_link_libraries(mqtt-test PRIVATE Async::MQTT5 OpenSSL::SSL) + +add_test(NAME mqtt-test COMMAND mqtt-test) diff --git a/test/unit/include/test_common/delayed_op.hpp b/test/include/test_common/delayed_op.hpp similarity index 100% rename from test/unit/include/test_common/delayed_op.hpp rename to test/include/test_common/delayed_op.hpp diff --git a/test/unit/include/test_common/message_exchange.hpp b/test/include/test_common/message_exchange.hpp similarity index 100% rename from test/unit/include/test_common/message_exchange.hpp rename to test/include/test_common/message_exchange.hpp diff --git a/test/unit/include/test_common/packet_util.hpp b/test/include/test_common/packet_util.hpp similarity index 100% rename from test/unit/include/test_common/packet_util.hpp rename to test/include/test_common/packet_util.hpp diff --git a/test/unit/include/test_common/protocol_logging.hpp b/test/include/test_common/protocol_logging.hpp similarity index 100% rename from test/unit/include/test_common/protocol_logging.hpp rename to test/include/test_common/protocol_logging.hpp diff --git a/test/unit/include/test_common/test_broker.hpp b/test/include/test_common/test_broker.hpp similarity index 81% rename from test/unit/include/test_common/test_broker.hpp rename to test/include/test_common/test_broker.hpp index 8d4e1fe..668d573 100644 --- a/test/unit/include/test_common/test_broker.hpp +++ b/test/include/test_common/test_broker.hpp @@ -288,61 +288,4 @@ private: } // end namespace async_mqtt5::test - - -// Funs temporarily moved out of network service -// -//void process_packet(const std::string& packet) { -// using enum control_code_e; -// -// auto code = extract_code(uint8_t(*packet.data())); -// if (code == connack) -// determine_network_properties(packet); -// else if (code == puback || code == pubcomp) -// _num_outgoing_publishes--; -//} -// -//void determine_network_properties(const std::string& connack) { -// auto begin = connack.cbegin() + 1 /* fixed header */; -// auto _ = decoders::type_parse(begin, connack.cend(), decoders::basic::varint_); -// auto rv = decoders::decode_connack(connack.size(), begin); -// const auto& [session_present, reason_code, ca_props] = *rv; -// -// if (ca_props[prop::receive_maximum]) -// _max_receive = *ca_props[prop::receive_maximum]; -// else -// _max_receive = MAX_LIMIT; -//} -// -//void count_outgoing_publishes( -// const std::vector& packets -//) { -// for (const auto& packet : packets) { -// auto code = extract_code(uint8_t(*packet.data())); -// if (code == control_code_e::publish) { -// auto flags = *packet.data() & 0b00001111; -// auto qos = extract_qos(flags); -// -// if (qos != qos_e::at_most_once) -// _num_outgoing_publishes++; -// -// BOOST_ASIO_CHECK_MESSAGE( -// _num_outgoing_publishes <= _max_receive, -// "There are more outgoing PUBLISH packets than\ -// it is allowed by the Maxmimum Receive Limit!" -// ); -// } -// } -//} - - -// write_to_network stuff - -//auto packets = to_packets(buffers); -//count_outgoing_publishes(packets); -//// TODO: this is just for debug right now -//if (!write_ec) -// for (const auto& packet : packets) -// test::log(to_readable_packet(packet, write_ec, false)); - #endif // ASYNC_MQTT5_TEST_TEST_BROKER_HPP diff --git a/test/unit/include/test_common/test_service.hpp b/test/include/test_common/test_service.hpp similarity index 100% rename from test/unit/include/test_common/test_service.hpp rename to test/include/test_common/test_service.hpp diff --git a/test/unit/include/test_common/test_stream.hpp b/test/include/test_common/test_stream.hpp similarity index 100% rename from test/unit/include/test_common/test_stream.hpp rename to test/include/test_common/test_stream.hpp diff --git a/test/unit/test/cancellation.cpp b/test/integration/cancellation.cpp similarity index 98% rename from test/unit/test/cancellation.cpp rename to test/integration/cancellation.cpp index 4d50ad0..0ca3514 100644 --- a/test/unit/test/cancellation.cpp +++ b/test/integration/cancellation.cpp @@ -190,8 +190,7 @@ BOOST_AUTO_TEST_CASE(rerunning_the_client) { using client_type = mqtt_client; client_type c(ioc, ""); - c.brokers("broker.hivemq.com", 1883) - .credentials("test-cli", "", "") + c.brokers("broker.hivemq.com,broker.hivemq.com", 1883) // to avoid reconnect backoff .run(); auto [ec] = co_await c.async_publish( diff --git a/test/unit/test/coroutine.cpp b/test/integration/coroutine.cpp similarity index 95% rename from test/unit/test/coroutine.cpp rename to test/integration/coroutine.cpp index 48a969e..7165cf2 100644 --- a/test/unit/test/coroutine.cpp +++ b/test/integration/coroutine.cpp @@ -149,8 +149,7 @@ BOOST_AUTO_TEST_CASE(websocket_tcp_client_check) { using client_type = mqtt_client; client_type c(ioc, ""); - c.credentials("websocket-tcp-tester", "", "") - .brokers("broker.hivemq.com/mqtt", 8000) + c.brokers("broker.hivemq.com/mqtt", 8000) .will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once }) .run(); @@ -186,8 +185,7 @@ BOOST_AUTO_TEST_CASE(openssl_tls_client_check) { using client_type = mqtt_client; client_type c(ioc, "", std::move(tls_context)); - c.credentials("openssl-tls-tester", "", "") - .brokers("broker.hivemq.com", 8883) + c.brokers("broker.hivemq.com", 8883) .will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once }) .run(); @@ -225,8 +223,7 @@ BOOST_AUTO_TEST_CASE(websocket_tls_client_check) { using client_type = mqtt_client; client_type c(ioc, "", std::move(tls_context)); - c.credentials("websocket-tls-tester", "", "") - .brokers("broker.hivemq.com/mqtt", 8884) + c.brokers("broker.hivemq.com/mqtt", 8884) .will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once }) .run(); diff --git a/test/unit/test/publish_rec_op.cpp b/test/integration/malformed_packet.cpp similarity index 64% rename from test/unit/test/publish_rec_op.cpp rename to test/integration/malformed_packet.cpp index e9e2e7b..0030860 100644 --- a/test/unit/test/publish_rec_op.cpp +++ b/test/integration/malformed_packet.cpp @@ -1,193 +1,14 @@ #include -#include -#include - #include -#include - #include "test_common/message_exchange.hpp" #include "test_common/test_service.hpp" #include "test_common/test_stream.hpp" using namespace async_mqtt5; -BOOST_AUTO_TEST_SUITE(publish_rec_op/*, *boost::unit_test::disabled()*/) - -template -void receive_publish() { - using test::after; - using std::chrono_literals::operator ""ms; - - constexpr int expected_handlers_called = 1; - int handlers_called = 0; - - std::string topic = "topic"; - std::string payload = "payload"; - - // packets - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt - ); - auto connack = encoders::encode_connack( - false, reason_codes::success.value(), {} - ); - - auto publish = encoders::encode_publish( - 1, topic, payload, qos, retain_e::no, dup_e::no, {} - ); - - auto puback = encoders::encode_puback(1, reason_codes::success.value(), {}); - - auto pubrec = encoders::encode_pubrec(1, reason_codes::success.value(), {}); - auto pubrel = encoders::encode_pubrel(1, reason_codes::success.value(), {}); - auto pubcomp = encoders::encode_pubcomp(1, reason_codes::success.value(), {}); - - test::msg_exchange broker_side; - error_code success {}; - - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish, after(10ms)); - - if constexpr (qos == qos_e::at_least_once) { - broker_side - .expect(puback) - .complete_with(success, after(1ms)); - } else if constexpr (qos == qos_e::exactly_once) { - broker_side - .expect(pubrec) - .complete_with(success, after(1ms)) - .reply_with(pubrel, after(2ms)) - .expect(pubcomp) - .complete_with(success, after(1ms)); - } - - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); - - using client_type = mqtt_client; - client_type c(executor, ""); - c.brokers("127.0.0.1") - .run(); - - c.async_receive( - [&]( - error_code ec, - std::string rec_topic, std::string rec_payload, - publish_props - ) { - ++handlers_called; - - BOOST_CHECK_MESSAGE(!ec, ec.message()); - BOOST_CHECK_EQUAL(topic, rec_topic); - BOOST_CHECK_EQUAL(payload, rec_payload); - - c.cancel(); - } - ); - - ioc.run_for(std::chrono::seconds(10)); - BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); - BOOST_CHECK(broker.received_all_expected()); -} - -BOOST_AUTO_TEST_CASE(receive_publish_qos0) { - receive_publish(); -} - -BOOST_AUTO_TEST_CASE(receive_publish_qos1) { - receive_publish(); -} - -BOOST_AUTO_TEST_CASE(receive_publish_qos2) { - receive_publish(); -} - -BOOST_AUTO_TEST_CASE(test_reconnect) { - using test::after; - using std::chrono_literals::operator ""ms; - - constexpr int expected_handlers_called = 1; - int handlers_called = 0; - - std::string topic = "topic"; - std::string payload = "payload"; - - // packets - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt - ); - auto connack = encoders::encode_connack( - false, reason_codes::success.value(), {} - ); - - auto publish = encoders::encode_publish( - 1, topic, payload, qos_e::exactly_once, retain_e::yes, dup_e::no, {} - ); - - auto pubrec = encoders::encode_pubrec(1, reason_codes::success.value(), {}); - auto pubrel = encoders::encode_pubrel(1, reason_codes::success.value(), {}); - auto pubcomp = encoders::encode_pubcomp(1, reason_codes::success.value(), {}); - - test::msg_exchange broker_side; - error_code success {}; - error_code fail = asio::error::not_connected; - - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish, after(10ms)) - .expect(pubrec) - .complete_with(success, after(1ms)) - .reply_with(pubrel, after(2ms)) - .expect(pubcomp) - .complete_with(fail, after(1ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(pubrel, after(10ms)) - .expect(pubcomp) - .complete_with(success, after(1ms)); - - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); - - using client_type = mqtt_client; - client_type c(executor, ""); - c.brokers("127.0.0.1") - .run(); - - c.async_receive( - [&]( - error_code ec, - std::string rec_topic, std::string rec_payload, - publish_props - ) { - ++handlers_called; - - BOOST_CHECK_MESSAGE(!ec, ec.message()); - BOOST_CHECK_EQUAL(topic, rec_topic); - BOOST_CHECK_EQUAL(payload, rec_payload); - - c.cancel(); - } - ); - - ioc.run_for(std::chrono::seconds(10)); - BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); - BOOST_CHECK(broker.received_all_expected()); -} +BOOST_AUTO_TEST_SUITE(malformed_packet/* , *boost::unit_test::disabled()*/) BOOST_AUTO_TEST_CASE(test_malformed_publish) { using test::after; @@ -230,11 +51,11 @@ BOOST_AUTO_TEST_CASE(test_malformed_publish) { using client_type = mqtt_client; client_type c(executor, ""); - c.brokers("127.0.0.1") + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff .run(); asio::steady_timer timer(c.get_executor()); - timer.expires_after(std::chrono::seconds(4)); + timer.expires_after(std::chrono::seconds(2)); timer.async_wait([&](auto) { c.cancel(); }); ioc.run(); @@ -303,7 +124,7 @@ BOOST_AUTO_TEST_CASE(test_malformed_pubrel, *boost::unit_test::disabled()) { using client_type = mqtt_client; client_type c(executor, ""); - c.brokers("127.0.0.1") + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff .run(); c.async_receive( @@ -322,7 +143,180 @@ BOOST_AUTO_TEST_CASE(test_malformed_pubrel, *boost::unit_test::disabled()) { } ); - ioc.run_for(std::chrono::seconds(10)); + ioc.run_for(std::chrono::seconds(2)); + BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); + BOOST_CHECK(broker.received_all_expected()); +} + +BOOST_AUTO_TEST_CASE(malformed_puback) { + using test::after; + using std::chrono_literals::operator ""ms; + + constexpr int expected_handlers_called = 1; + int handlers_called = 0; + + // packets + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt + ); + auto connack = encoders::encode_connack(false, reason_codes::success.value(), {}); + + auto publish = encoders::encode_publish( + 1, "t", "p", qos_e::at_least_once, retain_e::no, dup_e::no, {} + ); + auto malformed_puback = encoders::encode_puback(1, uint8_t(0x04), {}); + + auto publish_dup = encoders::encode_publish( + 1, "t", "p", qos_e::at_least_once, retain_e::no, dup_e::yes, {} + ); + auto puback = encoders::encode_puback(1, reason_codes::success.value(), {}); + + disconnect_props dc_props; + dc_props[prop::reason_string] = "Malformed PUBACK: invalid Reason Code"; + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), dc_props + ); + + test::msg_exchange broker_side; + error_code success {}; + + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(publish) + .complete_with(success, after(0ms)) + .reply_with(malformed_puback, after(0ms)) + .expect(disconnect) + .complete_with(success, after(0ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(publish_dup) + .complete_with(success, after(0ms)) + .reply_with(puback, after(0ms)); + + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); + + using client_type = mqtt_client; + client_type c(executor, ""); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .run(); + + c.async_publish( + "t", "p", retain_e::no, publish_props {}, + [&](error_code ec, reason_code rc, auto) { + ++handlers_called; + + BOOST_CHECK(!ec); + BOOST_CHECK_EQUAL(rc, reason_codes::success); + + c.cancel(); + } + ); + + ioc.run_for(std::chrono::seconds(2)); + BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); + BOOST_CHECK(broker.received_all_expected()); +} + + +BOOST_AUTO_TEST_CASE(malformed_pubrec_pubcomp) { + using test::after; + using std::chrono_literals::operator ""ms; + + constexpr int expected_handlers_called = 1; + int handlers_called = 0; + + // packets + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt + ); + auto connack = encoders::encode_connack(false, reason_codes::success.value(), {}); + + auto publish = encoders::encode_publish( + 1, "t", "p", qos_e::exactly_once, retain_e::no, dup_e::no, {} + ); + auto malformed_pubrec = encoders::encode_pubrec(1, uint8_t(0x04), {}); + + auto publish_dup = encoders::encode_publish( + 1, "t", "p", qos_e::exactly_once, retain_e::no, dup_e::yes, {} + ); + auto pubrec = encoders::encode_pubrec(1, reason_codes::success.value(), {}); + + auto pubrel = encoders::encode_pubrel(1, reason_codes::success.value(), {}); + auto malformed_pubcomp = encoders::encode_pubcomp(1, uint8_t(0x04), {}); + auto pubcomp = encoders::encode_pubcomp(1, reason_codes::success.value(), {}); + + disconnect_props dc_props; + dc_props[prop::reason_string] = "Malformed PUBREC: invalid Reason Code"; + auto disconnect_on_pubrec = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), dc_props + ); + + dc_props[prop::reason_string] = "Malformed PUBCOMP: invalid Reason Code"; + auto disconnect_on_pubcomp = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), dc_props + ); + + test::msg_exchange broker_side; + error_code success {}; + + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(publish) + .complete_with(success, after(0ms)) + .reply_with(malformed_pubrec, after(0ms)) + .expect(disconnect_on_pubrec) + .complete_with(success, after(0ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(publish_dup) + .complete_with(success, after(0ms)) + .reply_with(pubrec, after(0ms)) + .expect(pubrel) + .complete_with(success, after(0ms)) + .reply_with(malformed_pubcomp, after(0ms)) + .expect(disconnect_on_pubcomp) + .complete_with(success, after(0ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(pubrel) + .complete_with(success, after(0ms)) + .reply_with(pubcomp, after(0ms)); + + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); + + using client_type = mqtt_client; + client_type c(executor, ""); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .run(); + + c.async_publish( + "t", "p", retain_e::no, publish_props {}, + [&](error_code ec, reason_code rc, auto) { + ++handlers_called; + + BOOST_CHECK(!ec); + BOOST_CHECK_EQUAL(rc, reason_codes::success); + + c.cancel(); + } + ); + + ioc.run_for(std::chrono::seconds(6)); BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); BOOST_CHECK(broker.received_all_expected()); } diff --git a/test/integration/publish_receive.cpp b/test/integration/publish_receive.cpp new file mode 100644 index 0000000..5a2470f --- /dev/null +++ b/test/integration/publish_receive.cpp @@ -0,0 +1,190 @@ +#include + +#include +#include + +#include + +#include "test_common/message_exchange.hpp" +#include "test_common/test_service.hpp" +#include "test_common/test_stream.hpp" + +using namespace async_mqtt5; + +BOOST_AUTO_TEST_SUITE(publish_receive/*, *boost::unit_test::disabled()*/) + +template +void receive_publish() { + using test::after; + using std::chrono_literals::operator ""ms; + + constexpr int expected_handlers_called = 1; + int handlers_called = 0; + + std::string topic = "topic"; + std::string payload = "payload"; + + // packets + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt + ); + auto connack = encoders::encode_connack( + false, reason_codes::success.value(), {} + ); + + auto publish = encoders::encode_publish( + 1, topic, payload, qos, retain_e::no, dup_e::no, {} + ); + + auto puback = encoders::encode_puback(1, reason_codes::success.value(), {}); + + auto pubrec = encoders::encode_pubrec(1, reason_codes::success.value(), {}); + auto pubrel = encoders::encode_pubrel(1, reason_codes::success.value(), {}); + auto pubcomp = encoders::encode_pubcomp(1, reason_codes::success.value(), {}); + + test::msg_exchange broker_side; + error_code success {}; + + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish, after(10ms)); + + if constexpr (qos == qos_e::at_least_once) { + broker_side + .expect(puback) + .complete_with(success, after(1ms)); + } else if constexpr (qos == qos_e::exactly_once) { + broker_side + .expect(pubrec) + .complete_with(success, after(1ms)) + .reply_with(pubrel, after(2ms)) + .expect(pubcomp) + .complete_with(success, after(1ms)); + } + + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); + + using client_type = mqtt_client; + client_type c(executor, ""); + c.brokers("127.0.0.1") + .run(); + + c.async_receive( + [&]( + error_code ec, + std::string rec_topic, std::string rec_payload, + publish_props + ) { + ++handlers_called; + + BOOST_CHECK_MESSAGE(!ec, ec.message()); + BOOST_CHECK_EQUAL(topic, rec_topic); + BOOST_CHECK_EQUAL(payload, rec_payload); + + c.cancel(); + } + ); + + ioc.run_for(std::chrono::seconds(10)); + BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); + BOOST_CHECK(broker.received_all_expected()); +} + +BOOST_AUTO_TEST_CASE(test_receive_publish_qos0) { + receive_publish(); +} + +BOOST_AUTO_TEST_CASE(test_receive_publish_qos1) { + receive_publish(); +} + +BOOST_AUTO_TEST_CASE(test_receive_publish_qos2) { + receive_publish(); +} + +BOOST_AUTO_TEST_CASE(test_waiting_on_pubrel) { + using test::after; + using std::chrono_literals::operator ""ms; + + constexpr int expected_handlers_called = 1; + int handlers_called = 0; + + std::string topic = "topic"; + std::string payload = "payload"; + + // packets + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt + ); + auto connack = encoders::encode_connack( + false, reason_codes::success.value(), {} + ); + + auto publish = encoders::encode_publish( + 1, topic, payload, qos_e::exactly_once, retain_e::yes, dup_e::no, {} + ); + + auto pubrec = encoders::encode_pubrec(1, reason_codes::success.value(), {}); + auto pubrel = encoders::encode_pubrel(1, reason_codes::success.value(), {}); + auto pubcomp = encoders::encode_pubcomp(1, reason_codes::success.value(), {}); + + test::msg_exchange broker_side; + error_code success {}; + error_code fail = asio::error::not_connected; + + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish, after(10ms)) + .expect(pubrec) + .complete_with(success, after(1ms)) + .reply_with(pubrel, after(2ms)) + .expect(pubcomp) + .complete_with(fail, after(1ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(pubrel, after(10ms)) + .expect(pubcomp) + .complete_with(success, after(1ms)); + + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); + + using client_type = mqtt_client; + client_type c(executor, ""); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .run(); + + c.async_receive( + [&]( + error_code ec, + std::string rec_topic, std::string rec_payload, + publish_props + ) { + ++handlers_called; + + BOOST_CHECK_MESSAGE(!ec, ec.message()); + BOOST_CHECK_EQUAL(topic, rec_topic); + BOOST_CHECK_EQUAL(payload, rec_payload); + + c.cancel(); + } + ); + + ioc.run_for(std::chrono::seconds(10)); + BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); + BOOST_CHECK(broker.received_all_expected()); +} + +BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/unit/test/client_broker.cpp b/test/integration/publish_send.cpp similarity index 54% rename from test/unit/test/client_broker.cpp rename to test/integration/publish_send.cpp index c40ef4b..d0e7406 100644 --- a/test/unit/test/client_broker.cpp +++ b/test/integration/publish_send.cpp @@ -1,240 +1,19 @@ #include -#include - #include #include -#include - -#include +#include #include "test_common/message_exchange.hpp" +#include "test_common/test_service.hpp" #include "test_common/test_stream.hpp" using namespace async_mqtt5; -BOOST_AUTO_TEST_SUITE(framework/*, *boost::unit_test::disabled()*/) +BOOST_AUTO_TEST_SUITE(publish_send/*, *boost::unit_test::disabled()*/) -BOOST_AUTO_TEST_CASE(publish_qos_0) { - using test::after; - using std::chrono_literals::operator ""ms; - - constexpr int expected_handlers_called = 1; - int handlers_called = 0; - - // packets - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt - ); - auto connack = encoders::encode_connack( - false, reason_codes::success.value(), {} - ); - auto publish_1 = encoders::encode_publish( - 1, "t", "p_1", qos_e::at_most_once, retain_e::no, dup_e::no, {} - ); - - test::msg_exchange broker_side; - error_code success {}; - - broker_side - .expect(connect) - .complete_with(success, after(10ms)) - .reply_with(connack, after(20ms)) - .expect(publish_1); - - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); - - using client_type = mqtt_client; - client_type c(executor, ""); - c.brokers("127.0.0.1") - .run(); - - c.async_publish( - "t", "p_1", retain_e::no, publish_props{}, - [&](error_code ec) { - BOOST_CHECK_MESSAGE(!ec, ec.message()); - ++handlers_called; - } - ); - - asio::steady_timer timer(c.get_executor()); - timer.expires_after(std::chrono::seconds(1)); - timer.async_wait([&](auto) { c.cancel(); }); - - - ioc.run(); - BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); - BOOST_CHECK(broker.received_all_expected()); -} - - -BOOST_AUTO_TEST_CASE(two_publishes_qos_1_with_fail_on_write) { - using test::after; - using std::chrono_literals::operator ""ms; - - constexpr int expected_handlers_called = 2; - int handlers_called = 0; - - // packets - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt - ); - auto connack = encoders::encode_connack( - false, reason_codes::success.value(), {} - ); - auto publish_1 = encoders::encode_publish( - 1, "t", "p_1", qos_e::at_least_once, retain_e::no, dup_e::no, {} - ); - auto puback_1 = encoders::encode_puback( - 1, reason_codes::success.value(), {} - ); - auto publish_2 = encoders::encode_publish( - 2, "t", "p_2", qos_e::at_least_once, retain_e::no, dup_e::no, {} - ); - auto puback_2 = encoders::encode_puback( - 2, reason_codes::success.value(), {} - ); - - test::msg_exchange broker_side; - error_code success {}; - error_code fail = asio::error::not_connected; - - broker_side - .expect(connect) - .complete_with(success, after(10ms)) - .reply_with(connack, after(10ms)) - .expect(publish_1, publish_2) - .complete_with(fail, after(10ms)) - .expect(connect) - .complete_with(success, after(10ms)) - .reply_with(connack, after(10ms)) - .expect(publish_1, publish_2) - .complete_with(success, after(10ms)) - .reply_with(puback_1, puback_2, after(20ms)); - - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); - - using client_type = mqtt_client; - client_type c(executor, ""); - c.brokers("127.0.0.1") - .run(); - - c.async_publish( - "t", "p_1", retain_e::no, publish_props{}, - [&](error_code ec, reason_code rc, auto) { - BOOST_CHECK_MESSAGE(!ec, ec.message()); - BOOST_CHECK_MESSAGE(!rc, rc.message()); - ++handlers_called; - } - ); - - c.async_publish( - "t", "p_2", retain_e::no, publish_props{}, - [&](error_code ec, reason_code rc, auto) { - BOOST_CHECK_MESSAGE(!ec, ec.message()); - BOOST_CHECK_MESSAGE(!rc, rc.message()); - ++handlers_called; - } - ); - - asio::steady_timer timer(c.get_executor()); - timer.expires_after(std::chrono::seconds(6)); - timer.async_wait([&](auto) { c.cancel(); }); - - ioc.run(); - BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); - BOOST_CHECK(broker.received_all_expected()); -} - - -BOOST_AUTO_TEST_CASE(send_publish_qos_2_with_fail_on_read) { - using test::after; - using std::chrono_literals::operator ""ms; - - constexpr int expected_handlers_called = 1; - int handlers_called = 0; - - // packets - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt - ); - auto connack = encoders::encode_connack( - false, reason_codes::success.value(), {} - ); - auto publish = encoders::encode_publish( - 1, "t_1", "p_1", qos_e::exactly_once, retain_e::no, dup_e::no, {} - ); - auto pubrec = encoders::encode_pubrec( - 1, reason_codes::success.value(), {} - ); - auto pubrel = encoders::encode_pubrel( - 1, reason_codes::success.value(), {} - ); - auto pubcomp = encoders::encode_pubcomp( - 1, reason_codes::success.value(), {} - ); - - test::msg_exchange broker_side; - error_code success {}; - error_code fail = asio::error::not_connected; - - broker_side - .expect(connect) - .complete_with(success, after(10ms)) - .reply_with(connack, after(20ms)) - .expect(publish) - .complete_with(success, after(10ms)) - .reply_with(pubrec, after(25ms)) - .expect(pubrel) - .complete_with(success, after(10ms)) - .reply_with(fail, after(10ms)) - .expect(connect) - .complete_with(success, after(10ms)) - .reply_with(connack, after(20ms)) - .expect(pubrel) - .complete_with(success, after(10ms)) - .reply_with(pubcomp, after(20ms)); - - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); - - using client_type = mqtt_client; - client_type c(executor, ""); - - c.brokers("127.0.0.1") - .run(); - - c.async_publish( - "t_1", "p_1", retain_e::no, publish_props{}, - [&](error_code ec, reason_code rc, auto) { - BOOST_CHECK_MESSAGE(!ec, ec.message()); - BOOST_CHECK_MESSAGE(!rc, rc.message()); - ++handlers_called; - } - ); - - asio::steady_timer timer(c.get_executor()); - timer.expires_after(std::chrono::seconds(7)); - timer.async_wait([&](auto) { c.cancel(); }); - - ioc.run(); - BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); - BOOST_CHECK(broker.received_all_expected()); -} - -BOOST_AUTO_TEST_CASE(test_ordering_after_reconnect) { +BOOST_AUTO_TEST_CASE(ordering_after_reconnect) { using test::after; using std::chrono_literals::operator ""ms; @@ -299,7 +78,7 @@ BOOST_AUTO_TEST_CASE(test_ordering_after_reconnect) { using client_type = mqtt_client; client_type c(executor, ""); - c.brokers("127.0.0.1") + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff .run(); c.async_publish( @@ -308,6 +87,9 @@ BOOST_AUTO_TEST_CASE(test_ordering_after_reconnect) { BOOST_CHECK_MESSAGE(!ec, ec.message()); BOOST_CHECK_MESSAGE(!rc, rc.message()); ++handlers_called; + + if (handlers_called == expected_handlers_called) + c.cancel(); } ); @@ -317,14 +99,13 @@ BOOST_AUTO_TEST_CASE(test_ordering_after_reconnect) { BOOST_CHECK_MESSAGE(!ec, ec.message()); BOOST_CHECK_MESSAGE(!rc, rc.message()); ++handlers_called; + + if (handlers_called == expected_handlers_called) + c.cancel(); } ); - asio::steady_timer timer(c.get_executor()); - timer.expires_after(std::chrono::seconds(7)); - timer.async_wait([&](auto) { c.cancel(); }); - - ioc.run(); + ioc.run_for(std::chrono::seconds(1)); BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); BOOST_CHECK(broker.received_all_expected()); } @@ -421,14 +202,12 @@ BOOST_AUTO_TEST_CASE(throttling) { BOOST_CHECK_MESSAGE(!rc, rc.message()); BOOST_CHECK_EQUAL(handlers_called, 2); ++handlers_called; + + c.cancel(); } ); - asio::steady_timer timer(c.get_executor()); - timer.expires_after(std::chrono::seconds(2)); - timer.async_wait([&](auto) { c.cancel(); }); - - ioc.run(); + ioc.run_for(std::chrono::seconds(1)); BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); BOOST_CHECK(broker.received_all_expected()); } @@ -519,4 +298,5 @@ BOOST_AUTO_TEST_CASE(cancel_multiple_ops) { BOOST_CHECK(broker.received_all_expected()); } -BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/integration/read_message.cpp b/test/integration/read_message.cpp new file mode 100644 index 0000000..34434da --- /dev/null +++ b/test/integration/read_message.cpp @@ -0,0 +1,150 @@ +#include + +#include +#include + +#include + +#include "test_common/message_exchange.hpp" +#include "test_common/test_service.hpp" +#include "test_common/test_stream.hpp" + +using namespace async_mqtt5; + +BOOST_AUTO_TEST_SUITE(read_message/*, *boost::unit_test::disabled()*/) + +void test_receive_malformed_packet( + std::string malformed_packet, std::string reason_string +) { + using test::after; + using std::chrono_literals::operator ""ms; + + // packets + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt + ); + connack_props co_props; + co_props[prop::maximum_packet_size] = 2000; + auto connack = encoders::encode_connack(false, reason_codes::success.value(), co_props); + + disconnect_props dc_props; + dc_props[prop::reason_string] = reason_string; + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), dc_props + ); + + test::msg_exchange broker_side; + error_code success {}; + + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(malformed_packet, after(5ms)) + .expect(disconnect) + .complete_with(success, after(0ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)); + + + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); + + using client_type = mqtt_client; + client_type c(executor, ""); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .run(); + + asio::steady_timer timer(c.get_executor()); + timer.expires_after(std::chrono::milliseconds(100)); + timer.async_wait([&](auto) { c.cancel(); }); + + ioc.run(); + BOOST_CHECK(broker.received_all_expected()); +} + +BOOST_AUTO_TEST_CASE(forbidden_packet_type) { + test_receive_malformed_packet( + std::string({ 0x00 }), + "Malformed Packet received from the Server" + ); +} + +BOOST_AUTO_TEST_CASE(malformed_varint) { + test_receive_malformed_packet( + std::string({ 0x10, -1 /* 0xFF */, -1, -1, -1 }), + "Malformed Packet received from the Server" + ); +} + +BOOST_AUTO_TEST_CASE(malformed_fixed_header) { + test_receive_malformed_packet( + std::string({ 0x60, 1, 0 }), + "Malformed Packet received from the Server" + ); +} + +BOOST_AUTO_TEST_CASE(packet_larger_than_allowed) { + test_receive_malformed_packet( + std::string({ 0x10, -1, -1, -1, 0 }), + "Malformed Packet received from the Server" + ); +} + +BOOST_AUTO_TEST_CASE(receive_malformed_publish) { + test_receive_malformed_packet( + std::string({ 0x30, 1, -1 }), + "Malformed PUBLISH received: cannot decode" + ); +} + +BOOST_AUTO_TEST_CASE(receive_disconnect) { + using test::after; + using std::chrono_literals::operator ""ms; + + // packets + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt + ); + auto connack = encoders::encode_connack(false, reason_codes::success.value(), {}); + + auto disconnect = encoders::encode_disconnect(0x00, {}); + + test::msg_exchange broker_side; + error_code success {}; + + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(disconnect, after(5ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)); + + + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); + + using client_type = mqtt_client; + client_type c(executor, ""); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .run(); + + asio::steady_timer timer(c.get_executor()); + timer.expires_after(std::chrono::milliseconds(100)); + timer.async_wait([&](auto) { c.cancel(); }); + + ioc.run(); + BOOST_CHECK(broker.received_all_expected()); + +} + +BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/integration/resending.cpp b/test/integration/resending.cpp new file mode 100644 index 0000000..d491bee --- /dev/null +++ b/test/integration/resending.cpp @@ -0,0 +1,312 @@ +#include + +#include + +#include "test_common/message_exchange.hpp" +#include "test_common/test_service.hpp" +#include "test_common/test_stream.hpp" + +using namespace async_mqtt5; + +BOOST_AUTO_TEST_SUITE(resending/* , *boost::unit_test::disabled()*/) + +BOOST_AUTO_TEST_CASE(resend_multiple_publishes) { + using test::after; + using std::chrono_literals::operator ""ms; + + constexpr int expected_handlers_called = 2; + int handlers_called = 0; + + // packets + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt + ); + auto connack = encoders::encode_connack( + false, reason_codes::success.value(), {} + ); + auto publish_1 = encoders::encode_publish( + 1, "t", "p_1", qos_e::at_least_once, retain_e::no, dup_e::no, {} + ); + auto puback_1 = encoders::encode_puback( + 1, reason_codes::success.value(), {} + ); + auto publish_2 = encoders::encode_publish( + 2, "t", "p_2", qos_e::at_least_once, retain_e::no, dup_e::no, {} + ); + auto puback_2 = encoders::encode_puback( + 2, reason_codes::success.value(), {} + ); + + test::msg_exchange broker_side; + error_code success {}; + error_code fail = asio::error::not_connected; + + broker_side + .expect(connect) + .complete_with(success, after(10ms)) + .reply_with(connack, after(10ms)) + .expect(publish_1, publish_2) + .complete_with(fail, after(10ms)) + .expect(connect) + .complete_with(success, after(10ms)) + .reply_with(connack, after(10ms)) + .expect(publish_1, publish_2) + .complete_with(success, after(10ms)) + .reply_with(puback_1, puback_2, after(20ms)); + + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); + + using client_type = mqtt_client; + client_type c(executor, ""); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .run(); + + c.async_publish( + "t", "p_1", retain_e::no, publish_props{}, + [&](error_code ec, reason_code rc, auto) { + BOOST_CHECK_MESSAGE(!ec, ec.message()); + BOOST_CHECK_MESSAGE(!rc, rc.message()); + ++handlers_called; + + if (handlers_called == expected_handlers_called) + c.cancel(); + } + ); + + c.async_publish( + "t", "p_2", retain_e::no, publish_props{}, + [&](error_code ec, reason_code rc, auto) { + BOOST_CHECK_MESSAGE(!ec, ec.message()); + BOOST_CHECK_MESSAGE(!rc, rc.message()); + ++handlers_called; + + if (handlers_called == expected_handlers_called) + c.cancel(); + } + ); + + ioc.run_for(std::chrono::seconds(6)); + BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); + BOOST_CHECK(broker.received_all_expected()); +} + +BOOST_AUTO_TEST_CASE(resend_pubrel) { + using test::after; + using std::chrono_literals::operator ""ms; + + constexpr int expected_handlers_called = 1; + int handlers_called = 0; + + // packets + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt + ); + auto connack = encoders::encode_connack( + false, reason_codes::success.value(), {} + ); + auto publish = encoders::encode_publish( + 1, "t_1", "p_1", qos_e::exactly_once, retain_e::no, dup_e::no, {} + ); + auto pubrec = encoders::encode_pubrec( + 1, reason_codes::success.value(), {} + ); + auto pubrel = encoders::encode_pubrel( + 1, reason_codes::success.value(), {} + ); + auto pubcomp = encoders::encode_pubcomp( + 1, reason_codes::success.value(), {} + ); + + test::msg_exchange broker_side; + error_code success {}; + error_code fail = asio::error::not_connected; + + broker_side + .expect(connect) + .complete_with(success, after(10ms)) + .reply_with(connack, after(20ms)) + .expect(publish) + .complete_with(success, after(10ms)) + .reply_with(pubrec, after(25ms)) + .expect(pubrel) + .complete_with(success, after(10ms)) + .reply_with(fail, after(10ms)) + .expect(connect) + .complete_with(success, after(10ms)) + .reply_with(connack, after(20ms)) + .expect(pubrel) + .complete_with(success, after(10ms)) + .reply_with(pubcomp, after(20ms)); + + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); + + using client_type = mqtt_client; + client_type c(executor, ""); + + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .run(); + + c.async_publish( + "t_1", "p_1", retain_e::no, publish_props{}, + [&](error_code ec, reason_code rc, auto) { + BOOST_CHECK_MESSAGE(!ec, ec.message()); + BOOST_CHECK_MESSAGE(!rc, rc.message()); + ++handlers_called; + c.cancel(); + } + ); + + ioc.run_for(std::chrono::seconds(6)); + BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); + BOOST_CHECK(broker.received_all_expected()); +} + +BOOST_AUTO_TEST_CASE(resend_subscribe) { + using test::after; + using std::chrono_literals::operator ""ms; + + constexpr int expected_handlers_called = 1; + int handlers_called = 0; + + // data + std::vector topics = { + subscribe_topic { "topic", subscribe_options {} } + }; + subscribe_props subscribe_props; + + std::vector rcs = { reason_codes::granted_qos_0.value() }; + suback_props suback_props; + + // packets + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt + ); + auto connack = encoders::encode_connack(false, reason_codes::success.value(), {}); + + auto subscribe = encoders::encode_subscribe(1, topics, subscribe_props); + auto suback = encoders::encode_suback(1, rcs, suback_props); + + test::msg_exchange broker_side; + error_code success {}; + error_code fail = asio::error::not_connected; + + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(subscribe) + .complete_with(fail, after(0ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(subscribe) + .complete_with(success, after(0ms)) + .reply_with(suback, after(0ms)); + + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); + + using client_type = mqtt_client; + client_type c(executor, ""); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .run(); + + c.async_subscribe( + topics, subscribe_props, + [&](error_code ec, std::vector rcs, auto) { + handlers_called++; + + BOOST_CHECK(!ec); + BOOST_ASSERT(rcs.size() == 1); + BOOST_CHECK_EQUAL(rcs[0], reason_codes::granted_qos_0); + + c.cancel(); + } + ); + + ioc.run_for(std::chrono::seconds(10)); + BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); + BOOST_CHECK(broker.received_all_expected()); +} + +BOOST_AUTO_TEST_CASE(resend_unsubscribe) { + using test::after; + using std::chrono_literals::operator ""ms; + + constexpr int expected_handlers_called = 1; + int handlers_called = 0; + + // data + std::vector topics = { "topic" }; + unsubscribe_props unsubscribe_props; + + std::vector rcs = { reason_codes::success.value() }; + unsuback_props unsuback_props; + + // packets + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt + ); + auto connack = encoders::encode_connack(false, reason_codes::success.value(), {}); + + auto unsubscribe = encoders::encode_unsubscribe(1, topics, unsubscribe_props); + auto unsuback = encoders::encode_unsuback(1, rcs, unsuback_props); + + test::msg_exchange broker_side; + error_code success {}; + error_code fail = asio::error::not_connected; + + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(unsubscribe) + .complete_with(fail, after(0ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(unsubscribe) + .complete_with(success, after(0ms)) + .reply_with(unsuback, after(0ms)); + + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); + + using client_type = mqtt_client; + client_type c(executor, ""); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .run(); + + c.async_unsubscribe( + topics, unsubscribe_props, + [&](error_code ec, std::vector rcs, auto) { + handlers_called++; + + BOOST_CHECK(!ec); + BOOST_ASSERT(rcs.size() == 1); + BOOST_CHECK_EQUAL(rcs[0], reason_codes::success); + + c.cancel(); + } + ); + + ioc.run_for(std::chrono::seconds(10)); + BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); + BOOST_CHECK(broker.received_all_expected()); +} + +BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/unit/src/run_tests.cpp b/test/src/run_tests.cpp similarity index 100% rename from test/unit/src/run_tests.cpp rename to test/src/run_tests.cpp diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt deleted file mode 100644 index 74130ca..0000000 --- a/test/unit/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -cmake_minimum_required(VERSION 3.15) - -project(async-mqtt5-tests CXX) - -include(../../cmake/project-is-top-level.cmake) - -if(PROJECT_IS_TOP_LEVEL) - find_package(async-mqtt5 REQUIRED) - enable_testing() -endif() - -add_executable( - mqtt-test - src/run_tests.cpp - test/cancellation.cpp - test/client_broker.cpp - test/compilation_checks.cpp - test/coroutine.cpp - test/disconnect_op.cpp - test/publish_rec_op.cpp - test/publish_send_op.cpp - test/serialization.cpp - test/session.cpp - test/string_validation.cpp - test/subscribe_op.cpp - test/unsubscribe_op.cpp -) - -target_include_directories(mqtt-test PRIVATE include) -target_compile_definitions(mqtt-test PRIVATE BOOST_TEST_NO_MAIN=1) - -find_package(OpenSSL REQUIRED) -target_link_libraries(mqtt-test PRIVATE Async::MQTT5 OpenSSL::SSL) - -add_test(NAME mqtt-test COMMAND mqtt-test) diff --git a/test/unit/test/compilation_checks.cpp b/test/unit/compilation_checks.cpp similarity index 100% rename from test/unit/test/compilation_checks.cpp rename to test/unit/compilation_checks.cpp diff --git a/test/unit/test/disconnect_op.cpp b/test/unit/disconnect_op.cpp similarity index 100% rename from test/unit/test/disconnect_op.cpp rename to test/unit/disconnect_op.cpp diff --git a/test/unit/test/publish_send_op.cpp b/test/unit/publish_send_op.cpp similarity index 63% rename from test/unit/test/publish_send_op.cpp rename to test/unit/publish_send_op.cpp index ae262f0..c12fb0d 100644 --- a/test/unit/test/publish_send_op.cpp +++ b/test/unit/publish_send_op.cpp @@ -5,14 +5,10 @@ #include #include -#include - #include #include -#include "test_common/message_exchange.hpp" #include "test_common/test_service.hpp" -#include "test_common/test_stream.hpp" using namespace async_mqtt5; @@ -304,177 +300,5 @@ BOOST_AUTO_TEST_CASE(test_publish_cancellation) { BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); } -BOOST_AUTO_TEST_CASE(test_malformed_puback) { - using test::after; - using std::chrono_literals::operator ""ms; - - constexpr int expected_handlers_called = 1; - int handlers_called = 0; - - // packets - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt - ); - auto connack = encoders::encode_connack(false, reason_codes::success.value(), {}); - - auto publish = encoders::encode_publish( - 1, "t", "p", qos_e::at_least_once, retain_e::no, dup_e::no, {} - ); - auto malformed_puback = encoders::encode_puback(1, uint8_t(0x04), {}); - - auto publish_dup = encoders::encode_publish( - 1, "t", "p", qos_e::at_least_once, retain_e::no, dup_e::yes, {} - ); - auto puback = encoders::encode_puback(1, reason_codes::success.value(), {}); - - disconnect_props dc_props; - dc_props[prop::reason_string] = "Malformed PUBACK: invalid Reason Code"; - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), dc_props - ); - - test::msg_exchange broker_side; - error_code success {}; - - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .expect(publish) - .complete_with(success, after(0ms)) - .reply_with(malformed_puback, after(0ms)) - .expect(disconnect) - .complete_with(success, after(0ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .expect(publish_dup) - .complete_with(success, after(0ms)) - .reply_with(puback, after(0ms)); - - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); - - using client_type = mqtt_client; - client_type c(executor, ""); - c.brokers("127.0.0.1") - .run(); - - c.async_publish( - "t", "p", retain_e::no, publish_props {}, - [&](error_code ec, reason_code rc, auto) { - ++handlers_called; - - BOOST_CHECK(!ec); - BOOST_CHECK_EQUAL(rc, reason_codes::success); - - c.cancel(); - } - ); - - ioc.run_for(std::chrono::seconds(10)); - BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); - BOOST_CHECK(broker.received_all_expected()); -} - - -BOOST_AUTO_TEST_CASE(test_malformed_pubrec_pubcomp) { - using test::after; - using std::chrono_literals::operator ""ms; - - constexpr int expected_handlers_called = 1; - int handlers_called = 0; - - // packets - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt - ); - auto connack = encoders::encode_connack(false, reason_codes::success.value(), {}); - - auto publish = encoders::encode_publish( - 1, "t", "p", qos_e::exactly_once, retain_e::no, dup_e::no, {} - ); - auto malformed_pubrec = encoders::encode_pubrec(1, uint8_t(0x04), {}); - - auto publish_dup = encoders::encode_publish( - 1, "t", "p", qos_e::exactly_once, retain_e::no, dup_e::yes, {} - ); - auto pubrec = encoders::encode_pubrec(1, reason_codes::success.value(), {}); - - auto pubrel = encoders::encode_pubrel(1, reason_codes::success.value(), {}); - auto malformed_pubcomp = encoders::encode_pubcomp(1, uint8_t(0x04), {}); - auto pubcomp = encoders::encode_pubcomp(1, reason_codes::success.value(), {}); - - disconnect_props dc_props; - dc_props[prop::reason_string] = "Malformed PUBREC: invalid Reason Code"; - auto disconnect_on_pubrec = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), dc_props - ); - - dc_props[prop::reason_string] = "Malformed PUBCOMP: invalid Reason Code"; - auto disconnect_on_pubcomp = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), dc_props - ); - - test::msg_exchange broker_side; - error_code success {}; - - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .expect(publish) - .complete_with(success, after(0ms)) - .reply_with(malformed_pubrec, after(0ms)) - .expect(disconnect_on_pubrec) - .complete_with(success, after(0ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .expect(publish_dup) - .complete_with(success, after(0ms)) - .reply_with(pubrec, after(0ms)) - .expect(pubrel) - .complete_with(success, after(0ms)) - .reply_with(malformed_pubcomp, after(0ms)) - .expect(disconnect_on_pubcomp) - .complete_with(success, after(0ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .expect(pubrel) - .complete_with(success, after(0ms)) - .reply_with(pubcomp, after(0ms)); - - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); - - using client_type = mqtt_client; - client_type c(executor, ""); - c.brokers("127.0.0.1") - .run(); - - c.async_publish( - "t", "p", retain_e::no, publish_props {}, - [&](error_code ec, reason_code rc, auto) { - ++handlers_called; - - BOOST_CHECK(!ec); - BOOST_CHECK_EQUAL(rc, reason_codes::success); - - c.cancel(); - } - ); - - ioc.run_for(std::chrono::seconds(15)); - BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); - BOOST_CHECK(broker.received_all_expected()); -} BOOST_AUTO_TEST_SUITE_END() diff --git a/test/unit/test/serialization.cpp b/test/unit/serialization.cpp similarity index 100% rename from test/unit/test/serialization.cpp rename to test/unit/serialization.cpp diff --git a/test/unit/test/session.cpp b/test/unit/session.cpp similarity index 100% rename from test/unit/test/session.cpp rename to test/unit/session.cpp diff --git a/test/unit/test/string_validation.cpp b/test/unit/string_validation.cpp similarity index 100% rename from test/unit/test/string_validation.cpp rename to test/unit/string_validation.cpp diff --git a/test/unit/test/subscribe_op.cpp b/test/unit/subscribe_op.cpp similarity index 81% rename from test/unit/test/subscribe_op.cpp rename to test/unit/subscribe_op.cpp index 7c7af96..7c5058a 100644 --- a/test/unit/test/subscribe_op.cpp +++ b/test/unit/subscribe_op.cpp @@ -2,13 +2,9 @@ #include -#include - #include -#include "test_common/message_exchange.hpp" #include "test_common/test_service.hpp" -#include "test_common/test_stream.hpp" using namespace async_mqtt5; @@ -282,75 +278,4 @@ BOOST_AUTO_TEST_CASE(test_packet_too_large) { BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); } -BOOST_AUTO_TEST_CASE(test_resending) { - using test::after; - using std::chrono_literals::operator ""ms; - - constexpr int expected_handlers_called = 1; - int handlers_called = 0; - - // data - std::vector topics = { - subscribe_topic { "topic", subscribe_options {} } - }; - subscribe_props subscribe_props; - - std::vector rcs = { reason_codes::granted_qos_0.value() }; - suback_props suback_props; - - // packets - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt - ); - auto connack = encoders::encode_connack(false, reason_codes::success.value(), {}); - - auto subscribe = encoders::encode_subscribe(1, topics, subscribe_props); - auto suback = encoders::encode_suback(1, rcs, suback_props); - - test::msg_exchange broker_side; - error_code success {}; - error_code fail = asio::error::not_connected; - - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .expect(subscribe) - .complete_with(fail, after(0ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .expect(subscribe) - .complete_with(success, after(0ms)) - .reply_with(suback, after(0ms)); - - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); - - using client_type = mqtt_client; - client_type c(executor, ""); - c.brokers("127.0.0.1") - .run(); - - c.async_subscribe( - topics, subscribe_props, - [&](error_code ec, std::vector rcs, auto) { - handlers_called++; - - BOOST_CHECK(!ec); - BOOST_ASSERT(rcs.size() == 1); - BOOST_CHECK_EQUAL(rcs[0], reason_codes::granted_qos_0); - - c.cancel(); - } - ); - - ioc.run_for(std::chrono::seconds(10)); - BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); - BOOST_CHECK(broker.received_all_expected()); -} - BOOST_AUTO_TEST_SUITE_END() diff --git a/test/unit/test/unsubscribe_op.cpp b/test/unit/unsubscribe_op.cpp similarity index 67% rename from test/unit/test/unsubscribe_op.cpp rename to test/unit/unsubscribe_op.cpp index 1ab8403..d1b18d9 100644 --- a/test/unit/test/unsubscribe_op.cpp +++ b/test/unit/unsubscribe_op.cpp @@ -2,13 +2,9 @@ #include -#include - #include -#include "test_common/message_exchange.hpp" #include "test_common/test_service.hpp" -#include "test_common/test_stream.hpp" using namespace async_mqtt5; @@ -133,73 +129,4 @@ BOOST_AUTO_TEST_CASE(test_packet_too_large) { BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); } -BOOST_AUTO_TEST_CASE(test_resending) { - using test::after; - using std::chrono_literals::operator ""ms; - - constexpr int expected_handlers_called = 1; - int handlers_called = 0; - - // data - std::vector topics = { "topic " }; - unsubscribe_props unsubscribe_props; - - std::vector rcs = { reason_codes::success.value() }; - unsuback_props unsuback_props; - - // packets - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt - ); - auto connack = encoders::encode_connack(false, reason_codes::success.value(), {}); - - auto unsubscribe = encoders::encode_unsubscribe(1, topics, unsubscribe_props); - auto unsuback = encoders::encode_unsuback(1, rcs, unsuback_props); - - test::msg_exchange broker_side; - error_code success {}; - error_code fail = asio::error::not_connected; - - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .expect(unsubscribe) - .complete_with(fail, after(0ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .expect(unsubscribe) - .complete_with(success, after(0ms)) - .reply_with(unsuback, after(0ms)); - - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); - - using client_type = mqtt_client; - client_type c(executor, ""); - c.brokers("127.0.0.1") - .run(); - - c.async_unsubscribe( - topics, unsubscribe_props, - [&](error_code ec, std::vector rcs, auto) { - handlers_called++; - - BOOST_CHECK(!ec); - BOOST_ASSERT(rcs.size() == 1); - BOOST_CHECK_EQUAL(rcs[0], reason_codes::success); - - c.cancel(); - } - ); - - ioc.run_for(std::chrono::seconds(10)); - BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); - BOOST_CHECK(broker.received_all_expected()); -} - BOOST_AUTO_TEST_SUITE_END()