Index: include/llvm/Support/raw_abbrev_ostream.h =================================================================== --- /dev/null +++ include/llvm/Support/raw_abbrev_ostream.h @@ -0,0 +1,171 @@ +//==- raw_abbrev_ostream.h - Stream that can abbreviate content --*- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the raw_abbrev_ostream class. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_RAW_ABBREV_OSTREAM_H +#define LLVM_SUPPORT_RAW_ABBREV_OSTREAM_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/SHA1.h" +#include "llvm/Support/raw_ostream.h" + +namespace llvm { + +/// A raw_ostream that can replace some part of input with shorter text. +/// +/// This stream class can operate in two modes: transparent and abbreviating. In +/// transparent mode it behaves like usual stream and stores input in internal +/// buffer. In abbreviating mode it counts number of symbols it got in this +/// mode. If this number is less than specified limit, the input is put into the +/// buffer as is. If the number exceeds this limit, the input is replaced by +/// some text. Depending on the stream options, this text may be a hash +/// calculated for the input data or this data may be truncated at the specified +/// length. +/// +/// Example: +/// \code +/// raw_abbrev_ostream S; +/// +/// // Replace long text with its hash. +/// S << "Text: "; +/// S.setBeginMarker("(").setEndMarker(")").setLimit(6); +/// S.startAbbrev(); +/// S << "Text that may be long"; +/// StringRef Result = S.str(); +/// assert(Result == "Text: (E06D5F32D15DEAB57D9FE2002C3F39D9B1CF306C)"); +/// +/// // Short text is passed as is. +/// S.reset(); +/// S.setBeginMarker("(").setEndMarker(")").setLimit(100); +/// S << "Text: "; +/// S.startAbbrev(); +/// S << "Text that may be long"; +/// Result = S.str(); +/// assert(Result == "Text: Text that may be long"); +/// +/// // Truncate long text. +/// S.reset(); +/// S.setEndMarker("...").setLimit(6); +/// S.setTrunc().setHash(false); +/// S << "Text: "; +/// S.startAbbrev(); +/// S << "Text that may be long"; +/// Result = S.str(); +/// assert(Result == "Text: Text t..."); +/// \endcode +/// +class raw_abbrev_ostream : public raw_ostream { + + /// Maximal length of input that can be passed without being replaced + /// by abbreviated text. + size_t Limit = 0; + + /// This text is put before the hash value. + StringRef BeginMarker; + + /// This text is put after the abbreviated value. + StringRef EndMarker; + + /// If true, input is replaced by its hash if it exceeds Limit. + bool AbbreviateByHash = true; + + /// If true, initial part of of input is passed to output. + bool Truncate = false; + + /// Buffer that keeps the stream content. + SmallString<64> OS; + + /// Hash calculator. + SHA1 Hasher; + + /// Start of current fragment, if abbreviating mode is enabled, or + /// StringRef::npos in transparent mode. + size_t Start = StringRef::npos; + + /// Length of the current fragment, if abbreviating mode is enabled. + size_t Length = 0; + + /// See raw_ostream::write_impl. + void write_impl(const char *Ptr, size_t Size) override; + + /// Append calculated hash value at the end of stream buffer. + void appendHash(); + + /// Put abbreviated value into buffer. + void appendAbbrevValue(); + +public: + raw_abbrev_ostream() : raw_ostream(true) { } + + LLVM_NODISCARD StringRef getBeginMarker() const { return BeginMarker; } + LLVM_NODISCARD StringRef getEndMarker() const { return EndMarker; } + LLVM_NODISCARD size_t getLimit() const { return Limit; } + + /// Return true, if input will be abbreviated by hash. + LLVM_NODISCARD bool usesHash() const { return AbbreviateByHash; } + + /// Return true, if input will be abbreviated by truncation. + LLVM_NODISCARD bool usesTrunc() const { return Truncate; } + + /// Return true if the stream is in abbreviating mode. + LLVM_NODISCARD bool inAbbrevMode() const { return Start < StringRef::npos; } + + /// Return true if current fragment is long enough and will be abbreviated. + LLVM_NODISCARD bool isLong() const { return Length > Limit; } + + raw_abbrev_ostream &setBeginMarker(StringRef B = StringRef()) { + BeginMarker = B; + return *this; + } + + raw_abbrev_ostream &setEndMarker(StringRef E = StringRef()) { + EndMarker = E; + return *this; + } + + raw_abbrev_ostream &setHash(bool X = true) { + AbbreviateByHash = X; + return *this; + } + + raw_abbrev_ostream &setTrunc(bool X = true) { + Truncate = X; + return *this; + } + + /// Subsequent input may be abbreviated if it is longer that the specified + /// limit. + raw_abbrev_ostream &setLimit(size_t X) { + Limit = X; + return *this; + } + + /// Subsequent input may be abbreviated if it is longer that the specified + /// limit. + raw_abbrev_ostream &startAbbrev(); + + /// Subsequent input will be put into buffer as is. + raw_abbrev_ostream &stopAbbrev(); + + /// Put the stream into initial state. + void reset(); + + /// Get buffer containing the stream content. + LLVM_NODISCARD StringRef str(); + + uint64_t current_pos() const override; +}; + +} // end llvm namespace + +#endif \ No newline at end of file Index: lib/Support/CMakeLists.txt =================================================================== --- lib/Support/CMakeLists.txt +++ lib/Support/CMakeLists.txt @@ -114,6 +114,7 @@ Unicode.cpp YAMLParser.cpp YAMLTraits.cpp + raw_abbrev_ostream.cpp raw_os_ostream.cpp raw_ostream.cpp regcomp.c Index: lib/Support/raw_abbrev_ostream.cpp =================================================================== --- /dev/null +++ lib/Support/raw_abbrev_ostream.cpp @@ -0,0 +1,110 @@ +//===--- raw_abbrev_ostream.cpp - Implement the raw_abbvev_ostream class --===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This implements raw_abbrev_ostream. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/raw_abbrev_ostream.h" +using namespace llvm; + +const size_t HashTextSize = 40; // SHA1 hash is 160 bits. + +void raw_abbrev_ostream::write_impl(const char *Ptr, size_t Size) { + if (inAbbrevMode()) { + if (AbbreviateByHash) + Hasher.update(ArrayRef((const uint8_t *)Ptr, Size)); + size_t NewLength = Length + Size; + if (NewLength <= Limit) + OS.append(StringRef(Ptr, Size)); + else { + if (Truncate) { + if (Length < Limit) + OS.append(StringRef(Ptr, Limit - Length)); + } else + OS.set_size(Start); + } + Length = NewLength; + } else { + OS.append(StringRef(Ptr, Size)); + } +} + +void raw_abbrev_ostream::appendHash() { + static const char *const Digits = "0123456789ABCDEF"; + StringRef Bin = Hasher.result(); + for (size_t I = 0; I < Bin.size(); ++I) { + const unsigned char C = Bin[I]; + OS.push_back(Digits[C >> 4]); + OS.push_back(Digits[C & 15]); + } +} + +void raw_abbrev_ostream::appendAbbrevValue() { + if (AbbreviateByHash) { + OS.append(BeginMarker); + appendHash(); + } + OS.append(EndMarker); +} + + +void raw_abbrev_ostream::reset() { + Limit = 0; + BeginMarker = EndMarker = StringRef(); + AbbreviateByHash = true; + Truncate = false; + OS.clear(); + Hasher.init(); + Start = StringRef::npos; + Length = 0; +} + +raw_abbrev_ostream &raw_abbrev_ostream::startAbbrev() { + // If current mode is abbreviating, close it and start new fragment. + stopAbbrev(); + Start = OS.size(); + Length = 0; + Hasher.init(); + return *this; +} + +raw_abbrev_ostream &raw_abbrev_ostream::stopAbbrev() { + if (inAbbrevMode() && isLong()) + appendAbbrevValue(); + Start = StringRef::npos; + return *this; +} + +StringRef raw_abbrev_ostream::str() { + if (inAbbrevMode() && isLong()) { + // Abbreviating mode is active. Put current abbreviated value into buffer, + // get StringRef for it and then revert buffer state. + size_t PrevSize = OS.size(); + appendAbbrevValue(); + StringRef Result = OS.str(); + OS.set_size(PrevSize); + return Result; + } + return OS.str(); +} + +uint64_t raw_abbrev_ostream::current_pos() const { + if (inAbbrevMode() && isLong()) { + // Report position as if abbreviated value is put into buffer. It would + // be consistent with size of string returned by str(). + size_t Result = Start + EndMarker.size(); + if (AbbreviateByHash) + Result += HashTextSize + BeginMarker.size(); + if (Truncate) + Result += Limit; + return Result; + } + return OS.size(); +} Index: unittests/Support/CMakeLists.txt =================================================================== --- unittests/Support/CMakeLists.txt +++ unittests/Support/CMakeLists.txt @@ -62,6 +62,7 @@ YAMLIOTest.cpp YAMLParserTest.cpp formatted_raw_ostream_test.cpp + raw_abbrev_ostream_test.cpp raw_ostream_test.cpp raw_pwrite_stream_test.cpp raw_sha1_ostream_test.cpp Index: unittests/Support/raw_abbrev_ostream_test.cpp =================================================================== --- /dev/null +++ unittests/Support/raw_abbrev_ostream_test.cpp @@ -0,0 +1,316 @@ +//===- llvm/unittest/Support/raw_abbrev_ostream_test.cpp ------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/raw_abbrev_ostream.h" +#include "gtest/gtest.h" + +using namespace llvm; + +const size_t HashTextSize = 40; // SHA1 hash is 160 bits. + + +// If raw_abbrev_ostream is in transparent mode, it operates as usual stream. +TEST(raw_abbrev_ostreamTest, Transparent) { + llvm::raw_abbrev_ostream AbbrevStream; + ASSERT_TRUE(AbbrevStream.usesHash()); + ASSERT_FALSE(AbbrevStream.usesTrunc()); + ASSERT_EQ(AbbrevStream.getLimit(), 0); + ASSERT_TRUE(AbbrevStream.getBeginMarker().empty()); + ASSERT_TRUE(AbbrevStream.getEndMarker().empty()); + ASSERT_FALSE(AbbrevStream.inAbbrevMode()); + ASSERT_FALSE(AbbrevStream.isLong()); + ASSERT_TRUE(AbbrevStream.str().empty()); + ASSERT_EQ(AbbrevStream.current_pos(), 0); + + AbbrevStream << "Hello World!"; + ASSERT_FALSE(AbbrevStream.inAbbrevMode()); + ASSERT_FALSE(AbbrevStream.isLong()); + ASSERT_EQ("Hello World!", AbbrevStream.str()); + ASSERT_EQ(AbbrevStream.current_pos(), strlen("Hello World!")); + + AbbrevStream.stopAbbrev(); + ASSERT_FALSE(AbbrevStream.inAbbrevMode()); + ASSERT_FALSE(AbbrevStream.isLong()); + ASSERT_EQ("Hello World!", AbbrevStream.str()); + ASSERT_EQ(AbbrevStream.current_pos(), strlen("Hello World!")); + + AbbrevStream.reset(); + ASSERT_TRUE(AbbrevStream.usesHash()); + ASSERT_FALSE(AbbrevStream.usesTrunc()); + ASSERT_EQ(AbbrevStream.getLimit(), 0); + ASSERT_TRUE(AbbrevStream.getBeginMarker().empty()); + ASSERT_TRUE(AbbrevStream.getEndMarker().empty()); + ASSERT_FALSE(AbbrevStream.inAbbrevMode()); + ASSERT_FALSE(AbbrevStream.isLong()); + ASSERT_TRUE(AbbrevStream.str().empty()); + ASSERT_EQ(AbbrevStream.current_pos(), 0); + + AbbrevStream << "Another Text"; + ASSERT_FALSE(AbbrevStream.inAbbrevMode()); + ASSERT_FALSE(AbbrevStream.isLong()); + ASSERT_EQ("Another Text", AbbrevStream.str()); + ASSERT_EQ(AbbrevStream.current_pos(), strlen("Another Text")); + + AbbrevStream.reset(); + AbbrevStream << "Hello "; + AbbrevStream << "World!"; + ASSERT_FALSE(AbbrevStream.inAbbrevMode()); + ASSERT_FALSE(AbbrevStream.isLong()); + ASSERT_EQ("Hello World!", AbbrevStream.str()); + ASSERT_EQ(AbbrevStream.current_pos(), strlen("Hello World!")); +} + + +// The mode when stream coverts entire text to SHA1 hash. +TEST(raw_abbrev_ostreamTest, Hash) { + llvm::raw_abbrev_ostream AbbrevStream; + AbbrevStream.startAbbrev(); + ASSERT_TRUE(AbbrevStream.inAbbrevMode()); + ASSERT_FALSE(AbbrevStream.isLong()); + ASSERT_TRUE(AbbrevStream.str().empty()); + ASSERT_EQ(AbbrevStream.current_pos(), 0); + + AbbrevStream << "Hello World!"; + ASSERT_TRUE(AbbrevStream.inAbbrevMode()); + ASSERT_TRUE(AbbrevStream.isLong()); + ASSERT_EQ(AbbrevStream.str(), "2EF7BDE608CE5404E97D5F042F95F89F1C232871"); + ASSERT_EQ(AbbrevStream.current_pos(), HashTextSize); + + AbbrevStream << ""; + ASSERT_EQ(AbbrevStream.str(), "2EF7BDE608CE5404E97D5F042F95F89F1C232871"); + AbbrevStream.stopAbbrev(); + ASSERT_EQ(AbbrevStream.str(), "2EF7BDE608CE5404E97D5F042F95F89F1C232871"); + ASSERT_EQ(AbbrevStream.current_pos(), HashTextSize); + + AbbrevStream.reset(); + ASSERT_TRUE(AbbrevStream.usesHash()); + ASSERT_FALSE(AbbrevStream.usesTrunc()); + ASSERT_EQ(AbbrevStream.getLimit(), 0); + ASSERT_TRUE(AbbrevStream.getBeginMarker().empty()); + ASSERT_TRUE(AbbrevStream.getEndMarker().empty()); + ASSERT_FALSE(AbbrevStream.inAbbrevMode()); + ASSERT_FALSE(AbbrevStream.isLong()); + ASSERT_TRUE(AbbrevStream.str().empty()); + ASSERT_EQ(AbbrevStream.current_pos(), 0); + + AbbrevStream.startAbbrev(); + AbbrevStream << "Hello"; + ASSERT_TRUE(AbbrevStream.inAbbrevMode()); + ASSERT_TRUE(AbbrevStream.isLong()); + ASSERT_EQ(AbbrevStream.str(), "F7FF9E8B7BB2E09B70935A5D785E0CC5D9D0ABF0"); + ASSERT_EQ(AbbrevStream.current_pos(), HashTextSize); + AbbrevStream << " World!"; + ASSERT_TRUE(AbbrevStream.inAbbrevMode()); + ASSERT_TRUE(AbbrevStream.isLong()); + ASSERT_EQ(AbbrevStream.str(), "2EF7BDE608CE5404E97D5F042F95F89F1C232871"); + ASSERT_EQ(AbbrevStream.current_pos(), HashTextSize); +} + +// The mode when stream truncates text. +TEST(raw_abbrev_ostreamTest, Truncate) { + llvm::raw_abbrev_ostream AbbrevStream; + AbbrevStream.setTrunc().setHash(false).setEndMarker("..."); + ASSERT_FALSE(AbbrevStream.usesHash()); + ASSERT_TRUE(AbbrevStream.usesTrunc()); + ASSERT_EQ(AbbrevStream.getLimit(), 0); + ASSERT_TRUE(AbbrevStream.getBeginMarker().empty()); + ASSERT_EQ(AbbrevStream.getEndMarker(), "..."); + ASSERT_FALSE(AbbrevStream.inAbbrevMode()); + ASSERT_FALSE(AbbrevStream.isLong()); + ASSERT_TRUE(AbbrevStream.str().empty()); + ASSERT_EQ(AbbrevStream.current_pos(), 0); + + AbbrevStream.startAbbrev(); + AbbrevStream << "Hello"; + ASSERT_TRUE(AbbrevStream.inAbbrevMode()); + ASSERT_TRUE(AbbrevStream.isLong()); + ASSERT_EQ(AbbrevStream.str(), "..."); + ASSERT_EQ(AbbrevStream.current_pos(), strlen("...")); + + AbbrevStream.stopAbbrev(); + ASSERT_FALSE(AbbrevStream.inAbbrevMode()); + ASSERT_EQ(AbbrevStream.str(), "..."); + ASSERT_EQ(AbbrevStream.current_pos(), strlen("...")); + + AbbrevStream.reset(); + AbbrevStream.setTrunc().setHash(false).setEndMarker("...").setLimit(7); + AbbrevStream.startAbbrev() << "Hello World!"; + ASSERT_EQ(AbbrevStream.str(), "Hello W..."); + ASSERT_EQ(AbbrevStream.current_pos(), strlen("Hello W...")); + + AbbrevStream.stopAbbrev(); + ASSERT_FALSE(AbbrevStream.inAbbrevMode()); + ASSERT_EQ(AbbrevStream.str(), "Hello W..."); + ASSERT_EQ(AbbrevStream.current_pos(), strlen("Hello W...")); + + AbbrevStream.reset(); + AbbrevStream.setTrunc().setEndMarker(".").setBeginMarker("...").setLimit(7); + AbbrevStream.startAbbrev() << "Hello World!"; + const StringRef T1 = "Hello W...2EF7BDE608CE5404E97D5F042F95F89F1C232871."; + ASSERT_EQ(AbbrevStream.str(), T1); + ASSERT_EQ(AbbrevStream.current_pos(), T1.size()); + + AbbrevStream.stopAbbrev(); + ASSERT_FALSE(AbbrevStream.inAbbrevMode()); + ASSERT_EQ(AbbrevStream.str(), T1); + ASSERT_EQ(AbbrevStream.current_pos(), T1.size()); +} + + +TEST(raw_abbrev_ostreamTest, ModeSwitch) { + llvm::raw_abbrev_ostream AbbrevStream; + + // Switch from transparent mode and immediately back should not have effect. + AbbrevStream << "Hello "; + AbbrevStream.startAbbrev(); + AbbrevStream.stopAbbrev(); + AbbrevStream << "World!"; + ASSERT_EQ("Hello World!", AbbrevStream.str()); + ASSERT_EQ(AbbrevStream.current_pos(), strlen("Hello World!")); + + AbbrevStream.reset(); + AbbrevStream << "Hello "; + ASSERT_EQ("Hello ", AbbrevStream.str()); + ASSERT_EQ(AbbrevStream.current_pos(), strlen("Hello ")); + AbbrevStream.startAbbrev(); + AbbrevStream << "World!"; + ASSERT_EQ("Hello 9D781958AD163647E5C5E724D534D5D9DB7B800D", + AbbrevStream.str()); + ASSERT_EQ(AbbrevStream.current_pos(), strlen("Hello ") + HashTextSize); + AbbrevStream.stopAbbrev(); + AbbrevStream << " World"; + ASSERT_EQ("Hello 9D781958AD163647E5C5E724D534D5D9DB7B800D World", + AbbrevStream.str()); + ASSERT_EQ(AbbrevStream.current_pos(), + strlen("Hello ") + HashTextSize + strlen("World ")); +} + + +TEST(raw_abbrev_ostreamTest, Length) { + llvm::raw_abbrev_ostream AbbrevStream; + AbbrevStream.setLimit(5).startAbbrev(); + AbbrevStream << "Hello"; + ASSERT_EQ("Hello", AbbrevStream.str()); + ASSERT_EQ(AbbrevStream.current_pos(), strlen("Hello")); + + AbbrevStream << " "; + ASSERT_EQ("9646BA13A4E8EABECA4F5259BFD7DA41D368A1A6", AbbrevStream.str()); + ASSERT_EQ(AbbrevStream.current_pos(), HashTextSize); + + AbbrevStream << "World!"; + ASSERT_EQ("2EF7BDE608CE5404E97D5F042F95F89F1C232871", AbbrevStream.str()); + ASSERT_EQ(AbbrevStream.current_pos(), HashTextSize); + + AbbrevStream.stopAbbrev(); + ASSERT_EQ("2EF7BDE608CE5404E97D5F042F95F89F1C232871", AbbrevStream.str()); + ASSERT_EQ(AbbrevStream.current_pos(), HashTextSize); + + AbbrevStream.reset(); + AbbrevStream.setLimit(20).startAbbrev() << "Hello World!"; + ASSERT_EQ("Hello World!", AbbrevStream.str()); + ASSERT_EQ(AbbrevStream.current_pos(), strlen("Hello World!")); +} + +TEST(raw_abbrev_ostreamTest, Markers) { + llvm::raw_abbrev_ostream AbbrevStream; + AbbrevStream.setBeginMarker(".").setLimit(5); + AbbrevStream.startAbbrev(); + AbbrevStream << "Hello"; + ASSERT_EQ("Hello", AbbrevStream.str()); + ASSERT_EQ(AbbrevStream.current_pos(), strlen("Hello")); + + AbbrevStream << " "; + ASSERT_EQ(".9646BA13A4E8EABECA4F5259BFD7DA41D368A1A6", + AbbrevStream.str()); + // Starts new fragment. + AbbrevStream.startAbbrev(); + AbbrevStream.setEndMarker(")"); + AbbrevStream << "World!"; + ASSERT_EQ(".9646BA13A4E8EABECA4F5259BFD7DA41D368A1A6" + ".9D781958AD163647E5C5E724D534D5D9DB7B800D)", AbbrevStream.str()); + + AbbrevStream.reset(); + AbbrevStream.setBeginMarker("<").setEndMarker("..."); + AbbrevStream.setLimit(5).setTrunc().setHash(false); + AbbrevStream.startAbbrev(); + AbbrevStream << "Hello World!"; + ASSERT_EQ("Hello...", AbbrevStream.str()); +} + +TEST(raw_abbrev_ostreamTest, Combination) { + llvm::raw_abbrev_ostream AbbrevStream; + AbbrevStream.setBeginMarker(".").setLimit(5); + AbbrevStream << "struct.Foo"; + AbbrevStream.startAbbrev(); + AbbrevStream << '<'; + AbbrevStream << "int"; + AbbrevStream << '>'; + ASSERT_EQ("struct.Foo", AbbrevStream.str()); + + AbbrevStream.reset(); + AbbrevStream.setBeginMarker("."); + AbbrevStream << "struct.Foo"; + AbbrevStream.setLimit(5).startAbbrev(); + AbbrevStream << '<'; + AbbrevStream << "int, double"; + AbbrevStream << '>'; + ASSERT_EQ("struct.Foo.94979910F7FF30A19B3A475EE1F495EE06E2949E", + AbbrevStream.str()); + + AbbrevStream.stopAbbrev(); + AbbrevStream << " foo("; + AbbrevStream.setLimit(0).startAbbrev(); + AbbrevStream.setBeginMarker("."); + AbbrevStream << "int"; + AbbrevStream.stopAbbrev(); + AbbrevStream << ","; + AbbrevStream.startAbbrev(); + AbbrevStream << "double"; + AbbrevStream.stopAbbrev(); + AbbrevStream << ")"; + + ASSERT_EQ("struct.Foo.94979910F7FF30A19B3A475EE1F495EE06E2949E" + " foo(.46F8AB7C0CFF9DF7CD124852E26022A6BF89E315," + ".BDB36BB22DEB169275B3094BA9005A29EEDDD195)", + AbbrevStream.str()); +} + + +// Example from th description of class raw_abbrev_ostream. +TEST(raw_abbrev_ostreamTest, Example) { + using llvm::raw_abbrev_ostream; + + // Replace long text with its hash. + raw_abbrev_ostream S; + S << "Text: "; + S.setBeginMarker("(").setEndMarker(")").setLimit(6); + S.startAbbrev(); + S << "Text that may be long"; + StringRef Result = S.str(); + assert(Result == "Text: (E06D5F32D15DEAB57D9FE2002C3F39D9B1CF306C)"); + + // Short text is passed as is. + S.reset(); + S.setBeginMarker("(").setEndMarker(")").setLimit(100); + S << "Text: "; + S.startAbbrev(); + S << "Text that may be long"; + Result = S.str(); + assert(Result == "Text: Text that may be long"); + + // Truncate long text. + S.reset(); + S.setEndMarker("...").setLimit(6); + S.setTrunc().setHash(false); + S << "Text: "; + S.startAbbrev(); + S << "Text that may be long"; + Result = S.str(); + assert(Result == "Text: Text t..."); +}