conformance_test.cc 15 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

31
#include <set>
32 33
#include <stdarg.h>
#include <string>
34
#include <fstream>
35 36

#include "conformance.pb.h"
37
#include "conformance_test.h"
38

39
#include <google/protobuf/stubs/stringprintf.h>
40 41
#include <google/protobuf/stubs/strutil.h>
#include <google/protobuf/message.h>
42
#include <google/protobuf/text_format.h>
43
#include <google/protobuf/util/field_comparator.h>
44
#include <google/protobuf/util/json_util.h>
45
#include <google/protobuf/util/message_differencer.h>
46

47 48
using conformance::ConformanceRequest;
using conformance::ConformanceResponse;
49 50
using conformance::WireFormat;
using google::protobuf::TextFormat;
51
using google::protobuf::util::DefaultFieldComparator;
52 53 54
using google::protobuf::util::JsonToBinaryString;
using google::protobuf::util::MessageDifferencer;
using google::protobuf::util::Status;
55 56
using std::string;

57 58
namespace google {
namespace protobuf {
59

60
ConformanceTestSuite::ConformanceRequestSetting::ConformanceRequestSetting(
61 62 63 64 65
    ConformanceLevel level,
    conformance::WireFormat input_format,
    conformance::WireFormat output_format,
    conformance::TestCategory test_category,
    const Message& prototype_message,
66
    const string& test_name, const string& input)
67 68 69 70 71
    : level_(level),
      input_format_(input_format),
      output_format_(output_format),
      prototype_message_(prototype_message),
      test_name_(test_name) {
72 73 74 75 76 77 78 79 80 81 82
  switch (input_format) {
    case conformance::PROTOBUF: {
      request_.set_protobuf_payload(input);
      break;
    }

    case conformance::JSON: {
      request_.set_json_payload(input);
      break;
    }

83 84 85 86 87
    case conformance::JSPB: {
      request_.set_jspb_payload(input);
      break;
    }

88 89 90 91 92
    case conformance::TEXT_FORMAT: {
      request_.set_text_payload(input);
      break;
    }

93 94 95 96
    default:
      GOOGLE_LOG(FATAL) << "Unspecified input format";
  }

97
  request_.set_test_category(test_category);
98

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
  request_.set_message_type(prototype_message.GetDescriptor()->full_name());
  request_.set_requested_output_format(output_format);
}

Message* ConformanceTestSuite::ConformanceRequestSetting::
    GetTestMessage() const {
  return prototype_message_.New();
}

string ConformanceTestSuite::ConformanceRequestSetting::
    GetTestName() const {
  string rname =
      prototype_message_.GetDescriptor()->file()->syntax() ==
        FileDescriptor::SYNTAX_PROTO3 ? "Proto3" : "Proto2";

  return StrCat(ConformanceLevelToString(level_), ".",
                rname, ".",
                InputFormatString(input_format_),
                ".", test_name_, ".",
                OutputFormatString(output_format_));
}

string ConformanceTestSuite::ConformanceRequestSetting::
    ConformanceLevelToString(
        ConformanceLevel level) const {
  switch (level) {
    case REQUIRED: return "Required";
    case RECOMMENDED: return "Recommended";
  }
  GOOGLE_LOG(FATAL) << "Unknown value: " << level;
  return "";
}
131

132 133 134 135 136 137 138
string ConformanceTestSuite::ConformanceRequestSetting::
    InputFormatString(conformance::WireFormat format) const {
  switch (format) {
    case conformance::PROTOBUF:
      return "ProtobufInput";
    case conformance::JSON:
      return "JsonInput";
139 140
    case conformance::TEXT_FORMAT:
      return "TextFormatInput";
141 142 143
    default:
      GOOGLE_LOG(FATAL) << "Unspecified output format";
  }
144 145
  return "";
}
146

147 148 149 150 151 152 153
string ConformanceTestSuite::ConformanceRequestSetting::
    OutputFormatString(conformance::WireFormat format) const {
  switch (format) {
    case conformance::PROTOBUF:
      return "ProtobufOutput";
    case conformance::JSON:
      return "JsonOutput";
154 155
    case conformance::TEXT_FORMAT:
      return "TextFormatOutput";
156 157
    default:
      GOOGLE_LOG(FATAL) << "Unspecified output format";
158
  }
159
  return "";
160 161
}

162 163 164 165 166 167 168 169
void ConformanceTestSuite::ReportSuccess(const string& test_name) {
  if (expected_to_fail_.erase(test_name) != 0) {
    StringAppendF(&output_,
                  "ERROR: test %s is in the failure list, but test succeeded.  "
                  "Remove it from the failure list.\n",
                  test_name.c_str());
    unexpected_succeeding_tests_.insert(test_name);
  }
170 171 172
  successes_++;
}

173
void ConformanceTestSuite::ReportFailure(const string& test_name,
Bo Yang's avatar
Bo Yang committed
174
                                         ConformanceLevel level,
175 176
                                         const ConformanceRequest& request,
                                         const ConformanceResponse& response,
177 178
                                         const char* fmt, ...) {
  if (expected_to_fail_.erase(test_name) == 1) {
179 180 181
    expected_failures_++;
    if (!verbose_)
      return;
Bo Yang's avatar
Bo Yang committed
182 183
  } else if (level == RECOMMENDED && !enforce_recommended_) {
    StringAppendF(&output_, "WARNING, test=%s: ", test_name.c_str());
184
  } else {
185
    StringAppendF(&output_, "ERROR, test=%s: ", test_name.c_str());
186 187
    unexpected_failing_tests_.insert(test_name);
  }
188 189 190 191
  va_list args;
  va_start(args, fmt);
  StringAppendV(&output_, fmt, args);
  va_end(args);
192 193 194 195 196 197 198 199 200 201 202 203 204 205
  StringAppendF(&output_, " request=%s, response=%s\n",
                request.ShortDebugString().c_str(),
                response.ShortDebugString().c_str());
}

void ConformanceTestSuite::ReportSkip(const string& test_name,
                                      const ConformanceRequest& request,
                                      const ConformanceResponse& response) {
  if (verbose_) {
    StringAppendF(&output_, "SKIPPED, test=%s request=%s, response=%s\n",
                  test_name.c_str(), request.ShortDebugString().c_str(),
                  response.ShortDebugString().c_str());
  }
  skipped_.insert(test_name);
206 207
}

208
void ConformanceTestSuite::RunValidInputTest(
209 210 211
    const ConformanceRequestSetting& setting,
    const string& equivalent_text_format) {
  Message* reference_message = setting.GetTestMessage();
212
  GOOGLE_CHECK(
Jisi Liu's avatar
Jisi Liu committed
213
      TextFormat::ParseFromString(equivalent_text_format, reference_message))
214
          << "Failed to parse data for test case: " << setting.GetTestName()
215
          << ", data: " << equivalent_text_format;
216
  const string equivalent_wire_format = reference_message->SerializeAsString();
217
  RunValidBinaryInputTest(setting, equivalent_wire_format);
218 219 220
}

void ConformanceTestSuite::RunValidBinaryInputTest(
221 222
    const ConformanceRequestSetting& setting,
    const string& equivalent_wire_format) {
223 224 225 226 227 228 229 230 231 232 233 234 235
  const ConformanceRequest& request = setting.GetRequest();
  ConformanceResponse response;
  RunTest(setting.GetTestName(), request, &response);
  VerifyResponse(setting, equivalent_wire_format, response, true);
}

void ConformanceTestSuite::VerifyResponse(
    const ConformanceRequestSetting& setting,
    const string& equivalent_wire_format,
    const ConformanceResponse& response,
    bool need_report_success) {
  Message* test_message = setting.GetTestMessage();
  const ConformanceRequest& request = setting.GetRequest();
236 237 238
  const string& test_name = setting.GetTestName();
  ConformanceLevel level = setting.GetLevel();
  Message* reference_message = setting.GetTestMessage();
239

240 241 242
  GOOGLE_CHECK(
      reference_message->ParseFromString(equivalent_wire_format))
          << "Failed to parse wire data for test case: " << test_name;
243 244

  switch (response.result_case()) {
245
    case ConformanceResponse::RESULT_NOT_SET:
246
      ReportFailure(test_name, level, request, response,
247 248 249
                    "Response didn't have any field in the Response.");
      return;

250 251
    case ConformanceResponse::kParseError:
    case ConformanceResponse::kRuntimeError:
252
    case ConformanceResponse::kSerializeError:
253
      ReportFailure(test_name, level, request, response,
254
                    "Failed to parse input or produce output.");
255 256 257 258 259 260
      return;

    case ConformanceResponse::kSkipped:
      ReportSkip(test_name, request, response);
      return;

261
    default:
262
      if (!ParseResponse(response, setting, test_message)) return;
263 264 265
  }

  MessageDifferencer differencer;
266 267 268
  DefaultFieldComparator field_comparator;
  field_comparator.set_treat_nan_as_equal(true);
  differencer.set_field_comparator(&field_comparator);
269 270 271
  string differences;
  differencer.ReportDifferencesToString(&differences);

Jisi Liu's avatar
Jisi Liu committed
272 273 274
  bool check;
  check = differencer.Compare(*reference_message, *test_message);
  if (check) {
275 276 277
    if (need_report_success) {
      ReportSuccess(test_name);
    }
278
  } else {
Bo Yang's avatar
Bo Yang committed
279
    ReportFailure(test_name, level, request, response,
280 281 282 283
                  "Output was not equivalent to reference message: %s.",
                  differences.c_str());
  }
}
284

285 286 287 288 289
void ConformanceTestSuite::RunTest(const string& test_name,
                                   const ConformanceRequest& request,
                                   ConformanceResponse* response) {
  if (test_names_.insert(test_name).second == false) {
    GOOGLE_LOG(FATAL) << "Duplicated test name: " << test_name;
290
  }
291

292 293 294
  string serialized_request;
  string serialized_response;
  request.SerializeToString(&serialized_request);
295

296
  runner_->RunTest(test_name, serialized_request, &serialized_response);
297

298 299 300
  if (!response->ParseFromString(serialized_response)) {
    response->Clear();
    response->set_runtime_error("response proto could not be parsed.");
301
  }
Jisi Liu's avatar
Jisi Liu committed
302

303 304 305 306 307 308
  if (verbose_) {
    StringAppendF(&output_,
                  "conformance test: name=%s, request=%s, response=%s\n",
                  test_name.c_str(),
                  request.ShortDebugString().c_str(),
                  response->ShortDebugString().c_str());
309 310 311
  }
}

312 313 314 315
bool ConformanceTestSuite::CheckSetEmpty(
    const std::set<string>& set_to_check,
    const std::string& write_to_file,
    const std::string& msg) {
316 317 318 319
  if (set_to_check.empty()) {
    return true;
  } else {
    StringAppendF(&output_, "\n");
320
    StringAppendF(&output_, "%s\n\n", msg.c_str());
321
    for (std::set<string>::const_iterator iter = set_to_check.begin();
322
         iter != set_to_check.end(); ++iter) {
323
      StringAppendF(&output_, "  %s\n", iter->c_str());
324
    }
325
    StringAppendF(&output_, "\n");
326 327 328 329

    if (!write_to_file.empty()) {
      std::ofstream os(write_to_file);
      if (os) {
330
        for (std::set<string>::const_iterator iter = set_to_check.begin();
331 332 333 334 335 336 337 338 339
             iter != set_to_check.end(); ++iter) {
          os << *iter << "\n";
        }
      } else {
        StringAppendF(&output_, "Failed to open file: %s\n",
                      write_to_file.c_str());
      }
    }

340 341 342 343
    return false;
  }
}

344 345 346 347 348 349 350 351 352
string ConformanceTestSuite::WireFormatToString(
    WireFormat wire_format) {
  switch (wire_format) {
    case conformance::PROTOBUF:
      return "PROTOBUF";
    case conformance::JSON:
      return "JSON";
    case conformance::JSPB:
      return "JSPB";
353 354
    case conformance::TEXT_FORMAT:
      return "TEXT_FORMAT";
355 356 357 358 359 360 361 362 363
    case conformance::UNSPECIFIED:
      return "UNSPECIFIED";
    default:
      GOOGLE_LOG(FATAL) << "unknown wire type: "
                        << wire_format;
  }
  return "";
}

364 365 366
bool ConformanceTestSuite::RunSuite(ConformanceTestRunner* runner,
                                    std::string* output, const string& filename,
                                    conformance::FailureSet* failure_list) {
367 368
  runner_ = runner;
  successes_ = 0;
369 370
  expected_failures_ = 0;
  skipped_.clear();
371 372 373
  test_names_.clear();
  unexpected_failing_tests_.clear();
  unexpected_succeeding_tests_.clear();
374 375

  output_ = "\nCONFORMANCE TEST BEGIN ====================================\n\n";
376

377 378 379 380 381 382 383 384 385 386 387 388
  ConformanceRequest req;
  ConformanceResponse res;
  req.set_message_type(failure_list->GetTypeName());
  req.set_protobuf_payload("");
  req.set_requested_output_format(conformance::WireFormat::PROTOBUF);
  RunTest("FindFailures", req, &res);
  GOOGLE_CHECK(failure_list->MergeFromString(res.protobuf_payload()));
  failure_list_filename_ = filename;
  expected_to_fail_.clear();
  for (const string& failure : failure_list->failure()) {
    expected_to_fail_.insert(failure);
  }
389
  RunSuiteImpl();
390

391
  bool ok = true;
392
  if (!CheckSetEmpty(expected_to_fail_, "nonexistent_tests.txt",
393
                     "These tests were listed in the failure list, but they "
394 395 396 397
                     "don't exist.  Remove them from the failure list by "
                     "running:\n"
                     "  ./update_failure_list.py " + failure_list_filename_ +
                     " --remove nonexistent_tests.txt")) {
398 399
    ok = false;
  }
400
  if (!CheckSetEmpty(unexpected_failing_tests_, "failing_tests.txt",
401 402
                     "These tests failed.  If they can't be fixed right now, "
                     "you can add them to the failure list so the overall "
403 404 405 406 407 408 409 410 411 412 413 414
                     "suite can succeed.  Add them to the failure list by "
                     "running:\n"
                     "  ./update_failure_list.py " + failure_list_filename_ +
                     " --add failing_tests.txt")) {
    ok = false;
  }
  if (!CheckSetEmpty(unexpected_succeeding_tests_, "succeeding_tests.txt",
                     "These tests succeeded, even though they were listed in "
                     "the failure list.  Remove them from the failure list "
                     "by running:\n"
                     "  ./update_failure_list.py " + failure_list_filename_ +
                     " --remove succeeding_tests.txt")) {
415 416
    ok = false;
  }
417

418
  if (verbose_) {
419
    CheckSetEmpty(skipped_, "",
420 421 422
                  "These tests were skipped (probably because support for some "
                  "features is not implemented)");
  }
423 424 425 426 427 428 429 430

  StringAppendF(&output_,
                "CONFORMANCE SUITE %s: %d successes, %d skipped, "
                "%d expected failures, %d unexpected failures.\n",
                ok ? "PASSED" : "FAILED", successes_, skipped_.size(),
                expected_failures_, unexpected_failing_tests_.size());
  StringAppendF(&output_, "\n");

431
  output->assign(output_);
432 433

  return ok;
434
}
435 436 437

}  // namespace protobuf
}  // namespace google