From 630107ae8af0b262597f9e5cd1364a19b4752443 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Thu, 23 May 2019 21:54:42 +0200 Subject: [PATCH] Removed implicit conversion in comparison operators (issue #998) --- CHANGELOG.md | 2 + .../Operators/VariantComparisons.hpp | 274 +++++++++++------- src/ArduinoJson/Polyfills/safe_strcmp.hpp | 22 ++ .../Strings/ArduinoStringAdapter.hpp | 12 +- .../Strings/ConstRamStringAdapter.hpp | 9 +- .../Strings/FlashStringAdapter.hpp | 11 +- .../Strings/SizedFlashStringAdapter.hpp | 11 +- .../Strings/SizedRamStringAdapter.hpp | 8 +- src/ArduinoJson/Strings/StlStringAdapter.hpp | 5 + test/JsonObject/subscript.cpp | 2 +- test/JsonVariant/compare.cpp | 132 +++++++-- test/JsonVariant/createNested.cpp | 12 +- test/Misc/CMakeLists.txt | 1 + test/Misc/StringAdapters.cpp | 45 +++ 14 files changed, 398 insertions(+), 148 deletions(-) create mode 100644 src/ArduinoJson/Polyfills/safe_strcmp.hpp create mode 100644 test/Misc/StringAdapters.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index d29f4af6..ab1e6796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ HEAD * Made `deserializeJson()` more picky about trailing characters (issue #980) * Added `ARDUINOJSON_ENABLE_NAN` (default=0) to enable NaN in JSON (issue #973) * Added `ARDUINOJSON_ENABLE_INFINITY` (default=0) to enable Infinity in JSON +* Removed implicit conversion in comparison operators (issue #998) +* Added lexicographical comparison for `JsonVariant` > ### BREAKING CHANGES > diff --git a/src/ArduinoJson/Operators/VariantComparisons.hpp b/src/ArduinoJson/Operators/VariantComparisons.hpp index c10bcf8f..c12eed03 100644 --- a/src/ArduinoJson/Operators/VariantComparisons.hpp +++ b/src/ArduinoJson/Operators/VariantComparisons.hpp @@ -7,154 +7,216 @@ #include "../Variant/VariantRef.hpp" namespace ARDUINOJSON_NAMESPACE { + +template +struct Comparer; + template -struct is_simple_value { - static const bool value = is_integral::value || - is_floating_point::value || - is_same::value; +struct Comparer::value>::type> { + T rhs; + int result; + + explicit Comparer(T value) : rhs(value), result(1) {} + + void visitArray(const CollectionData &) {} + void visitObject(const CollectionData &) {} + void visitFloat(Float) {} + void visitString(const char *lhs) { + result = -adaptString(rhs).compare(lhs); + } + void visitRawJson(const char *, size_t) {} + void visitNegativeInteger(UInt) {} + void visitPositiveInteger(UInt) {} + void visitBoolean(bool) {} + void visitNull() { + result = adaptString(rhs).compare(NULL); + } +}; + +template +typename enable_if::value, int>::type sign(const T &value) { + return value < 0 ? -1 : value > 0 ? 1 : 0; +} + +template +typename enable_if::value, int>::type sign(const T &value) { + return value > 0 ? 1 : 0; +} + +template +struct Comparer::value || + is_floating_point::value>::type> { + T rhs; + int result; + + explicit Comparer(T value) : rhs(value), result(1) {} + + void visitArray(const CollectionData &) {} + void visitObject(const CollectionData &) {} + void visitFloat(Float lhs) { + result = sign(lhs - static_cast(rhs)); + } + void visitString(const char *) {} + void visitRawJson(const char *, size_t) {} + void visitNegativeInteger(UInt lhs) { + result = -sign(static_cast(lhs) + rhs); + } + void visitPositiveInteger(UInt lhs) { + result = static_cast(lhs) < rhs ? -1 : static_cast(lhs) > rhs ? 1 : 0; + } + void visitBoolean(bool) {} + void visitNull() {} +}; + +template <> +struct Comparer { + bool rhs; + int result; + + explicit Comparer(bool value) : rhs(value), result(1) {} + + void visitArray(const CollectionData &) {} + void visitObject(const CollectionData &) {} + void visitFloat(Float) {} + void visitString(const char *) {} + void visitRawJson(const char *, size_t) {} + void visitNegativeInteger(UInt) {} + void visitPositiveInteger(UInt) {} + void visitBoolean(bool lhs) { + result = static_cast(lhs - rhs); + } + void visitNull() {} }; template class VariantComparisons { + private: + template + static int compare(TVariant lhs, const T &rhs) { + Comparer comparer(rhs); + lhs.accept(comparer); + return comparer.result; + } + public: - // const char* == TVariant + // value == TVariant template - friend typename enable_if::value, bool>::type operator==( - T *lhs, TVariant rhs) { - return adaptString(lhs).equals(rhs.template as()); + friend bool operator==(T *lhs, TVariant rhs) { + return compare(rhs, lhs) == 0; + } + template + friend bool operator==(const T &lhs, TVariant rhs) { + return compare(rhs, lhs) == 0; } - // std::string == TVariant + // TVariant == value template - friend typename enable_if::value, bool>::type operator==( - const T &lhs, TVariant rhs) { - return adaptString(lhs).equals(rhs.template as()); + friend bool operator==(TVariant lhs, T *rhs) { + return compare(lhs, rhs) == 0; + } + template + friend bool operator==(TVariant lhs, const T &rhs) { + return compare(lhs, rhs) == 0; } - // TVariant == const char* + // value != TVariant template - friend typename enable_if::value, bool>::type operator==( - TVariant lhs, T *rhs) { - return adaptString(rhs).equals(lhs.template as()); + friend bool operator!=(T *lhs, TVariant rhs) { + return compare(rhs, lhs) != 0; + } + template + friend bool operator!=(const T &lhs, TVariant rhs) { + return compare(rhs, lhs) != 0; } - // TVariant == std::string + // TVariant != value template - friend typename enable_if::value, bool>::type operator==( - TVariant lhs, const T &rhs) { - return adaptString(rhs).equals(lhs.template as()); + friend bool operator!=(TVariant lhs, T *rhs) { + return compare(lhs, rhs) != 0; + } + template + friend bool operator!=(TVariant lhs, const T &rhs) { + return compare(lhs, rhs) != 0; } - // bool/int/float == TVariant + // value < TVariant template - friend typename enable_if::value, bool>::type operator==( - const T &lhs, TVariant rhs) { - return lhs == rhs.template as(); + friend bool operator<(T *lhs, TVariant rhs) { + return compare(rhs, lhs) > 0; + } + template + friend bool operator<(const T &lhs, TVariant rhs) { + return compare(rhs, lhs) > 0; } - // TVariant == bool/int/float + // TVariant < value template - friend typename enable_if::value, bool>::type operator==( - TVariant lhs, const T &rhs) { - return lhs.template as() == rhs; + friend bool operator<(TVariant lhs, T *rhs) { + return compare(lhs, rhs) < 0; + } + template + friend bool operator<(TVariant lhs, const T &rhs) { + return compare(lhs, rhs) < 0; } - // const char* != TVariant + // value <= TVariant template - friend typename enable_if::value, bool>::type operator!=( - T *lhs, TVariant rhs) { - return !adaptString(lhs).equals(rhs.template as()); + friend bool operator<=(T *lhs, TVariant rhs) { + return compare(rhs, lhs) >= 0; + } + template + friend bool operator<=(const T &lhs, TVariant rhs) { + return compare(rhs, lhs) >= 0; } - // std::string != TVariant + // TVariant <= value template - friend typename enable_if::value, bool>::type operator!=( - const T &lhs, TVariant rhs) { - return !adaptString(lhs).equals(rhs.template as()); + friend bool operator<=(TVariant lhs, T *rhs) { + return compare(lhs, rhs) <= 0; + } + template + friend bool operator<=(TVariant lhs, const T &rhs) { + return compare(lhs, rhs) <= 0; } - // TVariant != const char* + // value > TVariant template - friend typename enable_if::value, bool>::type operator!=( - TVariant lhs, T *rhs) { - return !adaptString(rhs).equals(lhs.template as()); + friend bool operator>(T *lhs, TVariant rhs) { + return compare(rhs, lhs) < 0; + } + template + friend bool operator>(const T &lhs, TVariant rhs) { + return compare(rhs, lhs) < 0; } - // TVariant != std::string + // TVariant > value template - friend typename enable_if::value, bool>::type operator!=( - TVariant lhs, const T &rhs) { - return !adaptString(rhs).equals(lhs.template as()); + friend bool operator>(TVariant lhs, T *rhs) { + return compare(lhs, rhs) > 0; + } + template + friend bool operator>(TVariant lhs, const T &rhs) { + return compare(lhs, rhs) > 0; } - // bool/int/float != TVariant + // value >= TVariant template - friend typename enable_if::value, bool>::type operator!=( - const T &lhs, TVariant rhs) { - return lhs != rhs.template as(); + friend bool operator>=(T *lhs, TVariant rhs) { + return compare(rhs, lhs) <= 0; + } + template + friend bool operator>=(const T &lhs, TVariant rhs) { + return compare(rhs, lhs) <= 0; } - // TVariant != bool/int/float + // TVariant >= value template - friend typename enable_if::value, bool>::type operator!=( - TVariant lhs, const T &rhs) { - return lhs.template as() != rhs; + friend bool operator>=(TVariant lhs, T *rhs) { + return compare(lhs, rhs) >= 0; } - - // bool/int/float < TVariant template - friend typename enable_if::value, bool>::type operator<( - const T &lhs, TVariant rhs) { - return lhs < rhs.template as(); - } - - // TVariant < bool/int/float - template - friend typename enable_if::value, bool>::type operator<( - TVariant lhs, const T &rhs) { - return lhs.template as() < rhs; - } - - // bool/int/float <= TVariant - template - friend typename enable_if::value, bool>::type operator<=( - const T &lhs, TVariant rhs) { - return lhs <= rhs.template as(); - } - - // TVariant <= bool/int/float - template - friend typename enable_if::value, bool>::type operator<=( - TVariant lhs, const T &rhs) { - return lhs.template as() <= rhs; - } - - // bool/int/float > TVariant - template - friend typename enable_if::value, bool>::type operator>( - const T &lhs, TVariant rhs) { - return lhs > rhs.template as(); - } - - // TVariant > bool/int/float - template - friend typename enable_if::value, bool>::type operator>( - TVariant lhs, const T &rhs) { - return lhs.template as() > rhs; - } - - // bool/int/float >= TVariant - template - friend typename enable_if::value, bool>::type operator>=( - const T &lhs, TVariant rhs) { - return lhs >= rhs.template as(); - } - - // TVariant >= bool/int/float - template - friend typename enable_if::value, bool>::type operator>=( - TVariant lhs, const T &rhs) { - return lhs.template as() >= rhs; + friend bool operator>=(TVariant lhs, const T &rhs) { + return compare(lhs, rhs) >= 0; } }; diff --git a/src/ArduinoJson/Polyfills/safe_strcmp.hpp b/src/ArduinoJson/Polyfills/safe_strcmp.hpp new file mode 100644 index 00000000..79f20acd --- /dev/null +++ b/src/ArduinoJson/Polyfills/safe_strcmp.hpp @@ -0,0 +1,22 @@ +// ArduinoJson - arduinojson.org +// Copyright Benoit Blanchon 2014-2019 +// MIT License + +#pragma once + +namespace ARDUINOJSON_NAMESPACE { + +inline int8_t safe_strcmp(const char* a, const char* b) { + if (a == b) return 0; + if (!a) return -1; + if (!b) return 1; + return static_cast(strcmp(a, b)); +} + +inline int8_t safe_strncmp(const char* a, const char* b, size_t n) { + if (a == b) return 0; + if (!a) return -1; + if (!b) return 1; + return static_cast(strncmp(a, b, n)); +} +} // namespace ARDUINOJSON_NAMESPACE diff --git a/src/ArduinoJson/Strings/ArduinoStringAdapter.hpp b/src/ArduinoJson/Strings/ArduinoStringAdapter.hpp index 7d9e45c1..0445b8e2 100644 --- a/src/ArduinoJson/Strings/ArduinoStringAdapter.hpp +++ b/src/ArduinoJson/Strings/ArduinoStringAdapter.hpp @@ -5,6 +5,7 @@ #pragma once #include +#include "../Polyfills/safe_strcmp.hpp" namespace ARDUINOJSON_NAMESPACE { @@ -25,11 +26,14 @@ class ArduinoStringAdapter { return !_str->c_str(); } - bool equals(const char* expected) const { + int8_t compare(const char* other) const { // Arduino's String::c_str() can return NULL - const char* actual = _str->c_str(); - if (!actual || !expected) return actual == expected; - return 0 == strcmp(actual, expected); + const char* me = _str->c_str(); + return safe_strcmp(me, other); + } + + bool equals(const char* expected) const { + return compare(expected) == 0; } const char* data() const { diff --git a/src/ArduinoJson/Strings/ConstRamStringAdapter.hpp b/src/ArduinoJson/Strings/ConstRamStringAdapter.hpp index c8736905..54f43a9b 100644 --- a/src/ArduinoJson/Strings/ConstRamStringAdapter.hpp +++ b/src/ArduinoJson/Strings/ConstRamStringAdapter.hpp @@ -6,6 +6,7 @@ #include // size_t #include // strcmp +#include "../Polyfills/safe_strcmp.hpp" namespace ARDUINOJSON_NAMESPACE { @@ -13,10 +14,12 @@ class ConstRamStringAdapter { public: ConstRamStringAdapter(const char* str = 0) : _str(str) {} + int8_t compare(const char* other) const { + return safe_strcmp(_str, other); + } + bool equals(const char* expected) const { - const char* actual = _str; - if (!actual || !expected) return actual == expected; - return strcmp(actual, expected) == 0; + return compare(expected) == 0; } bool isNull() const { diff --git a/src/ArduinoJson/Strings/FlashStringAdapter.hpp b/src/ArduinoJson/Strings/FlashStringAdapter.hpp index 79d26ef8..bb0fdd68 100644 --- a/src/ArduinoJson/Strings/FlashStringAdapter.hpp +++ b/src/ArduinoJson/Strings/FlashStringAdapter.hpp @@ -10,10 +10,15 @@ class FlashStringAdapter { public: FlashStringAdapter(const __FlashStringHelper* str) : _str(str) {} + int8_t compare(const char* other) const { + if (!other && !_str) return 0; + if (!_str) return -1; + if (!other) return 1; + return -strcmp_P(other, reinterpret_cast(_str)); + } + bool equals(const char* expected) const { - const char* actual = reinterpret_cast(_str); - if (!actual || !expected) return actual == expected; - return strcmp_P(expected, actual) == 0; + return compare(expected) == 0; } bool isNull() const { diff --git a/src/ArduinoJson/Strings/SizedFlashStringAdapter.hpp b/src/ArduinoJson/Strings/SizedFlashStringAdapter.hpp index f31b1e3c..0bddf4aa 100644 --- a/src/ArduinoJson/Strings/SizedFlashStringAdapter.hpp +++ b/src/ArduinoJson/Strings/SizedFlashStringAdapter.hpp @@ -11,10 +11,15 @@ class SizedFlashStringAdapter { SizedFlashStringAdapter(const __FlashStringHelper* str, size_t sz) : _str(str), _size(sz) {} + int8_t compare(const char* other) const { + if (!other && !_str) return 0; + if (!_str) return -1; + if (!other) return 1; + return -strncmp_P(other, reinterpret_cast(_str), _size); + } + bool equals(const char* expected) const { - const char* actual = reinterpret_cast(_str); - if (!actual || !expected) return actual == expected; - return strncmp_P(expected, actual, _size) == 0; + return compare(expected) == 0; } bool isNull() const { diff --git a/src/ArduinoJson/Strings/SizedRamStringAdapter.hpp b/src/ArduinoJson/Strings/SizedRamStringAdapter.hpp index 1333b94e..c8071578 100644 --- a/src/ArduinoJson/Strings/SizedRamStringAdapter.hpp +++ b/src/ArduinoJson/Strings/SizedRamStringAdapter.hpp @@ -12,10 +12,12 @@ class SizedRamStringAdapter { public: SizedRamStringAdapter(const char* str, size_t n) : _str(str), _size(n) {} + int8_t compare(const char* other) const { + return safe_strncmp(_str, other, _size) == 0; + } + bool equals(const char* expected) const { - const char* actual = reinterpret_cast(_str); - if (!actual || !expected) return actual == expected; - return strcmp(actual, expected) == 0; + return compare(expected) == 0; } bool isNull() const { diff --git a/src/ArduinoJson/Strings/StlStringAdapter.hpp b/src/ArduinoJson/Strings/StlStringAdapter.hpp index 956e666c..1ec3e37d 100644 --- a/src/ArduinoJson/Strings/StlStringAdapter.hpp +++ b/src/ArduinoJson/Strings/StlStringAdapter.hpp @@ -23,6 +23,11 @@ class StlStringAdapter { return false; } + int8_t compare(const char* other) const { + if (!other) return 1; + return static_cast(_str->compare(other)); + } + bool equals(const char* expected) const { if (!expected) return false; return *_str == expected; diff --git a/test/JsonObject/subscript.cpp b/test/JsonObject/subscript.cpp index 418c0d50..3d21cb73 100644 --- a/test/JsonObject/subscript.cpp +++ b/test/JsonObject/subscript.cpp @@ -161,7 +161,7 @@ TEST_CASE("JsonObject::operator[]") { obj[null] = 666; REQUIRE(obj.size() == 1); - REQUIRE(obj[null] == 0); + REQUIRE(obj[null] == null); } SECTION("obj[key].to()") { diff --git a/test/JsonVariant/compare.cpp b/test/JsonVariant/compare.cpp index e5ca0d5b..c53c64f2 100644 --- a/test/JsonVariant/compare.cpp +++ b/test/JsonVariant/compare.cpp @@ -337,23 +337,117 @@ TEST_CASE("JsonVariant comparisons") { REQUIRE(variant1 != variant3); REQUIRE_FALSE(variant1 == variant3); } - - // SECTION("VariantsOfDifferentTypes") { - // DynamicJsonDocument doc1(4096); - // JsonObject obj = doc1.to(); - - // DynamicJsonDocument doc2(4096); - // JsonArray arr = doc2.to(); - // JsonVariant variants[] = { - // true, 42, 666.667, "hello", arr, obj, - // }; - // size_t n = sizeof(variants) / sizeof(variants[0]); - - // for (size_t i = 0; i < n; i++) { - // for (size_t j = i + 1; j < n; j++) { - // REQUIRE(variants[i] != variants[j]); - // REQUIRE_FALSE(variants[i] == variants[j]); - // } - // } - // } +} + +class VariantComparisionFixture { + private: + StaticJsonDocument<256> doc; + JsonVariant variant; + + public: + VariantComparisionFixture() : variant(doc.to()) {} + + protected: + template + void setValue(const T& value) { + variant.set(value); + } + + template + void assertEqualsTo(const T& value) { + REQUIRE(variant == value); + REQUIRE(value == variant); + + REQUIRE_FALSE(variant != value); + REQUIRE_FALSE(value != variant); + } + + template + void assertDiffersFrom(const T& value) { + REQUIRE(variant != value); + REQUIRE(value != variant); + + REQUIRE_FALSE(variant == value); + REQUIRE_FALSE(value == variant); + } + + template + void assertGreaterThan(const T& value) { + REQUIRE((variant > value)); + REQUIRE((variant >= value)); + REQUIRE(value < variant); + REQUIRE(value <= variant); + + REQUIRE_FALSE((variant < value)); + REQUIRE_FALSE((variant <= value)); + REQUIRE_FALSE(value > variant); + REQUIRE_FALSE(value >= variant); + } + + template + void assertLowerThan(const T& value) { + REQUIRE(variant < value); + REQUIRE(variant <= value); + REQUIRE(value > variant); + REQUIRE(value >= variant); + + REQUIRE_FALSE(variant > value); + REQUIRE_FALSE(variant >= value); + REQUIRE_FALSE(value < variant); + REQUIRE_FALSE(value <= variant); + } +}; + +TEST_CASE_METHOD(VariantComparisionFixture, + "Compare variant with another type") { + SECTION("null") { + assertDiffersFrom(3); + assertDiffersFrom("world"); + } + + SECTION("string") { + setValue("hello"); + assertEqualsTo("hello"); + assertDiffersFrom(3); + assertDiffersFrom("world"); + assertGreaterThan("helln"); + assertLowerThan("hellp"); + } + + SECTION("positive integer") { + setValue(42); + assertEqualsTo(42); + assertDiffersFrom(43); + assertGreaterThan(41); + assertLowerThan(43); + assertDiffersFrom("world"); + } + + SECTION("negative integer") { + setValue(-42); + assertEqualsTo(-42); + assertDiffersFrom(42); + assertGreaterThan(-43); + assertLowerThan(-41); + assertDiffersFrom("world"); + } + + SECTION("double") { + setValue(42.0); + assertEqualsTo(42.0); + assertDiffersFrom(42.1); + assertGreaterThan(41.0); + assertLowerThan(43.0); + assertDiffersFrom("42.0"); + } + + SECTION("true") { + setValue(true); + assertEqualsTo(true); + assertDiffersFrom(false); + assertDiffersFrom(1); + assertDiffersFrom("true"); + assertDiffersFrom(1.0); + assertGreaterThan(false); + } } diff --git a/test/JsonVariant/createNested.cpp b/test/JsonVariant/createNested.cpp index 80327942..8db9d5af 100644 --- a/test/JsonVariant/createNested.cpp +++ b/test/JsonVariant/createNested.cpp @@ -14,7 +14,7 @@ TEST_CASE("JsonVariant::createNestedObject()") { SECTION("promotes to array") { JsonObject obj = variant.createNestedObject(); - obj["value"] = "42"; + obj["value"] = 42; REQUIRE(variant.is() == true); REQUIRE(variant[0]["value"] == 42); @@ -23,7 +23,7 @@ TEST_CASE("JsonVariant::createNestedObject()") { SECTION("works on MemberProxy") { JsonObject obj = variant["items"].createNestedObject(); - obj["value"] = "42"; + obj["value"] = 42; REQUIRE(variant["items"][0]["value"] == 42); } @@ -42,7 +42,7 @@ TEST_CASE("JsonVariant::createNestedArray()") { SECTION("works on MemberProxy") { JsonArray arr = variant["items"].createNestedArray(); - arr.add("42"); + arr.add(42); REQUIRE(variant["items"][0][0] == 42); } @@ -54,7 +54,7 @@ TEST_CASE("JsonVariant::createNestedObject(key)") { SECTION("promotes to object") { JsonObject obj = variant.createNestedObject("weather"); - obj["temp"] = "42"; + obj["temp"] = 42; REQUIRE(variant.is() == true); REQUIRE(variant["weather"]["temp"] == 42); @@ -62,7 +62,7 @@ TEST_CASE("JsonVariant::createNestedObject(key)") { SECTION("works on MemberProxy") { JsonObject obj = variant["status"].createNestedObject("weather"); - obj["temp"] = "42"; + obj["temp"] = 42; REQUIRE(variant["status"]["weather"]["temp"] == 42); } @@ -81,7 +81,7 @@ TEST_CASE("JsonVariant::createNestedArray(key)") { SECTION("works on MemberProxy") { JsonArray arr = variant["weather"].createNestedArray("temp"); - arr.add("42"); + arr.add(42); REQUIRE(variant["weather"]["temp"][0] == 42); } diff --git a/test/Misc/CMakeLists.txt b/test/Misc/CMakeLists.txt index e66246d7..0f70acc0 100644 --- a/test/Misc/CMakeLists.txt +++ b/test/Misc/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(MiscTests conflicts.cpp FloatParts.cpp StreamReader.cpp + StringAdapters.cpp StringWriter.cpp TypeTraits.cpp unsigned_char.cpp diff --git a/test/Misc/StringAdapters.cpp b/test/Misc/StringAdapters.cpp new file mode 100644 index 00000000..aaee45f7 --- /dev/null +++ b/test/Misc/StringAdapters.cpp @@ -0,0 +1,45 @@ +// ArduinoJson - arduinojson.org +// Copyright Benoit Blanchon 2014-2019 +// MIT License + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +TEST_CASE("ConstRamStringAdapter") { + SECTION("null") { + ConstRamStringAdapter adapter(NULL); + + REQUIRE(adapter.compare("bravo") < 0); + REQUIRE(adapter.compare(NULL) == 0); + + REQUIRE(adapter.equals(NULL)); + REQUIRE_FALSE(adapter.equals("charlie")); + } + + SECTION("non-null") { + ConstRamStringAdapter adapter("bravo"); + + REQUIRE(adapter.compare(NULL) > 0); + REQUIRE(adapter.compare("alpha") > 0); + REQUIRE(adapter.compare("bravo") == 0); + REQUIRE(adapter.compare("charlie") < 0); + + REQUIRE(adapter.equals("bravo")); + REQUIRE_FALSE(adapter.equals("charlie")); + } +} + +TEST_CASE("StlString") { + std::string str("bravo"); + StlStringAdapter adapter(str); + + REQUIRE(adapter.compare(NULL) > 0); + REQUIRE(adapter.compare("alpha") > 0); + REQUIRE(adapter.compare("bravo") == 0); + REQUIRE(adapter.compare("charlie") < 0); + + REQUIRE(adapter.equals("bravo")); + REQUIRE_FALSE(adapter.equals("charlie")); +}