diff --git a/src/mqtt/will_options.h b/src/mqtt/will_options.h index a685a96..2d5e0b5 100644 --- a/src/mqtt/will_options.h +++ b/src/mqtt/will_options.h @@ -47,10 +47,13 @@ class connect_options; */ class will_options { +public: /** The default QoS for the LWT, if unspecified */ static constexpr int DFLT_QOS = 0; /** The defalut retained flag for LWT, if unspecified */ static constexpr bool DFLT_RETAINED = false; + +private: /** A default C struct to support re-initializing variables */ static const MQTTAsync_willOptions DFLT_C_STRUCT; @@ -169,6 +172,12 @@ public: * @param opt The other options. */ will_options& operator=(will_options&& opt); + /** + * Expose the underlying C struct for the unit tests. + */ + #if defined(UNIT_TESTS) + const MQTTAsync_willOptions& c_struct() const { return opts_; } + #endif /** * Gets the LWT message topic name. * @return The LWT message topic name. diff --git a/test/cppunit/test.cpp b/test/cppunit/test.cpp index 141d8b7..12aec2d 100644 --- a/test/cppunit/test.cpp +++ b/test/cppunit/test.cpp @@ -20,7 +20,6 @@ #include "async_client_test.h" #include "async_client_v3_test.h" #include "client_test.h" -#include "will_options_test.h" #include "ssl_options_test.h" #include "connect_options_test.h" #include "disconnect_options_test.h" @@ -36,8 +35,6 @@ using namespace CppUnit; int main(int argc, char* argv[]) { - CPPUNIT_TEST_SUITE_REGISTRATION( mqtt::will_options_test ); - #if defined(OPENSSL) CPPUNIT_TEST_SUITE_REGISTRATION( mqtt::ssl_options_test ); #endif diff --git a/test/cppunit/will_options_test.h b/test/cppunit/will_options_test.h deleted file mode 100644 index 7b3d453..0000000 --- a/test/cppunit/will_options_test.h +++ /dev/null @@ -1,389 +0,0 @@ -// will_options_test.h -// Unit tests for the will_options class in the Paho MQTT C++ library. - -/******************************************************************************* - * Copyright (c) 2016 Frank Pagliughi - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Frank Pagliughi - initial implementation and documentation - *******************************************************************************/ - -#ifndef __mqtt_will_options_test_h -#define __mqtt_will_options_test_h - -#include "mqtt/will_options.h" -#include "dummy_async_client.h" -#include -#include -#include - -namespace mqtt { - -///////////////////////////////////////////////////////////////////////////// - -// Note that at this time, the LWT payload has been converted to binary -// from the previous version's use of a text string (NUL-terminated C str). -// We now fill in the 'payload' fields of the underlying C struct, -// MQTTAsync_willOptions. The 'message' field _must_ stay NULL for the C lib -// to use 'payload'. - -class will_options_test : public CppUnit::TestFixture -{ - CPPUNIT_TEST_SUITE( will_options_test ); - - CPPUNIT_TEST( test_dflt_constructor ); - CPPUNIT_TEST( test_string_buf_constructor ); - CPPUNIT_TEST( test_topic_buf_constructor ); - CPPUNIT_TEST( test_string_string_constructor ); - CPPUNIT_TEST( test_string_message_constructor ); - CPPUNIT_TEST( test_copy_constructor ); - CPPUNIT_TEST( test_move_constructor ); - CPPUNIT_TEST( test_copy_assignment ); - CPPUNIT_TEST( test_move_assignment ); - CPPUNIT_TEST( test_set_topic_str ); - CPPUNIT_TEST( test_set_payload ); - - CPPUNIT_TEST_SUITE_END(); - - const std::string EMPTY_STR; - const int DFLT_QOS = will_options::DFLT_QOS; - const bool DFLT_RETAINED = will_options::DFLT_RETAINED; - - // C struct signature/eyecatcher - const char* CSIG = "MQTW"; - const size_t CSIG_LEN = std::strlen(CSIG); - - const std::string TOPIC = "hello"; - const char* BUF = "Hello there"; - const size_t N = std::strlen(BUF); - const std::string PAYLOAD = std::string(BUF); - const int QOS = 1; - const bool RETAINED = true; - - mqtt::will_options orgOpts; - -public: - void setUp() { - orgOpts = mqtt::will_options(TOPIC, BUF, N, QOS, RETAINED); - } - void tearDown() {} - -// ---------------------------------------------------------------------- -// Test the default constructor -// ---------------------------------------------------------------------- - - void test_dflt_constructor() { - mqtt::will_options opts; - - CPPUNIT_ASSERT_EQUAL(EMPTY_STR, opts.get_topic()); - CPPUNIT_ASSERT_EQUAL(EMPTY_STR, opts.get_payload_str()); - CPPUNIT_ASSERT_EQUAL(DFLT_QOS, opts.get_qos()); - CPPUNIT_ASSERT_EQUAL(DFLT_RETAINED, opts.is_retained()); - - // Test the C struct - const auto& c_struct = opts.opts_; - - CPPUNIT_ASSERT(!memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); - CPPUNIT_ASSERT(c_struct.topicName != nullptr); - CPPUNIT_ASSERT_EQUAL(size_t(0), strlen(c_struct.topicName)); - CPPUNIT_ASSERT(c_struct.message == nullptr); - CPPUNIT_ASSERT_EQUAL(0, c_struct.payload.len); - CPPUNIT_ASSERT(c_struct.payload.data == nullptr); - } - -// ---------------------------------------------------------------------- -// Test the raw buffer (void*) constructor -// ---------------------------------------------------------------------- - - void test_string_buf_constructor() { - test::dummy_async_client cli; - mqtt::topic topic { cli, TOPIC }; - - mqtt::will_options opts(topic, BUF, N, QOS, true); - - CPPUNIT_ASSERT_EQUAL(TOPIC, opts.get_topic()); - CPPUNIT_ASSERT_EQUAL(PAYLOAD, opts.get_payload_str()); - CPPUNIT_ASSERT_EQUAL(QOS, opts.get_qos()); - CPPUNIT_ASSERT_EQUAL(RETAINED, opts.is_retained()); - - // Test the C struct - // Remember we now fill payload fields, not message - const auto& c_struct = opts.opts_; - - CPPUNIT_ASSERT(!memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); - CPPUNIT_ASSERT(!strcmp(c_struct.topicName, TOPIC.c_str())); - CPPUNIT_ASSERT(c_struct.message == nullptr); - CPPUNIT_ASSERT_EQUAL(N, size_t(c_struct.payload.len)); - CPPUNIT_ASSERT(!memcmp(BUF, c_struct.payload.data, N)); - } - -// ---------------------------------------------------------------------- -// Test the raw buffer (void*) constructor -// ---------------------------------------------------------------------- - - void test_topic_buf_constructor() { - mqtt::will_options opts(TOPIC, BUF, N, QOS, true); - - CPPUNIT_ASSERT_EQUAL(TOPIC, opts.get_topic()); - CPPUNIT_ASSERT_EQUAL(PAYLOAD, opts.get_payload_str()); - CPPUNIT_ASSERT_EQUAL(QOS, opts.get_qos()); - CPPUNIT_ASSERT_EQUAL(RETAINED, opts.is_retained()); - - // Test the C struct - const auto& c_struct = opts.opts_; - - CPPUNIT_ASSERT(!memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); - CPPUNIT_ASSERT(!strcmp(c_struct.topicName, TOPIC.c_str())); - CPPUNIT_ASSERT(c_struct.message == nullptr); - CPPUNIT_ASSERT_EQUAL(N, size_t(c_struct.payload.len)); - CPPUNIT_ASSERT(!memcmp(BUF, c_struct.payload.data, N)); - } - -// ---------------------------------------------------------------------- -// Test the string payload constructor -// ---------------------------------------------------------------------- - - void test_string_string_constructor() { - mqtt::will_options opts(TOPIC, PAYLOAD, QOS, true); - - CPPUNIT_ASSERT_EQUAL(TOPIC, opts.get_topic()); - CPPUNIT_ASSERT_EQUAL(PAYLOAD, opts.get_payload_str()); - CPPUNIT_ASSERT_EQUAL(QOS, opts.get_qos()); - CPPUNIT_ASSERT_EQUAL(RETAINED, opts.is_retained()); - - // Test the C struct - const auto& c_struct = opts.opts_; - - CPPUNIT_ASSERT(!memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); - CPPUNIT_ASSERT(!strcmp(c_struct.topicName, TOPIC.c_str())); - CPPUNIT_ASSERT(c_struct.message == nullptr); - CPPUNIT_ASSERT_EQUAL(PAYLOAD.size(), size_t(c_struct.payload.len)); - CPPUNIT_ASSERT(!memcmp(PAYLOAD.data(), c_struct.payload.data, PAYLOAD.size())); - } - -// ---------------------------------------------------------------------- -// Test the message payload constructor -// ---------------------------------------------------------------------- - - void test_string_message_constructor() { - mqtt::message msg(TOPIC, PAYLOAD, QOS, true); - mqtt::will_options opts(msg); - - CPPUNIT_ASSERT_EQUAL(TOPIC, opts.get_topic()); - CPPUNIT_ASSERT_EQUAL(PAYLOAD, opts.get_payload_str()); - CPPUNIT_ASSERT_EQUAL(QOS, opts.get_qos()); - CPPUNIT_ASSERT_EQUAL(RETAINED, opts.is_retained()); - - // Test the C struct - const auto& c_struct = opts.opts_; - - CPPUNIT_ASSERT(!memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); - CPPUNIT_ASSERT(!strcmp(c_struct.topicName, TOPIC.c_str())); - CPPUNIT_ASSERT(c_struct.message == nullptr); - CPPUNIT_ASSERT_EQUAL(PAYLOAD.size(), size_t(c_struct.payload.len)); - CPPUNIT_ASSERT(!memcmp(PAYLOAD.data(), c_struct.payload.data, PAYLOAD.size())); - } - -// ---------------------------------------------------------------------- -// Test the copy constructor -// ---------------------------------------------------------------------- - - void test_copy_constructor() { - mqtt::will_options opts(orgOpts); - - CPPUNIT_ASSERT_EQUAL(TOPIC, opts.get_topic()); - CPPUNIT_ASSERT_EQUAL(PAYLOAD, opts.get_payload_str()); - CPPUNIT_ASSERT_EQUAL(QOS, opts.get_qos()); - CPPUNIT_ASSERT_EQUAL(RETAINED, opts.is_retained()); - - // Check the C struct - const auto& c_struct = opts.opts_; - - CPPUNIT_ASSERT(!memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); - CPPUNIT_ASSERT(!strcmp(c_struct.topicName, TOPIC.c_str())); - CPPUNIT_ASSERT(c_struct.message == nullptr); - CPPUNIT_ASSERT_EQUAL(N, size_t(c_struct.payload.len)); - CPPUNIT_ASSERT(!memcmp(BUF, c_struct.payload.data, N)); - - // Make sure it's a true copy, not linked to the original - orgOpts.set_topic(EMPTY_STR); - orgOpts.set_payload(EMPTY_STR); - orgOpts.set_qos(DFLT_QOS); - orgOpts.set_retained(false); - - CPPUNIT_ASSERT_EQUAL(TOPIC, opts.get_topic()); - CPPUNIT_ASSERT_EQUAL(PAYLOAD, opts.get_payload_str()); - CPPUNIT_ASSERT_EQUAL(QOS, opts.get_qos()); - CPPUNIT_ASSERT_EQUAL(RETAINED, opts.is_retained()); - } - -// ---------------------------------------------------------------------- -// Test the move constructor -// ---------------------------------------------------------------------- - - void test_move_constructor() { - mqtt::will_options opts(std::move(orgOpts)); - - CPPUNIT_ASSERT_EQUAL(TOPIC, opts.get_topic()); - CPPUNIT_ASSERT_EQUAL(PAYLOAD, opts.get_payload_str()); - CPPUNIT_ASSERT_EQUAL(QOS, opts.get_qos()); - CPPUNIT_ASSERT_EQUAL(RETAINED, opts.is_retained()); - - // Check the C struct - const auto& c_struct = opts.opts_; - - CPPUNIT_ASSERT(!memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); - CPPUNIT_ASSERT(!strcmp(c_struct.topicName, TOPIC.c_str())); - CPPUNIT_ASSERT(c_struct.message == nullptr); - CPPUNIT_ASSERT_EQUAL(N, size_t(c_struct.payload.len)); - CPPUNIT_ASSERT(!memcmp(BUF, c_struct.payload.data, N)); - - // Check that the original was moved - CPPUNIT_ASSERT_EQUAL(EMPTY_STR, orgOpts.get_topic()); - CPPUNIT_ASSERT_EQUAL(EMPTY_STR, orgOpts.get_payload_str()); - } - -// ---------------------------------------------------------------------- -// Test the copy assignment operator=(const&) -// ---------------------------------------------------------------------- - - void test_copy_assignment() { - mqtt::will_options opts; - - opts = orgOpts; - - CPPUNIT_ASSERT_EQUAL(TOPIC, opts.get_topic()); - CPPUNIT_ASSERT_EQUAL(PAYLOAD, opts.get_payload_str()); - CPPUNIT_ASSERT_EQUAL(QOS, opts.get_qos()); - CPPUNIT_ASSERT_EQUAL(RETAINED, opts.is_retained()); - - // Check the C struct - const auto& c_struct = opts.opts_; - - CPPUNIT_ASSERT(!memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); - CPPUNIT_ASSERT(!strcmp(c_struct.topicName, TOPIC.c_str())); - CPPUNIT_ASSERT(c_struct.message == nullptr); - CPPUNIT_ASSERT_EQUAL(N, size_t(c_struct.payload.len)); - CPPUNIT_ASSERT(!memcmp(BUF, c_struct.payload.data, N)); - - // Make sure it's a true copy, not linked to the original - orgOpts.set_topic(EMPTY_STR); - orgOpts.set_payload(EMPTY_STR); - orgOpts.set_qos(DFLT_QOS); - orgOpts.set_retained(false); - - CPPUNIT_ASSERT_EQUAL(TOPIC, opts.get_topic()); - CPPUNIT_ASSERT_EQUAL(PAYLOAD, opts.get_payload_str()); - CPPUNIT_ASSERT_EQUAL(QOS, opts.get_qos()); - CPPUNIT_ASSERT_EQUAL(RETAINED, opts.is_retained()); - - // Self assignment should cause no harm - opts = opts; - - CPPUNIT_ASSERT_EQUAL(TOPIC, opts.get_topic()); - CPPUNIT_ASSERT_EQUAL(PAYLOAD, opts.get_payload_str()); - CPPUNIT_ASSERT_EQUAL(QOS, opts.get_qos()); - CPPUNIT_ASSERT_EQUAL(RETAINED, opts.is_retained()); - } - -// ---------------------------------------------------------------------- -// Test the move assignment, operator=(&&) -// ---------------------------------------------------------------------- - - void test_move_assignment() { - mqtt::will_options opts; - - opts = std::move(orgOpts); - - CPPUNIT_ASSERT_EQUAL(TOPIC, opts.get_topic()); - CPPUNIT_ASSERT_EQUAL(PAYLOAD, opts.get_payload_str()); - CPPUNIT_ASSERT_EQUAL(QOS, opts.get_qos()); - CPPUNIT_ASSERT_EQUAL(RETAINED, opts.is_retained()); - - // Check the C struct - const auto& c_struct = opts.opts_; - - CPPUNIT_ASSERT(!memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); - CPPUNIT_ASSERT(!strcmp(c_struct.topicName, TOPIC.c_str())); - CPPUNIT_ASSERT(c_struct.message == nullptr); - CPPUNIT_ASSERT_EQUAL(N, size_t(c_struct.payload.len)); - CPPUNIT_ASSERT(!memcmp(BUF, c_struct.payload.data, N)); - - // Check that the original was moved - CPPUNIT_ASSERT_EQUAL(EMPTY_STR, orgOpts.get_topic()); - CPPUNIT_ASSERT_EQUAL(EMPTY_STR, orgOpts.get_payload_str()); - - // Self assignment should cause no harm - // (clang++ is smart enough to warn about this) - #if !defined(__clang__) - opts = std::move(opts); - CPPUNIT_ASSERT_EQUAL(TOPIC, opts.get_topic()); - CPPUNIT_ASSERT_EQUAL(PAYLOAD, opts.get_payload_str()); - CPPUNIT_ASSERT_EQUAL(QOS, opts.get_qos()); - CPPUNIT_ASSERT_EQUAL(RETAINED, opts.is_retained()); - #endif - } - -// ---------------------------------------------------------------------- -// Test setting the (text) topic -// ---------------------------------------------------------------------- - - void test_set_topic_str() { - mqtt::will_options opts; - - opts.set_topic(TOPIC); - CPPUNIT_ASSERT_EQUAL(TOPIC, opts.get_topic()); - - const auto& c_struct = opts.opts_; - CPPUNIT_ASSERT(!strcmp(c_struct.topicName, TOPIC.c_str())); - - // Setting empty string should _not_ create nullptr entry, in - // C struct, rather a valid zero-length string. - opts.set_topic(EMPTY_STR); - - CPPUNIT_ASSERT_EQUAL(EMPTY_STR, opts.get_topic()); - CPPUNIT_ASSERT(opts.opts_.topicName != nullptr); - CPPUNIT_ASSERT_EQUAL(size_t(0), strlen(opts.opts_.topicName)); - } - -// ---------------------------------------------------------------------- -// Test setting the (binary) payload -// ---------------------------------------------------------------------- - - void test_set_payload() { - mqtt::will_options opts; - - opts.set_payload(PAYLOAD); - CPPUNIT_ASSERT_EQUAL(PAYLOAD, opts.get_payload_str()); - - const auto& c_struct = opts.opts_; - - CPPUNIT_ASSERT_EQUAL(PAYLOAD.size(), size_t(c_struct.payload.len)); - CPPUNIT_ASSERT(!memcmp(PAYLOAD.data(), c_struct.payload.data, PAYLOAD.size())); - - // Setting empty string set a valid, but zero-len payload - // TODO: We need to check what the C lib now accepts. - opts.set_payload(EMPTY_STR); - - CPPUNIT_ASSERT_EQUAL(EMPTY_STR, opts.get_payload_str()); - CPPUNIT_ASSERT_EQUAL(size_t(0), opts.get_payload().size()); - - CPPUNIT_ASSERT_EQUAL(0, opts.opts_.payload.len); - CPPUNIT_ASSERT(opts.opts_.payload.data != nullptr); - } -}; - -///////////////////////////////////////////////////////////////////////////// -// end namespace mqtt -} - -#endif // __mqtt_will_options_test_h diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 329c637..e78b894 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -34,6 +34,7 @@ add_executable(unit_tests unit_tests.cpp test_properties.cpp test_string_collection.cpp test_topic.cpp + test_will_options.cpp ) # --- Link for executables --- diff --git a/test/unit/test_will_options.cpp b/test/unit/test_will_options.cpp new file mode 100644 index 0000000..9ae3647 --- /dev/null +++ b/test/unit/test_will_options.cpp @@ -0,0 +1,366 @@ +// test_will_options.cpp +// +// Unit tests for the will_options class in the Paho MQTT C++ library. +// + +/******************************************************************************* + * Copyright (c) 2016-2020 Frank Pagliughi + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Frank Pagliughi - initial implementation and documentation + *******************************************************************************/ + +#define UNIT_TESTS + +#include +#include "catch2/catch.hpp" +#include "mqtt/will_options.h" +#include "mock_async_client.h" + +using namespace mqtt; + +static const std::string EMPTY_STR; +static const int DFLT_QOS = will_options::DFLT_QOS; +static const bool DFLT_RETAINED = will_options::DFLT_RETAINED; + +// C struct signature/eyecatcher +static const char* CSIG = "MQTW"; +static const size_t CSIG_LEN = std::strlen(CSIG); + +static const std::string TOPIC = "hello"; +static const char* BUF = "Hello there"; +static const size_t N = std::strlen(BUF); +static const std::string PAYLOAD = std::string(BUF); +static const int QOS = 1; +static const bool RETAINED = true; + +// ---------------------------------------------------------------------- +// Test the default constructor +// ---------------------------------------------------------------------- + +TEST_CASE("will_options default ctor", "[options]") +{ + mqtt::will_options opts; + + REQUIRE(EMPTY_STR == opts.get_topic()); + REQUIRE(EMPTY_STR == opts.get_payload_str()); + REQUIRE(DFLT_QOS == opts.get_qos()); + REQUIRE(DFLT_RETAINED == opts.is_retained()); + + // Test the C struct + const auto& c_struct = opts.c_struct(); + + REQUIRE(0 == memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); + REQUIRE(c_struct.topicName != nullptr); + REQUIRE(size_t(0) == strlen(c_struct.topicName)); + REQUIRE(c_struct.message == nullptr); + REQUIRE(0 == c_struct.payload.len); + REQUIRE(c_struct.payload.data == nullptr); +} + +// ---------------------------------------------------------------------- +// Test the raw buffer (void*) constructor +// ---------------------------------------------------------------------- + +TEST_CASE("will_options string buf ctor", "[options]") +{ + test::mock_async_client cli; + mqtt::topic topic { cli, TOPIC }; + + mqtt::will_options opts(topic, BUF, N, QOS, true); + + REQUIRE(TOPIC == opts.get_topic()); + REQUIRE(PAYLOAD == opts.get_payload_str()); + REQUIRE(QOS == opts.get_qos()); + REQUIRE(RETAINED == opts.is_retained()); + + // Test the C struct + // Remember we now fill payload fields, not message + const auto& c_struct = opts.c_struct(); + + REQUIRE(0 == memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); + REQUIRE(0 == strcmp(c_struct.topicName, TOPIC.c_str())); + REQUIRE(c_struct.message == nullptr); + REQUIRE(N == size_t(c_struct.payload.len)); + REQUIRE(0 == memcmp(BUF, c_struct.payload.data, N)); +} + +// ---------------------------------------------------------------------- +// Test the raw buffer (void*) constructor +// ---------------------------------------------------------------------- + +TEST_CASE("will_options topic buf ctor", "[options]") +{ + mqtt::will_options opts(TOPIC, BUF, N, QOS, true); + + REQUIRE(TOPIC == opts.get_topic()); + REQUIRE(PAYLOAD == opts.get_payload_str()); + REQUIRE(QOS == opts.get_qos()); + REQUIRE(RETAINED == opts.is_retained()); + + // Test the C struct + const auto& c_struct = opts.c_struct(); + + REQUIRE(0 == memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); + REQUIRE(0 == strcmp(c_struct.topicName, TOPIC.c_str())); + REQUIRE(c_struct.message == nullptr); + REQUIRE(N == size_t(c_struct.payload.len)); + REQUIRE(0 == memcmp(BUF, c_struct.payload.data, N)); +} + +// ---------------------------------------------------------------------- +// Test the string payload constructor +// ---------------------------------------------------------------------- + +TEST_CASE("will_options string string ctor", "[options]") +{ + mqtt::will_options opts(TOPIC, PAYLOAD, QOS, true); + + REQUIRE(TOPIC == opts.get_topic()); + REQUIRE(PAYLOAD == opts.get_payload_str()); + REQUIRE(QOS == opts.get_qos()); + REQUIRE(RETAINED == opts.is_retained()); + + // Test the C struct + const auto& c_struct = opts.c_struct(); + + REQUIRE(0 == memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); + REQUIRE(0 == strcmp(c_struct.topicName, TOPIC.c_str())); + REQUIRE(c_struct.message == nullptr); + REQUIRE(PAYLOAD.size() == size_t(c_struct.payload.len)); + REQUIRE(0 == memcmp(PAYLOAD.data(), c_struct.payload.data, PAYLOAD.size())); +} + +// ---------------------------------------------------------------------- +// Test the message payload constructor +// ---------------------------------------------------------------------- + +TEST_CASE("will_options string message ctor", "[options]") +{ + mqtt::message msg(TOPIC, PAYLOAD, QOS, true); + mqtt::will_options opts(msg); + + REQUIRE(TOPIC == opts.get_topic()); + REQUIRE(PAYLOAD == opts.get_payload_str()); + REQUIRE(QOS == opts.get_qos()); + REQUIRE(RETAINED == opts.is_retained()); + + // Test the C struct + const auto& c_struct = opts.c_struct(); + + REQUIRE(0 == memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); + REQUIRE(0 == strcmp(c_struct.topicName, TOPIC.c_str())); + REQUIRE(c_struct.message == nullptr); + REQUIRE(PAYLOAD.size() == size_t(c_struct.payload.len)); + REQUIRE(0 == memcmp(PAYLOAD.data(), c_struct.payload.data, PAYLOAD.size())); +} + +// ---------------------------------------------------------------------- +// Test the copy constructor +// ---------------------------------------------------------------------- + +TEST_CASE("will_options copy ctor", "[options]") +{ + auto orgOpts = mqtt::will_options(TOPIC, BUF, N, QOS, RETAINED); + + mqtt::will_options opts(orgOpts); + + REQUIRE(TOPIC == opts.get_topic()); + REQUIRE(PAYLOAD == opts.get_payload_str()); + REQUIRE(QOS == opts.get_qos()); + REQUIRE(RETAINED == opts.is_retained()); + + // Check the C struct + const auto& c_struct = opts.c_struct(); + + REQUIRE(0 == memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); + REQUIRE(0 == strcmp(c_struct.topicName, TOPIC.c_str())); + REQUIRE(c_struct.message == nullptr); + REQUIRE(N == size_t(c_struct.payload.len)); + REQUIRE(0 == memcmp(BUF, c_struct.payload.data, N)); + + // Make sure it's a true copy, not linked to the original + orgOpts.set_topic(EMPTY_STR); + orgOpts.set_payload(EMPTY_STR); + orgOpts.set_qos(DFLT_QOS); + orgOpts.set_retained(false); + + REQUIRE(TOPIC == opts.get_topic()); + REQUIRE(PAYLOAD == opts.get_payload_str()); + REQUIRE(QOS == opts.get_qos()); + REQUIRE(RETAINED == opts.is_retained()); +} + +// ---------------------------------------------------------------------- +// Test the move constructor +// ---------------------------------------------------------------------- + +TEST_CASE("will_options move ctor", "[options]") +{ + auto orgOpts = mqtt::will_options(TOPIC, BUF, N, QOS, RETAINED); + + mqtt::will_options opts(std::move(orgOpts)); + + REQUIRE(TOPIC == opts.get_topic()); + REQUIRE(PAYLOAD == opts.get_payload_str()); + REQUIRE(QOS == opts.get_qos()); + REQUIRE(RETAINED == opts.is_retained()); + + // Check the C struct + const auto& c_struct = opts.c_struct(); + + REQUIRE(0 == memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); + REQUIRE(0 == strcmp(c_struct.topicName, TOPIC.c_str())); + REQUIRE(c_struct.message == nullptr); + REQUIRE(N == size_t(c_struct.payload.len)); + REQUIRE(0 == memcmp(BUF, c_struct.payload.data, N)); + + // Check that the original was moved + REQUIRE(EMPTY_STR == orgOpts.get_topic()); + REQUIRE(EMPTY_STR == orgOpts.get_payload_str()); +} + +// ---------------------------------------------------------------------- +// Test the copy assignment operator=(const&) +// ---------------------------------------------------------------------- + +TEST_CASE("will_options copy assignment", "[options]") +{ + auto orgOpts = mqtt::will_options(TOPIC, BUF, N, QOS, RETAINED); + + mqtt::will_options opts; + opts = orgOpts; + + REQUIRE(TOPIC == opts.get_topic()); + REQUIRE(PAYLOAD == opts.get_payload_str()); + REQUIRE(QOS == opts.get_qos()); + REQUIRE(RETAINED == opts.is_retained()); + + // Check the C struct + const auto& c_struct = opts.c_struct(); + + REQUIRE(0 == memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); + REQUIRE(0 == strcmp(c_struct.topicName, TOPIC.c_str())); + REQUIRE(c_struct.message == nullptr); + REQUIRE(N == size_t(c_struct.payload.len)); + REQUIRE(0 == memcmp(BUF, c_struct.payload.data, N)); + + // Make sure it's a true copy, not linked to the original + orgOpts.set_topic(EMPTY_STR); + orgOpts.set_payload(EMPTY_STR); + orgOpts.set_qos(DFLT_QOS); + orgOpts.set_retained(false); + + REQUIRE(TOPIC == opts.get_topic()); + REQUIRE(PAYLOAD == opts.get_payload_str()); + REQUIRE(QOS == opts.get_qos()); + REQUIRE(RETAINED == opts.is_retained()); + + // Self assignment should cause no harm + opts = opts; + + REQUIRE(TOPIC == opts.get_topic()); + REQUIRE(PAYLOAD == opts.get_payload_str()); + REQUIRE(QOS == opts.get_qos()); + REQUIRE(RETAINED == opts.is_retained()); +} + +// ---------------------------------------------------------------------- +// Test the move assignment, operator=(&&) +// ---------------------------------------------------------------------- + +TEST_CASE("will_options move assignment", "[options]") +{ + auto orgOpts = mqtt::will_options(TOPIC, BUF, N, QOS, RETAINED); + + mqtt::will_options opts; + opts = std::move(orgOpts); + + REQUIRE(TOPIC == opts.get_topic()); + REQUIRE(PAYLOAD == opts.get_payload_str()); + REQUIRE(QOS == opts.get_qos()); + REQUIRE(RETAINED == opts.is_retained()); + + // Check the C struct + const auto& c_struct = opts.c_struct(); + + REQUIRE(0 == memcmp(&c_struct.struct_id, CSIG, CSIG_LEN)); + REQUIRE(0 == strcmp(c_struct.topicName, TOPIC.c_str())); + REQUIRE(c_struct.message == nullptr); + REQUIRE(N == size_t(c_struct.payload.len)); + REQUIRE(0 == memcmp(BUF, c_struct.payload.data, N)); + + // Check that the original was moved + REQUIRE(EMPTY_STR == orgOpts.get_topic()); + REQUIRE(EMPTY_STR == orgOpts.get_payload_str()); + + // Self assignment should cause no harm + // (clang++ is smart enough to warn about this) + #if !defined(__clang__) + opts = std::move(opts); + REQUIRE(TOPIC == opts.get_topic()); + REQUIRE(PAYLOAD == opts.get_payload_str()); + REQUIRE(QOS == opts.get_qos()); + REQUIRE(RETAINED == opts.is_retained()); + #endif +} + +// ---------------------------------------------------------------------- +// Test setting the (text) topic +// ---------------------------------------------------------------------- + +TEST_CASE("will_options set_topic_str", "[options]") +{ + mqtt::will_options opts; + + opts.set_topic(TOPIC); + REQUIRE(TOPIC == opts.get_topic()); + + const auto& c_struct = opts.c_struct(); + REQUIRE(0 == strcmp(c_struct.topicName, TOPIC.c_str())); + + // Setting empty string should _not_ create nullptr entry, in + // C struct, rather a valid zero-length string. + opts.set_topic(EMPTY_STR); + + REQUIRE(EMPTY_STR == opts.get_topic()); + REQUIRE(c_struct.topicName != nullptr); + REQUIRE(size_t(0) == strlen(c_struct.topicName)); +} + +// ---------------------------------------------------------------------- +// Test setting the (binary) payload +// ---------------------------------------------------------------------- + +TEST_CASE("will_options set_payload", "[options]") +{ + mqtt::will_options opts; + + opts.set_payload(PAYLOAD); + REQUIRE(PAYLOAD == opts.get_payload_str()); + + const auto& c_struct = opts.c_struct(); + + REQUIRE(PAYLOAD.size() == size_t(c_struct.payload.len)); + REQUIRE(0 == memcmp(PAYLOAD.data(), c_struct.payload.data, PAYLOAD.size())); + + // Setting empty string set a valid, but zero-len payload + // TODO: We need to check what the C lib now accepts. + opts.set_payload(EMPTY_STR); + + REQUIRE(EMPTY_STR == opts.get_payload_str()); + REQUIRE(size_t(0) == opts.get_payload().size()); + + REQUIRE(0 == c_struct.payload.len); + REQUIRE(c_struct.payload.data != nullptr); +} +