diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/gtest.cc | 285 | 
1 files changed, 285 insertions, 0 deletions
diff --git a/src/gtest.cc b/src/gtest.cc index ca3596eb..e4f3df3e 100644 --- a/src/gtest.cc +++ b/src/gtest.cc @@ -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;  }  | 
