#include #include "esphome/core/string_ref.h" #include #include namespace esphome::core::testing { TEST(StringRefStartsWith, ProperPrefixMatches) { StringRef ref("FR:R20:12345", 12); EXPECT_TRUE(ref.starts_with("FR:")); } TEST(StringRefStartsWith, WholeStringIsAPrefixOfItself) { StringRef ref("TP96", 4); EXPECT_TRUE(ref.starts_with("TP96")); } TEST(StringRefStartsWith, PrefixLongerThanViewFails) { StringRef ref("TP", 2); EXPECT_FALSE(ref.starts_with("TP96")); } TEST(StringRefStartsWith, DifferentContentFails) { StringRef ref("TP96", 4); EXPECT_FALSE(ref.starts_with("FR:")); } TEST(StringRefStartsWith, EmptyPrefixAlwaysMatches) { StringRef ref("abc", 3); EXPECT_TRUE(ref.starts_with("")); StringRef empty; EXPECT_TRUE(empty.starts_with("")); } TEST(StringRefStartsWith, EmptyViewOnlyMatchesEmptyPrefix) { StringRef empty; EXPECT_FALSE(empty.starts_with("a")); } TEST(StringRefStartsWith, WorksOnANonTerminatedBuffer) { // The reason the helper exists: a bounded view over a buffer with no // terminator anywhere near the viewed bytes. const char raw[] = {'R', 'a', 'd', 'o', 'n', 'X'}; StringRef ref(raw, 5); EXPECT_TRUE(ref.starts_with("Radon")); EXPECT_FALSE(ref.starts_with("RadonEye")); EXPECT_FALSE(ref.starts_with("adon")); } TEST(StringRefStartsWith, StdStringOverload) { StringRef ref("TP96", 4); EXPECT_TRUE(ref.starts_with(std::string("TP"))); EXPECT_FALSE(ref.starts_with(std::string("96"))); } TEST(StringRefStartsWith, RefOverloadComparesOnlyTheViewedLength) { // The prefix is a bounded view: bytes past its length must not be compared. StringRef ref("FR:123", 6); StringRef prefix("FR:xyz", 3); EXPECT_TRUE(ref.starts_with(prefix)); } // The generated api messages start their encode only string fields as a null pointer with zero // length; every member must treat that exactly like the default constructed empty string. TEST(StringRefNullEmpty, BehavesAsEmptyString) { const StringRef null_empty{nullptr, 0}; const StringRef empty; EXPECT_TRUE(null_empty.empty()); EXPECT_EQ(null_empty.size(), 0u); EXPECT_EQ(null_empty.c_str(), nullptr); EXPECT_EQ(null_empty.byte(), nullptr); EXPECT_TRUE(null_empty == empty); EXPECT_FALSE(null_empty < empty); EXPECT_FALSE(empty < null_empty); EXPECT_TRUE(null_empty == ""); // NOLINT(readability-container-size-empty) - operator under test EXPECT_TRUE(null_empty == std::string()); // NOLINT(readability-container-size-empty) - operator under test EXPECT_EQ(null_empty.compare(empty), 0); EXPECT_EQ(null_empty.compare(""), 0); EXPECT_LT(null_empty.compare("a"), 0); EXPECT_TRUE(null_empty.starts_with("")); EXPECT_FALSE(null_empty.starts_with("a")); EXPECT_EQ(null_empty.str(), std::string()); EXPECT_EQ(null_empty.substr(0), std::string()); EXPECT_EQ(null_empty.find('a'), std::string::npos); EXPECT_EQ(null_empty.find("a"), std::string::npos); char buf[4] = "xyz"; EXPECT_EQ(null_empty.copy(buf, sizeof(buf)), 0u); EXPECT_EQ(null_empty.begin(), null_empty.end()); } TEST(StringRefNullEmpty, ComparesAgainstText) { const StringRef null_empty{nullptr, 0}; const StringRef text("abc", 3); EXPECT_FALSE(null_empty == text); EXPECT_FALSE(text == null_empty); EXPECT_LT(null_empty.compare(text), 0); EXPECT_GT(text.compare(null_empty), 0); EXPECT_TRUE(null_empty < text); EXPECT_FALSE(text < null_empty); EXPECT_TRUE(text.starts_with(null_empty)); } TEST(StringRefNullEmpty, TwoNullViewsAreEqual) { const StringRef a{nullptr, 0}; const StringRef b{nullptr, 0}; EXPECT_TRUE(a == b); EXPECT_FALSE(a < b); EXPECT_EQ(a.compare(b), 0); EXPECT_TRUE(a.starts_with(b)); } // Every iterator endpoint of a null view is the same null position: nothing is dereferenced and // no offset is applied to the null pointer, so the range is simply empty. TEST(StringRefNullEmpty, IteratorEndpointsFormAnEmptyRange) { const StringRef null_empty{nullptr, 0}; EXPECT_EQ(null_empty.cbegin(), null_empty.cend()); EXPECT_EQ(null_empty.rbegin(), null_empty.rend()); EXPECT_EQ(null_empty.crbegin(), null_empty.crend()); EXPECT_EQ(std::distance(null_empty.begin(), null_empty.end()), 0); size_t visited = 0; for (char c : null_empty) { (void) c; visited++; } EXPECT_EQ(visited, 0u); // NOLINTNEXTLINE(bugprone-string-constructor) - empty range under test EXPECT_EQ(std::string(null_empty.begin(), null_empty.end()), std::string()); } // The pointer and length constructor accepts an empty range at a null pointer; the copy into a // std::string reads nothing. TEST(StringRefNullEmpty, ConvertsToEmptyStdString) { const StringRef null_empty{nullptr, 0}; const std::string copy = null_empty.str(); EXPECT_TRUE(copy.empty()); EXPECT_EQ(static_cast(null_empty), std::string()); EXPECT_EQ(null_empty.substr(0, 5), std::string()); std::string target("keep"); target += null_empty; EXPECT_EQ(target, "keep"); } // The number conversions hand the pointer to the C library; a null view must stop before that. TEST(StringRefNullEmpty, NumericConversionsReturnZero) { const StringRef null_empty{nullptr, 0}; size_t pos = 99; EXPECT_EQ(stoi(null_empty, &pos), 0); EXPECT_EQ(pos, 0u); pos = 99; EXPECT_EQ(stol(null_empty, &pos, 16), 0L); EXPECT_EQ(pos, 0u); pos = 99; EXPECT_EQ(stof(null_empty, &pos), 0.0f); EXPECT_EQ(pos, 0u); pos = 99; EXPECT_EQ(stod(null_empty, &pos), 0.0); EXPECT_EQ(pos, 0u); EXPECT_EQ(stoi(null_empty), 0); EXPECT_EQ(stof(null_empty), 0.0f); } } // namespace esphome::core::testing