| #include "test_manager.h" | |
| #include <algorithm> | |
| #include <cctype> | |
| #include <cstddef> | |
| #include <fstream> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/container/flat_hash_set.h" | |
| #include "absl/log/absl_check.h" | |
| #include "absl/log/absl_log.h" | |
| #include "absl/status/status.h" | |
| #include "absl/strings/ascii.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_join.h" | |
| #include "absl/strings/string_view.h" | |
| #include "google/protobuf/stubs/status_macros.h" | |
| namespace google { | |
| namespace protobuf { | |
| namespace conformance { | |
| namespace internal { | |
| namespace { | |
| constexpr int kMaximumWildcardExpansions = 20; | |
| constexpr int kFailureMessageLengthLimit = 128; | |
| bool InsertUniqueTest(absl::flat_hash_set<std::string>& set, | |
| absl::string_view value) { | |
| if (!set.emplace(value).second) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| void IncrementIfUnique(bool unique, int& counter) { | |
| if (unique) { | |
| ++counter; | |
| } | |
| } | |
| void Normalize(std::string& input) { | |
| input.erase(std::remove(input.begin(), input.end(), '\n'), input.end()); | |
| } | |
| // Sets up a failure message properly for our failure lists. | |
| std::string FormatFailureMessage(absl::string_view input) { | |
| std::string result(absl::StripAsciiWhitespace(input)); | |
| // Remove newlines | |
| Normalize(result); | |
| // Truncate failure message if needed | |
| if (result.length() > kFailureMessageLengthLimit) { | |
| result = result.substr(0, kFailureMessageLengthLimit); | |
| } | |
| return result; | |
| } | |
| std::string ReformatLine(size_t alignment, absl::string_view line) { | |
| size_t comment_pos = line.find('#'); | |
| absl::string_view test_name = | |
| absl::StripAsciiWhitespace(line.substr(0, comment_pos)); | |
| if (test_name.empty()) { | |
| return std::string(absl::StripAsciiWhitespace(line)); | |
| } | |
| absl::string_view message = | |
| absl::StripAsciiWhitespace(line.substr(comment_pos + 1)); | |
| ABSL_CHECK_GE(alignment, test_name.length()); | |
| std::string whitespace(alignment - test_name.length(), ' '); | |
| return absl::StrCat(test_name, whitespace, " # ", message); | |
| } | |
| } // namespace | |
| TestManager::~TestManager() { | |
| if (!finalized_) { | |
| ABSL_LOG(FATAL) | |
| << "TestManager::Finalize() was not called before destruction."; | |
| } | |
| } | |
| absl::Status TestManager::LoadFailureList(absl::string_view filename) { | |
| std::ifstream infile(std::string{filename}); | |
| if (!infile.is_open()) { | |
| return absl::InternalError( | |
| absl::StrCat("Couldn't open failure list file: ", filename)); | |
| } | |
| for (std::string line; std::getline(infile, line);) { | |
| failure_list_lines_.push_back(line); | |
| // Remove comments. | |
| std::string test_name = line.substr(0, line.find('#')); | |
| test_name.erase( | |
| std::remove_if(test_name.begin(), test_name.end(), ::isspace), | |
| test_name.end()); | |
| if (test_name.empty()) { // Skip empty lines. | |
| continue; | |
| } | |
| // If we remove whitespace from the beginning of a line, and what we have | |
| // left at first is a '#', then we have a comment. | |
| if (test_name[0] != '#') { | |
| // Find our failure message if it exists. Will be set to an empty string | |
| // if no message is found. Empty failure messages also pass our tests. | |
| size_t comment_pos = line.find('#'); | |
| std::string message; | |
| if (comment_pos != std::string::npos) { | |
| message = line.substr(comment_pos + 1); // +1 to skip the delimiter | |
| // If we had only whitespace after the delimiter, we will have an empty | |
| // failure message and the test will still pass. | |
| message = std::string(absl::StripAsciiWhitespace(message)); | |
| } | |
| RETURN_IF_ERROR(expected_failure_list_.Insert(test_name)); | |
| ABSL_CHECK(expected_failure_messages_.emplace(test_name, message).second); | |
| ABSL_CHECK(unseen_expected_failures_.emplace(test_name).second); | |
| } | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status TestManager::SaveFailureList(absl::string_view filename) const { | |
| std::ofstream outfile(std::string{filename}); | |
| // Calculate alignment. | |
| size_t alignment = 0; | |
| for (const std::string& line : failure_list_lines_) { | |
| absl::string_view test_name = absl::StripAsciiWhitespace( | |
| absl::string_view(line).substr(0, line.find('#'))); | |
| alignment = std::max(alignment, test_name.length()); | |
| } | |
| for (const auto& failure : new_failures_) { | |
| alignment = std::max(alignment, failure.first.length()); | |
| } | |
| // Output existing failure list, stripping out unseen tests and inserting | |
| // new failures. | |
| auto to_add = new_failures_.begin(); | |
| for (const std::string& line : failure_list_lines_) { | |
| absl::string_view test_name = absl::StripAsciiWhitespace( | |
| absl::string_view(line).substr(0, line.find('#'))); | |
| if (unseen_expected_failures_.contains(test_name) || | |
| seen_unexpected_successes_.contains(test_name)) { | |
| continue; | |
| } | |
| while (to_add != new_failures_.end() && test_name > to_add->first) { | |
| outfile << ReformatLine(alignment, absl::StrCat(to_add->first, " # ", | |
| to_add->second)) | |
| << "\n"; | |
| ++to_add; | |
| } | |
| outfile << ReformatLine(alignment, line) << "\n"; | |
| } | |
| // Add any remaining new failures. | |
| while (to_add != new_failures_.end()) { | |
| outfile << ReformatLine(alignment, | |
| absl::StrCat(to_add->first, " # ", to_add->second)) | |
| << "\n"; | |
| ++to_add; | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status TestManager::ReportSuccess(absl::string_view test_name) { | |
| bool unique = InsertUniqueTest(seen_tests_, test_name); | |
| auto failure_match = expected_failure_list_.WalkDownMatch(test_name); | |
| if (failure_match.has_value()) { | |
| // This was expected to fail, but it succeeded. | |
| IncrementIfUnique(unique, number_of_matches_[*failure_match]); | |
| IncrementIfUnique(unique, unexpected_successes_); | |
| unseen_expected_failures_.erase(*failure_match); | |
| seen_unexpected_successes_.insert(*failure_match); | |
| return absl::FailedPreconditionError( | |
| absl::StrCat("Unexpected success for test: ", test_name)); | |
| } | |
| // This wasn't expected to fail. | |
| IncrementIfUnique(unique, expected_successes_); | |
| return absl::OkStatus(); | |
| } | |
| absl::Status TestManager::ReportFailure(absl::string_view test_name, | |
| absl::string_view failure_message) { | |
| bool unique = InsertUniqueTest(seen_tests_, test_name); | |
| auto failure_match = expected_failure_list_.WalkDownMatch(test_name); | |
| std::string formatted_failure_message = FormatFailureMessage(failure_message); | |
| if (!failure_match.has_value()) { | |
| // This was not expected to fail. | |
| IncrementIfUnique(unique, unexpected_failures_); | |
| new_failures_[test_name] = formatted_failure_message; | |
| return absl::FailedPreconditionError( | |
| absl::StrCat("Unexpected failure for test: ", test_name)); | |
| } | |
| if (expected_failure_messages_[*failure_match] != formatted_failure_message) { | |
| IncrementIfUnique(unique, unexpected_failures_); | |
| new_failures_[*failure_match] = formatted_failure_message; | |
| return absl::FailedPreconditionError( | |
| absl::StrCat("Unexpected failure message for test: ", test_name, | |
| " expected: ", expected_failure_messages_[*failure_match], | |
| " actual: ", formatted_failure_message)); | |
| } | |
| unseen_expected_failures_.erase(*failure_match); | |
| if (number_of_matches_[*failure_match] > kMaximumWildcardExpansions) { | |
| IncrementIfUnique(unique, unexpected_failures_); | |
| return absl::FailedPreconditionError( | |
| absl::StrCat("The wildcard ", *failure_match, | |
| " served as matches to too many test " | |
| "names exceeding the max amount of ", | |
| kMaximumWildcardExpansions, " for test: ", test_name)); | |
| } | |
| IncrementIfUnique(unique, number_of_matches_[*failure_match]); | |
| IncrementIfUnique(unique, expected_failures_); | |
| return absl::OkStatus(); | |
| } | |
| absl::Status TestManager::ReportSkip(absl::string_view test_name) { | |
| bool unique = InsertUniqueTest(seen_tests_, test_name); | |
| IncrementIfUnique(unique, skipped_); | |
| return absl::OkStatus(); | |
| } | |
| absl::Status TestManager::Finalize() { | |
| finalized_ = true; | |
| if (!unseen_expected_failures_.empty()) { | |
| return absl::FailedPreconditionError( | |
| absl::StrCat("The following expected failures were not seen: ", | |
| absl::StrJoin(unseen_expected_failures_, ", "))); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| } // namespace internal | |
| } // namespace conformance | |
| } // namespace protobuf | |
| } // namespace google |