From 70f78a9f60ac7c7364bf4fe3c9032c25ed62f010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Korina=20=C5=A0imi=C4=8Devi=C4=87?= Date: Wed, 17 Jan 2024 07:57:48 +0100 Subject: [PATCH] Add new tests cases & test housekeeping Summary: related to T12015 - error.hpp covered - assemble_op.hpp fully covered - malformed_pubrel test is now functional - attempt at using fixtures in malformed_packet.cpp test suite to lower the amount of copy-paste code lines - test coverage ~89% Reviewers: ivica Reviewed By: ivica Subscribers: miljen, iljazovic Differential Revision: https://repo.mireo.local/D27395 --- test/integration/executors.cpp | 1 + test/integration/malformed_packet.cpp | 309 ++++++++++++++------------ test/integration/publish_receive.cpp | 1 + test/integration/read_message.cpp | 104 +++++++++ test/unit/error.cpp | 83 +++++++ 5 files changed, 359 insertions(+), 139 deletions(-) create mode 100644 test/unit/error.cpp diff --git a/test/integration/executors.cpp b/test/integration/executors.cpp index b4d6c19..1debc9f 100644 --- a/test/integration/executors.cpp +++ b/test/integration/executors.cpp @@ -172,6 +172,7 @@ BOOST_AUTO_TEST_CASE(async_run) { "t_1", "p_1", retain_e::no, {}, [&](error_code ec, reason_code rc, auto) { BOOST_CHECK_EQUAL(ec, asio::error::operation_aborted); + BOOST_CHECK_EQUAL(rc, reason_codes::empty); BOOST_CHECK(strand.running_in_this_thread()); ++handlers_called; } diff --git a/test/integration/malformed_packet.cpp b/test/integration/malformed_packet.cpp index a76282b..06c2bc5 100644 --- a/test/integration/malformed_packet.cpp +++ b/test/integration/malformed_packet.cpp @@ -8,22 +8,39 @@ using namespace async_mqtt5; -BOOST_AUTO_TEST_SUITE(malformed_packet/* , *boost::unit_test::disabled()*/) +struct shared_test_data { + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt + ); + const std::string connack = encoders::encode_connack( + true, reason_codes::success.value(), {} + ); + + std::string topic = "topic"; + std::string payload = "payload"; + + const std::string publish_qos2 = encoders::encode_publish( + 1, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::no, {} + ); + const std::string publish_qos2_dup = encoders::encode_publish( + 1, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::yes, {} + ); + + const std::string pubrec = encoders::encode_pubrec(1, uint8_t(0x00), {}); + const std::string pubrel = encoders::encode_pubrel(1, uint8_t(0x00), {}); + const std::string pubcomp = encoders::encode_pubcomp(1, uint8_t(0x00), {}); + +}; + +BOOST_FIXTURE_TEST_SUITE(malformed_packet, shared_test_data/* , *boost::unit_test::disabled()*/) BOOST_AUTO_TEST_CASE(test_malformed_publish) { 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 publish = encoders::encode_publish( - 1, "topic", "payload", static_cast(0b11), retain_e::yes, dup_e::no, {} + 1, topic, payload, static_cast(0b11), retain_e::yes, dup_e::no, {} ); disconnect_props dprops; @@ -62,92 +79,6 @@ BOOST_AUTO_TEST_CASE(test_malformed_publish) { BOOST_CHECK(broker.received_all_expected()); } -// does not receive the malformed pubrel -BOOST_AUTO_TEST_CASE(test_malformed_pubrel, *boost::unit_test::disabled()) { - 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 malformed_pubrel = encoders::encode_pubrel(1, 0x04, {}); - auto pubcomp = encoders::encode_pubcomp(1, reason_codes::success.value(), {}); - - disconnect_props dprops; - dprops[prop::reason_string] = "Malformed PUBREL received: invalid Reason Code"; - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), dprops - ); - - 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)) - .expect(pubrec) - .complete_with(success, after(1ms)) - .reply_with(malformed_pubrel, after(2ms)) - .expect(disconnect) - .complete_with(success, 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 - .async_run(asio::detached); - - 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(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; @@ -156,20 +87,15 @@ BOOST_AUTO_TEST_CASE(malformed_puback) { 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, {} + 1, topic, payload, 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, {} + 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::yes, {} ); - auto puback = encoders::encode_puback(1, reason_codes::success.value(), {}); + + auto puback = encoders::encode_puback(1, uint8_t(0x00), {}); + auto malformed_puback = encoders::encode_puback(1, uint8_t(0x04), {}); disconnect_props dc_props; dc_props[prop::reason_string] = "Malformed PUBACK: invalid Reason Code"; @@ -208,7 +134,7 @@ BOOST_AUTO_TEST_CASE(malformed_puback) { .async_run(asio::detached); c.async_publish( - "t", "p", retain_e::no, publish_props {}, + topic, payload, retain_e::no, publish_props {}, [&](error_code ec, reason_code rc, auto) { ++handlers_called; @@ -225,7 +151,7 @@ BOOST_AUTO_TEST_CASE(malformed_puback) { } -BOOST_AUTO_TEST_CASE(malformed_pubrec_pubcomp) { +BOOST_AUTO_TEST_CASE(malformed_pubrel) { using test::after; using std::chrono_literals::operator ""ms; @@ -233,34 +159,12 @@ BOOST_AUTO_TEST_CASE(malformed_pubrec_pubcomp) { 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 malformed_pubrel = encoders::encode_pubrel(1, uint8_t(0x04), {}); - 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 + disconnect_props dprops; + dprops[prop::reason_string] = "Malformed PUBREL received: invalid Reason Code"; + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), dprops ); test::msg_exchange broker_side; @@ -270,21 +174,148 @@ BOOST_AUTO_TEST_CASE(malformed_pubrec_pubcomp) { .expect(connect) .complete_with(success, after(0ms)) .reply_with(connack, after(0ms)) - .expect(publish) + .send(publish_qos2, after(10ms)) + .expect(pubrec) + .complete_with(success, after(1ms)) + .reply_with(malformed_pubrel, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(pubrel, after(100ms)) + .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 + .async_run(asio::detached); + + 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(6)); + BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); + BOOST_CHECK(broker.received_all_expected()); +} + +BOOST_AUTO_TEST_CASE(malformed_pubrec) { + using test::after; + using std::chrono_literals::operator ""ms; + + constexpr int expected_handlers_called = 1; + int handlers_called = 0; + + // packets + auto malformed_pubrec = encoders::encode_pubrec(1, uint8_t(0x04), {}); + + disconnect_props dprops; + dprops[prop::reason_string] = "Malformed PUBREC: invalid Reason Code"; + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), dprops + ); + + test::msg_exchange broker_side; + error_code success {}; + + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(publish_qos2) .complete_with(success, after(0ms)) .reply_with(malformed_pubrec, after(0ms)) - .expect(disconnect_on_pubrec) + .expect(disconnect) .complete_with(success, after(0ms)) .expect(connect) .complete_with(success, after(0ms)) .reply_with(connack, after(0ms)) - .expect(publish_dup) + .expect(publish_qos2_dup) + .complete_with(success, after(0ms)) + .reply_with(pubrec, 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 + .async_run(asio::detached); + + c.async_publish( + topic, payload, 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()); +} + + +BOOST_AUTO_TEST_CASE(malformed_pubcomp) { + using test::after; + using std::chrono_literals::operator ""ms; + + constexpr int expected_handlers_called = 1; + int handlers_called = 0; + + // packets + auto malformed_pubcomp = encoders::encode_pubcomp(1, uint8_t(0x04), {}); + + disconnect_props dprops; + dprops[prop::reason_string] = "Malformed PUBCOMP: invalid Reason Code"; + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), dprops + ); + + test::msg_exchange broker_side; + error_code success {}; + + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(publish_qos2) .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) + .expect(disconnect) .complete_with(success, after(0ms)) .expect(connect) .complete_with(success, after(0ms)) @@ -305,7 +336,7 @@ BOOST_AUTO_TEST_CASE(malformed_pubrec_pubcomp) { .async_run(asio::detached); c.async_publish( - "t", "p", retain_e::no, publish_props {}, + topic, payload, retain_e::no, publish_props {}, [&](error_code ec, reason_code rc, auto) { ++handlers_called; diff --git a/test/integration/publish_receive.cpp b/test/integration/publish_receive.cpp index ccafa87..26e8c7f 100644 --- a/test/integration/publish_receive.cpp +++ b/test/integration/publish_receive.cpp @@ -1,5 +1,6 @@ #include +#include #include #include diff --git a/test/integration/read_message.cpp b/test/integration/read_message.cpp index c30ad34..c9df2b0 100644 --- a/test/integration/read_message.cpp +++ b/test/integration/read_message.cpp @@ -147,4 +147,108 @@ BOOST_AUTO_TEST_CASE(receive_disconnect) { } +BOOST_AUTO_TEST_CASE(receive_pingresp) { + 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 pingresp = encoders::encode_pingresp(); + + test::msg_exchange broker_side; + error_code success {}; + + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(pingresp, after(10ms)); + + + 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") + .async_run(asio::detached); + + 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(send_byte_by_byte) { + using test::after; + using std::chrono_literals::operator ""ms; + + constexpr int expected_handlers_called = 1; + int handlers_called = 0; + + // data + 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( + 0, topic, payload, 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(0ms)) + .reply_with(connack, after(0ms)); + + for (size_t i = 0; i < publish.size(); i++) + broker_side.send( + std::string { publish[i] }, after(std::chrono::milliseconds(i + 2)) + ); + + 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") + .async_run(asio::detached); + + c.async_receive([&]( + error_code ec, std::string topic_, std::string payload_, publish_props + ) { + handlers_called++; + + BOOST_CHECK(!ec); + BOOST_CHECK_EQUAL(topic, topic_); + BOOST_CHECK_EQUAL(payload, payload_); + + c.cancel(); + }); + + ioc.run_for(std::chrono::milliseconds(100)); + BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); + BOOST_CHECK(broker.received_all_expected()); +} + BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/unit/error.cpp b/test/unit/error.cpp new file mode 100644 index 0000000..190c1d4 --- /dev/null +++ b/test/unit/error.cpp @@ -0,0 +1,83 @@ +#include + +#include +#include + +#include + +using namespace async_mqtt5; + +BOOST_AUTO_TEST_SUITE(error/*, *boost::unit_test::disabled()*/) + +BOOST_AUTO_TEST_CASE(client_ec_to_string) { + // Ensure that all branches of the switch/case are covered + + std::vector ecs = { + client::error::malformed_packet, + client::error::packet_too_large, + client::error::session_expired, + client::error::pid_overrun, + client::error::invalid_topic, + client::error::qos_not_supported, + client::error::retain_not_available, + client::error::topic_alias_maximum_reached, + client::error::wildcard_subscription_not_available, + client::error::subscription_identifier_not_available, + client::error::shared_subscription_not_available + }; + + const client::client_ec_category& cat = client::get_error_code_category(); + BOOST_CHECK_NO_THROW(cat.name()); + + constexpr auto default_output = "Unknown client error."; + for (auto ec : ecs) + BOOST_CHECK(cat.message(static_cast(ec)) != default_output); + + // default branch + BOOST_CHECK(cat.message(1) == default_output); +} + + +BOOST_AUTO_TEST_CASE(reason_code_to_string) { + // Ensure that all branches of the switch/case are covered + using namespace reason_codes; + + std::vector rcs = { + empty, success, normal_disconnection, + granted_qos_0, granted_qos_1, granted_qos_2, + disconnect_with_will_message, no_matching_subscribers, + no_subscription_existed, continue_authentication, reauthenticate, + unspecified_error, malformed_packet, protocol_error, + implementation_specific_error, unsupported_protocol_version, + client_id_not_valid,bad_username_or_password, + not_authorized, server_unavailable, server_busy, banned, + server_shutting_down, bad_authentication_method, keep_alive_timeout, + session_taken_over, topic_filter_invalid,topic_name_invalid, + packet_id_in_use, packet_id_not_found, receive_maximum_exceeded, + topic_alias_invalid, packet_too_large, message_rate_too_high, + quota_exceeded, administrative_action, payload_format_invalid, + retain_not_supported, qos_not_supported, use_another_server, + server_moved, shared_subscriptions_not_supported, connection_rate_exceeded, + maximum_connect_time, subscription_ids_not_supported, + wildcard_subscriptions_not_supported + }; + + BOOST_CHECK_EQUAL(rcs.size(), 46); + + constexpr auto default_output = "Invalid reason code"; + for (const auto& rc: rcs) + BOOST_CHECK(rc.message() != "Invalid reason code"); + + // default branch + BOOST_CHECK( + reason_code(0x05, reason_codes::category::suback).message() == default_output + ); +} + +BOOST_AUTO_TEST_CASE(reason_code_to_stream) { + std::ostringstream stream; + stream << reason_codes::success; + BOOST_CHECK_EQUAL(stream.str(), reason_codes::success.message()); +} + +BOOST_AUTO_TEST_SUITE_END();