From 1bb76182caee8239b71b9d6d21f479014d37ad5b Mon Sep 17 00:00:00 2001 From: misterg Date: Mon, 27 Aug 2018 13:54:59 -0400 Subject: [PATCH 01/16] Googletest export Code Cleanup PiperOrigin-RevId: 210393771 --- googletest/src/gtest.cc | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/googletest/src/gtest.cc b/googletest/src/gtest.cc index f476044b..97966377 100644 --- a/googletest/src/gtest.cc +++ b/googletest/src/gtest.cc @@ -459,8 +459,6 @@ FilePath GetCurrentExecutableName() { // Returns the output format, or "" for normal printed output. std::string UnitTestOptions::GetOutputFormat() { const char* const gtest_output_flag = GTEST_FLAG(output).c_str(); - if (gtest_output_flag == NULL) return std::string(""); - const char* const colon = strchr(gtest_output_flag, ':'); return (colon == NULL) ? std::string(gtest_output_flag) : @@ -469,11 +467,8 @@ std::string UnitTestOptions::GetOutputFormat() { // Returns the name of the requested output file, or the default if none // was explicitly specified. -// FIXME Remove GetAbsolutePathToOutputFile checking gtest_output_flag == NULL std::string UnitTestOptions::GetAbsolutePathToOutputFile() { const char* const gtest_output_flag = GTEST_FLAG(output).c_str(); - if (gtest_output_flag == NULL) - return ""; std::string format = GetOutputFormat(); if (format.empty()) @@ -3505,7 +3500,7 @@ class XmlUnitTestResultPrinter : public EmptyTestEventListener { // Creates a new XmlUnitTestResultPrinter. XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(const char* output_file) : output_file_(output_file) { - if (output_file_.c_str() == NULL || output_file_.empty()) { + if (output_file_.empty()) { GTEST_LOG_(FATAL) << "XML output file may not be null"; } } From 167c5e8188beb5dae002ac7571457e3c26eb6a3f Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Mon, 27 Aug 2018 14:53:25 -0400 Subject: [PATCH 02/16] Googletest export Fix Theta(N^2) memory usage of EXPECT_EQ(string) when the strings don't match. The underlying CalculateOptimalEdits() implementation used a simple dynamic-programming approach that always used N^2 memory and time. This meant that tests for equality of large strings were ticking time bombs: They'd work fine as long as the test passed, but as soon as the strings differed the test would OOM, which is very hard to debug. I switched it out for a Dijkstra search, which is still worst-case O(N^2), but in the usual case of mostly-matching strings, it is much closer to linear. PiperOrigin-RevId: 210405025 --- .../include/gtest/internal/gtest-internal.h | 14 +- googletest/src/gtest-all.cc | 3 +- googletest/src/gtest-edit-distance.cc | 507 ++++++++++++++++++ googletest/src/gtest.cc | 244 +-------- googletest/test/gtest_unittest.cc | 77 ++- 5 files changed, 580 insertions(+), 265 deletions(-) create mode 100644 googletest/src/gtest-edit-distance.cc diff --git a/googletest/include/gtest/internal/gtest-internal.h b/googletest/include/gtest/internal/gtest-internal.h index 9593a45a..1c2824dd 100644 --- a/googletest/include/gtest/internal/gtest-internal.h +++ b/googletest/include/gtest/internal/gtest-internal.h @@ -159,15 +159,15 @@ GTEST_DISABLE_MSC_WARNINGS_POP_() // 4275 #endif // GTEST_HAS_EXCEPTIONS -namespace edit_distance { // Returns the optimal edits to go from 'left' to 'right'. // All edits cost the same, with replace having lower priority than -// add/remove. -// Simple implementation of the Wagner-Fischer algorithm. -// See http://en.wikipedia.org/wiki/Wagner-Fischer_algorithm -enum EditType { kMatch, kAdd, kRemove, kReplace }; +// add/remove. Returns an approximation of the maximum memory used in +// 'memory_usage' if non-null. +// Uses a Dijkstra search, with a couple of simple bells and whistles added on. +enum EditType { kEditMatch, kEditAdd, kEditRemove, kEditReplace }; GTEST_API_ std::vector CalculateOptimalEdits( - const std::vector& left, const std::vector& right); + const std::vector& left, const std::vector& right, + size_t* memory_usage = NULL); // Same as above, but the input is represented as strings. GTEST_API_ std::vector CalculateOptimalEdits( @@ -179,8 +179,6 @@ GTEST_API_ std::string CreateUnifiedDiff(const std::vector& left, const std::vector& right, size_t context = 2); -} // namespace edit_distance - // Calculate the diff between 'left' and 'right' and return it in unified diff // format. // If not null, stores in 'total_line_count' the total number of lines found diff --git a/googletest/src/gtest-all.cc b/googletest/src/gtest-all.cc index b217a180..3363d398 100644 --- a/googletest/src/gtest-all.cc +++ b/googletest/src/gtest-all.cc @@ -38,10 +38,11 @@ #include "gtest/gtest.h" // The following lines pull in the real gtest *.cc files. -#include "src/gtest.cc" #include "src/gtest-death-test.cc" +#include "src/gtest-edit-distance.cc" #include "src/gtest-filepath.cc" #include "src/gtest-port.cc" #include "src/gtest-printers.cc" #include "src/gtest-test-part.cc" #include "src/gtest-typed-test.cc" +#include "src/gtest.cc" diff --git a/googletest/src/gtest-edit-distance.cc b/googletest/src/gtest-edit-distance.cc new file mode 100644 index 00000000..331a3b9b --- /dev/null +++ b/googletest/src/gtest-edit-distance.cc @@ -0,0 +1,507 @@ +// Copyright 2018, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +// Internal helper functions for finding optimal edit transformations +// between strings. + +#include "gtest/gtest.h" + +#include +#include +#include // NOLINT +#include +#include + +namespace testing { +namespace internal { +namespace { + +// The following implement data structures and code for a Dijkstra-search +// based implementation of optimal edit distance. + +// Posible states a node can be in. Either a node is unsettled (it hasn't been +// drawn from the priority queue yet), or it is settled and a back-link to its +// parent node is fixed. +enum EditSearchState { + kUnsettled, + kMatchParent, + kAddParent, + kRemoveParent, + kReplaceParent +}; + +// Custom container for search states. This is smaller and faster than a hash +// map, because the used states are dense along diagonals. +// Specifically, each state requires only 1 byte, whereas a hash_map would +// require storing the key, which would come to at least 8 bytes. std::map has +// an extra 32 bytes per node (3 pointers + 1 byte, padded), so even though +// there are circumstances where this class can have kBlockSize overhead per +// state, on average it does better than 40 bytes of overhead per state. +// In addition, in unopt builds (the usual way tests are run) the fewer +// allocations + better locality has this method running 10-50x faster than +// std::map for inputs that are large enough to measure. +class EditSearchMap { + public: + EditSearchMap(size_t left_size, size_t right_size) + : left_size_(left_size), right_size_(right_size) { + GTEST_CHECK_(left_size_ == left_size && right_size_ == right_size) + << "Overflow in size: Arguments too large"; + } + + // Gets a mutable reference to a state - this is actually of type + // EditSearchState - inserting if it does not exist. + unsigned char& insert(UInt32 left, UInt32 right) { + std::vector* vec; + size_t index1; + size_t index2; + if (left > right) { + vec = &left_nodes_; + index1 = left - right - 1; + index2 = right; + } else { + vec = &right_nodes_; + index1 = right - left; + index2 = left; + } + if (vec->size() <= index1) { + GTEST_CHECK_(vec->size() == index1) + << "Array diagonals should only grow by one " << vec->size() << " vs " + << index1; + vec->push_back(block_indices_.size()); + // Round up + block_indices_.resize( + block_indices_.size() + + (DiagonalLength(left, right) + kBlockSize - 1) / kBlockSize, + kUnallocatedBlock); + } + const size_t bucket = index2 / kBlockSize; + const size_t pos_in_bucket = index2 % kBlockSize; + UInt32& level2 = block_indices_[(*vec)[index1] + bucket]; + if (level2 == kUnallocatedBlock) { + level2 = nodes_.size(); + size_t diagonal_length = DiagonalLength(left, right); + GTEST_CHECK_(diagonal_length > index2) + << diagonal_length << " " << index2; + size_t block_size = kBlockSize; + if (diagonal_length / kBlockSize == bucket) { + // We can never get here if diagonal_length is a multiple of + // kBlockSize, which is what we want, since this would evaluate to 0. + block_size = diagonal_length % kBlockSize; + } + nodes_.resize(nodes_.size() + block_size); + } + return nodes_[level2 + pos_in_bucket]; + } + + size_t MemoryUsage() const { + return nodes_.capacity() + + sizeof(UInt32) * (left_nodes_.capacity() + right_nodes_.capacity() + + block_indices_.capacity()); + } + + private: + enum { kBlockSize = 1024, kUnallocatedBlock = 0xFFFFFFFFul }; + + size_t DiagonalLength(UInt32 left, UInt32 right) const { + return std::min(left_size_ - left, right_size_ - right) + + (left < right ? left : right); + } + + // The state space is conceptually a left_size_ by right_size_ sparse matrix + // of EditSearchStates. However, due to the access pattern of the search, it + // is much better to store the nodes per diagonal rather than per row. + UInt32 left_size_; + UInt32 right_size_; + // The nodes are stored by diagonals, split in two: Those to the left of the + // main diagonal are in left_nodes_, and everything else is in right_nodes_. + // The values are indices into block_indices_. + std::vector left_nodes_; + std::vector right_nodes_; + // Every entry here is an offset into the beginning of a kBlockSize-sized + // block in nodes_. An entire diagonal is allocated together here; for a + // diagonal of length <= kBlockSize, that's just a single entry, but for + // longer diagonals multiple contiguous index entries will be reserved at + // once. Unused entries will be assigned kUnallocatedBlock; this + // double-indirect scheme is used to save memory in the cases when an entire + // diagonal isn't needed. + std::vector block_indices_; + // This stores the actual EditSearchState data, pointed to by block_indices_. + std::vector nodes_; +}; + +struct EditHeapEntry { + EditHeapEntry(UInt32 l, UInt32 r, UInt64 c, EditSearchState s) + : left(l), right(r), cost(c), state(s) {} + + UInt32 left; + UInt32 right; + UInt64 cost : 61; + // The state that the node will get when this entry is settled. Therefore, + // this can never be kUnsettled. + UInt64 state : 3; + + bool operator>(const EditHeapEntry& other) const { return cost > other.cost; } +}; + +// Need a min-queue, so invert the comparator. +typedef std::priority_queue, + std::greater> + EditHeap; + +} // namespace + +std::vector CalculateOptimalEdits(const std::vector& left, + const std::vector& right, + size_t* memory_usage) { + const UInt64 kBaseCost = 100000; + // We make replace a little more expensive than add/remove to lower + // their priority. + const UInt64 kReplaceCost = 100001; + // In the common case where the vectors are the same (or almost the same) + // size, we know that an add will have to be followed by some later remove + // (or vice versa) in order to get the lengths to balance. We "borrow" some + // of the cost of the later operation and bring it forward into the earlier + // operation, to increase the cost of exploring (usually fruitlessly) around + // the beginning of the graph. + // However, there is a trade-off: This cheapens the cost of exploring around + // the beginning of the graph (in one direction) when the vectors are + // unequal in length. So we don't steal *all* the cost. + // You can view this as a form of A*, using an admissable heuristic that has + // been re-cast as a cost function that can be used in Dijkstra. + const UInt64 kTowardsGoalCost = 50003; + const UInt64 kAwayFromGoalCost = 2 * kBaseCost - kTowardsGoalCost; + + EditSearchMap node_map(left.size() + 1, right.size() + 1); + EditHeap heap; + heap.push(EditHeapEntry(0, 0, 0, kReplaceParent)); + + while (!heap.empty()) { + const EditHeapEntry current_entry = heap.top(); + heap.pop(); + + UInt32 left_pos = current_entry.left; + UInt32 right_pos = current_entry.right; + unsigned char& current_state = node_map.insert(left_pos, right_pos); + if (current_state != kUnsettled) { + // Node was already settled by a previous entry in the priority queue, + // this is a suboptimal path that should be ignored. + continue; + } + current_state = current_entry.state; + + if (left_pos == left.size() && right_pos == right.size()) { + // This is the normal exit point; if we terminate due to the heap being + // empty, we'll fail a check later. + break; + } + + // Special case: Since the cost of a match is zero, we can immediately + // settle the new node without putting it in the queue, since nothing can + // have a smaller cost than it. Furthermore, we don't need to relax the + // other two edges, since we know we don't need them: Any path from this + // node that would use them has an path via the match that is at least as + // cheap. Together, this means we can loop here until we stop matching. + while (left_pos < left.size() && right_pos < right.size() && + left[left_pos] == right[right_pos]) { + left_pos++; + right_pos++; + unsigned char& fast_forward_state = node_map.insert(left_pos, right_pos); + if (fast_forward_state != kUnsettled) { + // The search reached around and settled this node before settling the + // base node. This means we're completely done with this iteration; + // abort to the outer loop. + goto outer_loop_bottom; + // Otherwise, when can settle this node, even if it was created from + // another state - we know the cost of settling it now is optimal. + } + fast_forward_state = kMatchParent; + } + + // Relax adjacent nodes. We have no way to find or lower the cost of + // existing entries in the heap, so we just push new entries and throw + // them out at the top if the node is already settled. We *could* check to + // see if they're already settled before pushing, but it turns out to be + // ~not any faster, and more complicated to do so. + // + // If we're at an edge, there's only one node to relax. + if (left_pos >= left.size()) { + if (right_pos >= right.size()) { + break; // Can happen due to the fast-path loop above. + } + heap.push(EditHeapEntry(left_pos, right_pos + 1, + current_entry.cost + kTowardsGoalCost, + kAddParent)); + continue; + } + if (right_pos >= right.size()) { + heap.push(EditHeapEntry(left_pos + 1, right_pos, + current_entry.cost + kTowardsGoalCost, + kRemoveParent)); + continue; + } + // General case: Relax 3 edges. + heap.push(EditHeapEntry( + left_pos, right_pos + 1, + current_entry.cost + (right.size() + left_pos > right_pos + left.size() + ? kTowardsGoalCost + : kAwayFromGoalCost), + kAddParent)); + heap.push(EditHeapEntry( + left_pos + 1, right_pos, + current_entry.cost + (right.size() + left_pos < right_pos + left.size() + ? kTowardsGoalCost + : kAwayFromGoalCost), + kRemoveParent)); + heap.push(EditHeapEntry(left_pos + 1, right_pos + 1, + current_entry.cost + kReplaceCost, kReplaceParent)); + outer_loop_bottom : {} // Need the curlies to form a statement. + } + + // Reconstruct the best path. We do it in reverse order. + std::vector best_path; + UInt32 left_pos = left.size(); + UInt32 right_pos = right.size(); + while (left_pos != 0 || right_pos != 0) { + GTEST_CHECK_(left_pos <= left.size() && right_pos <= right.size()); + // The node must already exist, but if it somehow doesn't, it will be + // added as kUnsettled, which will crash below. + const unsigned char state = node_map.insert(left_pos, right_pos); + switch (state) { + case kAddParent: + right_pos--; + break; + case kRemoveParent: + left_pos--; + break; + case kMatchParent: + case kReplaceParent: + left_pos--; + right_pos--; + break; + default: + GTEST_LOG_(FATAL) << "Unsettled node at " << left_pos << "," + << right_pos; + } + best_path.push_back(static_cast(state - 1)); + } + std::reverse(best_path.begin(), best_path.end()); + if (memory_usage != NULL) { + *memory_usage = node_map.MemoryUsage(); + } + return best_path; +} + +namespace { + +// Helper class to convert string into ids with deduplication. +class InternalStrings { + public: + size_t GetId(const std::string* str) { + IdMap::iterator it = ids_.find(str); + if (it != ids_.end()) return it->second; + size_t id = ids_.size(); + return ids_[str] = id; + } + + private: + struct IdMapCmp { + bool operator()(const std::string* first, const std::string* second) const { + return *first < *second; + } + }; + typedef std::map IdMap; + IdMap ids_; +}; + +} // namespace + +std::vector CalculateOptimalEdits( + const std::vector& left, + const std::vector& right) { + std::vector left_ids, right_ids; + { + InternalStrings intern_table; + for (size_t i = 0; i < left.size(); ++i) { + left_ids.push_back(intern_table.GetId(&left[i])); + } + for (size_t i = 0; i < right.size(); ++i) { + right_ids.push_back(intern_table.GetId(&right[i])); + } + } + return CalculateOptimalEdits(left_ids, right_ids); +} + +namespace { + +// Helper class that holds the state for one hunk and prints it out to the +// stream. +// It reorders adds/removes when possible to group all removes before all +// adds. It also adds the hunk header before printing into the stream. +class Hunk { + public: + Hunk(size_t left_start, size_t right_start) + : left_start_(left_start), + right_start_(right_start), + adds_(), + removes_(), + common_() {} + + void PushLine(char edit, const char* line) { + switch (edit) { + case ' ': + ++common_; + FlushEdits(); + hunk_.push_back(std::make_pair(' ', line)); + break; + case '-': + ++removes_; + hunk_removes_.push_back(std::make_pair('-', line)); + break; + case '+': + ++adds_; + hunk_adds_.push_back(std::make_pair('+', line)); + break; + } + } + + void PrintTo(std::ostream* os) { + PrintHeader(os); + FlushEdits(); + for (std::list >::const_iterator it = + hunk_.begin(); + it != hunk_.end(); ++it) { + *os << it->first << it->second << "\n"; + } + } + + bool has_edits() const { return adds_ || removes_; } + + private: + void FlushEdits() { + hunk_.splice(hunk_.end(), hunk_removes_); + hunk_.splice(hunk_.end(), hunk_adds_); + } + + // Print a unified diff header for one hunk. + // The format is + // "@@ -, +, @@" + // where the left/right parts are omitted if unnecessary. + void PrintHeader(std::ostream* ss) const { + *ss << "@@ "; + if (removes_) { + *ss << "-" << left_start_ << "," << (removes_ + common_); + } + if (removes_ && adds_) { + *ss << " "; + } + if (adds_) { + *ss << "+" << right_start_ << "," << (adds_ + common_); + } + *ss << " @@\n"; + } + + size_t left_start_, right_start_; + size_t adds_, removes_, common_; + std::list > hunk_, hunk_adds_, hunk_removes_; +}; + +} // namespace + +// Create a list of diff hunks in Unified diff format. +// Each hunk has a header generated by PrintHeader above plus a body with +// lines prefixed with ' ' for no change, '-' for deletion and '+' for +// addition. +// 'context' represents the desired unchanged prefix/suffix around the diff. +// If two hunks are close enough that their contexts overlap, then they are +// joined into one hunk. +std::string CreateUnifiedDiff(const std::vector& left, + const std::vector& right, + size_t context) { + const std::vector edits = CalculateOptimalEdits(left, right); + + size_t l_i = 0, r_i = 0, edit_i = 0; + std::stringstream ss; + while (edit_i < edits.size()) { + // Find first edit. + while (edit_i < edits.size() && edits[edit_i] == kEditMatch) { + ++l_i; + ++r_i; + ++edit_i; + } + + // Find the first line to include in the hunk. + const size_t prefix_context = std::min(l_i, context); + Hunk hunk(l_i - prefix_context + 1, r_i - prefix_context + 1); + for (size_t i = prefix_context; i > 0; --i) { + hunk.PushLine(' ', left[l_i - i].c_str()); + } + + // Iterate the edits until we found enough suffix for the hunk or the input + // is over. + size_t n_suffix = 0; + for (; edit_i < edits.size(); ++edit_i) { + if (n_suffix >= context) { + // Continue only if the next hunk is very close. + std::vector::const_iterator it = edits.begin() + edit_i; + while (it != edits.end() && *it == kEditMatch) ++it; + if (it == edits.end() || (it - edits.begin()) - edit_i >= context) { + // There is no next edit or it is too far away. + break; + } + } + + EditType edit = edits[edit_i]; + // Reset count when a non match is found. + n_suffix = edit == kEditMatch ? n_suffix + 1 : 0; + + if (edit == kEditMatch || edit == kEditRemove || edit == kEditReplace) { + hunk.PushLine(edit == kEditMatch ? ' ' : '-', left[l_i].c_str()); + } + if (edit == kEditAdd || edit == kEditReplace) { + hunk.PushLine('+', right[r_i].c_str()); + } + + // Advance indices, depending on edit type. + l_i += edit != kEditAdd; + r_i += edit != kEditRemove; + } + + if (!hunk.has_edits()) { + // We are done. We don't want this hunk. + break; + } + + hunk.PrintTo(&ss); + } + return ss.str(); +} + +} // namespace internal +} // namespace testing diff --git a/googletest/src/gtest.cc b/googletest/src/gtest.cc index 97966377..ba706c1e 100644 --- a/googletest/src/gtest.cc +++ b/googletest/src/gtest.cc @@ -46,7 +46,6 @@ #include #include #include -#include #include #include // NOLINT #include @@ -1068,246 +1067,6 @@ AssertionResult AssertionFailure(const Message& message) { namespace internal { -namespace edit_distance { -std::vector CalculateOptimalEdits(const std::vector& left, - const std::vector& right) { - std::vector > costs( - left.size() + 1, std::vector(right.size() + 1)); - std::vector > best_move( - left.size() + 1, std::vector(right.size() + 1)); - - // Populate for empty right. - for (size_t l_i = 0; l_i < costs.size(); ++l_i) { - costs[l_i][0] = static_cast(l_i); - best_move[l_i][0] = kRemove; - } - // Populate for empty left. - for (size_t r_i = 1; r_i < costs[0].size(); ++r_i) { - costs[0][r_i] = static_cast(r_i); - best_move[0][r_i] = kAdd; - } - - for (size_t l_i = 0; l_i < left.size(); ++l_i) { - for (size_t r_i = 0; r_i < right.size(); ++r_i) { - if (left[l_i] == right[r_i]) { - // Found a match. Consume it. - costs[l_i + 1][r_i + 1] = costs[l_i][r_i]; - best_move[l_i + 1][r_i + 1] = kMatch; - continue; - } - - const double add = costs[l_i + 1][r_i]; - const double remove = costs[l_i][r_i + 1]; - const double replace = costs[l_i][r_i]; - if (add < remove && add < replace) { - costs[l_i + 1][r_i + 1] = add + 1; - best_move[l_i + 1][r_i + 1] = kAdd; - } else if (remove < add && remove < replace) { - costs[l_i + 1][r_i + 1] = remove + 1; - best_move[l_i + 1][r_i + 1] = kRemove; - } else { - // We make replace a little more expensive than add/remove to lower - // their priority. - costs[l_i + 1][r_i + 1] = replace + 1.00001; - best_move[l_i + 1][r_i + 1] = kReplace; - } - } - } - - // Reconstruct the best path. We do it in reverse order. - std::vector best_path; - for (size_t l_i = left.size(), r_i = right.size(); l_i > 0 || r_i > 0;) { - EditType move = best_move[l_i][r_i]; - best_path.push_back(move); - l_i -= move != kAdd; - r_i -= move != kRemove; - } - std::reverse(best_path.begin(), best_path.end()); - return best_path; -} - -namespace { - -// Helper class to convert string into ids with deduplication. -class InternalStrings { - public: - size_t GetId(const std::string& str) { - IdMap::iterator it = ids_.find(str); - if (it != ids_.end()) return it->second; - size_t id = ids_.size(); - return ids_[str] = id; - } - - private: - typedef std::map IdMap; - IdMap ids_; -}; - -} // namespace - -std::vector CalculateOptimalEdits( - const std::vector& left, - const std::vector& right) { - std::vector left_ids, right_ids; - { - InternalStrings intern_table; - for (size_t i = 0; i < left.size(); ++i) { - left_ids.push_back(intern_table.GetId(left[i])); - } - for (size_t i = 0; i < right.size(); ++i) { - right_ids.push_back(intern_table.GetId(right[i])); - } - } - return CalculateOptimalEdits(left_ids, right_ids); -} - -namespace { - -// Helper class that holds the state for one hunk and prints it out to the -// stream. -// It reorders adds/removes when possible to group all removes before all -// adds. It also adds the hunk header before printint into the stream. -class Hunk { - public: - Hunk(size_t left_start, size_t right_start) - : left_start_(left_start), - right_start_(right_start), - adds_(), - removes_(), - common_() {} - - void PushLine(char edit, const char* line) { - switch (edit) { - case ' ': - ++common_; - FlushEdits(); - hunk_.push_back(std::make_pair(' ', line)); - break; - case '-': - ++removes_; - hunk_removes_.push_back(std::make_pair('-', line)); - break; - case '+': - ++adds_; - hunk_adds_.push_back(std::make_pair('+', line)); - break; - } - } - - void PrintTo(std::ostream* os) { - PrintHeader(os); - FlushEdits(); - for (std::list >::const_iterator it = - hunk_.begin(); - it != hunk_.end(); ++it) { - *os << it->first << it->second << "\n"; - } - } - - bool has_edits() const { return adds_ || removes_; } - - private: - void FlushEdits() { - hunk_.splice(hunk_.end(), hunk_removes_); - hunk_.splice(hunk_.end(), hunk_adds_); - } - - // Print a unified diff header for one hunk. - // The format is - // "@@ -, +, @@" - // where the left/right parts are omitted if unnecessary. - void PrintHeader(std::ostream* ss) const { - *ss << "@@ "; - if (removes_) { - *ss << "-" << left_start_ << "," << (removes_ + common_); - } - if (removes_ && adds_) { - *ss << " "; - } - if (adds_) { - *ss << "+" << right_start_ << "," << (adds_ + common_); - } - *ss << " @@\n"; - } - - size_t left_start_, right_start_; - size_t adds_, removes_, common_; - std::list > hunk_, hunk_adds_, hunk_removes_; -}; - -} // namespace - -// Create a list of diff hunks in Unified diff format. -// Each hunk has a header generated by PrintHeader above plus a body with -// lines prefixed with ' ' for no change, '-' for deletion and '+' for -// addition. -// 'context' represents the desired unchanged prefix/suffix around the diff. -// If two hunks are close enough that their contexts overlap, then they are -// joined into one hunk. -std::string CreateUnifiedDiff(const std::vector& left, - const std::vector& right, - size_t context) { - const std::vector edits = CalculateOptimalEdits(left, right); - - size_t l_i = 0, r_i = 0, edit_i = 0; - std::stringstream ss; - while (edit_i < edits.size()) { - // Find first edit. - while (edit_i < edits.size() && edits[edit_i] == kMatch) { - ++l_i; - ++r_i; - ++edit_i; - } - - // Find the first line to include in the hunk. - const size_t prefix_context = std::min(l_i, context); - Hunk hunk(l_i - prefix_context + 1, r_i - prefix_context + 1); - for (size_t i = prefix_context; i > 0; --i) { - hunk.PushLine(' ', left[l_i - i].c_str()); - } - - // Iterate the edits until we found enough suffix for the hunk or the input - // is over. - size_t n_suffix = 0; - for (; edit_i < edits.size(); ++edit_i) { - if (n_suffix >= context) { - // Continue only if the next hunk is very close. - std::vector::const_iterator it = edits.begin() + edit_i; - while (it != edits.end() && *it == kMatch) ++it; - if (it == edits.end() || (it - edits.begin()) - edit_i >= context) { - // There is no next edit or it is too far away. - break; - } - } - - EditType edit = edits[edit_i]; - // Reset count when a non match is found. - n_suffix = edit == kMatch ? n_suffix + 1 : 0; - - if (edit == kMatch || edit == kRemove || edit == kReplace) { - hunk.PushLine(edit == kMatch ? ' ' : '-', left[l_i].c_str()); - } - if (edit == kAdd || edit == kReplace) { - hunk.PushLine('+', right[r_i].c_str()); - } - - // Advance indices, depending on edit type. - l_i += edit != kAdd; - r_i += edit != kRemove; - } - - if (!hunk.has_edits()) { - // We are done. We don't want this hunk. - break; - } - - hunk.PrintTo(&ss); - } - return ss.str(); -} - -} // namespace edit_distance - namespace { // The string representation of the values received in EqFailure() are already @@ -1379,8 +1138,7 @@ AssertionResult EqFailure(const char* lhs_expression, const std::vector rhs_lines = SplitEscapedString(rhs_value); if (lhs_lines.size() > 1 || rhs_lines.size() > 1) { - msg << "\nWith diff:\n" - << edit_distance::CreateUnifiedDiff(lhs_lines, rhs_lines); + msg << "\nWith diff:\n" << CreateUnifiedDiff(lhs_lines, rhs_lines); } } diff --git a/googletest/test/gtest_unittest.cc b/googletest/test/gtest_unittest.cc index f7213fbf..7f6a2ac4 100644 --- a/googletest/test/gtest_unittest.cc +++ b/googletest/test/gtest_unittest.cc @@ -215,6 +215,7 @@ using testing::GTEST_FLAG(stream_result_to); using testing::GTEST_FLAG(throw_on_failure); using testing::IsNotSubstring; using testing::IsSubstring; +using testing::kMaxStackTraceDepth; using testing::Message; using testing::ScopedFakeTestPartResultReporter; using testing::StaticAssertTypeEq; @@ -234,16 +235,18 @@ using testing::internal::AlwaysTrue; using testing::internal::AppendUserMessage; using testing::internal::ArrayAwareFind; using testing::internal::ArrayEq; +using testing::internal::CalculateOptimalEdits; using testing::internal::CodePointToUtf8; using testing::internal::CompileAssertTypesEqual; using testing::internal::CopyArray; using testing::internal::CountIf; +using testing::internal::CreateUnifiedDiff; +using testing::internal::EditType; using testing::internal::EqFailure; using testing::internal::FloatingPoint; using testing::internal::ForEach; using testing::internal::FormatEpochTimeInMillisAsIso8601; using testing::internal::FormatTimeInMillisAsSeconds; -using testing::internal::GTestFlagSaver; using testing::internal::GetCurrentOsStackTraceExceptTop; using testing::internal::GetElementOr; using testing::internal::GetNextRandomSeed; @@ -252,6 +255,7 @@ using testing::internal::GetTestTypeId; using testing::internal::GetTimeInMillis; using testing::internal::GetTypeId; using testing::internal::GetUnitTestImpl; +using testing::internal::GTestFlagSaver; using testing::internal::ImplicitlyConvertible; using testing::internal::Int32; using testing::internal::Int32FromEnvOrDie; @@ -259,6 +263,8 @@ using testing::internal::IsAProtocolMessage; using testing::internal::IsContainer; using testing::internal::IsContainerTest; using testing::internal::IsNotContainer; +using testing::internal::kMaxRandomSeed; +using testing::internal::kTestTypeIdInGoogleTest; using testing::internal::NativeArray; using testing::internal::OsStackTraceGetter; using testing::internal::OsStackTraceGetterInterface; @@ -280,12 +286,6 @@ using testing::internal::TestResultAccessor; using testing::internal::UInt32; using testing::internal::UnitTestImpl; using testing::internal::WideStringToUtf8; -using testing::internal::edit_distance::CalculateOptimalEdits; -using testing::internal::edit_distance::CreateUnifiedDiff; -using testing::internal::edit_distance::EditType; -using testing::internal::kMaxRandomSeed; -using testing::internal::kTestTypeIdInGoogleTest; -using testing::kMaxStackTraceDepth; #if GTEST_HAS_STREAM_REDIRECTION using testing::internal::CaptureStdout; @@ -3517,14 +3517,14 @@ TEST(EditDistance, TestCases) { {__LINE__, "ABCD", "abcd", "////", "@@ -1,4 +1,4 @@\n-A\n-B\n-C\n-D\n+a\n+b\n+c\n+d\n"}, // Path finding. - {__LINE__, "ABCDEFGH", "ABXEGH1", " -/ - +", + {__LINE__, "ABCDEFGH", "ABXEGH1", " /- - +", "@@ -1,8 +1,7 @@\n A\n B\n-C\n-D\n+X\n E\n-F\n G\n H\n+1\n"}, - {__LINE__, "AAAABCCCC", "ABABCDCDC", "- / + / ", - "@@ -1,9 +1,9 @@\n-A\n A\n-A\n+B\n A\n B\n C\n+D\n C\n-C\n+D\n C\n"}, - {__LINE__, "ABCDE", "BCDCD", "- +/", + {__LINE__, "AAAABCCCC", "ABABCDCDC", " -/ + / ", + "@@ -1,9 +1,9 @@\n A\n-A\n-A\n+B\n A\n B\n C\n+D\n C\n-C\n+D\n C\n"}, + {__LINE__, "ABCDE", "BCDCD", "- /+", "@@ -1,5 +1,5 @@\n-A\n B\n C\n D\n-E\n+C\n+D\n"}, - {__LINE__, "ABCDEFGHIJKL", "BCDCDEFGJKLJK", "- ++ -- ++", - "@@ -1,4 +1,5 @@\n-A\n B\n+C\n+D\n C\n D\n" + {__LINE__, "ABCDEFGHIJKL", "BGDCDEFGJKLJK", "- ++ -- ++", + "@@ -1,4 +1,5 @@\n-A\n B\n+G\n+D\n C\n D\n" "@@ -6,7 +7,7 @@\n F\n G\n-H\n-I\n J\n K\n L\n+J\n+K\n"}, {}}; for (const Case* c = kCases; c->left; ++c) { @@ -3542,6 +3542,57 @@ TEST(EditDistance, TestCases) { } } +// Tests that we can run CalculateOptimalEdits for a large vector, i.e. we can +// compute diffs for large strings. +TEST(EditDistance, LargeVectorWithDiffs) { + const int kSize = 300000; + std::vector left; + std::vector right; + std::vector expected(kSize, testing::internal::kEditMatch); + + left.reserve(kSize); + right.reserve(kSize); + for (int i = 0; i < kSize; ++i) { + // Make the contents of the vectors unique. This greatly speeds up + // the algorithm, since it doesn't spend time finding matches for + // different alignments. + left.push_back(i); + right.push_back(i); + } + + for (int i = 0; i < 10; ++i) { + right[i] = kSize + i; + expected[i] = testing::internal::kEditReplace; + right[kSize - i - 1] = kSize * 2 + i; + expected[kSize - i - 1] = testing::internal::kEditReplace; + } + size_t memory_usage; + EXPECT_EQ(CalculateOptimalEdits(left, right, &memory_usage), expected); + EXPECT_GT(memory_usage, kSize); + EXPECT_LT(memory_usage, kSize * 2); +} + +// Tests that we can run CalculateOptimalEdits for two vectors N and M, where +// M = N plus additional junk at the end. The current algorithm only does O(M) +// "real" work in this case, but allocates some extra memory. We test that this +// is still fast enough for common cases, and we aren't allocating an +// excessive amount of extra memory. +TEST(EditDistance, LargeVectorWithTrailingJunk) { + const int kSize = 200000; + const int kAdditionalSize = 2000; + std::vector left(kSize, 0); + std::vector right(kSize + kAdditionalSize, 0); + std::vector expected(kSize + kAdditionalSize, + testing::internal::kEditMatch); + for (int i = 0; i < kAdditionalSize; ++i) { + expected[i + kSize] = testing::internal::kEditAdd; + } + size_t memory_usage; + EXPECT_EQ(CalculateOptimalEdits(left, right, &memory_usage), expected); + EXPECT_GT(memory_usage, kSize); + EXPECT_LT(memory_usage, 6000000); +} + // Tests EqFailure(), used for implementing *EQ* assertions. TEST(AssertionTest, EqFailure) { const std::string foo_val("5"), bar_val("6"); From 52f8183e7f3620cf03f321a2624eb0d4f7649f4c Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Mon, 27 Aug 2018 17:40:13 -0400 Subject: [PATCH 03/16] Googletest export Breaks Windows builds PiperOrigin-RevId: 210434120 --- .../include/gtest/internal/gtest-internal.h | 14 +- googletest/src/gtest-all.cc | 3 +- googletest/src/gtest-edit-distance.cc | 507 ------------------ googletest/src/gtest.cc | 244 ++++++++- googletest/test/gtest_unittest.cc | 77 +-- 5 files changed, 265 insertions(+), 580 deletions(-) delete mode 100644 googletest/src/gtest-edit-distance.cc diff --git a/googletest/include/gtest/internal/gtest-internal.h b/googletest/include/gtest/internal/gtest-internal.h index 1c2824dd..9593a45a 100644 --- a/googletest/include/gtest/internal/gtest-internal.h +++ b/googletest/include/gtest/internal/gtest-internal.h @@ -159,15 +159,15 @@ GTEST_DISABLE_MSC_WARNINGS_POP_() // 4275 #endif // GTEST_HAS_EXCEPTIONS +namespace edit_distance { // Returns the optimal edits to go from 'left' to 'right'. // All edits cost the same, with replace having lower priority than -// add/remove. Returns an approximation of the maximum memory used in -// 'memory_usage' if non-null. -// Uses a Dijkstra search, with a couple of simple bells and whistles added on. -enum EditType { kEditMatch, kEditAdd, kEditRemove, kEditReplace }; +// add/remove. +// Simple implementation of the Wagner-Fischer algorithm. +// See http://en.wikipedia.org/wiki/Wagner-Fischer_algorithm +enum EditType { kMatch, kAdd, kRemove, kReplace }; GTEST_API_ std::vector CalculateOptimalEdits( - const std::vector& left, const std::vector& right, - size_t* memory_usage = NULL); + const std::vector& left, const std::vector& right); // Same as above, but the input is represented as strings. GTEST_API_ std::vector CalculateOptimalEdits( @@ -179,6 +179,8 @@ GTEST_API_ std::string CreateUnifiedDiff(const std::vector& left, const std::vector& right, size_t context = 2); +} // namespace edit_distance + // Calculate the diff between 'left' and 'right' and return it in unified diff // format. // If not null, stores in 'total_line_count' the total number of lines found diff --git a/googletest/src/gtest-all.cc b/googletest/src/gtest-all.cc index 3363d398..b217a180 100644 --- a/googletest/src/gtest-all.cc +++ b/googletest/src/gtest-all.cc @@ -38,11 +38,10 @@ #include "gtest/gtest.h" // The following lines pull in the real gtest *.cc files. +#include "src/gtest.cc" #include "src/gtest-death-test.cc" -#include "src/gtest-edit-distance.cc" #include "src/gtest-filepath.cc" #include "src/gtest-port.cc" #include "src/gtest-printers.cc" #include "src/gtest-test-part.cc" #include "src/gtest-typed-test.cc" -#include "src/gtest.cc" diff --git a/googletest/src/gtest-edit-distance.cc b/googletest/src/gtest-edit-distance.cc deleted file mode 100644 index 331a3b9b..00000000 --- a/googletest/src/gtest-edit-distance.cc +++ /dev/null @@ -1,507 +0,0 @@ -// Copyright 2018, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// -// Internal helper functions for finding optimal edit transformations -// between strings. - -#include "gtest/gtest.h" - -#include -#include -#include // NOLINT -#include -#include - -namespace testing { -namespace internal { -namespace { - -// The following implement data structures and code for a Dijkstra-search -// based implementation of optimal edit distance. - -// Posible states a node can be in. Either a node is unsettled (it hasn't been -// drawn from the priority queue yet), or it is settled and a back-link to its -// parent node is fixed. -enum EditSearchState { - kUnsettled, - kMatchParent, - kAddParent, - kRemoveParent, - kReplaceParent -}; - -// Custom container for search states. This is smaller and faster than a hash -// map, because the used states are dense along diagonals. -// Specifically, each state requires only 1 byte, whereas a hash_map would -// require storing the key, which would come to at least 8 bytes. std::map has -// an extra 32 bytes per node (3 pointers + 1 byte, padded), so even though -// there are circumstances where this class can have kBlockSize overhead per -// state, on average it does better than 40 bytes of overhead per state. -// In addition, in unopt builds (the usual way tests are run) the fewer -// allocations + better locality has this method running 10-50x faster than -// std::map for inputs that are large enough to measure. -class EditSearchMap { - public: - EditSearchMap(size_t left_size, size_t right_size) - : left_size_(left_size), right_size_(right_size) { - GTEST_CHECK_(left_size_ == left_size && right_size_ == right_size) - << "Overflow in size: Arguments too large"; - } - - // Gets a mutable reference to a state - this is actually of type - // EditSearchState - inserting if it does not exist. - unsigned char& insert(UInt32 left, UInt32 right) { - std::vector* vec; - size_t index1; - size_t index2; - if (left > right) { - vec = &left_nodes_; - index1 = left - right - 1; - index2 = right; - } else { - vec = &right_nodes_; - index1 = right - left; - index2 = left; - } - if (vec->size() <= index1) { - GTEST_CHECK_(vec->size() == index1) - << "Array diagonals should only grow by one " << vec->size() << " vs " - << index1; - vec->push_back(block_indices_.size()); - // Round up - block_indices_.resize( - block_indices_.size() + - (DiagonalLength(left, right) + kBlockSize - 1) / kBlockSize, - kUnallocatedBlock); - } - const size_t bucket = index2 / kBlockSize; - const size_t pos_in_bucket = index2 % kBlockSize; - UInt32& level2 = block_indices_[(*vec)[index1] + bucket]; - if (level2 == kUnallocatedBlock) { - level2 = nodes_.size(); - size_t diagonal_length = DiagonalLength(left, right); - GTEST_CHECK_(diagonal_length > index2) - << diagonal_length << " " << index2; - size_t block_size = kBlockSize; - if (diagonal_length / kBlockSize == bucket) { - // We can never get here if diagonal_length is a multiple of - // kBlockSize, which is what we want, since this would evaluate to 0. - block_size = diagonal_length % kBlockSize; - } - nodes_.resize(nodes_.size() + block_size); - } - return nodes_[level2 + pos_in_bucket]; - } - - size_t MemoryUsage() const { - return nodes_.capacity() + - sizeof(UInt32) * (left_nodes_.capacity() + right_nodes_.capacity() + - block_indices_.capacity()); - } - - private: - enum { kBlockSize = 1024, kUnallocatedBlock = 0xFFFFFFFFul }; - - size_t DiagonalLength(UInt32 left, UInt32 right) const { - return std::min(left_size_ - left, right_size_ - right) + - (left < right ? left : right); - } - - // The state space is conceptually a left_size_ by right_size_ sparse matrix - // of EditSearchStates. However, due to the access pattern of the search, it - // is much better to store the nodes per diagonal rather than per row. - UInt32 left_size_; - UInt32 right_size_; - // The nodes are stored by diagonals, split in two: Those to the left of the - // main diagonal are in left_nodes_, and everything else is in right_nodes_. - // The values are indices into block_indices_. - std::vector left_nodes_; - std::vector right_nodes_; - // Every entry here is an offset into the beginning of a kBlockSize-sized - // block in nodes_. An entire diagonal is allocated together here; for a - // diagonal of length <= kBlockSize, that's just a single entry, but for - // longer diagonals multiple contiguous index entries will be reserved at - // once. Unused entries will be assigned kUnallocatedBlock; this - // double-indirect scheme is used to save memory in the cases when an entire - // diagonal isn't needed. - std::vector block_indices_; - // This stores the actual EditSearchState data, pointed to by block_indices_. - std::vector nodes_; -}; - -struct EditHeapEntry { - EditHeapEntry(UInt32 l, UInt32 r, UInt64 c, EditSearchState s) - : left(l), right(r), cost(c), state(s) {} - - UInt32 left; - UInt32 right; - UInt64 cost : 61; - // The state that the node will get when this entry is settled. Therefore, - // this can never be kUnsettled. - UInt64 state : 3; - - bool operator>(const EditHeapEntry& other) const { return cost > other.cost; } -}; - -// Need a min-queue, so invert the comparator. -typedef std::priority_queue, - std::greater> - EditHeap; - -} // namespace - -std::vector CalculateOptimalEdits(const std::vector& left, - const std::vector& right, - size_t* memory_usage) { - const UInt64 kBaseCost = 100000; - // We make replace a little more expensive than add/remove to lower - // their priority. - const UInt64 kReplaceCost = 100001; - // In the common case where the vectors are the same (or almost the same) - // size, we know that an add will have to be followed by some later remove - // (or vice versa) in order to get the lengths to balance. We "borrow" some - // of the cost of the later operation and bring it forward into the earlier - // operation, to increase the cost of exploring (usually fruitlessly) around - // the beginning of the graph. - // However, there is a trade-off: This cheapens the cost of exploring around - // the beginning of the graph (in one direction) when the vectors are - // unequal in length. So we don't steal *all* the cost. - // You can view this as a form of A*, using an admissable heuristic that has - // been re-cast as a cost function that can be used in Dijkstra. - const UInt64 kTowardsGoalCost = 50003; - const UInt64 kAwayFromGoalCost = 2 * kBaseCost - kTowardsGoalCost; - - EditSearchMap node_map(left.size() + 1, right.size() + 1); - EditHeap heap; - heap.push(EditHeapEntry(0, 0, 0, kReplaceParent)); - - while (!heap.empty()) { - const EditHeapEntry current_entry = heap.top(); - heap.pop(); - - UInt32 left_pos = current_entry.left; - UInt32 right_pos = current_entry.right; - unsigned char& current_state = node_map.insert(left_pos, right_pos); - if (current_state != kUnsettled) { - // Node was already settled by a previous entry in the priority queue, - // this is a suboptimal path that should be ignored. - continue; - } - current_state = current_entry.state; - - if (left_pos == left.size() && right_pos == right.size()) { - // This is the normal exit point; if we terminate due to the heap being - // empty, we'll fail a check later. - break; - } - - // Special case: Since the cost of a match is zero, we can immediately - // settle the new node without putting it in the queue, since nothing can - // have a smaller cost than it. Furthermore, we don't need to relax the - // other two edges, since we know we don't need them: Any path from this - // node that would use them has an path via the match that is at least as - // cheap. Together, this means we can loop here until we stop matching. - while (left_pos < left.size() && right_pos < right.size() && - left[left_pos] == right[right_pos]) { - left_pos++; - right_pos++; - unsigned char& fast_forward_state = node_map.insert(left_pos, right_pos); - if (fast_forward_state != kUnsettled) { - // The search reached around and settled this node before settling the - // base node. This means we're completely done with this iteration; - // abort to the outer loop. - goto outer_loop_bottom; - // Otherwise, when can settle this node, even if it was created from - // another state - we know the cost of settling it now is optimal. - } - fast_forward_state = kMatchParent; - } - - // Relax adjacent nodes. We have no way to find or lower the cost of - // existing entries in the heap, so we just push new entries and throw - // them out at the top if the node is already settled. We *could* check to - // see if they're already settled before pushing, but it turns out to be - // ~not any faster, and more complicated to do so. - // - // If we're at an edge, there's only one node to relax. - if (left_pos >= left.size()) { - if (right_pos >= right.size()) { - break; // Can happen due to the fast-path loop above. - } - heap.push(EditHeapEntry(left_pos, right_pos + 1, - current_entry.cost + kTowardsGoalCost, - kAddParent)); - continue; - } - if (right_pos >= right.size()) { - heap.push(EditHeapEntry(left_pos + 1, right_pos, - current_entry.cost + kTowardsGoalCost, - kRemoveParent)); - continue; - } - // General case: Relax 3 edges. - heap.push(EditHeapEntry( - left_pos, right_pos + 1, - current_entry.cost + (right.size() + left_pos > right_pos + left.size() - ? kTowardsGoalCost - : kAwayFromGoalCost), - kAddParent)); - heap.push(EditHeapEntry( - left_pos + 1, right_pos, - current_entry.cost + (right.size() + left_pos < right_pos + left.size() - ? kTowardsGoalCost - : kAwayFromGoalCost), - kRemoveParent)); - heap.push(EditHeapEntry(left_pos + 1, right_pos + 1, - current_entry.cost + kReplaceCost, kReplaceParent)); - outer_loop_bottom : {} // Need the curlies to form a statement. - } - - // Reconstruct the best path. We do it in reverse order. - std::vector best_path; - UInt32 left_pos = left.size(); - UInt32 right_pos = right.size(); - while (left_pos != 0 || right_pos != 0) { - GTEST_CHECK_(left_pos <= left.size() && right_pos <= right.size()); - // The node must already exist, but if it somehow doesn't, it will be - // added as kUnsettled, which will crash below. - const unsigned char state = node_map.insert(left_pos, right_pos); - switch (state) { - case kAddParent: - right_pos--; - break; - case kRemoveParent: - left_pos--; - break; - case kMatchParent: - case kReplaceParent: - left_pos--; - right_pos--; - break; - default: - GTEST_LOG_(FATAL) << "Unsettled node at " << left_pos << "," - << right_pos; - } - best_path.push_back(static_cast(state - 1)); - } - std::reverse(best_path.begin(), best_path.end()); - if (memory_usage != NULL) { - *memory_usage = node_map.MemoryUsage(); - } - return best_path; -} - -namespace { - -// Helper class to convert string into ids with deduplication. -class InternalStrings { - public: - size_t GetId(const std::string* str) { - IdMap::iterator it = ids_.find(str); - if (it != ids_.end()) return it->second; - size_t id = ids_.size(); - return ids_[str] = id; - } - - private: - struct IdMapCmp { - bool operator()(const std::string* first, const std::string* second) const { - return *first < *second; - } - }; - typedef std::map IdMap; - IdMap ids_; -}; - -} // namespace - -std::vector CalculateOptimalEdits( - const std::vector& left, - const std::vector& right) { - std::vector left_ids, right_ids; - { - InternalStrings intern_table; - for (size_t i = 0; i < left.size(); ++i) { - left_ids.push_back(intern_table.GetId(&left[i])); - } - for (size_t i = 0; i < right.size(); ++i) { - right_ids.push_back(intern_table.GetId(&right[i])); - } - } - return CalculateOptimalEdits(left_ids, right_ids); -} - -namespace { - -// Helper class that holds the state for one hunk and prints it out to the -// stream. -// It reorders adds/removes when possible to group all removes before all -// adds. It also adds the hunk header before printing into the stream. -class Hunk { - public: - Hunk(size_t left_start, size_t right_start) - : left_start_(left_start), - right_start_(right_start), - adds_(), - removes_(), - common_() {} - - void PushLine(char edit, const char* line) { - switch (edit) { - case ' ': - ++common_; - FlushEdits(); - hunk_.push_back(std::make_pair(' ', line)); - break; - case '-': - ++removes_; - hunk_removes_.push_back(std::make_pair('-', line)); - break; - case '+': - ++adds_; - hunk_adds_.push_back(std::make_pair('+', line)); - break; - } - } - - void PrintTo(std::ostream* os) { - PrintHeader(os); - FlushEdits(); - for (std::list >::const_iterator it = - hunk_.begin(); - it != hunk_.end(); ++it) { - *os << it->first << it->second << "\n"; - } - } - - bool has_edits() const { return adds_ || removes_; } - - private: - void FlushEdits() { - hunk_.splice(hunk_.end(), hunk_removes_); - hunk_.splice(hunk_.end(), hunk_adds_); - } - - // Print a unified diff header for one hunk. - // The format is - // "@@ -, +, @@" - // where the left/right parts are omitted if unnecessary. - void PrintHeader(std::ostream* ss) const { - *ss << "@@ "; - if (removes_) { - *ss << "-" << left_start_ << "," << (removes_ + common_); - } - if (removes_ && adds_) { - *ss << " "; - } - if (adds_) { - *ss << "+" << right_start_ << "," << (adds_ + common_); - } - *ss << " @@\n"; - } - - size_t left_start_, right_start_; - size_t adds_, removes_, common_; - std::list > hunk_, hunk_adds_, hunk_removes_; -}; - -} // namespace - -// Create a list of diff hunks in Unified diff format. -// Each hunk has a header generated by PrintHeader above plus a body with -// lines prefixed with ' ' for no change, '-' for deletion and '+' for -// addition. -// 'context' represents the desired unchanged prefix/suffix around the diff. -// If two hunks are close enough that their contexts overlap, then they are -// joined into one hunk. -std::string CreateUnifiedDiff(const std::vector& left, - const std::vector& right, - size_t context) { - const std::vector edits = CalculateOptimalEdits(left, right); - - size_t l_i = 0, r_i = 0, edit_i = 0; - std::stringstream ss; - while (edit_i < edits.size()) { - // Find first edit. - while (edit_i < edits.size() && edits[edit_i] == kEditMatch) { - ++l_i; - ++r_i; - ++edit_i; - } - - // Find the first line to include in the hunk. - const size_t prefix_context = std::min(l_i, context); - Hunk hunk(l_i - prefix_context + 1, r_i - prefix_context + 1); - for (size_t i = prefix_context; i > 0; --i) { - hunk.PushLine(' ', left[l_i - i].c_str()); - } - - // Iterate the edits until we found enough suffix for the hunk or the input - // is over. - size_t n_suffix = 0; - for (; edit_i < edits.size(); ++edit_i) { - if (n_suffix >= context) { - // Continue only if the next hunk is very close. - std::vector::const_iterator it = edits.begin() + edit_i; - while (it != edits.end() && *it == kEditMatch) ++it; - if (it == edits.end() || (it - edits.begin()) - edit_i >= context) { - // There is no next edit or it is too far away. - break; - } - } - - EditType edit = edits[edit_i]; - // Reset count when a non match is found. - n_suffix = edit == kEditMatch ? n_suffix + 1 : 0; - - if (edit == kEditMatch || edit == kEditRemove || edit == kEditReplace) { - hunk.PushLine(edit == kEditMatch ? ' ' : '-', left[l_i].c_str()); - } - if (edit == kEditAdd || edit == kEditReplace) { - hunk.PushLine('+', right[r_i].c_str()); - } - - // Advance indices, depending on edit type. - l_i += edit != kEditAdd; - r_i += edit != kEditRemove; - } - - if (!hunk.has_edits()) { - // We are done. We don't want this hunk. - break; - } - - hunk.PrintTo(&ss); - } - return ss.str(); -} - -} // namespace internal -} // namespace testing diff --git a/googletest/src/gtest.cc b/googletest/src/gtest.cc index ba706c1e..97966377 100644 --- a/googletest/src/gtest.cc +++ b/googletest/src/gtest.cc @@ -46,6 +46,7 @@ #include #include #include +#include #include #include // NOLINT #include @@ -1067,6 +1068,246 @@ AssertionResult AssertionFailure(const Message& message) { namespace internal { +namespace edit_distance { +std::vector CalculateOptimalEdits(const std::vector& left, + const std::vector& right) { + std::vector > costs( + left.size() + 1, std::vector(right.size() + 1)); + std::vector > best_move( + left.size() + 1, std::vector(right.size() + 1)); + + // Populate for empty right. + for (size_t l_i = 0; l_i < costs.size(); ++l_i) { + costs[l_i][0] = static_cast(l_i); + best_move[l_i][0] = kRemove; + } + // Populate for empty left. + for (size_t r_i = 1; r_i < costs[0].size(); ++r_i) { + costs[0][r_i] = static_cast(r_i); + best_move[0][r_i] = kAdd; + } + + for (size_t l_i = 0; l_i < left.size(); ++l_i) { + for (size_t r_i = 0; r_i < right.size(); ++r_i) { + if (left[l_i] == right[r_i]) { + // Found a match. Consume it. + costs[l_i + 1][r_i + 1] = costs[l_i][r_i]; + best_move[l_i + 1][r_i + 1] = kMatch; + continue; + } + + const double add = costs[l_i + 1][r_i]; + const double remove = costs[l_i][r_i + 1]; + const double replace = costs[l_i][r_i]; + if (add < remove && add < replace) { + costs[l_i + 1][r_i + 1] = add + 1; + best_move[l_i + 1][r_i + 1] = kAdd; + } else if (remove < add && remove < replace) { + costs[l_i + 1][r_i + 1] = remove + 1; + best_move[l_i + 1][r_i + 1] = kRemove; + } else { + // We make replace a little more expensive than add/remove to lower + // their priority. + costs[l_i + 1][r_i + 1] = replace + 1.00001; + best_move[l_i + 1][r_i + 1] = kReplace; + } + } + } + + // Reconstruct the best path. We do it in reverse order. + std::vector best_path; + for (size_t l_i = left.size(), r_i = right.size(); l_i > 0 || r_i > 0;) { + EditType move = best_move[l_i][r_i]; + best_path.push_back(move); + l_i -= move != kAdd; + r_i -= move != kRemove; + } + std::reverse(best_path.begin(), best_path.end()); + return best_path; +} + +namespace { + +// Helper class to convert string into ids with deduplication. +class InternalStrings { + public: + size_t GetId(const std::string& str) { + IdMap::iterator it = ids_.find(str); + if (it != ids_.end()) return it->second; + size_t id = ids_.size(); + return ids_[str] = id; + } + + private: + typedef std::map IdMap; + IdMap ids_; +}; + +} // namespace + +std::vector CalculateOptimalEdits( + const std::vector& left, + const std::vector& right) { + std::vector left_ids, right_ids; + { + InternalStrings intern_table; + for (size_t i = 0; i < left.size(); ++i) { + left_ids.push_back(intern_table.GetId(left[i])); + } + for (size_t i = 0; i < right.size(); ++i) { + right_ids.push_back(intern_table.GetId(right[i])); + } + } + return CalculateOptimalEdits(left_ids, right_ids); +} + +namespace { + +// Helper class that holds the state for one hunk and prints it out to the +// stream. +// It reorders adds/removes when possible to group all removes before all +// adds. It also adds the hunk header before printint into the stream. +class Hunk { + public: + Hunk(size_t left_start, size_t right_start) + : left_start_(left_start), + right_start_(right_start), + adds_(), + removes_(), + common_() {} + + void PushLine(char edit, const char* line) { + switch (edit) { + case ' ': + ++common_; + FlushEdits(); + hunk_.push_back(std::make_pair(' ', line)); + break; + case '-': + ++removes_; + hunk_removes_.push_back(std::make_pair('-', line)); + break; + case '+': + ++adds_; + hunk_adds_.push_back(std::make_pair('+', line)); + break; + } + } + + void PrintTo(std::ostream* os) { + PrintHeader(os); + FlushEdits(); + for (std::list >::const_iterator it = + hunk_.begin(); + it != hunk_.end(); ++it) { + *os << it->first << it->second << "\n"; + } + } + + bool has_edits() const { return adds_ || removes_; } + + private: + void FlushEdits() { + hunk_.splice(hunk_.end(), hunk_removes_); + hunk_.splice(hunk_.end(), hunk_adds_); + } + + // Print a unified diff header for one hunk. + // The format is + // "@@ -, +, @@" + // where the left/right parts are omitted if unnecessary. + void PrintHeader(std::ostream* ss) const { + *ss << "@@ "; + if (removes_) { + *ss << "-" << left_start_ << "," << (removes_ + common_); + } + if (removes_ && adds_) { + *ss << " "; + } + if (adds_) { + *ss << "+" << right_start_ << "," << (adds_ + common_); + } + *ss << " @@\n"; + } + + size_t left_start_, right_start_; + size_t adds_, removes_, common_; + std::list > hunk_, hunk_adds_, hunk_removes_; +}; + +} // namespace + +// Create a list of diff hunks in Unified diff format. +// Each hunk has a header generated by PrintHeader above plus a body with +// lines prefixed with ' ' for no change, '-' for deletion and '+' for +// addition. +// 'context' represents the desired unchanged prefix/suffix around the diff. +// If two hunks are close enough that their contexts overlap, then they are +// joined into one hunk. +std::string CreateUnifiedDiff(const std::vector& left, + const std::vector& right, + size_t context) { + const std::vector edits = CalculateOptimalEdits(left, right); + + size_t l_i = 0, r_i = 0, edit_i = 0; + std::stringstream ss; + while (edit_i < edits.size()) { + // Find first edit. + while (edit_i < edits.size() && edits[edit_i] == kMatch) { + ++l_i; + ++r_i; + ++edit_i; + } + + // Find the first line to include in the hunk. + const size_t prefix_context = std::min(l_i, context); + Hunk hunk(l_i - prefix_context + 1, r_i - prefix_context + 1); + for (size_t i = prefix_context; i > 0; --i) { + hunk.PushLine(' ', left[l_i - i].c_str()); + } + + // Iterate the edits until we found enough suffix for the hunk or the input + // is over. + size_t n_suffix = 0; + for (; edit_i < edits.size(); ++edit_i) { + if (n_suffix >= context) { + // Continue only if the next hunk is very close. + std::vector::const_iterator it = edits.begin() + edit_i; + while (it != edits.end() && *it == kMatch) ++it; + if (it == edits.end() || (it - edits.begin()) - edit_i >= context) { + // There is no next edit or it is too far away. + break; + } + } + + EditType edit = edits[edit_i]; + // Reset count when a non match is found. + n_suffix = edit == kMatch ? n_suffix + 1 : 0; + + if (edit == kMatch || edit == kRemove || edit == kReplace) { + hunk.PushLine(edit == kMatch ? ' ' : '-', left[l_i].c_str()); + } + if (edit == kAdd || edit == kReplace) { + hunk.PushLine('+', right[r_i].c_str()); + } + + // Advance indices, depending on edit type. + l_i += edit != kAdd; + r_i += edit != kRemove; + } + + if (!hunk.has_edits()) { + // We are done. We don't want this hunk. + break; + } + + hunk.PrintTo(&ss); + } + return ss.str(); +} + +} // namespace edit_distance + namespace { // The string representation of the values received in EqFailure() are already @@ -1138,7 +1379,8 @@ AssertionResult EqFailure(const char* lhs_expression, const std::vector rhs_lines = SplitEscapedString(rhs_value); if (lhs_lines.size() > 1 || rhs_lines.size() > 1) { - msg << "\nWith diff:\n" << CreateUnifiedDiff(lhs_lines, rhs_lines); + msg << "\nWith diff:\n" + << edit_distance::CreateUnifiedDiff(lhs_lines, rhs_lines); } } diff --git a/googletest/test/gtest_unittest.cc b/googletest/test/gtest_unittest.cc index 7f6a2ac4..f7213fbf 100644 --- a/googletest/test/gtest_unittest.cc +++ b/googletest/test/gtest_unittest.cc @@ -215,7 +215,6 @@ using testing::GTEST_FLAG(stream_result_to); using testing::GTEST_FLAG(throw_on_failure); using testing::IsNotSubstring; using testing::IsSubstring; -using testing::kMaxStackTraceDepth; using testing::Message; using testing::ScopedFakeTestPartResultReporter; using testing::StaticAssertTypeEq; @@ -235,18 +234,16 @@ using testing::internal::AlwaysTrue; using testing::internal::AppendUserMessage; using testing::internal::ArrayAwareFind; using testing::internal::ArrayEq; -using testing::internal::CalculateOptimalEdits; using testing::internal::CodePointToUtf8; using testing::internal::CompileAssertTypesEqual; using testing::internal::CopyArray; using testing::internal::CountIf; -using testing::internal::CreateUnifiedDiff; -using testing::internal::EditType; using testing::internal::EqFailure; using testing::internal::FloatingPoint; using testing::internal::ForEach; using testing::internal::FormatEpochTimeInMillisAsIso8601; using testing::internal::FormatTimeInMillisAsSeconds; +using testing::internal::GTestFlagSaver; using testing::internal::GetCurrentOsStackTraceExceptTop; using testing::internal::GetElementOr; using testing::internal::GetNextRandomSeed; @@ -255,7 +252,6 @@ using testing::internal::GetTestTypeId; using testing::internal::GetTimeInMillis; using testing::internal::GetTypeId; using testing::internal::GetUnitTestImpl; -using testing::internal::GTestFlagSaver; using testing::internal::ImplicitlyConvertible; using testing::internal::Int32; using testing::internal::Int32FromEnvOrDie; @@ -263,8 +259,6 @@ using testing::internal::IsAProtocolMessage; using testing::internal::IsContainer; using testing::internal::IsContainerTest; using testing::internal::IsNotContainer; -using testing::internal::kMaxRandomSeed; -using testing::internal::kTestTypeIdInGoogleTest; using testing::internal::NativeArray; using testing::internal::OsStackTraceGetter; using testing::internal::OsStackTraceGetterInterface; @@ -286,6 +280,12 @@ using testing::internal::TestResultAccessor; using testing::internal::UInt32; using testing::internal::UnitTestImpl; using testing::internal::WideStringToUtf8; +using testing::internal::edit_distance::CalculateOptimalEdits; +using testing::internal::edit_distance::CreateUnifiedDiff; +using testing::internal::edit_distance::EditType; +using testing::internal::kMaxRandomSeed; +using testing::internal::kTestTypeIdInGoogleTest; +using testing::kMaxStackTraceDepth; #if GTEST_HAS_STREAM_REDIRECTION using testing::internal::CaptureStdout; @@ -3517,14 +3517,14 @@ TEST(EditDistance, TestCases) { {__LINE__, "ABCD", "abcd", "////", "@@ -1,4 +1,4 @@\n-A\n-B\n-C\n-D\n+a\n+b\n+c\n+d\n"}, // Path finding. - {__LINE__, "ABCDEFGH", "ABXEGH1", " /- - +", + {__LINE__, "ABCDEFGH", "ABXEGH1", " -/ - +", "@@ -1,8 +1,7 @@\n A\n B\n-C\n-D\n+X\n E\n-F\n G\n H\n+1\n"}, - {__LINE__, "AAAABCCCC", "ABABCDCDC", " -/ + / ", - "@@ -1,9 +1,9 @@\n A\n-A\n-A\n+B\n A\n B\n C\n+D\n C\n-C\n+D\n C\n"}, - {__LINE__, "ABCDE", "BCDCD", "- /+", + {__LINE__, "AAAABCCCC", "ABABCDCDC", "- / + / ", + "@@ -1,9 +1,9 @@\n-A\n A\n-A\n+B\n A\n B\n C\n+D\n C\n-C\n+D\n C\n"}, + {__LINE__, "ABCDE", "BCDCD", "- +/", "@@ -1,5 +1,5 @@\n-A\n B\n C\n D\n-E\n+C\n+D\n"}, - {__LINE__, "ABCDEFGHIJKL", "BGDCDEFGJKLJK", "- ++ -- ++", - "@@ -1,4 +1,5 @@\n-A\n B\n+G\n+D\n C\n D\n" + {__LINE__, "ABCDEFGHIJKL", "BCDCDEFGJKLJK", "- ++ -- ++", + "@@ -1,4 +1,5 @@\n-A\n B\n+C\n+D\n C\n D\n" "@@ -6,7 +7,7 @@\n F\n G\n-H\n-I\n J\n K\n L\n+J\n+K\n"}, {}}; for (const Case* c = kCases; c->left; ++c) { @@ -3542,57 +3542,6 @@ TEST(EditDistance, TestCases) { } } -// Tests that we can run CalculateOptimalEdits for a large vector, i.e. we can -// compute diffs for large strings. -TEST(EditDistance, LargeVectorWithDiffs) { - const int kSize = 300000; - std::vector left; - std::vector right; - std::vector expected(kSize, testing::internal::kEditMatch); - - left.reserve(kSize); - right.reserve(kSize); - for (int i = 0; i < kSize; ++i) { - // Make the contents of the vectors unique. This greatly speeds up - // the algorithm, since it doesn't spend time finding matches for - // different alignments. - left.push_back(i); - right.push_back(i); - } - - for (int i = 0; i < 10; ++i) { - right[i] = kSize + i; - expected[i] = testing::internal::kEditReplace; - right[kSize - i - 1] = kSize * 2 + i; - expected[kSize - i - 1] = testing::internal::kEditReplace; - } - size_t memory_usage; - EXPECT_EQ(CalculateOptimalEdits(left, right, &memory_usage), expected); - EXPECT_GT(memory_usage, kSize); - EXPECT_LT(memory_usage, kSize * 2); -} - -// Tests that we can run CalculateOptimalEdits for two vectors N and M, where -// M = N plus additional junk at the end. The current algorithm only does O(M) -// "real" work in this case, but allocates some extra memory. We test that this -// is still fast enough for common cases, and we aren't allocating an -// excessive amount of extra memory. -TEST(EditDistance, LargeVectorWithTrailingJunk) { - const int kSize = 200000; - const int kAdditionalSize = 2000; - std::vector left(kSize, 0); - std::vector right(kSize + kAdditionalSize, 0); - std::vector expected(kSize + kAdditionalSize, - testing::internal::kEditMatch); - for (int i = 0; i < kAdditionalSize; ++i) { - expected[i + kSize] = testing::internal::kEditAdd; - } - size_t memory_usage; - EXPECT_EQ(CalculateOptimalEdits(left, right, &memory_usage), expected); - EXPECT_GT(memory_usage, kSize); - EXPECT_LT(memory_usage, 6000000); -} - // Tests EqFailure(), used for implementing *EQ* assertions. TEST(AssertionTest, EqFailure) { const std::string foo_val("5"), bar_val("6"); From 03867b5389516a0f185af52672cf5472fa0c159c Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 28 Aug 2018 09:40:18 -0400 Subject: [PATCH 04/16] Googletest export Add the possibility of specifying the name in type parameterized tests. Similar to how the last parameter of INSTANTIATE_TEST_CASE_P allows to override the name for (non-type) parametrized tests, this adds the possibility of adding a parameter to INSTANTIATE_TYPED_TEST_CASE_P. The argument has to be a class, which contains a static templated function GetName(int), returning the name for type T. PiperOrigin-RevId: 210532231 --- googletest/include/gtest/gtest-typed-test.h | 102 ++++++++++++------ .../include/gtest/internal/gtest-internal.h | 84 +++++++++++---- googletest/test/googletest-output-test_.cc | 38 +++++++ googletest/test/gtest-typed-test_test.cc | 74 +++++++++++++ 4 files changed, 246 insertions(+), 52 deletions(-) diff --git a/googletest/include/gtest/gtest-typed-test.h b/googletest/include/gtest/gtest-typed-test.h index 61d8907e..29b08599 100644 --- a/googletest/include/gtest/gtest-typed-test.h +++ b/googletest/include/gtest/gtest-typed-test.h @@ -83,6 +83,24 @@ TYPED_TEST(FooTest, DoesBlah) { TYPED_TEST(FooTest, HasPropertyA) { ... } +// TYPED_TEST_CASE takes an optional third argument which allows to specify a +// class that generates custom test name suffixes based on the type. This should +// be a class which has a static template function GetName(int index) returning +// a string for each type. The provided integer index equals the index of the +// type in the provided type list. In many cases the index can be ignored. +// +// For example: +// class MyTypeNames { +// public: +// template +// static std::string GetName(int) { +// if (std::is_same()) return "char"; +// if (std::is_same()) return "int"; +// if (std::is_same()) return "unsigned_int"; +// } +// }; +// TYPED_TEST_CASE(FooTest, MyTypes, MyTypeNames); + #endif // 0 // Type-parameterized tests are abstract test patterns parameterized @@ -144,6 +162,11 @@ INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); // If the type list contains only one type, you can write that type // directly without Types<...>: // INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, int); +// +// Similar to the optional argument of TYPED_TEST_CASE above, +// INSTANTIATE_TEST_CASE_P takes an optional fourth argument which allows to +// generate custom names. +// INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes, MyTypeNames); #endif // 0 @@ -160,32 +183,45 @@ INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); // given test case. # define GTEST_TYPE_PARAMS_(TestCaseName) gtest_type_params_##TestCaseName##_ +// Expands to the name of the typedef for the NameGenerator, responsible for +// creating the suffixes of the name. +#define GTEST_NAME_GENERATOR_(TestCaseName) \ + gtest_type_params_##TestCaseName##_NameGenerator + // The 'Types' template argument below must have spaces around it // since some compilers may choke on '>>' when passing a template // instance (e.g. Types) -# define TYPED_TEST_CASE(CaseName, Types) \ - typedef ::testing::internal::TypeList< Types >::type \ - GTEST_TYPE_PARAMS_(CaseName) +# define TYPED_TEST_CASE(CaseName, Types, ...) \ + typedef ::testing::internal::TypeList< Types >::type GTEST_TYPE_PARAMS_( \ + CaseName); \ + typedef ::testing::internal::NameGeneratorSelector<__VA_ARGS__>::type \ + GTEST_NAME_GENERATOR_(CaseName) -# define TYPED_TEST(CaseName, TestName) \ - template \ - class GTEST_TEST_CLASS_NAME_(CaseName, TestName) \ - : public CaseName { \ - private: \ - typedef CaseName TestFixture; \ - typedef gtest_TypeParam_ TypeParam; \ - virtual void TestBody(); \ - }; \ - bool gtest_##CaseName##_##TestName##_registered_ GTEST_ATTRIBUTE_UNUSED_ = \ - ::testing::internal::TypeParameterizedTest< \ - CaseName, \ - ::testing::internal::TemplateSel< \ - GTEST_TEST_CLASS_NAME_(CaseName, TestName)>, \ - GTEST_TYPE_PARAMS_(CaseName)>::Register(\ - "", ::testing::internal::CodeLocation(__FILE__, __LINE__), \ - #CaseName, #TestName, 0); \ - template \ - void GTEST_TEST_CLASS_NAME_(CaseName, TestName)::TestBody() +# define TYPED_TEST(CaseName, TestName) \ + template \ + class GTEST_TEST_CLASS_NAME_(CaseName, TestName) \ + : public CaseName { \ + private: \ + typedef CaseName TestFixture; \ + typedef gtest_TypeParam_ TypeParam; \ + virtual void TestBody(); \ + }; \ + bool gtest_##CaseName##_##TestName##_registered_ GTEST_ATTRIBUTE_UNUSED_ = \ + ::testing::internal::TypeParameterizedTest< \ + CaseName, \ + ::testing::internal::TemplateSel, \ + GTEST_TYPE_PARAMS_( \ + CaseName)>::Register("", \ + ::testing::internal::CodeLocation( \ + __FILE__, __LINE__), \ + #CaseName, #TestName, 0, \ + ::testing::internal::GenerateNames< \ + GTEST_NAME_GENERATOR_(CaseName), \ + GTEST_TYPE_PARAMS_(CaseName)>()); \ + template \ + void GTEST_TEST_CLASS_NAME_(CaseName, \ + TestName)::TestBody() #endif // GTEST_HAS_TYPED_TEST @@ -250,15 +286,19 @@ INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); // The 'Types' template argument below must have spaces around it // since some compilers may choke on '>>' when passing a template // instance (e.g. Types) -# define INSTANTIATE_TYPED_TEST_CASE_P(Prefix, CaseName, Types) \ - bool gtest_##Prefix##_##CaseName GTEST_ATTRIBUTE_UNUSED_ = \ - ::testing::internal::TypeParameterizedTestCase::type>::Register(\ - #Prefix, \ - ::testing::internal::CodeLocation(__FILE__, __LINE__), \ - >EST_TYPED_TEST_CASE_P_STATE_(CaseName), \ - #CaseName, GTEST_REGISTERED_TEST_NAMES_(CaseName)) +# define INSTANTIATE_TYPED_TEST_CASE_P(Prefix, CaseName, Types, ...) \ + bool gtest_##Prefix##_##CaseName GTEST_ATTRIBUTE_UNUSED_ = \ + ::testing::internal::TypeParameterizedTestCase< \ + CaseName, GTEST_CASE_NAMESPACE_(CaseName)::gtest_AllTests_, \ + ::testing::internal::TypeList< Types >::type>:: \ + Register(#Prefix, \ + ::testing::internal::CodeLocation(__FILE__, __LINE__), \ + >EST_TYPED_TEST_CASE_P_STATE_(CaseName), #CaseName, \ + GTEST_REGISTERED_TEST_NAMES_(CaseName), \ + ::testing::internal::GenerateNames< \ + ::testing::internal::NameGeneratorSelector< \ + __VA_ARGS__>::type, \ + ::testing::internal::TypeList< Types >::type>()) #endif // GTEST_HAS_TYPED_TEST_P diff --git a/googletest/include/gtest/internal/gtest-internal.h b/googletest/include/gtest/internal/gtest-internal.h index 9593a45a..b762f61f 100644 --- a/googletest/include/gtest/internal/gtest-internal.h +++ b/googletest/include/gtest/internal/gtest-internal.h @@ -606,6 +606,37 @@ inline std::string GetPrefixUntilComma(const char* str) { void SplitString(const ::std::string& str, char delimiter, ::std::vector< ::std::string>* dest); +// The default argument to the template below for the case when the user does +// not provide a name generator. +struct DefaultNameGenerator { + template + static std::string GetName(int i) { + return StreamableToString(i); + } +}; + +template +struct NameGeneratorSelector { + typedef Provided type; +}; + +template +void GenerateNamesRecursively(Types0, std::vector*, int) {} + +template +void GenerateNamesRecursively(Types, std::vector* result, int i) { + result->push_back(NameGenerator::template GetName(i)); + GenerateNamesRecursively(typename Types::Tail(), result, + i + 1); +} + +template +std::vector GenerateNames() { + std::vector result; + GenerateNamesRecursively(Types(), &result, 0); + return result; +} + // TypeParameterizedTest::Register() // registers a list of type-parameterized tests with Google Test. The // return value is insignificant - we just need to return something @@ -620,10 +651,10 @@ class TypeParameterizedTest { // specified in INSTANTIATE_TYPED_TEST_CASE_P(Prefix, TestCase, // Types). Valid values for 'index' are [0, N - 1] where N is the // length of Types. - static bool Register(const char* prefix, - const CodeLocation& code_location, - const char* case_name, const char* test_names, - int index) { + static bool Register(const char* prefix, const CodeLocation& code_location, + const char* case_name, const char* test_names, int index, + const std::vector& type_names = + GenerateNames()) { typedef typename Types::Head Type; typedef Fixture FixtureClass; typedef typename GTEST_BIND_(TestSel, Type) TestClass; @@ -631,20 +662,23 @@ class TypeParameterizedTest { // First, registers the first type-parameterized test in the type // list. MakeAndRegisterTestInfo( - (std::string(prefix) + (prefix[0] == '\0' ? "" : "/") + case_name + "/" - + StreamableToString(index)).c_str(), + (std::string(prefix) + (prefix[0] == '\0' ? "" : "/") + case_name + + "/" + type_names[index]) + .c_str(), StripTrailingSpaces(GetPrefixUntilComma(test_names)).c_str(), GetTypeName().c_str(), NULL, // No value parameter. - code_location, - GetTypeId(), - TestClass::SetUpTestCase, - TestClass::TearDownTestCase, - new TestFactoryImpl); + code_location, GetTypeId(), TestClass::SetUpTestCase, + TestClass::TearDownTestCase, new TestFactoryImpl); // Next, recurses (at compile time) with the tail of the type list. - return TypeParameterizedTest - ::Register(prefix, code_location, case_name, test_names, index + 1); + return TypeParameterizedTest::Register(prefix, + code_location, + case_name, + test_names, + index + 1, + type_names); } }; @@ -654,7 +688,9 @@ class TypeParameterizedTest { public: static bool Register(const char* /*prefix*/, const CodeLocation&, const char* /*case_name*/, const char* /*test_names*/, - int /*index*/) { + int /*index*/, + const std::vector& = + std::vector() /*type_names*/) { return true; } }; @@ -667,8 +703,10 @@ template class TypeParameterizedTestCase { public: static bool Register(const char* prefix, CodeLocation code_location, - const TypedTestCasePState* state, - const char* case_name, const char* test_names) { + const TypedTestCasePState* state, const char* case_name, + const char* test_names, + const std::vector& type_names = + GenerateNames()) { std::string test_name = StripTrailingSpaces( GetPrefixUntilComma(test_names)); if (!state->TestExists(test_name)) { @@ -685,12 +723,14 @@ class TypeParameterizedTestCase { // First, register the first test in 'Test' for each type in 'Types'. TypeParameterizedTest::Register( - prefix, test_location, case_name, test_names, 0); + prefix, test_location, case_name, test_names, 0, type_names); // Next, recurses (at compile time) with the tail of the test list. - return TypeParameterizedTestCase - ::Register(prefix, code_location, state, - case_name, SkipComma(test_names)); + return TypeParameterizedTestCase::Register(prefix, code_location, + state, case_name, + SkipComma(test_names), + type_names); } }; @@ -700,7 +740,9 @@ class TypeParameterizedTestCase { public: static bool Register(const char* /*prefix*/, const CodeLocation&, const TypedTestCasePState* /*state*/, - const char* /*case_name*/, const char* /*test_names*/) { + const char* /*case_name*/, const char* /*test_names*/, + const std::vector& = + std::vector() /*type_names*/) { return true; } }; diff --git a/googletest/test/googletest-output-test_.cc b/googletest/test/googletest-output-test_.cc index 3860cf45..d6f7e0ba 100644 --- a/googletest/test/googletest-output-test_.cc +++ b/googletest/test/googletest-output-test_.cc @@ -801,6 +801,28 @@ TYPED_TEST(TypedTest, Failure) { EXPECT_EQ(1, TypeParam()) << "Expected failure"; } +typedef testing::Types TypesForTestWithNames; + +template +class TypedTestWithNames : public testing::Test {}; + +class TypedTestNames { + public: + template + static std::string GetName(int i) { + if (testing::internal::IsSame::value) + return std::string("char_") + ::testing::PrintToString(i); + if (testing::internal::IsSame::value) + return std::string("int_") + ::testing::PrintToString(i); + } +}; + +TYPED_TEST_CASE(TypedTestWithNames, TypesForTestWithNames, TypedTestNames); + +TYPED_TEST(TypedTestWithNames, Success) {} + +TYPED_TEST(TypedTestWithNames, Failure) { FAIL(); } + #endif // GTEST_HAS_TYPED_TEST // This #ifdef block tests the output of type-parameterized tests. @@ -825,6 +847,22 @@ REGISTER_TYPED_TEST_CASE_P(TypedTestP, Success, Failure); typedef testing::Types UnsignedTypes; INSTANTIATE_TYPED_TEST_CASE_P(Unsigned, TypedTestP, UnsignedTypes); +class TypedTestPNames { + public: + template + static std::string GetName(int i) { + if (testing::internal::IsSame::value) { + return std::string("unsigned_char_") + ::testing::PrintToString(i); + } + if (testing::internal::IsSame::value) { + return std::string("unsigned_int_") + ::testing::PrintToString(i); + } + } +}; + +INSTANTIATE_TYPED_TEST_CASE_P(UnsignedCustomName, TypedTestP, UnsignedTypes, + TypedTestPNames); + #endif // GTEST_HAS_TYPED_TEST_P #if GTEST_HAS_DEATH_TEST diff --git a/googletest/test/gtest-typed-test_test.cc b/googletest/test/gtest-typed-test_test.cc index eddb52bf..e9eed639 100644 --- a/googletest/test/gtest-typed-test_test.cc +++ b/googletest/test/gtest-typed-test_test.cc @@ -165,6 +165,40 @@ TYPED_TEST(NumericTest, DefaultIsZero) { } // namespace library1 +// Tests that custom names work. +template +class TypedTestWithNames : public Test {}; + +class TypedTestNames { + public: + template + static std::string GetName(int i) { + if (testing::internal::IsSame::value) { + return std::string("char_") + ::testing::PrintToString(i); + } + if (testing::internal::IsSame::value) { + return std::string("int_") + ::testing::PrintToString(i); + } + } +}; + +TYPED_TEST_CASE(TypedTestWithNames, TwoTypes, TypedTestNames); + +TYPED_TEST(TypedTestWithNames, TestCaseName) { + if (testing::internal::IsSame::value) { + EXPECT_STREQ(::testing::UnitTest::GetInstance() + ->current_test_info() + ->test_case_name(), + "TypedTestWithNames/char_0"); + } + if (testing::internal::IsSame::value) { + EXPECT_STREQ(::testing::UnitTest::GetInstance() + ->current_test_info() + ->test_case_name(), + "TypedTestWithNames/int_1"); + } +} + #endif // GTEST_HAS_TYPED_TEST // This #ifdef block tests type-parameterized tests. @@ -265,6 +299,46 @@ REGISTER_TYPED_TEST_CASE_P(DerivedTest, typedef Types MyTwoTypes; INSTANTIATE_TYPED_TEST_CASE_P(My, DerivedTest, MyTwoTypes); +// Tests that custom names work with type parametrized tests. We reuse the +// TwoTypes from above here. +template +class TypeParametrizedTestWithNames : public Test {}; + +TYPED_TEST_CASE_P(TypeParametrizedTestWithNames); + +TYPED_TEST_P(TypeParametrizedTestWithNames, TestCaseName) { + if (testing::internal::IsSame::value) { + EXPECT_STREQ(::testing::UnitTest::GetInstance() + ->current_test_info() + ->test_case_name(), + "CustomName/TypeParametrizedTestWithNames/p_char_0"); + } + if (testing::internal::IsSame::value) { + EXPECT_STREQ(::testing::UnitTest::GetInstance() + ->current_test_info() + ->test_case_name(), + "CustomName/TypeParametrizedTestWithNames/p_int_1"); + } +} + +REGISTER_TYPED_TEST_CASE_P(TypeParametrizedTestWithNames, TestCaseName); + +class TypeParametrizedTestNames { + public: + template + static std::string GetName(int i) { + if (testing::internal::IsSame::value) { + return std::string("p_char_") + ::testing::PrintToString(i); + } + if (testing::internal::IsSame::value) { + return std::string("p_int_") + ::testing::PrintToString(i); + } + } +}; + +INSTANTIATE_TYPED_TEST_CASE_P(CustomName, TypeParametrizedTestWithNames, + TwoTypes, TypeParametrizedTestNames); + // Tests that multiple TYPED_TEST_CASE_P's can be defined in the same // translation unit. From 65d03530e6a1f243f89941a5c7400a0ce89d8de7 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 28 Aug 2018 10:28:15 -0400 Subject: [PATCH 05/16] Googletest export Fix line that was wrapping in the middle of a link This looks uglier, but has the advantage that the link is kept in one piece. PiperOrigin-RevId: 210537337 --- googlemock/include/gmock/gmock-generated-matchers.h | 4 ++-- googlemock/include/gmock/gmock-generated-matchers.h.pump | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/googlemock/include/gmock/gmock-generated-matchers.h b/googlemock/include/gmock/gmock-generated-matchers.h index 0d46d23b..41d53048 100644 --- a/googlemock/include/gmock/gmock-generated-matchers.h +++ b/googlemock/include/gmock/gmock-generated-matchers.h @@ -1381,8 +1381,8 @@ AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8, M9 m9, M10 m10) { // ================ // // To learn more about using these macros, please search for 'MATCHER' -// on https://github.com/google/googletest/blob/master/googlemock/docs/ -// CookBook.md +// on +// https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md #define MATCHER(name, description)\ class name##Matcher {\ diff --git a/googlemock/include/gmock/gmock-generated-matchers.h.pump b/googlemock/include/gmock/gmock-generated-matchers.h.pump index 9fe0fd7a..1a59fede 100644 --- a/googlemock/include/gmock/gmock-generated-matchers.h.pump +++ b/googlemock/include/gmock/gmock-generated-matchers.h.pump @@ -592,7 +592,8 @@ $$ // show up in the generated code. // ================ // // To learn more about using these macros, please search for 'MATCHER' -// on https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md +// on +// https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md $range i 0..n $for i From 9d9d7a6a8904b628dd17456a6714155b19be84b8 Mon Sep 17 00:00:00 2001 From: Gennadiy Civil Date: Tue, 28 Aug 2018 23:11:51 -0400 Subject: [PATCH 06/16] Update googletest-output-test-golden-lin.txt --- .../googletest-output-test-golden-lin.txt | 60 +++++++++++++++++-- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/googletest/test/googletest-output-test-golden-lin.txt b/googletest/test/googletest-output-test-golden-lin.txt index b0c51898..7711cdc6 100644 --- a/googletest/test/googletest-output-test-golden-lin.txt +++ b/googletest/test/googletest-output-test-golden-lin.txt @@ -12,7 +12,7 @@ Expected equality of these values: 3 Stack trace: (omitted) -[==========] Running 68 tests from 30 test cases. +[==========] Running 76 tests from 34 test cases. [----------] Global test environment set-up. FooEnvironment::SetUp() called. BarEnvironment::SetUp() called. @@ -606,6 +606,24 @@ Expected failure Stack trace: (omitted) [ FAILED ] TypedTest/0.Failure, where TypeParam = int +[----------] 2 tests from TypedTestWithNames/char_0, where TypeParam = char +[ RUN ] TypedTestWithNames/char_0.Success +[ OK ] TypedTestWithNames/char_0.Success +[ RUN ] TypedTestWithNames/char_0.Failure +googletest-output-test_.cc:#: Failure +Failed +Stack trace: (omitted) + +[ FAILED ] TypedTestWithNames/char_0.Failure, where TypeParam = char +[----------] 2 tests from TypedTestWithNames/int_1, where TypeParam = int +[ RUN ] TypedTestWithNames/int_1.Success +[ OK ] TypedTestWithNames/int_1.Success +[ RUN ] TypedTestWithNames/int_1.Failure +googletest-output-test_.cc:#: Failure +Failed +Stack trace: (omitted) + +[ FAILED ] TypedTestWithNames/int_1.Failure, where TypeParam = int [----------] 2 tests from Unsigned/TypedTestP/0, where TypeParam = unsigned char [ RUN ] Unsigned/TypedTestP/0.Success [ OK ] Unsigned/TypedTestP/0.Success @@ -634,6 +652,34 @@ Expected failure Stack trace: (omitted) [ FAILED ] Unsigned/TypedTestP/1.Failure, where TypeParam = unsigned int +[----------] 2 tests from UnsignedCustomName/TypedTestP/unsigned_char_0, where TypeParam = unsigned char +[ RUN ] UnsignedCustomName/TypedTestP/unsigned_char_0.Success +[ OK ] UnsignedCustomName/TypedTestP/unsigned_char_0.Success +[ RUN ] UnsignedCustomName/TypedTestP/unsigned_char_0.Failure +googletest-output-test_.cc:#: Failure +Expected equality of these values: + 1U + Which is: 1 + TypeParam() + Which is: '\0' +Expected failure +Stack trace: (omitted) + +[ FAILED ] UnsignedCustomName/TypedTestP/unsigned_char_0.Failure, where TypeParam = unsigned char +[----------] 2 tests from UnsignedCustomName/TypedTestP/unsigned_int_1, where TypeParam = unsigned int +[ RUN ] UnsignedCustomName/TypedTestP/unsigned_int_1.Success +[ OK ] UnsignedCustomName/TypedTestP/unsigned_int_1.Success +[ RUN ] UnsignedCustomName/TypedTestP/unsigned_int_1.Failure +googletest-output-test_.cc:#: Failure +Expected equality of these values: + 1U + Which is: 1 + TypeParam() + Which is: 0 +Expected failure +Stack trace: (omitted) + +[ FAILED ] UnsignedCustomName/TypedTestP/unsigned_int_1.Failure, where TypeParam = unsigned int [----------] 4 tests from ExpectFailureTest [ RUN ] ExpectFailureTest.ExpectFatalFailure (expecting 1 failure) @@ -860,9 +906,9 @@ Failed Expected fatal failure. Stack trace: (omitted) -[==========] 68 tests from 30 test cases ran. -[ PASSED ] 22 tests. -[ FAILED ] 46 tests, listed below: +[==========] 76 tests from 34 test cases ran. +[ PASSED ] 26 tests. +[ FAILED ] 50 tests, listed below: [ FAILED ] NonfatalFailureTest.EscapesStringOperands [ FAILED ] NonfatalFailureTest.DiffForLongStrings [ FAILED ] FatalFailureTest.FatalFailureInSubroutine @@ -898,8 +944,12 @@ Stack trace: (omitted) [ FAILED ] ExpectFatalFailureTest.FailsWhenStatementReturns [ FAILED ] ExpectFatalFailureTest.FailsWhenStatementThrows [ FAILED ] TypedTest/0.Failure, where TypeParam = int +[ FAILED ] TypedTestWithNames/char_0.Failure, where TypeParam = char +[ FAILED ] TypedTestWithNames/int_1.Failure, where TypeParam = int [ FAILED ] Unsigned/TypedTestP/0.Failure, where TypeParam = unsigned char [ FAILED ] Unsigned/TypedTestP/1.Failure, where TypeParam = unsigned int +[ FAILED ] UnsignedCustomName/TypedTestP/unsigned_char_0.Failure, where TypeParam = unsigned char +[ FAILED ] UnsignedCustomName/TypedTestP/unsigned_int_1.Failure, where TypeParam = unsigned int [ FAILED ] ExpectFailureTest.ExpectFatalFailure [ FAILED ] ExpectFailureTest.ExpectNonFatalFailure [ FAILED ] ExpectFailureTest.ExpectFatalFailureOnAllThreads @@ -910,7 +960,7 @@ Stack trace: (omitted) [ FAILED ] PrintingFailingParams/FailingParamTest.Fails/0, where GetParam() = 2 [ FAILED ] PrintingStrings/ParamTest.Failure/a, where GetParam() = "a" -46 FAILED TESTS +50 FAILED TESTS  YOU HAVE 1 DISABLED TEST Note: Google Test filter = FatalFailureTest.*:LoggingTest.* From 8c0e0d5c17f9d7395bd0e0caaa8f4707561be2a2 Mon Sep 17 00:00:00 2001 From: Gennadiy Civil Date: Tue, 28 Aug 2018 23:25:51 -0400 Subject: [PATCH 07/16] MSVC warnings silence --- googletest/test/googletest-output-test_.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/googletest/test/googletest-output-test_.cc b/googletest/test/googletest-output-test_.cc index d6f7e0ba..cfae8818 100644 --- a/googletest/test/googletest-output-test_.cc +++ b/googletest/test/googletest-output-test_.cc @@ -39,6 +39,10 @@ #include +#if _MSC_VER + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4127: /* conditional expression is constant */) +#endif // . _MSC_VER + #if GTEST_IS_THREADSAFE using testing::ScopedFakeTestPartResultReporter; using testing::TestPartResultArray; @@ -1098,6 +1102,8 @@ int main(int argc, char **argv) { // are registered, and torn down in the reverse order. testing::AddGlobalTestEnvironment(new FooEnvironment); testing::AddGlobalTestEnvironment(new BarEnvironment); - +#if _MSC_VER + GTEST_DISABLE_MSC_WARNINGS_POP() +#endif // . _MSC_VER return RunAllTests(); } From e8e26d25bd0bf708a4509f9fe2a6445898c45d75 Mon Sep 17 00:00:00 2001 From: Gennadiy Civil Date: Tue, 28 Aug 2018 23:36:50 -0400 Subject: [PATCH 08/16] typo --- googletest/test/googletest-output-test_.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googletest/test/googletest-output-test_.cc b/googletest/test/googletest-output-test_.cc index cfae8818..e499324a 100644 --- a/googletest/test/googletest-output-test_.cc +++ b/googletest/test/googletest-output-test_.cc @@ -40,7 +40,7 @@ #include #if _MSC_VER - GTEST_DISABLE_MSC_WARNINGS_PUSH_(4127: /* conditional expression is constant */) + GTEST_DISABLE_MSC_WARNINGS_PUSH_(4127 /* conditional expression is constant */) #endif // . _MSC_VER #if GTEST_IS_THREADSAFE From 9b2016a01d19709b2ea94b5cc880b830e4d537ef Mon Sep 17 00:00:00 2001 From: Gennadiy Civil Date: Tue, 28 Aug 2018 23:46:23 -0400 Subject: [PATCH 09/16] typo --- googletest/test/googletest-output-test_.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/googletest/test/googletest-output-test_.cc b/googletest/test/googletest-output-test_.cc index e499324a..180630e2 100644 --- a/googletest/test/googletest-output-test_.cc +++ b/googletest/test/googletest-output-test_.cc @@ -1103,7 +1103,7 @@ int main(int argc, char **argv) { testing::AddGlobalTestEnvironment(new FooEnvironment); testing::AddGlobalTestEnvironment(new BarEnvironment); #if _MSC_VER - GTEST_DISABLE_MSC_WARNINGS_POP() + GTEST_DISABLE_MSC_WARNINGS_POP_() // 4127 #endif // . _MSC_VER return RunAllTests(); } From e103fa4f2a472a4bc217531ec075aabf56a785b9 Mon Sep 17 00:00:00 2001 From: Gennadiy Civil Date: Tue, 28 Aug 2018 23:55:17 -0400 Subject: [PATCH 10/16] Disable MCVS warnings --- googletest/test/gtest-typed-test_test.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/googletest/test/gtest-typed-test_test.cc b/googletest/test/gtest-typed-test_test.cc index e9eed639..c38bb94a 100644 --- a/googletest/test/gtest-typed-test_test.cc +++ b/googletest/test/gtest-typed-test_test.cc @@ -35,6 +35,10 @@ #include "gtest/gtest.h" +#if _MSC_VER +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4127 /* conditional expression is constant */) +#endif // _MSC_VER + using testing::Test; // Used for testing that SetUpTestCase()/TearDownTestCase(), fixture @@ -450,4 +454,8 @@ INSTANTIATE_TYPED_TEST_CASE_P(My, TrimmedTest, TrimTypes); // must be defined). This dummy test keeps gtest_main linked in. TEST(DummyTest, TypedTestsAreNotSupportedOnThisPlatform) {} +#if _MSC_VER +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4127 +#endif // _MSC_VER + #endif // #if !defined(GTEST_HAS_TYPED_TEST) && !defined(GTEST_HAS_TYPED_TEST_P) From e8ebde4f21794da0055d62d2a88a66321c0c030c Mon Sep 17 00:00:00 2001 From: Gennadiy Civil Date: Wed, 29 Aug 2018 12:37:54 -0400 Subject: [PATCH 11/16] Testing, trying to figure out clang errors Possibly related to travis env --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0e17bc27..c7b8a1ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,11 +30,9 @@ matrix: compiler: gcc env: BUILD_TYPE=Debug VERBOSE=1 CXX_FLAGS=-std=c++11 - os: linux - group: deprecated-2017Q4 compiler: clang env: BUILD_TYPE=Debug VERBOSE=1 - os: linux - group: deprecated-2017Q4 compiler: clang env: BUILD_TYPE=Release VERBOSE=1 CXX_FLAGS=-std=c++11 - os: linux From 964748a9025309dfbfe3df4d173ca6a71f8c81c6 Mon Sep 17 00:00:00 2001 From: Gennadiy Civil Date: Wed, 29 Aug 2018 21:16:33 -0400 Subject: [PATCH 12/16] Update .travis.yml --- .travis.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c7b8a1ea..33e3766a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,8 +30,16 @@ matrix: compiler: gcc env: BUILD_TYPE=Debug VERBOSE=1 CXX_FLAGS=-std=c++11 - os: linux + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-precise-3.7 + packages: + - clang-3.7 + env: + - MATRIX_EVAL="CC=clang-3.7 && CXX=clang++-3.7" BUILD_TYPE=Debug VERBOSE=1 compiler: clang - env: BUILD_TYPE=Debug VERBOSE=1 - os: linux compiler: clang env: BUILD_TYPE=Release VERBOSE=1 CXX_FLAGS=-std=c++11 From a83e98d0e7c7bddbaa1bb4d4558a63e97d5d97fc Mon Sep 17 00:00:00 2001 From: Gennadiy Civil Date: Wed, 29 Aug 2018 21:21:42 -0400 Subject: [PATCH 13/16] Update .travis.yml Bring travil yml back to the master branch state --- .travis.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 33e3766a..0e17bc27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,17 +30,11 @@ matrix: compiler: gcc env: BUILD_TYPE=Debug VERBOSE=1 CXX_FLAGS=-std=c++11 - os: linux - addons: - apt: - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-precise-3.7 - packages: - - clang-3.7 - env: - - MATRIX_EVAL="CC=clang-3.7 && CXX=clang++-3.7" BUILD_TYPE=Debug VERBOSE=1 + group: deprecated-2017Q4 compiler: clang + env: BUILD_TYPE=Debug VERBOSE=1 - os: linux + group: deprecated-2017Q4 compiler: clang env: BUILD_TYPE=Release VERBOSE=1 CXX_FLAGS=-std=c++11 - os: linux From d7d21c0b91603a60f0ffc69b8d689fc0b342d607 Mon Sep 17 00:00:00 2001 From: Gennadiy Civil Date: Wed, 29 Aug 2018 21:51:31 -0400 Subject: [PATCH 14/16] clang 3.7->3.9 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0e17bc27..4e7413a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,10 +72,10 @@ addons: # https://github.com/travis-ci/apt-source-whitelist/blob/master/ubuntu.json sources: - ubuntu-toolchain-r-test - - llvm-toolchain-precise-3.7 + - llvm-toolchain-precise-3.9 packages: - g++-4.9 - - clang-3.7 + - clang-3.9 notifications: email: false From f1e529a808ffa52166dd2014372f56c440ed16c4 Mon Sep 17 00:00:00 2001 From: Gennadiy Civil Date: Wed, 29 Aug 2018 21:52:07 -0400 Subject: [PATCH 15/16] clang 3.7 -> 3.9 --- ci/install-linux.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/install-linux.sh b/ci/install-linux.sh index 02a19439..05e2cb28 100755 --- a/ci/install-linux.sh +++ b/ci/install-linux.sh @@ -41,7 +41,7 @@ if [ "${TRAVIS_SUDO}" = "true" ]; then echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" | \ sudo tee /etc/apt/sources.list.d/bazel.list curl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add - - sudo apt-get update && sudo apt-get install -y bazel gcc-4.9 g++-4.9 clang-3.7 + sudo apt-get update && sudo apt-get install -y bazel gcc-4.9 g++-4.9 clang-3.9 elif [ "${CXX}" = "clang++" ]; then # Use ccache, assuming $HOME/bin is in the path, which is true in the Travis build environment. ln -sf /usr/bin/ccache $HOME/bin/${CXX}; From 9ad739833e16daa0f2909da7daee19a9dd608f29 Mon Sep 17 00:00:00 2001 From: Gennadiy Civil Date: Wed, 29 Aug 2018 22:32:08 -0400 Subject: [PATCH 16/16] Update gmock-matchers.h --- googlemock/include/gmock/gmock-matchers.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/googlemock/include/gmock/gmock-matchers.h b/googlemock/include/gmock/gmock-matchers.h index 3975cd07..9cd1a065 100644 --- a/googlemock/include/gmock/gmock-matchers.h +++ b/googlemock/include/gmock/gmock-matchers.h @@ -56,8 +56,9 @@ # include // NOLINT -- must be after gtest.h #endif -GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ -/* class A needs to have dll-interface to be used by clients of class B */) +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 5046 \ +/* class A needs to have dll-interface to be used by clients of class B */ \ +/* Symbol involving type with internal linkage not defined */) namespace testing { @@ -5270,7 +5271,7 @@ PolymorphicMatcher > VariantWith( } // namespace testing -GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 5046 // Include any custom callback matchers added by the local installation. // We must include this header at the end to make sure it can use the