conformance_test.cc 14.9 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
    : level_(level),
      input_format_(input_format),
      output_format_(output_format),
      prototype_message_(prototype_message),
Hao Nguyen's avatar
Hao Nguyen committed
71
      prototype_message_for_compare_(prototype_message.New()),
72
      test_name_(test_name) {
73 74 75 76 77 78 79 80 81 82 83
  switch (input_format) {
    case conformance::PROTOBUF: {
      request_.set_protobuf_payload(input);
      break;
    }

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

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

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

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

98
  request_.set_test_category(test_category);
99

100 101 102 103 104 105
  request_.set_message_type(prototype_message.GetDescriptor()->full_name());
  request_.set_requested_output_format(output_format);
}

Message* ConformanceTestSuite::ConformanceRequestSetting::
    GetTestMessage() const {
Hao Nguyen's avatar
Hao Nguyen committed
106
  return prototype_message_for_compare_->New();
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
}

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 "";
}
132

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

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

163 164 165 166 167 168 169 170
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);
  }
171 172 173
  successes_++;
}

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

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

void ConformanceTestSuite::RunValidBinaryInputTest(
222 223
    const ConformanceRequestSetting& setting,
    const string& equivalent_wire_format) {
224 225 226 227 228 229 230 231 232 233 234 235 236
  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();
237 238 239
  const string& test_name = setting.GetTestName();
  ConformanceLevel level = setting.GetLevel();
  Message* reference_message = setting.GetTestMessage();
240

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

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

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

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

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

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

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

286 287 288 289 290
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;
291
  }
292

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

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

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

304 305 306 307 308 309
  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());
310 311 312
  }
}

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

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

341 342 343 344
    return false;
  }
}

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

365 366 367 368
void ConformanceTestSuite::AddExpectedFailedTest(const std::string& test_name) {
  expected_to_fail_.insert(test_name);
}

369 370 371
bool ConformanceTestSuite::RunSuite(ConformanceTestRunner* runner,
                                    std::string* output, const string& filename,
                                    conformance::FailureSet* failure_list) {
372 373
  runner_ = runner;
  successes_ = 0;
374 375
  expected_failures_ = 0;
  skipped_.clear();
376 377 378
  test_names_.clear();
  unexpected_failing_tests_.clear();
  unexpected_succeeding_tests_.clear();
379 380

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

382 383 384
  failure_list_filename_ = filename;
  expected_to_fail_.clear();
  for (const string& failure : failure_list->failure()) {
385
    AddExpectedFailedTest(failure);
386
  }
387
  RunSuiteImpl();
388

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

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

  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");

429
  output->assign(output_);
430 431

  return ok;
432
}
433 434 435

}  // namespace protobuf
}  // namespace google