1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc. All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 // * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 // * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 // * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31 #include "conformance_test.h"
32
33 #include <stdarg.h>
34
35 #include <fstream>
36 #include <set>
37 #include <string>
38
39 #include <google/protobuf/stubs/stringprintf.h>
40 #include <google/protobuf/message.h>
41 #include <google/protobuf/text_format.h>
42 #include <google/protobuf/util/field_comparator.h>
43 #include <google/protobuf/util/json_util.h>
44 #include <google/protobuf/util/message_differencer.h>
45 #include "conformance.pb.h"
46
47 using conformance::ConformanceRequest;
48 using conformance::ConformanceResponse;
49 using conformance::WireFormat;
50 using google::protobuf::TextFormat;
51 using google::protobuf::util::DefaultFieldComparator;
52 using google::protobuf::util::MessageDifferencer;
53 using std::string;
54
55 namespace {
56
ToOctString(const string & binary_string)57 static string ToOctString(const string& binary_string) {
58 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 } // namespace
73
74 namespace google {
75 namespace protobuf {
76
ConformanceRequestSetting(ConformanceLevel level,conformance::WireFormat input_format,conformance::WireFormat output_format,conformance::TestCategory test_category,const Message & prototype_message,const string & test_name,const string & input)77 ConformanceTestSuite::ConformanceRequestSetting::ConformanceRequestSetting(
78 ConformanceLevel level,
79 conformance::WireFormat input_format,
80 conformance::WireFormat output_format,
81 conformance::TestCategory test_category,
82 const Message& prototype_message,
83 const string& test_name, const string& input)
84 : level_(level),
85 input_format_(input_format),
86 output_format_(output_format),
87 prototype_message_(prototype_message),
88 prototype_message_for_compare_(prototype_message.New()),
89 test_name_(test_name) {
90 switch (input_format) {
91 case conformance::PROTOBUF: {
92 request_.set_protobuf_payload(input);
93 break;
94 }
95
96 case conformance::JSON: {
97 request_.set_json_payload(input);
98 break;
99 }
100
101 case conformance::JSPB: {
102 request_.set_jspb_payload(input);
103 break;
104 }
105
106 case conformance::TEXT_FORMAT: {
107 request_.set_text_payload(input);
108 break;
109 }
110
111 default:
112 GOOGLE_LOG(FATAL) << "Unspecified input format";
113 }
114
115 request_.set_test_category(test_category);
116
117 request_.set_message_type(prototype_message.GetDescriptor()->full_name());
118 request_.set_requested_output_format(output_format);
119 }
120
121 std::unique_ptr<Message>
NewTestMessage() const122 ConformanceTestSuite::ConformanceRequestSetting::NewTestMessage() const {
123 return std::unique_ptr<Message>(prototype_message_for_compare_->New());
124 }
125
126 string ConformanceTestSuite::ConformanceRequestSetting::
GetTestName() const127 GetTestName() const {
128 string rname =
129 prototype_message_.GetDescriptor()->file()->syntax() ==
130 FileDescriptor::SYNTAX_PROTO3 ? "Proto3" : "Proto2";
131
132 return StrCat(ConformanceLevelToString(level_), ".", rname, ".",
133 InputFormatString(input_format_), ".", test_name_, ".",
134 OutputFormatString(output_format_));
135 }
136
137 string ConformanceTestSuite::ConformanceRequestSetting::
ConformanceLevelToString(ConformanceLevel level) const138 ConformanceLevelToString(
139 ConformanceLevel level) const {
140 switch (level) {
141 case REQUIRED: return "Required";
142 case RECOMMENDED: return "Recommended";
143 }
144 GOOGLE_LOG(FATAL) << "Unknown value: " << level;
145 return "";
146 }
147
148 string ConformanceTestSuite::ConformanceRequestSetting::
InputFormatString(conformance::WireFormat format) const149 InputFormatString(conformance::WireFormat format) const {
150 switch (format) {
151 case conformance::PROTOBUF:
152 return "ProtobufInput";
153 case conformance::JSON:
154 return "JsonInput";
155 case conformance::TEXT_FORMAT:
156 return "TextFormatInput";
157 default:
158 GOOGLE_LOG(FATAL) << "Unspecified output format";
159 }
160 return "";
161 }
162
163 string ConformanceTestSuite::ConformanceRequestSetting::
OutputFormatString(conformance::WireFormat format) const164 OutputFormatString(conformance::WireFormat format) const {
165 switch (format) {
166 case conformance::PROTOBUF:
167 return "ProtobufOutput";
168 case conformance::JSON:
169 return "JsonOutput";
170 case conformance::TEXT_FORMAT:
171 return "TextFormatOutput";
172 default:
173 GOOGLE_LOG(FATAL) << "Unspecified output format";
174 }
175 return "";
176 }
177
ReportSuccess(const string & test_name)178 void ConformanceTestSuite::ReportSuccess(const string& test_name) {
179 if (expected_to_fail_.erase(test_name) != 0) {
180 StringAppendF(&output_,
181 "ERROR: test %s is in the failure list, but test succeeded. "
182 "Remove it from the failure list.\n",
183 test_name.c_str());
184 unexpected_succeeding_tests_.insert(test_name);
185 }
186 successes_++;
187 }
188
ReportFailure(const string & test_name,ConformanceLevel level,const ConformanceRequest & request,const ConformanceResponse & response,const char * fmt,...)189 void ConformanceTestSuite::ReportFailure(const string& test_name,
190 ConformanceLevel level,
191 const ConformanceRequest& request,
192 const ConformanceResponse& response,
193 const char* fmt, ...) {
194 if (expected_to_fail_.erase(test_name) == 1) {
195 expected_failures_++;
196 if (!verbose_)
197 return;
198 } else if (level == RECOMMENDED && !enforce_recommended_) {
199 StringAppendF(&output_, "WARNING, test=%s: ", test_name.c_str());
200 } else {
201 StringAppendF(&output_, "ERROR, test=%s: ", test_name.c_str());
202 unexpected_failing_tests_.insert(test_name);
203 }
204 va_list args;
205 va_start(args, fmt);
206 StringAppendV(&output_, fmt, args);
207 va_end(args);
208 StringAppendF(&output_, " request=%s, response=%s\n",
209 request.ShortDebugString().c_str(),
210 response.ShortDebugString().c_str());
211 }
212
ReportSkip(const string & test_name,const ConformanceRequest & request,const ConformanceResponse & response)213 void ConformanceTestSuite::ReportSkip(const string& test_name,
214 const ConformanceRequest& request,
215 const ConformanceResponse& response) {
216 if (verbose_) {
217 StringAppendF(&output_, "SKIPPED, test=%s request=%s, response=%s\n",
218 test_name.c_str(), request.ShortDebugString().c_str(),
219 response.ShortDebugString().c_str());
220 }
221 skipped_.insert(test_name);
222 }
223
RunValidInputTest(const ConformanceRequestSetting & setting,const string & equivalent_text_format)224 void ConformanceTestSuite::RunValidInputTest(
225 const ConformanceRequestSetting& setting,
226 const string& equivalent_text_format) {
227 std::unique_ptr<Message> reference_message(setting.NewTestMessage());
228 GOOGLE_CHECK(TextFormat::ParseFromString(equivalent_text_format,
229 reference_message.get()))
230 << "Failed to parse data for test case: " << setting.GetTestName()
231 << ", data: " << equivalent_text_format;
232 const string equivalent_wire_format = reference_message->SerializeAsString();
233 RunValidBinaryInputTest(setting, equivalent_wire_format);
234 }
235
RunValidBinaryInputTest(const ConformanceRequestSetting & setting,const string & equivalent_wire_format,bool require_same_wire_format)236 void ConformanceTestSuite::RunValidBinaryInputTest(
237 const ConformanceRequestSetting& setting,
238 const string& equivalent_wire_format, bool require_same_wire_format) {
239 const ConformanceRequest& request = setting.GetRequest();
240 ConformanceResponse response;
241 RunTest(setting.GetTestName(), request, &response);
242 VerifyResponse(setting, equivalent_wire_format, response, true,
243 require_same_wire_format);
244 }
245
VerifyResponse(const ConformanceRequestSetting & setting,const string & equivalent_wire_format,const ConformanceResponse & response,bool need_report_success,bool require_same_wire_format)246 void ConformanceTestSuite::VerifyResponse(
247 const ConformanceRequestSetting& setting,
248 const string& equivalent_wire_format, const ConformanceResponse& response,
249 bool need_report_success, bool require_same_wire_format) {
250 std::unique_ptr<Message> test_message(setting.NewTestMessage());
251 const ConformanceRequest& request = setting.GetRequest();
252 const string& test_name = setting.GetTestName();
253 ConformanceLevel level = setting.GetLevel();
254 std::unique_ptr<Message> reference_message = setting.NewTestMessage();
255
256 GOOGLE_CHECK(reference_message->ParseFromString(equivalent_wire_format))
257 << "Failed to parse wire data for test case: " << test_name;
258
259 switch (response.result_case()) {
260 case ConformanceResponse::RESULT_NOT_SET:
261 ReportFailure(test_name, level, request, response,
262 "Response didn't have any field in the Response.");
263 return;
264
265 case ConformanceResponse::kParseError:
266 case ConformanceResponse::kRuntimeError:
267 case ConformanceResponse::kSerializeError:
268 ReportFailure(test_name, level, request, response,
269 "Failed to parse input or produce output.");
270 return;
271
272 case ConformanceResponse::kSkipped:
273 ReportSkip(test_name, request, response);
274 return;
275
276 default:
277 if (!ParseResponse(response, setting, test_message.get())) return;
278 }
279
280 MessageDifferencer differencer;
281 DefaultFieldComparator field_comparator;
282 field_comparator.set_treat_nan_as_equal(true);
283 differencer.set_field_comparator(&field_comparator);
284 string differences;
285 differencer.ReportDifferencesToString(&differences);
286
287 bool check = false;
288
289 if (require_same_wire_format) {
290 GOOGLE_DCHECK_EQ(response.result_case(), ConformanceResponse::kProtobufPayload);
291 const string& protobuf_payload = response.protobuf_payload();
292 check = equivalent_wire_format == protobuf_payload;
293 differences = StrCat("Expect: ", ToOctString(equivalent_wire_format),
294 ", but got: ", ToOctString(protobuf_payload));
295 } else {
296 check = differencer.Compare(*reference_message, *test_message);
297 }
298
299 if (check) {
300 if (need_report_success) {
301 ReportSuccess(test_name);
302 }
303 } else {
304 ReportFailure(test_name, level, request, response,
305 "Output was not equivalent to reference message: %s.",
306 differences.c_str());
307 }
308 }
309
RunTest(const string & test_name,const ConformanceRequest & request,ConformanceResponse * response)310 void ConformanceTestSuite::RunTest(const string& test_name,
311 const ConformanceRequest& request,
312 ConformanceResponse* response) {
313 if (test_names_.insert(test_name).second == false) {
314 GOOGLE_LOG(FATAL) << "Duplicated test name: " << test_name;
315 }
316
317 string serialized_request;
318 string serialized_response;
319 request.SerializeToString(&serialized_request);
320
321 runner_->RunTest(test_name, serialized_request, &serialized_response);
322
323 if (!response->ParseFromString(serialized_response)) {
324 response->Clear();
325 response->set_runtime_error("response proto could not be parsed.");
326 }
327
328 if (verbose_) {
329 StringAppendF(&output_,
330 "conformance test: name=%s, request=%s, response=%s\n",
331 test_name.c_str(),
332 request.ShortDebugString().c_str(),
333 response->ShortDebugString().c_str());
334 }
335 }
336
CheckSetEmpty(const std::set<string> & set_to_check,const std::string & write_to_file,const std::string & msg)337 bool ConformanceTestSuite::CheckSetEmpty(
338 const std::set<string>& set_to_check,
339 const std::string& write_to_file,
340 const std::string& msg) {
341 if (set_to_check.empty()) {
342 return true;
343 } else {
344 StringAppendF(&output_, "\n");
345 StringAppendF(&output_, "%s\n\n", msg.c_str());
346 for (std::set<string>::const_iterator iter = set_to_check.begin();
347 iter != set_to_check.end(); ++iter) {
348 StringAppendF(&output_, " %s\n", iter->c_str());
349 }
350 StringAppendF(&output_, "\n");
351
352 if (!write_to_file.empty()) {
353 std::ofstream os(write_to_file);
354 if (os) {
355 for (std::set<string>::const_iterator iter = set_to_check.begin();
356 iter != set_to_check.end(); ++iter) {
357 os << *iter << "\n";
358 }
359 } else {
360 StringAppendF(&output_, "Failed to open file: %s\n",
361 write_to_file.c_str());
362 }
363 }
364
365 return false;
366 }
367 }
368
WireFormatToString(WireFormat wire_format)369 string ConformanceTestSuite::WireFormatToString(
370 WireFormat wire_format) {
371 switch (wire_format) {
372 case conformance::PROTOBUF:
373 return "PROTOBUF";
374 case conformance::JSON:
375 return "JSON";
376 case conformance::JSPB:
377 return "JSPB";
378 case conformance::TEXT_FORMAT:
379 return "TEXT_FORMAT";
380 case conformance::UNSPECIFIED:
381 return "UNSPECIFIED";
382 default:
383 GOOGLE_LOG(FATAL) << "unknown wire type: " << wire_format;
384 }
385 return "";
386 }
387
AddExpectedFailedTest(const std::string & test_name)388 void ConformanceTestSuite::AddExpectedFailedTest(const std::string& test_name) {
389 expected_to_fail_.insert(test_name);
390 }
391
RunSuite(ConformanceTestRunner * runner,std::string * output,const string & filename,conformance::FailureSet * failure_list)392 bool ConformanceTestSuite::RunSuite(ConformanceTestRunner* runner,
393 std::string* output, const string& filename,
394 conformance::FailureSet* failure_list) {
395 runner_ = runner;
396 successes_ = 0;
397 expected_failures_ = 0;
398 skipped_.clear();
399 test_names_.clear();
400 unexpected_failing_tests_.clear();
401 unexpected_succeeding_tests_.clear();
402
403 output_ = "\nCONFORMANCE TEST BEGIN ====================================\n\n";
404
405 failure_list_filename_ = filename;
406 expected_to_fail_.clear();
407 for (const string& failure : failure_list->failure()) {
408 AddExpectedFailedTest(failure);
409 }
410 RunSuiteImpl();
411
412 bool ok = true;
413 if (!CheckSetEmpty(expected_to_fail_, "nonexistent_tests.txt",
414 "These tests were listed in the failure list, but they "
415 "don't exist. Remove them from the failure list by "
416 "running:\n"
417 " ./update_failure_list.py " + failure_list_filename_ +
418 " --remove nonexistent_tests.txt")) {
419 ok = false;
420 }
421 if (!CheckSetEmpty(unexpected_failing_tests_, "failing_tests.txt",
422 "These tests failed. If they can't be fixed right now, "
423 "you can add them to the failure list so the overall "
424 "suite can succeed. Add them to the failure list by "
425 "running:\n"
426 " ./update_failure_list.py " + failure_list_filename_ +
427 " --add failing_tests.txt")) {
428 ok = false;
429 }
430 if (!CheckSetEmpty(unexpected_succeeding_tests_, "succeeding_tests.txt",
431 "These tests succeeded, even though they were listed in "
432 "the failure list. Remove them from the failure list "
433 "by running:\n"
434 " ./update_failure_list.py " + failure_list_filename_ +
435 " --remove succeeding_tests.txt")) {
436 ok = false;
437 }
438
439 if (verbose_) {
440 CheckSetEmpty(skipped_, "",
441 "These tests were skipped (probably because support for some "
442 "features is not implemented)");
443 }
444
445 StringAppendF(&output_,
446 "CONFORMANCE SUITE %s: %d successes, %zu skipped, "
447 "%d expected failures, %zu unexpected failures.\n",
448 ok ? "PASSED" : "FAILED", successes_, skipped_.size(),
449 expected_failures_, unexpected_failing_tests_.size());
450 StringAppendF(&output_, "\n");
451
452 output->assign(output_);
453
454 return ok;
455 }
456
457 } // namespace protobuf
458 } // namespace google
459