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