• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 
8 #include "conformance_test.h"
9 
10 #include <stdarg.h>
11 
12 #include <algorithm>
13 #include <cstddef>
14 #include <cstdint>
15 #include <cstdio>
16 #include <fstream>
17 #include <memory>
18 #include <string>
19 #include <utility>
20 
21 #include "google/protobuf/util/field_comparator.h"
22 #include "google/protobuf/util/message_differencer.h"
23 #include "absl/container/btree_map.h"
24 #include "absl/container/flat_hash_set.h"
25 #include "absl/log/absl_check.h"
26 #include "absl/log/absl_log.h"
27 #include "absl/status/status.h"
28 #include "absl/strings/str_cat.h"
29 #include "absl/strings/str_format.h"
30 #include "absl/strings/string_view.h"
31 #include "conformance/conformance.pb.h"
32 #include "failure_list_trie_node.h"
33 #include "google/protobuf/descriptor_legacy.h"
34 #include "google/protobuf/endian.h"
35 #include "google/protobuf/message.h"
36 #include "google/protobuf/text_format.h"
37 
38 using conformance::ConformanceRequest;
39 using conformance::ConformanceResponse;
40 using conformance::TestStatus;
41 using conformance::WireFormat;
42 using google::protobuf::util::DefaultFieldComparator;
43 using google::protobuf::util::MessageDifferencer;
44 using std::string;
45 
46 namespace {
47 
ReplaceAll(std::string & input,std::string replace_word,std::string replace_by)48 static void ReplaceAll(std::string& input, std::string replace_word,
49                        std::string replace_by) {
50   size_t pos = input.find(replace_word);
51   while (pos != std::string::npos) {
52     input.replace(pos, replace_word.length(), replace_by);
53     pos = input.find(replace_word, pos + replace_by.length());
54   }
55 }
56 
ToOctString(const std::string & binary_string)57 static std::string ToOctString(const std::string& binary_string) {
58   std::string oct_string;
59   for (size_t i = 0; i < binary_string.size(); i++) {
60     uint8_t c = binary_string.at(i);
61     uint8_t high = c / 64;
62     uint8_t mid = (c % 64) / 8;
63     uint8_t low = c % 8;
64     oct_string.push_back('\\');
65     oct_string.push_back('0' + high);
66     oct_string.push_back('0' + mid);
67     oct_string.push_back('0' + low);
68   }
69   return oct_string;
70 }
71 
72 // Returns full filename path of written .txt file if successful
ProduceOctalSerialized(const std::string & request,uint32_t len)73 static std::string ProduceOctalSerialized(const std::string& request,
74                                           uint32_t len) {
75   char* len_split_bytes = static_cast<char*>(static_cast<void*>(&len));
76 
77   std::string out;
78 
79   std::string hex_repr;
80   for (int i = 0; i < 4; i++) {
81     auto conversion = (unsigned int)static_cast<uint8_t>(len_split_bytes[i]);
82     std::string hex = absl::StrFormat("\\x%x", conversion);
83     absl::StrAppend(&hex_repr, hex);
84   }
85 
86   absl::StrAppend(&out, hex_repr);
87 
88   absl::StrAppend(&out, ToOctString(request));
89 
90   return out;
91 }
92 
WriteToFile(const std::string & octal_serialized,const std::string & output_dir,const std::string & test_name)93 static std::string WriteToFile(const std::string& octal_serialized,
94                                const std::string& output_dir,
95                                const std::string& test_name) {
96   std::string test_name_txt = test_name;
97   ReplaceAll(test_name_txt, ".", "_");
98   absl::StrAppend(&test_name_txt, ".txt");
99   std::string full_filename;
100   if (!output_dir.empty()) {
101     full_filename = output_dir;
102     if (*output_dir.rbegin() != '/') {
103       full_filename.push_back('/');
104     }
105     absl::StrAppend(&full_filename, test_name_txt);
106   }
107   std::ofstream os{std::string(full_filename)};
108   if (os) {
109     os << octal_serialized;
110     return full_filename;
111   } else {
112     ABSL_LOG(INFO) << "Failed to open file for debugging: " << full_filename
113                    << "\n";
114     return "";
115   }
116 }
117 
118 // Removes all newlines.
Normalize(std::string & input)119 static void Normalize(std::string& input) {
120   input.erase(std::remove(input.begin(), input.end(), '\n'), input.end());
121 }
122 
123 // Sets up a failure message properly for our failure lists.
FormatFailureMessage(const TestStatus & input)124 static TestStatus FormatFailureMessage(const TestStatus& input) {
125   // Make copy just this once, as we need to modify it for our failure lists.
126   std::string formatted_failure_message = input.failure_message();
127   // Remove newlines
128   Normalize(formatted_failure_message);
129   // Truncate failure message if needed
130   if (formatted_failure_message.length() > 128) {
131     formatted_failure_message = formatted_failure_message.substr(0, 128);
132   }
133   TestStatus properly_formatted;
134   properly_formatted.set_name(input.name());
135   properly_formatted.set_failure_message(formatted_failure_message);
136   return properly_formatted;
137 }
138 
CheckSetEmpty(const absl::btree_map<std::string,TestStatus> & set_to_check,absl::string_view write_to_file,absl::string_view msg,absl::string_view output_dir,std::string * output)139 bool CheckSetEmpty(const absl::btree_map<std::string, TestStatus>& set_to_check,
140                    absl::string_view write_to_file, absl::string_view msg,
141                    absl::string_view output_dir, std::string* output) {
142   if (set_to_check.empty()) return true;
143 
144   absl::StrAppendFormat(output, "\n");
145   absl::StrAppendFormat(output, "%s\n\n", msg);
146   for (const auto& pair : set_to_check) {
147     absl::StrAppendFormat(output, "  %s # %s\n", pair.first,
148                           pair.second.failure_message());
149   }
150   absl::StrAppendFormat(output, "\n");
151 
152   if (!write_to_file.empty()) {
153     std::string full_filename;
154     absl::string_view filename = write_to_file;
155     if (!output_dir.empty()) {
156       full_filename = std::string(output_dir);
157       absl::StrAppend(&full_filename, write_to_file);
158       filename = full_filename;
159     }
160     std::ofstream os{std::string(filename)};
161     if (os) {
162       for (const auto& pair : set_to_check) {
163         // Additions will not have a 'matched_name' while removals will.
164         string potential_add_or_removal = pair.second.matched_name().empty()
165                                               ? pair.first
166                                               : pair.second.matched_name();
167         os << potential_add_or_removal << " # " << pair.second.failure_message()
168            << "\n";
169       }
170     } else {
171       absl::StrAppendFormat(output,
172                             "Failed to open file: %s\n",
173                             filename);
174     }
175   }
176 
177   return false;
178 }
179 
180 }  // namespace
181 
182 namespace google {
183 namespace protobuf {
184 
185 constexpr int kMaximumWildcardExpansions = 5;
186 
ConformanceRequestSetting(ConformanceLevel level,conformance::WireFormat input_format,conformance::WireFormat output_format,conformance::TestCategory test_category,const Message & prototype_message,const std::string & test_name,const std::string & input)187 ConformanceTestSuite::ConformanceRequestSetting::ConformanceRequestSetting(
188     ConformanceLevel level, conformance::WireFormat input_format,
189     conformance::WireFormat output_format,
190     conformance::TestCategory test_category, const Message& prototype_message,
191     const std::string& test_name, const std::string& input)
192     : level_(level),
193       input_format_(input_format),
194       output_format_(output_format),
195       prototype_message_(prototype_message),
196       prototype_message_for_compare_(prototype_message.New()),
197       test_name_(test_name) {
198   switch (input_format) {
199     case conformance::PROTOBUF: {
200       request_.set_protobuf_payload(input);
201       break;
202     }
203 
204     case conformance::JSON: {
205       request_.set_json_payload(input);
206       break;
207     }
208 
209     case conformance::JSPB: {
210       request_.set_jspb_payload(input);
211       break;
212     }
213 
214     case conformance::TEXT_FORMAT: {
215       request_.set_text_payload(input);
216       break;
217     }
218 
219     default:
220       ABSL_LOG(FATAL) << "Unspecified input format";
221   }
222 
223   request_.set_test_category(test_category);
224 
225   request_.set_message_type(prototype_message.GetDescriptor()->full_name());
226   request_.set_requested_output_format(output_format);
227 }
228 
229 std::unique_ptr<Message>
NewTestMessage() const230 ConformanceTestSuite::ConformanceRequestSetting::NewTestMessage() const {
231   return std::unique_ptr<Message>(prototype_message_for_compare_->New());
232 }
233 
234 std::string
GetSyntaxIdentifier() const235 ConformanceTestSuite::ConformanceRequestSetting::GetSyntaxIdentifier() const {
236   switch (FileDescriptorLegacy(prototype_message_.GetDescriptor()->file())
237               .edition()) {
238     case Edition::EDITION_PROTO3:
239       return "Proto3";
240     case Edition::EDITION_PROTO2:
241       return "Proto2";
242     default: {
243       std::string id = "Editions";
244       if (prototype_message_.GetDescriptor()->name() == "TestAllTypesProto2") {
245         absl::StrAppend(&id, "_Proto2");
246       } else if (prototype_message_.GetDescriptor()->name() ==
247                  "TestAllTypesProto3") {
248         absl::StrAppend(&id, "_Proto3");
249       }
250       return id;
251     }
252   }
253 }
254 
GetTestName() const255 string ConformanceTestSuite::ConformanceRequestSetting::GetTestName() const {
256   return absl::StrCat(ConformanceLevelToString(level_), ".",
257                       GetSyntaxIdentifier(), ".",
258                       InputFormatString(input_format_), ".", test_name_, ".",
259                       OutputFormatString(output_format_));
260 }
261 
262 std::string
ConformanceLevelToString(ConformanceLevel level) const263 ConformanceTestSuite::ConformanceRequestSetting::ConformanceLevelToString(
264     ConformanceLevel level) const {
265   switch (level) {
266     case REQUIRED:
267       return "Required";
268     case RECOMMENDED:
269       return "Recommended";
270   }
271   ABSL_LOG(FATAL) << "Unknown value: " << level;
272   return "";
273 }
274 
InputFormatString(conformance::WireFormat format) const275 std::string ConformanceTestSuite::ConformanceRequestSetting::InputFormatString(
276     conformance::WireFormat format) const {
277   switch (format) {
278     case conformance::PROTOBUF:
279       return "ProtobufInput";
280     case conformance::JSON:
281       return "JsonInput";
282     case conformance::TEXT_FORMAT:
283       return "TextFormatInput";
284     default:
285       ABSL_LOG(FATAL) << "Unspecified output format";
286   }
287   return "";
288 }
289 
OutputFormatString(conformance::WireFormat format) const290 std::string ConformanceTestSuite::ConformanceRequestSetting::OutputFormatString(
291     conformance::WireFormat format) const {
292   switch (format) {
293     case conformance::PROTOBUF:
294       return "ProtobufOutput";
295     case conformance::JSON:
296       return "JsonOutput";
297     case conformance::TEXT_FORMAT:
298       return "TextFormatOutput";
299     default:
300       ABSL_LOG(FATAL) << "Unspecified output format";
301   }
302   return "";
303 }
304 
TruncateDebugPayload(string * payload)305 void ConformanceTestSuite::TruncateDebugPayload(string* payload) {
306   if (payload != nullptr && payload->size() > 200) {
307     payload->resize(200);
308     payload->append("...(truncated)");
309   }
310 }
311 
TruncateRequest(const ConformanceRequest & request)312 ConformanceRequest ConformanceTestSuite::TruncateRequest(
313     const ConformanceRequest& request) {
314   ConformanceRequest debug_request(request);
315   switch (debug_request.payload_case()) {
316     case ConformanceRequest::kProtobufPayload:
317       TruncateDebugPayload(debug_request.mutable_protobuf_payload());
318       break;
319     case ConformanceRequest::kJsonPayload:
320       TruncateDebugPayload(debug_request.mutable_json_payload());
321       break;
322     case ConformanceRequest::kTextPayload:
323       TruncateDebugPayload(debug_request.mutable_text_payload());
324       break;
325     case ConformanceRequest::kJspbPayload:
326       TruncateDebugPayload(debug_request.mutable_jspb_payload());
327       break;
328     default:
329       // Do nothing.
330       break;
331   }
332   return debug_request;
333 }
334 
TruncateResponse(const ConformanceResponse & response)335 ConformanceResponse ConformanceTestSuite::TruncateResponse(
336     const ConformanceResponse& response) {
337   ConformanceResponse debug_response(response);
338   switch (debug_response.result_case()) {
339     case ConformanceResponse::kProtobufPayload:
340       TruncateDebugPayload(debug_response.mutable_protobuf_payload());
341       break;
342     case ConformanceResponse::kJsonPayload:
343       TruncateDebugPayload(debug_response.mutable_json_payload());
344       break;
345     case ConformanceResponse::kTextPayload:
346       TruncateDebugPayload(debug_response.mutable_text_payload());
347       break;
348     case ConformanceResponse::kJspbPayload:
349       TruncateDebugPayload(debug_response.mutable_jspb_payload());
350       break;
351     default:
352       // Do nothing.
353       break;
354   }
355   return debug_response;
356 }
357 
ReportSuccess(const TestStatus & test)358 void ConformanceTestSuite::ReportSuccess(const TestStatus& test) {
359   if (expected_to_fail_.contains(test.name())) {
360     absl::StrAppendFormat(&output_,
361                           "ERROR: test %s (matched to %s) is in the failure "
362                           "list, but test succeeded.  "
363                           "Remove its match from the failure list.\n",
364                           test.name(),
365                           expected_to_fail_[test.name()].matched_name());
366     unexpected_succeeding_tests_[test.name()] = expected_to_fail_[test.name()];
367   }
368   expected_to_fail_.erase(test.name());
369   successes_++;
370 }
371 
ReportFailure(TestStatus & test,ConformanceLevel level,const ConformanceRequest & request,const ConformanceResponse & response)372 void ConformanceTestSuite::ReportFailure(TestStatus& test,
373                                          ConformanceLevel level,
374                                          const ConformanceRequest& request,
375                                          const ConformanceResponse& response) {
376   if (expected_to_fail_.contains(test.name())) {
377     // Make copy just this once, as we need to modify them for comparison.
378     // Failure message from the failure list.
379     string expected_failure_message =
380         expected_to_fail_[test.name()].failure_message();
381     // Actual failure message from the test run.
382     std::string actual_failure_message = test.failure_message();
383 
384     Normalize(actual_failure_message);
385     if (actual_failure_message.rfind(expected_failure_message, 0) == 0) {
386       // Our failure messages match.
387       expected_failures_++;
388     } else {
389       // We want to add the test to the failure list with its correct failure
390       // message.
391       unexpected_failure_messages_[test.name()] = FormatFailureMessage(test);
392       // We want to remove the test from the failure list. That means passing
393       // to it the same failure message that was in the list.
394       TestStatus incorrect_failure_message;
395       incorrect_failure_message.set_name(test.name());
396       incorrect_failure_message.set_failure_message(expected_failure_message);
397       incorrect_failure_message.set_matched_name(
398           expected_to_fail_[test.name()].matched_name());
399 
400       expected_failure_messages_[test.name()] = incorrect_failure_message;
401     }
402     expected_to_fail_.erase(test.name());
403     if (!verbose_) return;
404   } else if (level == RECOMMENDED && !enforce_recommended_) {
405     absl::StrAppendFormat(&output_, "WARNING, test=%s: ", test.name());
406   } else {
407     absl::StrAppendFormat(&output_, "ERROR, test=%s: ", test.name());
408 
409     unexpected_failing_tests_[test.name()] = FormatFailureMessage(test);
410   }
411 
412   absl::StrAppendFormat(&output_, "%s, request=%s, response=%s\n",
413                         test.failure_message(),
414                         TruncateRequest(request).ShortDebugString(),
415                         TruncateResponse(response).ShortDebugString());
416 }
417 
ReportSkip(const TestStatus & test,const ConformanceRequest & request,const ConformanceResponse & response)418 void ConformanceTestSuite::ReportSkip(const TestStatus& test,
419                                       const ConformanceRequest& request,
420                                       const ConformanceResponse& response) {
421   if (verbose_) {
422     absl::StrAppendFormat(
423         &output_, "SKIPPED, test=%s request=%s, response=%s\n", test.name(),
424         request.ShortDebugString(), response.ShortDebugString());
425   }
426   skipped_[test.name()] = test;
427 }
428 
RunValidInputTest(const ConformanceRequestSetting & setting,const std::string & equivalent_text_format)429 void ConformanceTestSuite::RunValidInputTest(
430     const ConformanceRequestSetting& setting,
431     const std::string& equivalent_text_format) {
432   std::unique_ptr<Message> reference_message(setting.NewTestMessage());
433   ABSL_CHECK(TextFormat::ParseFromString(equivalent_text_format,
434                                          reference_message.get()))
435       << "Failed to parse data for test case: " << setting.GetTestName()
436       << ", data: " << equivalent_text_format;
437   const std::string equivalent_wire_format =
438       reference_message->SerializeAsString();
439   RunValidBinaryInputTest(setting, equivalent_wire_format);
440 }
441 
RunValidBinaryInputTest(const ConformanceRequestSetting & setting,const std::string & equivalent_wire_format,bool require_same_wire_format)442 void ConformanceTestSuite::RunValidBinaryInputTest(
443     const ConformanceRequestSetting& setting,
444     const std::string& equivalent_wire_format, bool require_same_wire_format) {
445   const ConformanceRequest& request = setting.GetRequest();
446   ConformanceResponse response;
447   if (!RunTest(setting.GetTestName(), request, &response)) {
448     return;
449   }
450 
451   VerifyResponse(setting, equivalent_wire_format, response, true,
452                  require_same_wire_format);
453 }
454 
VerifyResponse(const ConformanceRequestSetting & setting,const std::string & equivalent_wire_format,const ConformanceResponse & response,bool need_report_success,bool require_same_wire_format)455 void ConformanceTestSuite::VerifyResponse(
456     const ConformanceRequestSetting& setting,
457     const std::string& equivalent_wire_format,
458     const ConformanceResponse& response, bool need_report_success,
459     bool require_same_wire_format) {
460   std::unique_ptr<Message> test_message(setting.NewTestMessage());
461   const ConformanceRequest& request = setting.GetRequest();
462   const std::string& test_name = setting.GetTestName();
463   ConformanceLevel level = setting.GetLevel();
464   std::unique_ptr<Message> reference_message = setting.NewTestMessage();
465 
466   ABSL_CHECK(reference_message->ParseFromString(equivalent_wire_format))
467       << "Failed to parse wire data for test case: " << test_name;
468 
469   TestStatus test;
470   test.set_name(test_name);
471 
472   switch (response.result_case()) {
473     case ConformanceResponse::RESULT_NOT_SET:
474       test.set_failure_message(
475           "Response didn't have any field in the Response.");
476       ReportFailure(test, level, request, response);
477       return;
478 
479     case ConformanceResponse::kParseError:
480     case ConformanceResponse::kTimeoutError:
481     case ConformanceResponse::kRuntimeError:
482     case ConformanceResponse::kSerializeError:
483       test.set_failure_message("Failed to parse input or produce output.");
484       ReportFailure(test, level, request, response);
485       return;
486 
487     case ConformanceResponse::kSkipped:
488       ReportSkip(test, request, response);
489       return;
490 
491     default:
492       if (!ParseResponse(response, setting, test_message.get())) return;
493   }
494 
495   MessageDifferencer differencer;
496   DefaultFieldComparator field_comparator;
497   field_comparator.set_treat_nan_as_equal(true);
498   differencer.set_field_comparator(&field_comparator);
499   std::string differences;
500   differencer.ReportDifferencesToString(&differences);
501 
502   bool check = false;
503 
504   if (require_same_wire_format) {
505     ABSL_DCHECK_EQ(response.result_case(),
506                    ConformanceResponse::kProtobufPayload);
507     const std::string& protobuf_payload = response.protobuf_payload();
508     check = equivalent_wire_format == protobuf_payload;
509     differences = absl::StrCat("Expect: ", ToOctString(equivalent_wire_format),
510                                ", but got: ", ToOctString(protobuf_payload));
511   } else {
512     check = differencer.Compare(*reference_message, *test_message);
513   }
514   if (check) {
515     if (need_report_success) {
516       ReportSuccess(test);
517     }
518   } else {
519     test.set_failure_message(absl::StrCat(
520         "Output was not equivalent to reference message: ", differences));
521     ReportFailure(test, level, request, response);
522   }
523 }
524 
RunTest(const std::string & test_name,const ConformanceRequest & request,ConformanceResponse * response)525 bool ConformanceTestSuite::RunTest(const std::string& test_name,
526                                    const ConformanceRequest& request,
527                                    ConformanceResponse* response) {
528   if (test_names_ran_.insert(test_name).second == false) {
529     ABSL_LOG(FATAL) << "Duplicated test name: " << test_name;
530   }
531 
532   // In essence, find what wildcarded test names expand to or direct matches
533   // (without wildcards).
534   auto result = failure_list_root_.WalkDownMatch(test_name);
535   if (result.has_value()) {
536     string matched_equivalent = result.value();
537     unmatched_.erase(matched_equivalent);
538     TestStatus expansion;
539     expansion.set_name(test_name);
540     expansion.set_matched_name(matched_equivalent);
541     expansion.set_failure_message(saved_failure_messages_[matched_equivalent]);
542     expected_to_fail_[test_name] = expansion;
543 
544     if (number_of_matches_.contains(matched_equivalent)) {
545       if (number_of_matches_[matched_equivalent] > kMaximumWildcardExpansions &&
546           !exceeded_max_matches_.contains(matched_equivalent)) {
547         exceeded_max_matches_[matched_equivalent] = expansion;
548       }
549       number_of_matches_[matched_equivalent]++;
550     } else {
551       number_of_matches_[matched_equivalent] = 1;
552     }
553   }
554 
555   std::string serialized_request;
556   std::string serialized_response;
557   request.SerializeToString(&serialized_request);
558 
559   uint32_t len = internal::little_endian::FromHost(
560       static_cast<uint32_t>(serialized_request.size()));
561 
562   if (isolated_) {
563     if (names_to_test_.erase(test_name) ==
564         0) {  // Tests were asked to be run in isolated mode, but this test was
565               // not asked to be run.
566       expected_to_fail_.erase(test_name);
567       return false;
568     }
569     if (debug_) {
570       std::string octal = ProduceOctalSerialized(serialized_request, len);
571       std::string full_filename = WriteToFile(octal, output_dir_, test_name);
572       if (!full_filename.empty()) {
573         absl::StrAppendFormat(
574             &output_, "Produced octal serialized request file for test %s\n",
575             test_name);
576         absl::StrAppendFormat(
577             &output_,
578             "  To pipe the "
579             "serialized request directly to "
580             "the "
581             "testee run from the root of your workspace:\n    printf $("
582             "<\"%s\") | %s\n\n",
583             full_filename, testee_);
584         absl::StrAppendFormat(
585             &output_,
586             "  To inspect the wire format of the serialized request with "
587             "protoscope run "
588             "(Disclaimer: This may not work properly on non-Linux "
589             "platforms):\n  "
590             "  "
591             "contents=$(<\"%s\"); sub=$(cut -d \\\\ -f 6- <<< "
592             "$contents) ; printf \"\\\\${sub}\" | protoscope \n\n\n",
593             full_filename);
594       }
595     }
596   }
597 
598   response->set_protobuf_payload(serialized_request);
599 
600   runner_->RunTest(test_name, len, serialized_request, &serialized_response);
601 
602   if (!response->ParseFromString(serialized_response)) {
603     response->Clear();
604     response->set_runtime_error("response proto could not be parsed.");
605   }
606 
607   if (verbose_) {
608     absl::StrAppendFormat(
609         &output_, "conformance test: name=%s, request=%s, response=%s\n",
610         test_name, TruncateRequest(request).ShortDebugString(),
611         TruncateResponse(*response).ShortDebugString());
612   }
613   return true;
614 }
615 
WireFormatToString(WireFormat wire_format)616 std::string ConformanceTestSuite::WireFormatToString(WireFormat wire_format) {
617   switch (wire_format) {
618     case conformance::PROTOBUF:
619       return "PROTOBUF";
620     case conformance::JSON:
621       return "JSON";
622     case conformance::JSPB:
623       return "JSPB";
624     case conformance::TEXT_FORMAT:
625       return "TEXT_FORMAT";
626     case conformance::UNSPECIFIED:
627       return "UNSPECIFIED";
628     default:
629       ABSL_LOG(FATAL) << "unknown wire type: " << wire_format;
630   }
631   return "";
632 }
633 
AddExpectedFailedTest(const TestStatus & expected_failure)634 bool ConformanceTestSuite::AddExpectedFailedTest(
635     const TestStatus& expected_failure) {
636   absl::Status attempt = failure_list_root_.Insert(expected_failure.name());
637   if (!attempt.ok()) {
638     absl::StrAppend(&output_, attempt.message(), "\n\n");
639     return false;
640   }
641   unmatched_[expected_failure.name()] = expected_failure;
642   saved_failure_messages_[expected_failure.name()] =
643       expected_failure.failure_message();
644   return true;
645 }
646 
RunSuite(ConformanceTestRunner * runner,std::string * output,const std::string & filename,conformance::FailureSet * failure_list)647 bool ConformanceTestSuite::RunSuite(ConformanceTestRunner* runner,
648                                     std::string* output,
649                                     const std::string& filename,
650                                     conformance::FailureSet* failure_list) {
651   runner_ = runner;
652   failure_list_root_ = FailureListTrieNode("root");
653   successes_ = 0;
654   expected_failures_ = 0;
655   skipped_.clear();
656   test_names_ran_.clear();
657   unexpected_failing_tests_.clear();
658   unexpected_succeeding_tests_.clear();
659 
660   std::string mode = debug_ ? "DEBUG" : "TEST";
661   absl::StrAppendFormat(
662       &output_, "CONFORMANCE %s BEGIN ====================================\n\n",
663       mode);
664 
665   failure_list_filename_ = filename;
666   expected_to_fail_.clear();
667   for (const TestStatus& expected_failure : failure_list->test()) {
668     if (!AddExpectedFailedTest(expected_failure)) {
669       output->assign(output_);
670       return false;
671     }
672   }
673 
674   RunSuiteImpl();
675 
676   if (*output_dir_.rbegin() != '/') {
677     output_dir_.push_back('/');
678   }
679 
680   bool ok = true;
681   if (!CheckSetEmpty(
682           unmatched_, "unmatched.txt",
683           absl::StrCat(
684               "These test names were listed in the failure list, but they "
685               "didn't match any actual test name.  Remove them from the "
686               "failure list by running from the root of your workspace:\n"
687               "  bazel run "
688               "//google/protobuf/conformance:update_failure_list -- ",
689               failure_list_filename_, " --remove ", output_dir_,
690               "unmatched.txt"),
691           output_dir_, &output_)) {
692     ok = false;
693   }
694 
695   if (!CheckSetEmpty(
696           expected_failure_messages_, "expected_failure_messages.txt",
697           absl::StrCat(
698               "These tests (either expanded from wildcard(s) or direct "
699               "matches) were listed in the failure list, but their "
700               "failure messages do not match.  Remove their match from the "
701               "failure list by running from the root of your workspace:\n"
702               "  bazel run ",
703               "//google/protobuf/conformance:update_failure_list -- ",
704               failure_list_filename_, " --remove ", output_dir_,
705               "expected_failure_messages.txt"),
706           output_dir_, &output_)) {
707     ok = false;
708   }
709 
710   if (!CheckSetEmpty(
711           unexpected_succeeding_tests_, "succeeding_tests.txt",
712           absl::StrCat(
713               "These tests succeeded, even though they were listed in "
714               "the failure list (expanded from wildcard(s) or direct matches). "
715               " Remove their match from the failure list by "
716               "running from the root of your workspace:\n"
717               "  bazel run "
718               "//google/protobuf/conformance:update_failure_list -- ",
719               failure_list_filename_, " --remove ", output_dir_,
720               "succeeding_tests.txt"),
721           output_dir_, &output_)) {
722     ok = false;
723   }
724 
725   if (!CheckSetEmpty(
726           exceeded_max_matches_, "exceeded_max_matches.txt",
727           absl::StrFormat(
728               "These failure list entries served as matches to too many test "
729               "names exceeding the max amount of %d.  "
730               "Remove them from the failure list by running from the root of "
731               "your workspace:\n"
732               "  bazel run "
733               "//google/protobuf/conformance:update_failure_list -- %s "
734               "--remove %sexceeded_max_matches.txt",
735               kMaximumWildcardExpansions, failure_list_filename_, output_dir_),
736           output_dir_, &output_)) {
737     ok = false;
738   }
739 
740   if (!CheckSetEmpty(
741           unexpected_failure_messages_, "unexpected_failure_messages.txt",
742           absl::StrCat(
743               "These tests (expanded from wildcard(s) or direct matches from "
744               "the failure list) failed because their failure messages did "
745               "not match.  If they can't be fixed right now, "
746               "you can add them to the failure list so the overall "
747               "suite can succeed.  Add them to the failure list by "
748               "running from the root of your workspace:\n"
749               "  bazel run "
750               "//google/protobuf/conformance:update_failure_list -- ",
751               failure_list_filename_, " --add ", output_dir_,
752               "unexpected_failure_messages.txt"),
753           output_dir_, &output_)) {
754     ok = false;
755   }
756 
757   if (!CheckSetEmpty(
758           unexpected_failing_tests_, "failing_tests.txt",
759           absl::StrCat(
760               "These tests failed.  If they can't be fixed right now, "
761               "you can add them to the failure list so the overall "
762               "suite can succeed.  Add them to the failure list by "
763               "running from the root of your workspace:\n"
764               "  bazel run "
765               "//google/protobuf/conformance:update_failure_list -- ",
766               failure_list_filename_, " --add ", output_dir_,
767               "failing_tests.txt"),
768           output_dir_, &output_)) {
769     ok = false;
770   }
771 
772   if (verbose_) {
773     CheckSetEmpty(skipped_, "",
774                   "These tests were skipped (probably because support for some "
775                   "features is not implemented)",
776                   output_dir_, &output_);
777   }
778 
779   absl::StrAppendFormat(&output_,
780                         "CONFORMANCE SUITE %s: %d successes, %zu skipped, "
781                         "%d expected failures, %zu unexpected failures.\n",
782                         ok ? "PASSED" : "FAILED", successes_, skipped_.size(),
783                         expected_failures_, unexpected_failing_tests_.size());
784   absl::StrAppendFormat(&output_, "\n");
785 
786   output->assign(output_);
787 
788   return ok;
789 }
790 
791 }  // namespace protobuf
792 }  // namespace google
793