diff options
| -rw-r--r-- | llvm/include/llvm/DebugInfo/PDB/Native/HashTable.h | 115 | ||||
| -rw-r--r-- | llvm/include/llvm/DebugInfo/PDB/Native/InfoStream.h | 2 | ||||
| -rw-r--r-- | llvm/include/llvm/DebugInfo/PDB/Native/NamedStreamMap.h | 25 | ||||
| -rw-r--r-- | llvm/lib/DebugInfo/PDB/Native/HashTable.cpp | 94 | ||||
| -rw-r--r-- | llvm/lib/DebugInfo/PDB/Native/InfoStream.cpp | 3 | ||||
| -rw-r--r-- | llvm/lib/DebugInfo/PDB/Native/InfoStreamBuilder.cpp | 3 | ||||
| -rw-r--r-- | llvm/lib/DebugInfo/PDB/Native/NamedStreamMap.cpp | 146 | ||||
| -rw-r--r-- | llvm/tools/llvm-pdbutil/Diff.cpp | 4 | ||||
| -rw-r--r-- | llvm/unittests/DebugInfo/PDB/HashTableTest.cpp | 41 |
9 files changed, 245 insertions, 188 deletions
diff --git a/llvm/include/llvm/DebugInfo/PDB/Native/HashTable.h b/llvm/include/llvm/DebugInfo/PDB/Native/HashTable.h index 05c70c4f217..231da056c3a 100644 --- a/llvm/include/llvm/DebugInfo/PDB/Native/HashTable.h +++ b/llvm/include/llvm/DebugInfo/PDB/Native/HashTable.h @@ -54,10 +54,67 @@ public: HashTableIterator begin() const; HashTableIterator end() const; - HashTableIterator find(uint32_t K); + /// Find the entry with the specified key value. + HashTableIterator find(uint32_t K) const; + + /// Find the entry whose key has the specified hash value, using the specified + /// traits defining hash function and equality. + template <typename Traits, typename Key, typename Context> + HashTableIterator find_as(const Key &K, const Context &Ctx) const { + uint32_t H = Traits::hash(K, Ctx) % capacity(); + uint32_t I = H; + Optional<uint32_t> FirstUnused; + do { + if (isPresent(I)) { + if (Traits::realKey(Buckets[I].first, Ctx) == K) + return HashTableIterator(*this, I, false); + } else { + if (!FirstUnused) + FirstUnused = I; + // Insertion occurs via linear probing from the slot hint, and will be + // inserted at the first empty / deleted location. Therefore, if we are + // probing and find a location that is neither present nor deleted, then + // nothing must have EVER been inserted at this location, and thus it is + // not possible for a matching value to occur later. + if (!isDeleted(I)) + break; + } + I = (I + 1) % capacity(); + } while (I != H); + + // The only way FirstUnused would not be set is if every single entry in the + // table were Present. But this would violate the load factor constraints + // that we impose, so it should never happen. + assert(FirstUnused); + return HashTableIterator(*this, *FirstUnused, true); + } + + /// Set the entry with the specified key to the specified value. void set(uint32_t K, uint32_t V); + + /// Set the entry using a key type that the specified Traits can convert + /// from a real key to an internal key. + template <typename Traits, typename Key, typename Context> + bool set_as(const Key &K, uint32_t V, Context &Ctx) { + return set_as_internal<Traits, Key, Context>(K, V, None, Ctx); + } + void remove(uint32_t K); + + template <typename Traits, typename Key, typename Context> + void remove_as(const Key &K, Context &Ctx) { + auto Iter = find_as<Traits, Key, Context>(K, Ctx); + // It wasn't here to begin with, just exit. + if (Iter == end()) + return; + + assert(Present.test(Iter.index())); + assert(!Deleted.test(Iter.index())); + Deleted.set(Iter.index()); + Present.reset(Iter.index()); + } + uint32_t get(uint32_t K); protected: @@ -69,8 +126,62 @@ protected: mutable SparseBitVector<> Deleted; private: + /// Set the entry using a key type that the specified Traits can convert + /// from a real key to an internal key. + template <typename Traits, typename Key, typename Context> + bool set_as_internal(const Key &K, uint32_t V, Optional<uint32_t> InternalKey, + Context &Ctx) { + auto Entry = find_as<Traits, Key, Context>(K, Ctx); + if (Entry != end()) { + assert(isPresent(Entry.index())); + assert(Traits::realKey(Buckets[Entry.index()].first, Ctx) == K); + // We're updating, no need to do anything special. + Buckets[Entry.index()].second = V; + return false; + } + + auto &B = Buckets[Entry.index()]; + assert(!isPresent(Entry.index())); + assert(Entry.isEnd()); + B.first = InternalKey ? *InternalKey : Traits::lowerKey(K, Ctx); + B.second = V; + Present.set(Entry.index()); + Deleted.reset(Entry.index()); + + grow<Traits, Key, Context>(Ctx); + + assert((find_as<Traits, Key, Context>(K, Ctx)) != end()); + return true; + } + static uint32_t maxLoad(uint32_t capacity); - void grow(); + + template <typename Traits, typename Key, typename Context> + void grow(Context &Ctx) { + uint32_t S = size(); + if (S < maxLoad(capacity())) + return; + assert(capacity() != UINT32_MAX && "Can't grow Hash table!"); + + uint32_t NewCapacity = + (capacity() <= INT32_MAX) ? capacity() * 2 : UINT32_MAX; + + // Growing requires rebuilding the table and re-hashing every item. Make a + // copy with a larger capacity, insert everything into the copy, then swap + // it in. + HashTable NewMap(NewCapacity); + for (auto I : Present) { + auto RealKey = Traits::realKey(Buckets[I].first, Ctx); + NewMap.set_as_internal<Traits, Key, Context>(RealKey, Buckets[I].second, + Buckets[I].first, Ctx); + } + + Buckets.swap(NewMap.Buckets); + std::swap(Present, NewMap.Present); + std::swap(Deleted, NewMap.Deleted); + assert(capacity() == NewCapacity); + assert(size() == S); + } static Error readSparseBitVector(BinaryStreamReader &Stream, SparseBitVector<> &V); diff --git a/llvm/include/llvm/DebugInfo/PDB/Native/InfoStream.h b/llvm/include/llvm/DebugInfo/PDB/Native/InfoStream.h index fb8271cb5eb..499d2d6e2ea 100644 --- a/llvm/include/llvm/DebugInfo/PDB/Native/InfoStream.h +++ b/llvm/include/llvm/DebugInfo/PDB/Native/InfoStream.h @@ -51,7 +51,7 @@ public: BinarySubstreamRef getNamedStreamsBuffer() const; uint32_t getNamedStreamIndex(llvm::StringRef Name) const; - iterator_range<StringMapConstIterator<uint32_t>> named_streams() const; + StringMap<uint32_t> named_streams() const; private: std::unique_ptr<msf::MappedBlockStream> Stream; diff --git a/llvm/include/llvm/DebugInfo/PDB/Native/NamedStreamMap.h b/llvm/include/llvm/DebugInfo/PDB/Native/NamedStreamMap.h index 17a82b7ce12..9cdccd2741b 100644 --- a/llvm/include/llvm/DebugInfo/PDB/Native/NamedStreamMap.h +++ b/llvm/include/llvm/DebugInfo/PDB/Native/NamedStreamMap.h @@ -28,29 +28,30 @@ namespace pdb { class NamedStreamMap { friend class NamedStreamMapBuilder; - struct FinalizationInfo { - uint32_t StringDataBytes = 0; - uint32_t SerializedLength = 0; - }; - public: NamedStreamMap(); Error load(BinaryStreamReader &Stream); Error commit(BinaryStreamWriter &Writer) const; - uint32_t finalize(); + uint32_t calculateSerializedLength() const; uint32_t size() const; bool get(StringRef Stream, uint32_t &StreamNo) const; void set(StringRef Stream, uint32_t StreamNo); - void remove(StringRef Stream); - const StringMap<uint32_t> &getStringMap() const { return Mapping; } - iterator_range<StringMapConstIterator<uint32_t>> entries() const; + + uint32_t appendStringData(StringRef S); + StringRef getString(uint32_t Offset) const; + uint32_t hashString(uint32_t Offset) const; + + StringMap<uint32_t> entries() const; private: - Optional<FinalizationInfo> FinalizedInfo; - HashTable FinalizedHashTable; - StringMap<uint32_t> Mapping; + /// Closed hash table from Offset -> StreamNumber, where Offset is the offset + /// of the stream name in NamesBuffer. + HashTable OffsetIndexMap; + + /// Buffer of string data. + std::vector<char> NamesBuffer; }; } // end namespace pdb diff --git a/llvm/lib/DebugInfo/PDB/Native/HashTable.cpp b/llvm/lib/DebugInfo/PDB/Native/HashTable.cpp index 439217f91d0..d3eef553271 100644 --- a/llvm/lib/DebugInfo/PDB/Native/HashTable.cpp +++ b/llvm/lib/DebugInfo/PDB/Native/HashTable.cpp @@ -22,6 +22,14 @@ using namespace llvm; using namespace llvm::pdb; +namespace { +struct IdentityTraits { + static uint32_t hash(uint32_t K, const HashTable &Ctx) { return K; } + static uint32_t realKey(uint32_t K, const HashTable &Ctx) { return K; } + static uint32_t lowerKey(uint32_t K, const HashTable &Ctx) { return K; } +}; +} // namespace + HashTable::HashTable() : HashTable(8) {} HashTable::HashTable(uint32_t Capacity) { Buckets.resize(Capacity); } @@ -119,69 +127,15 @@ HashTableIterator HashTable::end() const { return HashTableIterator(*this, 0, true); } -HashTableIterator HashTable::find(uint32_t K) { - uint32_t H = K % capacity(); - uint32_t I = H; - Optional<uint32_t> FirstUnused; - do { - if (isPresent(I)) { - if (Buckets[I].first == K) - return HashTableIterator(*this, I, false); - } else { - if (!FirstUnused) - FirstUnused = I; - // Insertion occurs via linear probing from the slot hint, and will be - // inserted at the first empty / deleted location. Therefore, if we are - // probing and find a location that is neither present nor deleted, then - // nothing must have EVER been inserted at this location, and thus it is - // not possible for a matching value to occur later. - if (!isDeleted(I)) - break; - } - I = (I + 1) % capacity(); - } while (I != H); - - // The only way FirstUnused would not be set is if every single entry in the - // table were Present. But this would violate the load factor constraints - // that we impose, so it should never happen. - assert(FirstUnused); - return HashTableIterator(*this, *FirstUnused, true); +HashTableIterator HashTable::find(uint32_t K) const { + return find_as<IdentityTraits>(K, *this); } void HashTable::set(uint32_t K, uint32_t V) { - auto Entry = find(K); - if (Entry != end()) { - assert(isPresent(Entry.index())); - assert(Buckets[Entry.index()].first == K); - // We're updating, no need to do anything special. - Buckets[Entry.index()].second = V; - return; - } - - auto &B = Buckets[Entry.index()]; - assert(!isPresent(Entry.index())); - assert(Entry.isEnd()); - B.first = K; - B.second = V; - Present.set(Entry.index()); - Deleted.reset(Entry.index()); - - grow(); - - assert(find(K) != end()); + set_as<IdentityTraits, uint32_t>(K, V, *this); } -void HashTable::remove(uint32_t K) { - auto Iter = find(K); - // It wasn't here to begin with, just exit. - if (Iter == end()) - return; - - assert(Present.test(Iter.index())); - assert(!Deleted.test(Iter.index())); - Deleted.set(Iter.index()); - Present.reset(Iter.index()); -} +void HashTable::remove(uint32_t K) { remove_as<IdentityTraits>(K, *this); } uint32_t HashTable::get(uint32_t K) { auto I = find(K); @@ -191,30 +145,6 @@ uint32_t HashTable::get(uint32_t K) { uint32_t HashTable::maxLoad(uint32_t capacity) { return capacity * 2 / 3 + 1; } -void HashTable::grow() { - uint32_t S = size(); - if (S < maxLoad(capacity())) - return; - assert(capacity() != UINT32_MAX && "Can't grow Hash table!"); - - uint32_t NewCapacity = - (capacity() <= INT32_MAX) ? capacity() * 2 : UINT32_MAX; - - // Growing requires rebuilding the table and re-hashing every item. Make a - // copy with a larger capacity, insert everything into the copy, then swap - // it in. - HashTable NewMap(NewCapacity); - for (auto I : Present) { - NewMap.set(Buckets[I].first, Buckets[I].second); - } - - Buckets.swap(NewMap.Buckets); - std::swap(Present, NewMap.Present); - std::swap(Deleted, NewMap.Deleted); - assert(capacity() == NewCapacity); - assert(size() == S); -} - Error HashTable::readSparseBitVector(BinaryStreamReader &Stream, SparseBitVector<> &V) { uint32_t NumWords; diff --git a/llvm/lib/DebugInfo/PDB/Native/InfoStream.cpp b/llvm/lib/DebugInfo/PDB/Native/InfoStream.cpp index 17c9392a9dd..b4f217abf85 100644 --- a/llvm/lib/DebugInfo/PDB/Native/InfoStream.cpp +++ b/llvm/lib/DebugInfo/PDB/Native/InfoStream.cpp @@ -99,8 +99,7 @@ uint32_t InfoStream::getNamedStreamIndex(llvm::StringRef Name) const { return Result; } -iterator_range<StringMapConstIterator<uint32_t>> -InfoStream::named_streams() const { +StringMap<uint32_t> InfoStream::named_streams() const { return NamedStreams.entries(); } diff --git a/llvm/lib/DebugInfo/PDB/Native/InfoStreamBuilder.cpp b/llvm/lib/DebugInfo/PDB/Native/InfoStreamBuilder.cpp index 6450ae752f9..6ab748c160b 100644 --- a/llvm/lib/DebugInfo/PDB/Native/InfoStreamBuilder.cpp +++ b/llvm/lib/DebugInfo/PDB/Native/InfoStreamBuilder.cpp @@ -41,7 +41,8 @@ void InfoStreamBuilder::addFeature(PdbRaw_FeatureSig Sig) { } Error InfoStreamBuilder::finalizeMsfLayout() { - uint32_t Length = sizeof(InfoStreamHeader) + NamedStreams.finalize() + + uint32_t Length = sizeof(InfoStreamHeader) + + NamedStreams.calculateSerializedLength() + (Features.size() + 1) * sizeof(uint32_t); if (auto EC = Msf.setStreamSize(StreamPDB, Length)) return EC; diff --git a/llvm/lib/DebugInfo/PDB/Native/NamedStreamMap.cpp b/llvm/lib/DebugInfo/PDB/Native/NamedStreamMap.cpp index 6cdf6dde04d..983b6ebf36a 100644 --- a/llvm/lib/DebugInfo/PDB/Native/NamedStreamMap.cpp +++ b/llvm/lib/DebugInfo/PDB/Native/NamedStreamMap.cpp @@ -11,6 +11,7 @@ #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/iterator_range.h" +#include "llvm/DebugInfo/PDB/Native/Hash.h" #include "llvm/DebugInfo/PDB/Native/HashTable.h" #include "llvm/DebugInfo/PDB/Native/RawError.h" #include "llvm/Support/BinaryStreamReader.h" @@ -26,127 +27,100 @@ using namespace llvm; using namespace llvm::pdb; -// FIXME: This shouldn't be necessary, but if we insert the strings in any -// other order, cvdump cannot read the generated name map. This suggests that -// we may be using the wrong hash function. A closer inspection of the cvdump -// source code may reveal something, but for now this at least makes us work, -// even if only by accident. -static constexpr const char *OrderedStreamNames[] = {"/LinkInfo", "/names", - "/src/headerblock"}; +namespace { +struct NamedStreamMapTraits { + static uint16_t hash(StringRef S, const NamedStreamMap &NS) { + // In the reference implementation, this uses + // HASH Hasher<ULONG*, USHORT*>::hashPbCb(PB pb, size_t cb, ULONG ulMod). + // Here, the type HASH is a typedef of unsigned short. + // ** It is not a bug that we truncate the result of hashStringV1, in fact + // it is a bug if we do not! ** + return static_cast<uint16_t>(hashStringV1(S)); + } + static StringRef realKey(uint32_t Offset, const NamedStreamMap &NS) { + return NS.getString(Offset); + } + static uint32_t lowerKey(StringRef S, NamedStreamMap &NS) { + return NS.appendStringData(S); + } +}; +} // namespace -NamedStreamMap::NamedStreamMap() = default; +NamedStreamMap::NamedStreamMap() {} Error NamedStreamMap::load(BinaryStreamReader &Stream) { - Mapping.clear(); - FinalizedHashTable.clear(); - FinalizedInfo.reset(); - uint32_t StringBufferSize; if (auto EC = Stream.readInteger(StringBufferSize)) return joinErrors(std::move(EC), make_error<RawError>(raw_error_code::corrupt_file, "Expected string buffer size")); - BinaryStreamRef StringsBuffer; - if (auto EC = Stream.readStreamRef(StringsBuffer, StringBufferSize)) - return EC; - - HashTable OffsetIndexMap; - if (auto EC = OffsetIndexMap.load(Stream)) + StringRef Buffer; + if (auto EC = Stream.readFixedString(Buffer, StringBufferSize)) return EC; + NamesBuffer.assign(Buffer.begin(), Buffer.end()); - uint32_t NameOffset; - uint32_t NameIndex; - for (const auto &Entry : OffsetIndexMap) { - std::tie(NameOffset, NameIndex) = Entry; - - // Compute the offset of the start of the string relative to the stream. - BinaryStreamReader NameReader(StringsBuffer); - NameReader.setOffset(NameOffset); - // Pump out our c-string from the stream. - StringRef Str; - if (auto EC = NameReader.readCString(Str)) - return joinErrors(std::move(EC), - make_error<RawError>(raw_error_code::corrupt_file, - "Expected name map name")); - - // Add this to a string-map from name to stream number. - Mapping.insert({Str, NameIndex}); - } - - return Error::success(); + return OffsetIndexMap.load(Stream); } Error NamedStreamMap::commit(BinaryStreamWriter &Writer) const { - assert(FinalizedInfo.hasValue()); - // The first field is the number of bytes of string data. - if (auto EC = Writer.writeInteger(FinalizedInfo->StringDataBytes)) + if (auto EC = Writer.writeInteger<uint32_t>(NamesBuffer.size())) return EC; - for (const auto &Name : OrderedStreamNames) { - auto Item = Mapping.find(Name); - if (Item == Mapping.end()) - continue; - if (auto EC = Writer.writeCString(Item->getKey())) - return EC; - } + // Then the actual string data. + StringRef Data(NamesBuffer.data(), NamesBuffer.size()); + if (auto EC = Writer.writeFixedString(Data)) + return EC; // And finally the Offset Index map. - if (auto EC = FinalizedHashTable.commit(Writer)) + if (auto EC = OffsetIndexMap.commit(Writer)) return EC; return Error::success(); } -uint32_t NamedStreamMap::finalize() { - if (FinalizedInfo.hasValue()) - return FinalizedInfo->SerializedLength; - - // Build the finalized hash table. - FinalizedHashTable.clear(); - FinalizedInfo.emplace(); +uint32_t NamedStreamMap::calculateSerializedLength() const { + return sizeof(uint32_t) // String data size + + NamesBuffer.size() // String data + + OffsetIndexMap.calculateSerializedLength(); // Offset Index Map +} - for (const auto &Name : OrderedStreamNames) { - auto Item = Mapping.find(Name); - if (Item == Mapping.end()) - continue; - FinalizedHashTable.set(FinalizedInfo->StringDataBytes, Item->getValue()); - FinalizedInfo->StringDataBytes += Item->getKeyLength() + 1; - } +uint32_t NamedStreamMap::size() const { return OffsetIndexMap.size(); } - // Number of bytes of string data. - FinalizedInfo->SerializedLength += sizeof(support::ulittle32_t); - // Followed by that many actual bytes of string data. - FinalizedInfo->SerializedLength += FinalizedInfo->StringDataBytes; - // Followed by the mapping from Offset to Index. - FinalizedInfo->SerializedLength += - FinalizedHashTable.calculateSerializedLength(); - return FinalizedInfo->SerializedLength; +StringRef NamedStreamMap::getString(uint32_t Offset) const { + assert(NamesBuffer.size() > Offset); + return StringRef(NamesBuffer.data() + Offset); } -iterator_range<StringMapConstIterator<uint32_t>> -NamedStreamMap::entries() const { - return make_range<StringMapConstIterator<uint32_t>>(Mapping.begin(), - Mapping.end()); +uint32_t NamedStreamMap::hashString(uint32_t Offset) const { + return hashStringV1(getString(Offset)); } -uint32_t NamedStreamMap::size() const { return Mapping.size(); } - bool NamedStreamMap::get(StringRef Stream, uint32_t &StreamNo) const { - auto Iter = Mapping.find(Stream); - if (Iter == Mapping.end()) + auto Iter = OffsetIndexMap.find_as<NamedStreamMapTraits>(Stream, *this); + if (Iter == OffsetIndexMap.end()) return false; - StreamNo = Iter->second; + StreamNo = (*Iter).second; return true; } -void NamedStreamMap::set(StringRef Stream, uint32_t StreamNo) { - FinalizedInfo.reset(); - Mapping[Stream] = StreamNo; +StringMap<uint32_t> NamedStreamMap::entries() const { + StringMap<uint32_t> Result; + for (const auto &Entry : OffsetIndexMap) { + StringRef Stream(NamesBuffer.data() + Entry.first); + Result.try_emplace(Stream, Entry.second); + } + return Result; } -void NamedStreamMap::remove(StringRef Stream) { - FinalizedInfo.reset(); - Mapping.erase(Stream); +uint32_t NamedStreamMap::appendStringData(StringRef S) { + uint32_t Offset = NamesBuffer.size(); + NamesBuffer.insert(NamesBuffer.end(), S.begin(), S.end()); + NamesBuffer.push_back('\0'); + return Offset; +} + +void NamedStreamMap::set(StringRef Stream, uint32_t StreamNo) { + OffsetIndexMap.set_as<NamedStreamMapTraits>(Stream, StreamNo, *this); } diff --git a/llvm/tools/llvm-pdbutil/Diff.cpp b/llvm/tools/llvm-pdbutil/Diff.cpp index 286dc51c29b..9aac4b33407 100644 --- a/llvm/tools/llvm-pdbutil/Diff.cpp +++ b/llvm/tools/llvm-pdbutil/Diff.cpp @@ -417,8 +417,8 @@ Error DiffStyle::diffInfoStream() { IS2.getFeatureSignatures()); D.print("Named Stream Size", IS1.getNamedStreamMapByteSize(), IS2.getNamedStreamMapByteSize()); - StringMap<uint32_t> NSL = IS1.getNamedStreams().getStringMap(); - StringMap<uint32_t> NSR = IS2.getNamedStreams().getStringMap(); + StringMap<uint32_t> NSL = IS1.getNamedStreams().entries(); + StringMap<uint32_t> NSR = IS2.getNamedStreams().entries(); D.diffUnorderedMap<EquivalentDiffProvider>("Named Stream", NSL, NSR); return Error::success(); } diff --git a/llvm/unittests/DebugInfo/PDB/HashTableTest.cpp b/llvm/unittests/DebugInfo/PDB/HashTableTest.cpp index f1968e55e86..03df2967fab 100644 --- a/llvm/unittests/DebugInfo/PDB/HashTableTest.cpp +++ b/llvm/unittests/DebugInfo/PDB/HashTableTest.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "llvm/DebugInfo/PDB/Native/HashTable.h" +#include "llvm/DebugInfo/PDB/Native/NamedStreamMap.h" #include "llvm/Support/BinaryByteStream.h" #include "llvm/Support/BinaryStreamReader.h" #include "llvm/Support/BinaryStreamWriter.h" @@ -166,3 +167,43 @@ TEST(HashTableTest, Serialization) { EXPECT_EQ(Table.Present, Table2.Present); EXPECT_EQ(Table.Deleted, Table2.Deleted); } + +TEST(HashTableTest, NamedStreamMap) { + std::vector<StringRef> Streams = {"One", "Two", "Three", "Four", + "Five", "Six", "Seven"}; + StringMap<uint32_t> ExpectedIndices; + for (uint32_t I = 0; I < Streams.size(); ++I) + ExpectedIndices[Streams[I]] = I + 1; + + // To verify the hash table actually works, we want to verify that insertion + // order doesn't matter. So try inserting in every possible order of 7 items. + do { + NamedStreamMap NSM; + for (StringRef S : Streams) + NSM.set(S, ExpectedIndices[S]); + + EXPECT_EQ(Streams.size(), NSM.size()); + + uint32_t N; + EXPECT_TRUE(NSM.get("One", N)); + EXPECT_EQ(1, N); + + EXPECT_TRUE(NSM.get("Two", N)); + EXPECT_EQ(2, N); + + EXPECT_TRUE(NSM.get("Three", N)); + EXPECT_EQ(3, N); + + EXPECT_TRUE(NSM.get("Four", N)); + EXPECT_EQ(4, N); + + EXPECT_TRUE(NSM.get("Five", N)); + EXPECT_EQ(5, N); + + EXPECT_TRUE(NSM.get("Six", N)); + EXPECT_EQ(6, N); + + EXPECT_TRUE(NSM.get("Seven", N)); + EXPECT_EQ(7, N); + } while (std::next_permutation(Streams.begin(), Streams.end())); +} |

