[string] change String<kSize> to inherit from StringWriter (#6658)

This commit changes the `String<kSize>` class to inherit from
`StringWriter` class. It helps simplify their use allowing
`StringWriter` methods to be directly called on an `String` instance
while ensuring the implementation is still shared among all template
variants of `String<kSize>` class. This change also ensures that
`String<kSize>` is always initialized and set to an empty string from
its constructor.
This commit is contained in:
Abtin Keshavarzian
2021-05-21 12:08:26 -07:00
committed by GitHub
parent 6891fe6e0c
commit 5915483063
17 changed files with 137 additions and 176 deletions
+14 -17
View File
@@ -59,7 +59,6 @@ static void Log(otLogLevel aLogLevel,
va_list aArgs)
{
ot::String<OPENTHREAD_CONFIG_LOG_MAX_SIZE> logString;
ot::StringWriter writer(logString);
#if OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE
VerifyOrExit(otLoggingGetLevel() >= aLogLevel);
@@ -97,12 +96,12 @@ static void Log(otLogLevel aLogLevel,
break;
}
writer.Append("%s", levelStr);
logString.Append("%s", levelStr);
}
#endif // OPENTHREAD_CONFIG_LOG_PREPEND_LEVEL
writer.Append("%s", aRegionPrefix);
writer.AppendVarArgs(aFormat, aArgs);
logString.Append("%s", aRegionPrefix);
logString.AppendVarArgs(aFormat, aArgs);
otPlatLog(aLogLevel, aLogRegion, "%s" OPENTHREAD_CONFIG_LOG_SUFFIX, logString.AsCString());
#if OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE
@@ -236,28 +235,27 @@ enum : uint8_t
static void DumpLine(otLogLevel aLogLevel, otLogRegion aLogRegion, const uint8_t *aBytes, const size_t aLength)
{
ot::String<kStringLineLength> string;
ot::StringWriter writer(string);
writer.Append("|");
string.Append("|");
for (uint8_t i = 0; i < kDumpBytesPerLine; i++)
{
if (i < aLength)
{
writer.Append(" %02X", aBytes[i]);
string.Append(" %02X", aBytes[i]);
}
else
{
writer.Append(" ..");
string.Append(" ..");
}
if (!((i + 1) % 8))
{
writer.Append(" |");
string.Append(" |");
}
}
writer.Append(" ");
string.Append(" ");
for (uint8_t i = 0; i < kDumpBytesPerLine; i++)
{
@@ -273,7 +271,7 @@ static void DumpLine(otLogLevel aLogLevel, otLogRegion aLogRegion, const uint8_t
}
}
writer.Append("%c", c);
string.Append("%c", c);
}
otLogDump(aLogLevel, aLogRegion, "%s", string.AsCString());
@@ -288,18 +286,17 @@ void otDump(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aId, const
size_t idLen = strlen(aId);
ot::String<kStringLineLength> string;
ot::StringWriter writer(string);
for (size_t i = 0; i < (kWidth - idLen) / 2 - 5; i++)
{
writer.Append("=");
string.Append("=");
}
writer.Append("[%s len=%03u]", aId, static_cast<unsigned>(aLength));
string.Append("[%s len=%03u]", aId, static_cast<unsigned>(aLength));
for (size_t i = 0; i < (kWidth - idLen) / 2 - 4; i++)
{
writer.Append("=");
string.Append("=");
}
otLogDump(aLogLevel, aLogRegion, "%s", string.AsCString());
@@ -310,11 +307,11 @@ void otDump(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aId, const
OT_MIN((aLength - i), static_cast<size_t>(kDumpBytesPerLine)));
}
writer.Clear();
string.Clear();
for (size_t i = 0; i < kWidth; i++)
{
writer.Append("-");
string.Append("-");
}
otLogDump(aLogLevel, aLogRegion, "%s", string.AsCString());
+3 -4
View File
@@ -212,7 +212,6 @@ void Notifier::LogEvents(Events aEvents) const
bool addSpace = false;
bool didLog = false;
String<kFlagsStringBufferSize> string;
StringWriter writer(string);
for (uint8_t bit = 0; bit < sizeof(Events::Flags) * CHAR_BIT; bit++)
{
@@ -220,16 +219,16 @@ void Notifier::LogEvents(Events aEvents) const
if (flags & (1 << bit))
{
if (writer.GetLength() >= kFlagsStringLineLimit)
if (string.GetLength() >= kFlagsStringLineLimit)
{
otLogInfoCore("Notifier: StateChanged (0x%08x) %s%s ...", aEvents.GetAsFlags(), didLog ? "... " : "[",
string.AsCString());
writer.Clear();
string.Clear();
didLog = true;
addSpace = false;
}
writer.Append("%s%s", addSpace ? " " : "", EventToString(static_cast<Event>(1 << bit)));
string.Append("%s%s", addSpace ? " " : "", EventToString(static_cast<Event>(1 << bit)));
addSpace = true;
flags ^= (1 << bit);
+30 -45
View File
@@ -90,37 +90,6 @@ const char *StringFind(const char *aString, char aChar);
*/
bool StringEndsWith(const char *aString, char aChar);
class StringWriter;
/**
* This class defines a fixed-size string.
*
*/
template <uint16_t kSize> class String
{
friend class StringWriter;
static_assert(kSize > 0, "String buffer cannot be empty.");
public:
/**
* This method clears the string (sets it to empty).
*
*/
void Clear(void) { mBuffer[0] = '\0'; }
/**
* This method returns the string as a null-terminated C string.
*
* @returns The null-terminated C string.
*
*/
const char *AsCString(void) const { return mBuffer; }
private:
char mBuffer[kSize];
};
/**
* This class implements writing to a string buffer.
*
@@ -137,20 +106,6 @@ public:
*/
StringWriter(char *aBuffer, uint16_t aSize);
/**
* This constructor initializes the object as cleared on a fixed-size string.
*
* @tparam kSize The size of the string (number of chars).
*
* @param[in] aStringBuffer A reference to a `String<kSize>` to write into.
*
*/
template <uint16_t kSize>
explicit StringWriter(String<kSize> &aStringBuffer)
: StringWriter(aStringBuffer.mBuffer, kSize)
{
}
/**
* This method clears the string writer.
*
@@ -227,6 +182,36 @@ private:
const uint16_t mSize;
};
/**
* This class defines a fixed-size string.
*
*/
template <uint16_t kSize> class String : public StringWriter
{
static_assert(kSize > 0, "String buffer cannot be empty.");
public:
/**
* This constructor initializes the string as empty.
*
*/
String(void)
: StringWriter(mBuffer, sizeof(mBuffer))
{
}
/**
* This method returns the string as a null-terminated C string.
*
* @returns The null-terminated C string.
*
*/
const char *AsCString(void) const { return mBuffer; }
private:
char mBuffer[kSize];
};
/**
* This function validates whether a given byte sequence (string) follows UTF-8 encoding.
*
+8 -9
View File
@@ -95,13 +95,12 @@ exit:
ChannelMask::InfoString ChannelMask::ToString(void) const
{
InfoString string;
StringWriter writer(string);
uint8_t channel = kChannelIteratorFirst;
bool addComma = false;
Error error;
InfoString string;
uint8_t channel = kChannelIteratorFirst;
bool addComma = false;
Error error;
writer.Append("{");
string.Append("{");
error = GetNextChannel(channel);
@@ -120,16 +119,16 @@ ChannelMask::InfoString ChannelMask::ToString(void) const
rangeEnd = channel;
}
writer.Append("%s%d", addComma ? ", " : " ", rangeStart);
string.Append("%s%d", addComma ? ", " : " ", rangeStart);
addComma = true;
if (rangeStart < rangeEnd)
{
writer.Append("%s%d", rangeEnd == rangeStart + 1 ? ", " : "-", rangeEnd);
string.Append("%s%d", rangeEnd == rangeStart + 1 ? ", " : "-", rangeEnd);
}
}
writer.Append(" }");
string.Append(" }");
return string;
}
+16 -18
View File
@@ -1381,27 +1381,26 @@ exit:
Frame::InfoString Frame::ToInfoString(void) const
{
InfoString string;
StringWriter writer(string);
uint8_t commandId, type;
Address src, dst;
InfoString string;
uint8_t commandId, type;
Address src, dst;
writer.Append("len:%d, seqnum:%d, type:", mLength, GetSequence());
string.Append("len:%d, seqnum:%d, type:", mLength, GetSequence());
type = GetType();
switch (type)
{
case kFcfFrameBeacon:
writer.Append("Beacon");
string.Append("Beacon");
break;
case kFcfFrameData:
writer.Append("Data");
string.Append("Data");
break;
case kFcfFrameAck:
writer.Append("Ack");
string.Append("Ack");
break;
case kFcfFrameMacCmd:
@@ -1413,33 +1412,33 @@ Frame::InfoString Frame::ToInfoString(void) const
switch (commandId)
{
case kMacCmdDataRequest:
writer.Append("Cmd(DataReq)");
string.Append("Cmd(DataReq)");
break;
case kMacCmdBeaconRequest:
writer.Append("Cmd(BeaconReq)");
string.Append("Cmd(BeaconReq)");
break;
default:
writer.Append("Cmd(%d)", commandId);
string.Append("Cmd(%d)", commandId);
break;
}
break;
default:
writer.Append("%d", type);
string.Append("%d", type);
break;
}
IgnoreError(GetSrcAddr(src));
IgnoreError(GetDstAddr(dst));
writer.Append(", src:%s, dst:%s, sec:%s, ackreq:%s", src.ToString().AsCString(), dst.ToString().AsCString(),
string.Append(", src:%s, dst:%s, sec:%s, ackreq:%s", src.ToString().AsCString(), dst.ToString().AsCString(),
GetSecurityEnabled() ? "yes" : "no", GetAckRequest() ? "yes" : "no");
#if OPENTHREAD_CONFIG_MULTI_RADIO
writer.Append(", radio:%s", RadioTypeToString(GetRadioType()));
string.Append(", radio:%s", RadioTypeToString(GetRadioType()));
#endif
return string;
@@ -1447,13 +1446,12 @@ Frame::InfoString Frame::ToInfoString(void) const
BeaconPayload::InfoString BeaconPayload::ToInfoString(void) const
{
NetworkName name;
InfoString string;
StringWriter writer(string);
NetworkName name;
InfoString string;
IgnoreError(name.Set(GetNetworkName()));
writer.Append("name:%s, xpanid:%s, id:%d, ver:%d, joinable:%s, native:%s", name.GetAsCString(),
string.Append("name:%s, xpanid:%s, id:%d, ver:%d, joinable:%s, native:%s", name.GetAsCString(),
mExtendedPanId.ToString().AsCString(), GetProtocolId(), GetProtocolVersion(),
IsJoiningPermitted() ? "yes" : "no", IsNative() ? "yes" : "no");
return string;
+14 -18
View File
@@ -65,10 +65,9 @@ void ExtAddress::GenerateRandom(void)
ExtAddress::InfoString ExtAddress::ToString(void) const
{
InfoString string;
StringWriter writer(string);
InfoString string;
writer.AppendHexBytes(m8, sizeof(ExtAddress));
string.AppendHexBytes(m8, sizeof(ExtAddress));
return string;
}
@@ -93,20 +92,19 @@ void ExtAddress::CopyAddress(uint8_t *aDst, const uint8_t *aSrc, CopyByteOrder a
Address::InfoString Address::ToString(void) const
{
InfoString string;
StringWriter writer(string);
InfoString string;
if (mType == kTypeExtended)
{
writer.AppendHexBytes(GetExtended().m8, sizeof(ExtAddress));
string.AppendHexBytes(GetExtended().m8, sizeof(ExtAddress));
}
else if (mType == kTypeNone)
{
writer.Append("None");
string.Append("None");
}
else
{
writer.Append("0x%04x", GetShort());
string.Append("0x%04x", GetShort());
}
return string;
@@ -114,10 +112,9 @@ Address::InfoString Address::ToString(void) const
ExtendedPanId::InfoString ExtendedPanId::ToString(void) const
{
InfoString string;
StringWriter writer(string);
InfoString string;
writer.AppendHexBytes(m8, sizeof(ExtendedPanId));
string.AppendHexBytes(m8, sizeof(ExtendedPanId));
return string;
}
@@ -220,15 +217,14 @@ void RadioTypes::AddAll(void)
RadioTypes::InfoString RadioTypes::ToString(void) const
{
InfoString string;
StringWriter writer(string);
bool addComma = false;
InfoString string;
bool addComma = false;
writer.Append("{");
string.Append("{");
#if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
if (Contains(kRadioTypeIeee802154))
{
writer.Append("%s%s", addComma ? ", " : " ", RadioTypeToString(kRadioTypeIeee802154));
string.Append("%s%s", addComma ? ", " : " ", RadioTypeToString(kRadioTypeIeee802154));
addComma = true;
}
#endif
@@ -236,14 +232,14 @@ RadioTypes::InfoString RadioTypes::ToString(void) const
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
if (Contains(kRadioTypeTrel))
{
writer.Append("%s%s", addComma ? ", " : " ", RadioTypeToString(kRadioTypeTrel));
string.Append("%s%s", addComma ? ", " : " ", RadioTypeToString(kRadioTypeTrel));
addComma = true;
}
#endif
OT_UNUSED_VARIABLE(addComma);
writer.Append(" }");
string.Append(" }");
return string;
}
+6 -7
View File
@@ -164,25 +164,24 @@ bool JoinerDiscerner::operator==(const JoinerDiscerner &aOther) const
JoinerDiscerner::InfoString JoinerDiscerner::ToString(void) const
{
InfoString str;
StringWriter writer(str);
InfoString string;
if (mLength <= sizeof(uint16_t) * CHAR_BIT)
{
writer.Append("0x%04x", static_cast<uint16_t>(mValue));
string.Append("0x%04x", static_cast<uint16_t>(mValue));
}
else if (mLength <= sizeof(uint32_t) * CHAR_BIT)
{
writer.Append("0x%08x", static_cast<uint32_t>(mValue));
string.Append("0x%08x", static_cast<uint32_t>(mValue));
}
else
{
writer.Append("0x%x-%08x", static_cast<uint32_t>(mValue >> 32), static_cast<uint32_t>(mValue));
string.Append("0x%x-%08x", static_cast<uint32_t>(mValue >> 32), static_cast<uint32_t>(mValue));
}
writer.Append("/len:%d", mLength);
string.Append("/len:%d", mLength);
return str;
return string;
}
void SteeringData::Init(uint8_t aLength)
+2 -3
View File
@@ -88,10 +88,9 @@ exit:
Address::InfoString Address::ToString(void) const
{
InfoString string;
StringWriter writer(string);
InfoString string;
writer.Append("%d.%d.%d.%d", mBytes[0], mBytes[1], mBytes[2], mBytes[3]);
string.Append("%d.%d.%d.%d", mBytes[0], mBytes[1], mBytes[2], mBytes[3]);
return string;
}
+6 -9
View File
@@ -126,10 +126,9 @@ bool Prefix::IsValidNat64(void) const
Prefix::InfoString Prefix::ToString(void) const
{
InfoString string;
StringWriter writer(string);
InfoString string;
ToString(writer);
ToString(string);
return string;
}
@@ -257,10 +256,9 @@ bool InterfaceIdentifier::IsAnycastServiceLocator(void) const
InterfaceIdentifier::InfoString InterfaceIdentifier::ToString(void) const
{
InfoString string;
StringWriter writer(string);
InfoString string;
writer.AppendHexBytes(mFields.m8, kSize);
string.AppendHexBytes(mFields.m8, kSize);
return string;
}
@@ -641,10 +639,9 @@ exit:
Address::InfoString Address::ToString(void) const
{
InfoString string;
StringWriter writer(string);
InfoString string;
ToString(writer);
ToString(string);
return string;
}
+2 -3
View File
@@ -38,10 +38,9 @@ namespace Ip6 {
SockAddr::InfoString SockAddr::ToString(void) const
{
InfoString string;
StringWriter writer(string);
InfoString string;
writer.Append("[%s]:%u", GetAddress().ToString().AsCString(), GetPort());
string.Append("[%s]:%u", GetAddress().ToString().AsCString(), GetPort());
return string;
}
+8 -9
View File
@@ -77,35 +77,34 @@ uint16_t Header::GetSize(Type aType)
Header::InfoString Header::ToString(void) const
{
Type type = GetType();
InfoString string;
StringWriter writer(string);
Type type = GetType();
InfoString string;
switch (type)
{
case kTypeBroadcast:
writer.Append("broadcast ch:%d", GetChannel());
string.Append("broadcast ch:%d", GetChannel());
break;
case kTypeUnicast:
writer.Append("unicast ch:%d", GetChannel());
string.Append("unicast ch:%d", GetChannel());
break;
case kTypeAck:
writer.Append("ack");
string.Append("ack");
break;
}
writer.Append(" panid:%04x num:%lu src:%s", GetPanId(), GetPacketNumber(), GetSource().ToString().AsCString());
string.Append(" panid:%04x num:%lu src:%s", GetPanId(), GetPacketNumber(), GetSource().ToString().AsCString());
if ((type == kTypeUnicast) || (type == kTypeAck))
{
writer.Append(" dst:%s", GetDestination().ToString().AsCString());
string.Append(" dst:%s", GetDestination().ToString().AsCString());
}
if ((type == kTypeUnicast) || (type == kTypeBroadcast))
{
writer.Append(GetAckMode() == kNoAck ? " no-ack" : " ack-req");
string.Append(GetAckMode() == kNoAck ? " no-ack" : " ack-req");
}
return string;
+4 -6
View File
@@ -106,11 +106,10 @@ exit:
RssAverager::InfoString RssAverager::ToString(void) const
{
InfoString string;
StringWriter writer(string);
InfoString string;
VerifyOrExit(mCount != 0);
writer.Append("%d.%s", -(mAverage >> kPrecisionBitShift), kDigitsString[mAverage & kPrecisionBitMask]);
string.Append("%d.%s", -(mAverage >> kPrecisionBitShift), kDigitsString[mAverage & kPrecisionBitMask]);
exit:
return string;
@@ -167,10 +166,9 @@ uint8_t LinkQualityInfo::GetLinkMargin(void) const
LinkQualityInfo::InfoString LinkQualityInfo::ToInfoString(void) const
{
InfoString string;
StringWriter writer(string);
InfoString string;
writer.Append("aveRss:%s, lastRss:%d, linkQuality:%d", mRssAverager.ToString().AsCString(), GetLastRss(),
string.Append("aveRss:%s, lastRss:%d, linkQuality:%d", mRssAverager.ToString().AsCString(), GetLastRss(),
GetLinkQuality());
return string;
+1 -2
View File
@@ -4127,11 +4127,10 @@ void Mle::Log(MessageAction aAction, MessageType aType, const Ip6::Address &aAdd
};
String<kRlocStringSize> rlocString;
StringWriter writer(rlocString);
if (aRloc != Mac::kShortAddrInvalid)
{
writer.Append(",0x%04x", aRloc);
rlocString.Append(",0x%04x", aRloc);
}
otLogInfoMle("%s %s%s (%s%s)", MessageActionToString(aAction), MessageTypeToString(aType),
+2 -3
View File
@@ -55,10 +55,9 @@ void DeviceMode::Set(const ModeConfig &aModeConfig)
DeviceMode::InfoString DeviceMode::ToString(void) const
{
InfoString string;
StringWriter writer(string);
InfoString string;
writer.Append("rx-on:%s ftd:%s full-net:%s", IsRxOnWhenIdle() ? "yes" : "no", IsFullThreadDevice() ? "yes" : "no",
string.Append("rx-on:%s ftd:%s full-net:%s", IsRxOnWhenIdle() ? "yes" : "no", IsFullThreadDevice() ? "yes" : "no",
IsFullNetworkData() ? "yes" : "no");
return string;
+2 -3
View File
@@ -361,7 +361,6 @@ void RadioSelector::Log(otLogLevel aLogLevel,
const Neighbor &aNeighbor)
{
String<kRadioPreferenceStringSize> preferenceString;
StringWriter writer(preferenceString);
bool isFirstEntry = true;
VerifyOrExit(otLoggingGetLevel() >= aLogLevel);
@@ -370,8 +369,8 @@ void RadioSelector::Log(otLogLevel aLogLevel,
{
if (aNeighbor.GetSupportedRadioTypes().Contains(radio))
{
writer.Append("%s%s:%d", isFirstEntry ? "" : " ", RadioTypeToString(radio),
aNeighbor.GetRadioPreference(radio));
preferenceString.Append("%s%s:%d", isFirstEntry ? "" : " ", RadioTypeToString(radio),
aNeighbor.GetRadioPreference(radio));
isFirstEntry = false;
}
}
+1 -2
View File
@@ -192,11 +192,10 @@ void ChannelMonitor::LogResults(void)
#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_UTIL == 1)
const size_t kStringSize = 128;
String<kStringSize> logString;
StringWriter writer(logString);
for (uint16_t channel : mChannelOccupancy)
{
writer.Append("%02x ", channel >> 8);
logString.Append("%02x ", channel >> 8);
}
otLogInfoUtil("ChannelMonitor: %u [%s]", mSampleCount, logString.AsCString());
+18 -18
View File
@@ -49,13 +49,12 @@ template <uint16_t kSize> void PrintString(const char *aName, const String<kSize
void TestStringWriter(void)
{
String<kStringSize> str;
StringWriter writer(str);
constexpr char kLongString[] = "abcdefghijklmnopqratuvwxyzabcdefghijklmnopqratuvwxyz";
printf("\nTest 1: StringWriter constructor\n");
VerifyOrQuit(writer.GetSize() == kStringSize, "GetSize() failed");
VerifyOrQuit(writer.GetLength() == 0, "GetLength() failed for empty string");
VerifyOrQuit(str.GetSize() == kStringSize, "GetSize() failed");
VerifyOrQuit(str.GetLength() == 0, "GetLength() failed for empty string");
VerifyOrQuit(strcmp(str.AsCString(), "") == 0, "String content is incorrect");
@@ -65,40 +64,41 @@ void TestStringWriter(void)
printf("\nTest 2: StringWriter::Append() method\n");
writer.Append("Hi");
VerifyOrQuit(writer.GetLength() == 2, "GetLength() failed");
str.Append("Hi");
VerifyOrQuit(str.GetLength() == 2, "GetLength() failed");
VerifyOrQuit(strcmp(str.AsCString(), "Hi") == 0, "String content is incorrect");
PrintString("str", str);
writer.Append("%s%d", "!", 12);
VerifyOrQuit(writer.GetLength() == 5, "GetLength() failed");
str.Append("%s%d", "!", 12);
VerifyOrQuit(str.GetLength() == 5, "GetLength() failed");
VerifyOrQuit(strcmp(str.AsCString(), "Hi!12") == 0, "String content is incorrect");
PrintString("str", str);
writer.Append(kLongString);
VerifyOrQuit(writer.IsTruncated() && writer.GetLength() == 5 + sizeof(kLongString) - 1,
str.Append(kLongString);
VerifyOrQuit(str.IsTruncated() && str.GetLength() == 5 + sizeof(kLongString) - 1,
"String::Append() did not handle overflow buffer correctly");
PrintString("str", str);
printf("\nTest 3: StringWriter::Clear() method\n");
writer.Clear();
writer.Append("Hello");
VerifyOrQuit(writer.GetLength() == 5, "GetLength() failed for empty string");
str.Clear();
str.Append("Hello");
VerifyOrQuit(str.GetLength() == 5, "GetLength() failed for empty string");
VerifyOrQuit(strcmp(str.AsCString(), "Hello") == 0, "String content is incorrect");
PrintString("str", str);
writer.Clear();
VerifyOrQuit(writer.GetLength() == 0, "GetLength() failed for empty string");
str.Clear();
VerifyOrQuit(str.GetLength() == 0, "GetLength() failed for empty string");
VerifyOrQuit(strcmp(str.AsCString(), "") == 0, "String content is incorrect");
writer.Append("%d", 12);
VerifyOrQuit(writer.GetLength() == 2, "GetLength() failed");
str.Append("%d", 12);
VerifyOrQuit(str.GetLength() == 2, "GetLength() failed");
VerifyOrQuit(strcmp(str.AsCString(), "12") == 0, "String content is incorrect");
PrintString("str", str);
writer.Clear().Append(kLongString);
VerifyOrQuit(writer.IsTruncated() && writer.GetLength() == sizeof(kLongString) - 1,
str.Clear();
str.Append(kLongString);
VerifyOrQuit(str.IsTruncated() && str.GetLength() == sizeof(kLongString) - 1,
"String::Clear() + String::Append() did not handle overflow buffer correctly");
PrintString("str", str);