| OLD | NEW |
| (Empty) | |
| 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 <stdarg.h> |
| 32 #include <string> |
| 33 |
| 34 #include "conformance.pb.h" |
| 35 #include "conformance_test.h" |
| 36 #include <google/protobuf/stubs/common.h> |
| 37 #include <google/protobuf/stubs/stringprintf.h> |
| 38 #include <google/protobuf/text_format.h> |
| 39 #include <google/protobuf/util/json_util.h> |
| 40 #include <google/protobuf/util/message_differencer.h> |
| 41 #include <google/protobuf/util/type_resolver_util.h> |
| 42 #include <google/protobuf/wire_format_lite.h> |
| 43 |
| 44 using conformance::ConformanceRequest; |
| 45 using conformance::ConformanceResponse; |
| 46 using conformance::TestAllTypes; |
| 47 using conformance::WireFormat; |
| 48 using google::protobuf::Descriptor; |
| 49 using google::protobuf::FieldDescriptor; |
| 50 using google::protobuf::internal::WireFormatLite; |
| 51 using google::protobuf::TextFormat; |
| 52 using google::protobuf::util::JsonToBinaryString; |
| 53 using google::protobuf::util::MessageDifferencer; |
| 54 using google::protobuf::util::NewTypeResolverForDescriptorPool; |
| 55 using google::protobuf::util::Status; |
| 56 using std::string; |
| 57 |
| 58 namespace { |
| 59 |
| 60 static const char kTypeUrlPrefix[] = "type.googleapis.com"; |
| 61 |
| 62 static string GetTypeUrl(const Descriptor* message) { |
| 63 return string(kTypeUrlPrefix) + "/" + message->full_name(); |
| 64 } |
| 65 |
| 66 /* Routines for building arbitrary protos *************************************/ |
| 67 |
| 68 // We would use CodedOutputStream except that we want more freedom to build |
| 69 // arbitrary protos (even invalid ones). |
| 70 |
| 71 const string empty; |
| 72 |
| 73 string cat(const string& a, const string& b, |
| 74 const string& c = empty, |
| 75 const string& d = empty, |
| 76 const string& e = empty, |
| 77 const string& f = empty, |
| 78 const string& g = empty, |
| 79 const string& h = empty, |
| 80 const string& i = empty, |
| 81 const string& j = empty, |
| 82 const string& k = empty, |
| 83 const string& l = empty) { |
| 84 string ret; |
| 85 ret.reserve(a.size() + b.size() + c.size() + d.size() + e.size() + f.size() + |
| 86 g.size() + h.size() + i.size() + j.size() + k.size() + l.size()); |
| 87 ret.append(a); |
| 88 ret.append(b); |
| 89 ret.append(c); |
| 90 ret.append(d); |
| 91 ret.append(e); |
| 92 ret.append(f); |
| 93 ret.append(g); |
| 94 ret.append(h); |
| 95 ret.append(i); |
| 96 ret.append(j); |
| 97 ret.append(k); |
| 98 ret.append(l); |
| 99 return ret; |
| 100 } |
| 101 |
| 102 // The maximum number of bytes that it takes to encode a 64-bit varint. |
| 103 #define VARINT_MAX_LEN 10 |
| 104 |
| 105 size_t vencode64(uint64_t val, char *buf) { |
| 106 if (val == 0) { buf[0] = 0; return 1; } |
| 107 size_t i = 0; |
| 108 while (val) { |
| 109 uint8_t byte = val & 0x7fU; |
| 110 val >>= 7; |
| 111 if (val) byte |= 0x80U; |
| 112 buf[i++] = byte; |
| 113 } |
| 114 return i; |
| 115 } |
| 116 |
| 117 string varint(uint64_t x) { |
| 118 char buf[VARINT_MAX_LEN]; |
| 119 size_t len = vencode64(x, buf); |
| 120 return string(buf, len); |
| 121 } |
| 122 |
| 123 // TODO: proper byte-swapping for big-endian machines. |
| 124 string fixed32(void *data) { return string(static_cast<char*>(data), 4); } |
| 125 string fixed64(void *data) { return string(static_cast<char*>(data), 8); } |
| 126 |
| 127 string delim(const string& buf) { return cat(varint(buf.size()), buf); } |
| 128 string uint32(uint32_t u32) { return fixed32(&u32); } |
| 129 string uint64(uint64_t u64) { return fixed64(&u64); } |
| 130 string flt(float f) { return fixed32(&f); } |
| 131 string dbl(double d) { return fixed64(&d); } |
| 132 string zz32(int32_t x) { return varint(WireFormatLite::ZigZagEncode32(x)); } |
| 133 string zz64(int64_t x) { return varint(WireFormatLite::ZigZagEncode64(x)); } |
| 134 |
| 135 string tag(uint32_t fieldnum, char wire_type) { |
| 136 return varint((fieldnum << 3) | wire_type); |
| 137 } |
| 138 |
| 139 string submsg(uint32_t fn, const string& buf) { |
| 140 return cat( tag(fn, WireFormatLite::WIRETYPE_LENGTH_DELIMITED), delim(buf) ); |
| 141 } |
| 142 |
| 143 #define UNKNOWN_FIELD 666 |
| 144 |
| 145 uint32_t GetFieldNumberForType(FieldDescriptor::Type type, bool repeated) { |
| 146 const Descriptor* d = TestAllTypes().GetDescriptor(); |
| 147 for (int i = 0; i < d->field_count(); i++) { |
| 148 const FieldDescriptor* f = d->field(i); |
| 149 if (f->type() == type && f->is_repeated() == repeated) { |
| 150 return f->number(); |
| 151 } |
| 152 } |
| 153 GOOGLE_LOG(FATAL) << "Couldn't find field with type " << (int)type; |
| 154 return 0; |
| 155 } |
| 156 |
| 157 string UpperCase(string str) { |
| 158 for (int i = 0; i < str.size(); i++) { |
| 159 str[i] = toupper(str[i]); |
| 160 } |
| 161 return str; |
| 162 } |
| 163 |
| 164 } // anonymous namespace |
| 165 |
| 166 namespace google { |
| 167 namespace protobuf { |
| 168 |
| 169 void ConformanceTestSuite::ReportSuccess(const string& test_name) { |
| 170 if (expected_to_fail_.erase(test_name) != 0) { |
| 171 StringAppendF(&output_, |
| 172 "ERROR: test %s is in the failure list, but test succeeded. " |
| 173 "Remove it from the failure list.\n", |
| 174 test_name.c_str()); |
| 175 unexpected_succeeding_tests_.insert(test_name); |
| 176 } |
| 177 successes_++; |
| 178 } |
| 179 |
| 180 void ConformanceTestSuite::ReportFailure(const string& test_name, |
| 181 const ConformanceRequest& request, |
| 182 const ConformanceResponse& response, |
| 183 const char* fmt, ...) { |
| 184 if (expected_to_fail_.erase(test_name) == 1) { |
| 185 expected_failures_++; |
| 186 if (!verbose_) |
| 187 return; |
| 188 } else { |
| 189 StringAppendF(&output_, "ERROR, test=%s: ", test_name.c_str()); |
| 190 unexpected_failing_tests_.insert(test_name); |
| 191 } |
| 192 va_list args; |
| 193 va_start(args, fmt); |
| 194 StringAppendV(&output_, fmt, args); |
| 195 va_end(args); |
| 196 StringAppendF(&output_, " request=%s, response=%s\n", |
| 197 request.ShortDebugString().c_str(), |
| 198 response.ShortDebugString().c_str()); |
| 199 } |
| 200 |
| 201 void ConformanceTestSuite::ReportSkip(const string& test_name, |
| 202 const ConformanceRequest& request, |
| 203 const ConformanceResponse& response) { |
| 204 if (verbose_) { |
| 205 StringAppendF(&output_, "SKIPPED, test=%s request=%s, response=%s\n", |
| 206 test_name.c_str(), request.ShortDebugString().c_str(), |
| 207 response.ShortDebugString().c_str()); |
| 208 } |
| 209 skipped_.insert(test_name); |
| 210 } |
| 211 |
| 212 void ConformanceTestSuite::RunTest(const string& test_name, |
| 213 const ConformanceRequest& request, |
| 214 ConformanceResponse* response) { |
| 215 if (test_names_.insert(test_name).second == false) { |
| 216 GOOGLE_LOG(FATAL) << "Duplicated test name: " << test_name; |
| 217 } |
| 218 |
| 219 string serialized_request; |
| 220 string serialized_response; |
| 221 request.SerializeToString(&serialized_request); |
| 222 |
| 223 runner_->RunTest(serialized_request, &serialized_response); |
| 224 |
| 225 if (!response->ParseFromString(serialized_response)) { |
| 226 response->Clear(); |
| 227 response->set_runtime_error("response proto could not be parsed."); |
| 228 } |
| 229 |
| 230 if (verbose_) { |
| 231 StringAppendF(&output_, "conformance test: name=%s, request=%s, response=%s\
n", |
| 232 test_name.c_str(), |
| 233 request.ShortDebugString().c_str(), |
| 234 response->ShortDebugString().c_str()); |
| 235 } |
| 236 } |
| 237 |
| 238 void ConformanceTestSuite::RunValidInputTest( |
| 239 const string& test_name, const string& input, WireFormat input_format, |
| 240 const string& equivalent_text_format, WireFormat requested_output) { |
| 241 TestAllTypes reference_message; |
| 242 GOOGLE_CHECK( |
| 243 TextFormat::ParseFromString(equivalent_text_format, &reference_message)); |
| 244 |
| 245 ConformanceRequest request; |
| 246 ConformanceResponse response; |
| 247 |
| 248 switch (input_format) { |
| 249 case conformance::PROTOBUF: |
| 250 request.set_protobuf_payload(input); |
| 251 break; |
| 252 |
| 253 case conformance::JSON: |
| 254 request.set_json_payload(input); |
| 255 break; |
| 256 |
| 257 case conformance::UNSPECIFIED: |
| 258 GOOGLE_LOG(FATAL) << "Unspecified input format"; |
| 259 |
| 260 } |
| 261 |
| 262 request.set_requested_output_format(requested_output); |
| 263 |
| 264 RunTest(test_name, request, &response); |
| 265 |
| 266 TestAllTypes test_message; |
| 267 |
| 268 switch (response.result_case()) { |
| 269 case ConformanceResponse::kParseError: |
| 270 case ConformanceResponse::kRuntimeError: |
| 271 ReportFailure(test_name, request, response, |
| 272 "Failed to parse valid JSON input."); |
| 273 return; |
| 274 |
| 275 case ConformanceResponse::kSkipped: |
| 276 ReportSkip(test_name, request, response); |
| 277 return; |
| 278 |
| 279 case ConformanceResponse::kJsonPayload: { |
| 280 if (requested_output != conformance::JSON) { |
| 281 ReportFailure( |
| 282 test_name, request, response, |
| 283 "Test was asked for protobuf output but provided JSON instead."); |
| 284 return; |
| 285 } |
| 286 string binary_protobuf; |
| 287 Status status = |
| 288 JsonToBinaryString(type_resolver_.get(), type_url_, |
| 289 response.json_payload(), &binary_protobuf); |
| 290 if (!status.ok()) { |
| 291 ReportFailure(test_name, request, response, |
| 292 "JSON output we received from test was unparseable."); |
| 293 return; |
| 294 } |
| 295 |
| 296 GOOGLE_CHECK(test_message.ParseFromString(binary_protobuf)); |
| 297 break; |
| 298 } |
| 299 |
| 300 case ConformanceResponse::kProtobufPayload: { |
| 301 if (requested_output != conformance::PROTOBUF) { |
| 302 ReportFailure( |
| 303 test_name, request, response, |
| 304 "Test was asked for JSON output but provided protobuf instead."); |
| 305 return; |
| 306 } |
| 307 |
| 308 if (!test_message.ParseFromString(response.protobuf_payload())) { |
| 309 ReportFailure(test_name, request, response, |
| 310 "Protobuf output we received from test was unparseable."); |
| 311 return; |
| 312 } |
| 313 |
| 314 break; |
| 315 } |
| 316 } |
| 317 |
| 318 MessageDifferencer differencer; |
| 319 string differences; |
| 320 differencer.ReportDifferencesToString(&differences); |
| 321 |
| 322 if (differencer.Equals(reference_message, test_message)) { |
| 323 ReportSuccess(test_name); |
| 324 } else { |
| 325 ReportFailure(test_name, request, response, |
| 326 "Output was not equivalent to reference message: %s.", |
| 327 differences.c_str()); |
| 328 } |
| 329 } |
| 330 |
| 331 // Expect that this precise protobuf will cause a parse error. |
| 332 void ConformanceTestSuite::ExpectParseFailureForProto( |
| 333 const string& proto, const string& test_name) { |
| 334 ConformanceRequest request; |
| 335 ConformanceResponse response; |
| 336 request.set_protobuf_payload(proto); |
| 337 string effective_test_name = "ProtobufInput." + test_name; |
| 338 |
| 339 // We don't expect output, but if the program erroneously accepts the protobuf |
| 340 // we let it send its response as this. We must not leave it unspecified. |
| 341 request.set_requested_output_format(conformance::PROTOBUF); |
| 342 |
| 343 RunTest(effective_test_name, request, &response); |
| 344 if (response.result_case() == ConformanceResponse::kParseError) { |
| 345 ReportSuccess(effective_test_name); |
| 346 } else { |
| 347 ReportFailure(effective_test_name, request, response, |
| 348 "Should have failed to parse, but didn't."); |
| 349 } |
| 350 } |
| 351 |
| 352 // Expect that this protobuf will cause a parse error, even if it is followed |
| 353 // by valid protobuf data. We can try running this twice: once with this |
| 354 // data verbatim and once with this data followed by some valid data. |
| 355 // |
| 356 // TODO(haberman): implement the second of these. |
| 357 void ConformanceTestSuite::ExpectHardParseFailureForProto( |
| 358 const string& proto, const string& test_name) { |
| 359 return ExpectParseFailureForProto(proto, test_name); |
| 360 } |
| 361 |
| 362 void ConformanceTestSuite::RunValidJsonTest( |
| 363 const string& test_name, const string& input_json, |
| 364 const string& equivalent_text_format) { |
| 365 RunValidInputTest("JsonInput." + test_name + ".JsonOutput", input_json, |
| 366 conformance::JSON, equivalent_text_format, |
| 367 conformance::PROTOBUF); |
| 368 RunValidInputTest("JsonInput." + test_name + ".ProtobufOutput", input_json, co
nformance::JSON, |
| 369 equivalent_text_format, conformance::JSON); |
| 370 } |
| 371 |
| 372 void ConformanceTestSuite::TestPrematureEOFForType(FieldDescriptor::Type type) { |
| 373 // Incomplete values for each wire type. |
| 374 static const string incompletes[6] = { |
| 375 string("\x80"), // VARINT |
| 376 string("abcdefg"), // 64BIT |
| 377 string("\x80"), // DELIMITED (partial length) |
| 378 string(), // START_GROUP (no value required) |
| 379 string(), // END_GROUP (no value required) |
| 380 string("abc") // 32BIT |
| 381 }; |
| 382 |
| 383 uint32_t fieldnum = GetFieldNumberForType(type, false); |
| 384 uint32_t rep_fieldnum = GetFieldNumberForType(type, true); |
| 385 WireFormatLite::WireType wire_type = WireFormatLite::WireTypeForFieldType( |
| 386 static_cast<WireFormatLite::FieldType>(type)); |
| 387 const string& incomplete = incompletes[wire_type]; |
| 388 const string type_name = |
| 389 UpperCase(string(".") + FieldDescriptor::TypeName(type)); |
| 390 |
| 391 ExpectParseFailureForProto( |
| 392 tag(fieldnum, wire_type), |
| 393 "PrematureEofBeforeKnownNonRepeatedValue" + type_name); |
| 394 |
| 395 ExpectParseFailureForProto( |
| 396 tag(rep_fieldnum, wire_type), |
| 397 "PrematureEofBeforeKnownRepeatedValue" + type_name); |
| 398 |
| 399 ExpectParseFailureForProto( |
| 400 tag(UNKNOWN_FIELD, wire_type), |
| 401 "PrematureEofBeforeUnknownValue" + type_name); |
| 402 |
| 403 ExpectParseFailureForProto( |
| 404 cat( tag(fieldnum, wire_type), incomplete ), |
| 405 "PrematureEofInsideKnownNonRepeatedValue" + type_name); |
| 406 |
| 407 ExpectParseFailureForProto( |
| 408 cat( tag(rep_fieldnum, wire_type), incomplete ), |
| 409 "PrematureEofInsideKnownRepeatedValue" + type_name); |
| 410 |
| 411 ExpectParseFailureForProto( |
| 412 cat( tag(UNKNOWN_FIELD, wire_type), incomplete ), |
| 413 "PrematureEofInsideUnknownValue" + type_name); |
| 414 |
| 415 if (wire_type == WireFormatLite::WIRETYPE_LENGTH_DELIMITED) { |
| 416 ExpectParseFailureForProto( |
| 417 cat( tag(fieldnum, wire_type), varint(1) ), |
| 418 "PrematureEofInDelimitedDataForKnownNonRepeatedValue" + type_name); |
| 419 |
| 420 ExpectParseFailureForProto( |
| 421 cat( tag(rep_fieldnum, wire_type), varint(1) ), |
| 422 "PrematureEofInDelimitedDataForKnownRepeatedValue" + type_name); |
| 423 |
| 424 // EOF in the middle of delimited data for unknown value. |
| 425 ExpectParseFailureForProto( |
| 426 cat( tag(UNKNOWN_FIELD, wire_type), varint(1) ), |
| 427 "PrematureEofInDelimitedDataForUnknownValue" + type_name); |
| 428 |
| 429 if (type == FieldDescriptor::TYPE_MESSAGE) { |
| 430 // Submessage ends in the middle of a value. |
| 431 string incomplete_submsg = |
| 432 cat( tag(WireFormatLite::TYPE_INT32, WireFormatLite::WIRETYPE_VARINT), |
| 433 incompletes[WireFormatLite::WIRETYPE_VARINT] ); |
| 434 ExpectHardParseFailureForProto( |
| 435 cat( tag(fieldnum, WireFormatLite::WIRETYPE_LENGTH_DELIMITED), |
| 436 varint(incomplete_submsg.size()), |
| 437 incomplete_submsg ), |
| 438 "PrematureEofInSubmessageValue" + type_name); |
| 439 } |
| 440 } else if (type != FieldDescriptor::TYPE_GROUP) { |
| 441 // Non-delimited, non-group: eligible for packing. |
| 442 |
| 443 // Packed region ends in the middle of a value. |
| 444 ExpectHardParseFailureForProto( |
| 445 cat( tag(rep_fieldnum, WireFormatLite::WIRETYPE_LENGTH_DELIMITED), |
| 446 varint(incomplete.size()), |
| 447 incomplete ), |
| 448 "PrematureEofInPackedFieldValue" + type_name); |
| 449 |
| 450 // EOF in the middle of packed region. |
| 451 ExpectParseFailureForProto( |
| 452 cat( tag(rep_fieldnum, WireFormatLite::WIRETYPE_LENGTH_DELIMITED), |
| 453 varint(1) ), |
| 454 "PrematureEofInPackedField" + type_name); |
| 455 } |
| 456 } |
| 457 |
| 458 void ConformanceTestSuite::SetFailureList(const vector<string>& failure_list) { |
| 459 expected_to_fail_.clear(); |
| 460 std::copy(failure_list.begin(), failure_list.end(), |
| 461 std::inserter(expected_to_fail_, expected_to_fail_.end())); |
| 462 } |
| 463 |
| 464 bool ConformanceTestSuite::CheckSetEmpty(const set<string>& set_to_check, |
| 465 const char* msg) { |
| 466 if (set_to_check.empty()) { |
| 467 return true; |
| 468 } else { |
| 469 StringAppendF(&output_, "\n"); |
| 470 StringAppendF(&output_, "%s:\n", msg); |
| 471 for (set<string>::const_iterator iter = set_to_check.begin(); |
| 472 iter != set_to_check.end(); ++iter) { |
| 473 StringAppendF(&output_, " %s\n", iter->c_str()); |
| 474 } |
| 475 StringAppendF(&output_, "\n"); |
| 476 return false; |
| 477 } |
| 478 } |
| 479 |
| 480 bool ConformanceTestSuite::RunSuite(ConformanceTestRunner* runner, |
| 481 std::string* output) { |
| 482 runner_ = runner; |
| 483 successes_ = 0; |
| 484 expected_failures_ = 0; |
| 485 skipped_.clear(); |
| 486 test_names_.clear(); |
| 487 unexpected_failing_tests_.clear(); |
| 488 unexpected_succeeding_tests_.clear(); |
| 489 type_resolver_.reset(NewTypeResolverForDescriptorPool( |
| 490 kTypeUrlPrefix, DescriptorPool::generated_pool())); |
| 491 type_url_ = GetTypeUrl(TestAllTypes::descriptor()); |
| 492 |
| 493 output_ = "\nCONFORMANCE TEST BEGIN ====================================\n\n"; |
| 494 |
| 495 for (int i = 1; i <= FieldDescriptor::MAX_TYPE; i++) { |
| 496 if (i == FieldDescriptor::TYPE_GROUP) continue; |
| 497 TestPrematureEOFForType(static_cast<FieldDescriptor::Type>(i)); |
| 498 } |
| 499 |
| 500 RunValidJsonTest("HelloWorld", "{\"optionalString\":\"Hello, World!\"}", |
| 501 "optional_string: 'Hello, World!'"); |
| 502 |
| 503 bool ok = |
| 504 CheckSetEmpty(expected_to_fail_, |
| 505 "These tests were listed in the failure list, but they " |
| 506 "don't exist. Remove them from the failure list") && |
| 507 |
| 508 CheckSetEmpty(unexpected_failing_tests_, |
| 509 "These tests failed. If they can't be fixed right now, " |
| 510 "you can add them to the failure list so the overall " |
| 511 "suite can succeed") && |
| 512 |
| 513 CheckSetEmpty(unexpected_succeeding_tests_, |
| 514 "These tests succeeded, even though they were listed in " |
| 515 "the failure list. Remove them from the failure list"); |
| 516 |
| 517 CheckSetEmpty(skipped_, |
| 518 "These tests were skipped (probably because support for some " |
| 519 "features is not implemented)"); |
| 520 |
| 521 StringAppendF(&output_, |
| 522 "CONFORMANCE SUITE %s: %d successes, %d skipped, " |
| 523 "%d expected failures, %d unexpected failures.\n", |
| 524 ok ? "PASSED" : "FAILED", successes_, skipped_.size(), |
| 525 expected_failures_, unexpected_failing_tests_.size()); |
| 526 StringAppendF(&output_, "\n"); |
| 527 |
| 528 output->assign(output_); |
| 529 |
| 530 return ok; |
| 531 } |
| 532 |
| 533 } // namespace protobuf |
| 534 } // namespace google |
| OLD | NEW |