|
|
|
@ -46,6 +46,8 @@ |
|
|
|
|
#include <algorithm> |
|
|
|
|
#include <iomanip> |
|
|
|
|
#include <limits> |
|
|
|
|
#include <list> |
|
|
|
|
#include <map> |
|
|
|
|
#include <ostream> // NOLINT |
|
|
|
|
#include <sstream> |
|
|
|
|
#include <vector> |
|
|
|
@ -80,6 +82,7 @@ |
|
|
|
|
#elif GTEST_OS_WINDOWS_MOBILE // We are on Windows CE.
|
|
|
|
|
|
|
|
|
|
# include <windows.h> // NOLINT
|
|
|
|
|
# undef min |
|
|
|
|
|
|
|
|
|
#elif GTEST_OS_WINDOWS // We are on Windows proper.
|
|
|
|
|
|
|
|
|
@ -102,6 +105,7 @@ |
|
|
|
|
// cpplint thinks that the header is already included, so we want to
|
|
|
|
|
// silence it.
|
|
|
|
|
# include <windows.h> // NOLINT
|
|
|
|
|
# undef min |
|
|
|
|
|
|
|
|
|
#else |
|
|
|
|
|
|
|
|
@ -981,6 +985,276 @@ AssertionResult AssertionFailure(const Message& message) { |
|
|
|
|
|
|
|
|
|
namespace internal { |
|
|
|
|
|
|
|
|
|
namespace edit_distance { |
|
|
|
|
std::vector<EditType> CalculateOptimalEdits(const std::vector<size_t>& left, |
|
|
|
|
const std::vector<size_t>& right) { |
|
|
|
|
std::vector<std::vector<double> > costs( |
|
|
|
|
left.size() + 1, std::vector<double>(right.size() + 1)); |
|
|
|
|
std::vector<std::vector<EditType> > best_move( |
|
|
|
|
left.size() + 1, std::vector<EditType>(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<double>(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<double>(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<EditType> 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<std::string, size_t> IdMap; |
|
|
|
|
IdMap ids_; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
std::vector<EditType> CalculateOptimalEdits( |
|
|
|
|
const std::vector<std::string>& left, |
|
|
|
|
const std::vector<std::string>& right) { |
|
|
|
|
std::vector<size_t> 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<std::pair<char, const char*> >::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
|
|
|
|
|
// "@@ -<left_start>,<left_length> +<right_start>,<right_length> @@"
|
|
|
|
|
// where the left/right parts are ommitted 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<std::pair<char, const char*> > 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<std::string>& left, |
|
|
|
|
const std::vector<std::string>& right, |
|
|
|
|
size_t context) { |
|
|
|
|
const std::vector<EditType> 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<EditType>::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
|
|
|
|
|
// escaped. Split them on escaped '\n' boundaries. Leave all other escaped
|
|
|
|
|
// characters the same.
|
|
|
|
|
std::vector<std::string> SplitEscapedString(const std::string& str) { |
|
|
|
|
std::vector<std::string> lines; |
|
|
|
|
size_t start = 0, end = str.size(); |
|
|
|
|
if (end > 2 && str[0] == '"' && str[end - 1] == '"') { |
|
|
|
|
++start; |
|
|
|
|
--end; |
|
|
|
|
} |
|
|
|
|
bool escaped = false; |
|
|
|
|
for (size_t i = start; i + 1 < end; ++i) { |
|
|
|
|
if (escaped) { |
|
|
|
|
escaped = false; |
|
|
|
|
if (str[i] == 'n') { |
|
|
|
|
lines.push_back(str.substr(start, i - start - 1)); |
|
|
|
|
start = i + 1; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
escaped = str[i] == '\\'; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
lines.push_back(str.substr(start, end - start)); |
|
|
|
|
return lines; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
// Constructs and returns the message for an equality assertion
|
|
|
|
|
// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure.
|
|
|
|
|
//
|
|
|
|
@ -1015,6 +1289,17 @@ AssertionResult EqFailure(const char* expected_expression, |
|
|
|
|
msg << "\nWhich is: " << expected_value; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!expected_value.empty() && !actual_value.empty()) { |
|
|
|
|
const std::vector<std::string> expected_lines = |
|
|
|
|
SplitEscapedString(expected_value); |
|
|
|
|
const std::vector<std::string> actual_lines = |
|
|
|
|
SplitEscapedString(actual_value); |
|
|
|
|
if (expected_lines.size() > 1 || actual_lines.size() > 1) { |
|
|
|
|
msg << "\nWith diff:\n" |
|
|
|
|
<< edit_distance::CreateUnifiedDiff(expected_lines, actual_lines); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return AssertionFailure() << msg; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|