Implementation of go/conformanceDebuggability. Refactor ForkPipeRunner::RunTest(): There's no need for rerunning through argv for common flags between suites. This also enables "debug_test_names" to be shared between suites.

PiperOrigin-RevId: 656509695
pull/17585/head
Yamil Morales 7 months ago committed by Copybara-Service
parent 50f58a6e20
commit eceb8ccc0d
  1. 1
      conformance/BUILD.bazel
  2. 22
      conformance/binary_json_conformance_suite.cc
  3. 12
      conformance/conformance_cpp.cc
  4. 36
      conformance/conformance_python.py
  5. 114
      conformance/conformance_test.cc
  6. 39
      conformance/conformance_test.h
  7. 162
      conformance/conformance_test_runner.cc
  8. 5
      conformance/text_format_conformance_suite.cc

@ -233,6 +233,7 @@ cc_binary(
"//src/google/protobuf",
"//src/google/protobuf:port",
"//src/google/protobuf:protobuf_lite",
"//src/google/protobuf/json",
"//src/google/protobuf/stubs",
"//src/google/protobuf/util:json_util",
"//src/google/protobuf/util:type_resolver",

@ -445,7 +445,10 @@ void BinaryAndJsonConformanceSuiteImpl<MessageType>::
absl::StrCat(setting.ConformanceLevelToString(level), ".",
setting.GetSyntaxIdentifier(), ".ProtobufInput.", test_name);
suite_.RunTest(effective_test_name, request, &response);
if (!suite_.RunTest(effective_test_name, request, &response)) {
return;
}
TestStatus test;
test.set_name(effective_test_name);
if (response.result_case() == ConformanceResponse::kParseError) {
@ -641,7 +644,9 @@ void BinaryAndJsonConformanceSuiteImpl<
setting.ConformanceLevelToString(level), ".",
setting.GetSyntaxIdentifier(), ".JsonInput.", test_name, ".Validator");
suite_.RunTest(effective_test_name, request, &response);
if (!suite_.RunTest(effective_test_name, request, &response)) {
return;
}
TestStatus test;
test.set_name(effective_test_name);
@ -689,7 +694,9 @@ void BinaryAndJsonConformanceSuiteImpl<MessageType>::ExpectParseFailureForJson(
absl::StrCat(setting.ConformanceLevelToString(level), ".",
SyntaxIdentifier(), ".JsonInput.", test_name);
suite_.RunTest(effective_test_name, request, &response);
if (!suite_.RunTest(effective_test_name, request, &response)) {
return;
}
TestStatus test;
test.set_name(effective_test_name);
@ -722,7 +729,9 @@ void BinaryAndJsonConformanceSuiteImpl<MessageType>::
absl::StrCat(setting.ConformanceLevelToString(level), ".",
SyntaxIdentifier(), ".", test_name, ".JsonOutput");
suite_.RunTest(effective_test_name, request, &response);
if (!suite_.RunTest(effective_test_name, request, &response)) {
return;
}
TestStatus test;
test.set_name(effective_test_name);
@ -1438,7 +1447,9 @@ void BinaryAndJsonConformanceSuiteImpl<MessageType>::TestUnknownOrdering() {
conformance::BINARY_TEST, prototype, "UnknownOrdering", serialized);
const ConformanceRequest& request = setting.GetRequest();
ConformanceResponse response;
suite_.RunTest(setting.GetTestName(), request, &response);
if (!suite_.RunTest(setting.GetTestName(), request, &response)) {
return;
}
MessageType response_message;
TestStatus test;
@ -1447,6 +1458,7 @@ void BinaryAndJsonConformanceSuiteImpl<MessageType>::TestUnknownOrdering() {
suite_.ReportSkip(test, request, response);
return;
}
suite_.ParseResponse(response, setting, &response_message);
const UnknownFieldSet& ufs = response_message.unknown_fields();

@ -7,15 +7,15 @@
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <memory>
#include <string>
#include "google/protobuf/util/json_util.h"
#include "google/protobuf/util/type_resolver_util.h"
#include "absl/log/absl_check.h"
#include "absl/log/absl_log.h"
#include "absl/status/status.h"
@ -27,12 +27,15 @@
#include "editions/golden/test_messages_proto2_editions.pb.h"
#include "editions/golden/test_messages_proto3_editions.pb.h"
#include "google/protobuf/endian.h"
#include "google/protobuf/json/json.h"
#include "google/protobuf/message.h"
#include "google/protobuf/test_messages_proto2.pb.h"
#include "google/protobuf/test_messages_proto3.pb.h"
#include "google/protobuf/test_messages_proto3.pb.h"
#include "google/protobuf/text_format.h"
#include "google/protobuf/util/json_util.h"
#include "google/protobuf/util/type_resolver.h"
#include "google/protobuf/util/type_resolver_util.h"
#include "google/protobuf/stubs/status_macros.h"
// Must be included last.
@ -241,8 +244,9 @@ absl::StatusOr<bool> Harness::ServeConformanceRequest() {
serialized_output.size()));
if (verbose_) {
ABSL_LOG(INFO) << "conformance-cpp: request=" << request.ShortDebugString()
<< ", response=" << response->ShortDebugString();
ABSL_LOG(INFO) << "conformance-cpp: request="
<< google::protobuf::ShortFormat(request)
<< ", response=" << google::protobuf::ShortFormat(*response);
}
return false;
}

@ -102,32 +102,34 @@ def do_test(request):
response.protobuf_payload = failure_set.SerializeToString()
return response
isJson = (request.WhichOneof('payload') == 'json_payload')
isJson = request.WhichOneof("payload") == "json_payload"
test_message = _create_test_message(request.message_type)
if (not isJson) and (test_message is None):
raise ProtocolError("Protobuf request doesn't have specific payload type")
try:
if request.WhichOneof('payload') == 'protobuf_payload':
if request.WhichOneof("payload") == "protobuf_payload":
try:
test_message.ParseFromString(request.protobuf_payload)
except message.DecodeError as e:
response.parse_error = str(e)
return response
elif request.WhichOneof('payload') == 'json_payload':
elif request.WhichOneof("payload") == "json_payload":
try:
ignore_unknown_fields = \
request.test_category == \
conformance_pb2.JSON_IGNORE_UNKNOWN_PARSING_TEST
json_format.Parse(request.json_payload, test_message,
ignore_unknown_fields)
ignore_unknown_fields = (
request.test_category
== conformance_pb2.JSON_IGNORE_UNKNOWN_PARSING_TEST
)
json_format.Parse(
request.json_payload, test_message, ignore_unknown_fields
)
except Exception as e:
response.parse_error = str(e)
return response
elif request.WhichOneof('payload') == 'text_payload':
elif request.WhichOneof("payload") == "text_payload":
try:
text_format.Parse(request.text_payload, test_message)
except Exception as e:
@ -152,7 +154,8 @@ def do_test(request):
elif request.requested_output_format == conformance_pb2.TEXT_FORMAT:
response.text_payload = text_format.MessageToString(
test_message, print_unknown_fields=request.print_unknown_fields)
test_message, print_unknown_fields=request.print_unknown_fields
)
except Exception as e:
response.runtime_error = str(e)
@ -163,7 +166,7 @@ def do_test(request):
def do_test_io():
length_bytes = sys.stdin.buffer.read(4)
if len(length_bytes) == 0:
return False # EOF
return False # EOF
elif len(length_bytes) != 4:
raise IOError("I/O error")
@ -183,15 +186,20 @@ def do_test_io():
sys.stdout.buffer.flush()
if verbose:
sys.stderr.write("conformance_python: request=%s, response=%s\n" % (
request.ShortDebugString().c_str(),
response.ShortDebugString().c_str()))
sys.stderr.write(
"conformance_python: request=%s, response=%s\n"
% (
request.ShortDebugString().c_str(),
response.ShortDebugString().c_str(),
)
)
global test_count
test_count += 1
return True
while True:
if not do_test_io():
sys.stderr.write(

@ -21,6 +21,7 @@
#include "google/protobuf/util/field_comparator.h"
#include "google/protobuf/util/message_differencer.h"
#include "absl/container/btree_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/log/absl_check.h"
#include "absl/log/absl_log.h"
#include "absl/strings/str_cat.h"
@ -29,6 +30,7 @@
#include "conformance/conformance.pb.h"
#include "conformance/conformance.pb.h"
#include "google/protobuf/descriptor_legacy.h"
#include "google/protobuf/endian.h"
#include "google/protobuf/message.h"
#include "google/protobuf/text_format.h"
@ -42,6 +44,15 @@ using std::string;
namespace {
static void ReplaceAll(std::string& input, std::string replace_word,
std::string replace_by) {
size_t pos = input.find(replace_word);
while (pos != std::string::npos) {
input.replace(pos, replace_word.length(), replace_by);
pos = input.find(replace_word, pos + replace_by.length());
}
}
static std::string ToOctString(const std::string& binary_string) {
std::string oct_string;
for (size_t i = 0; i < binary_string.size(); i++) {
@ -57,6 +68,52 @@ static std::string ToOctString(const std::string& binary_string) {
return oct_string;
}
// Returns full filename path of written .txt file if successful
static std::string ProduceOctalSerialized(const std::string& request,
uint32_t len) {
char* len_split_bytes = static_cast<char*>(static_cast<void*>(&len));
std::string out;
std::string hex_repr;
for (int i = 0; i < 4; i++) {
auto conversion = (unsigned int)static_cast<uint8_t>(len_split_bytes[i]);
std::string hex = absl::StrFormat("\\x%x", conversion);
absl::StrAppend(&hex_repr, hex);
}
absl::StrAppend(&out, hex_repr);
absl::StrAppend(&out, ToOctString(request));
return out;
}
static std::string WriteToFile(const std::string& octal_serialized,
const std::string& output_dir,
const std::string& test_name) {
std::string test_name_txt = test_name;
ReplaceAll(test_name_txt, ".", "_");
absl::StrAppend(&test_name_txt, ".txt");
std::string full_filename;
if (!output_dir.empty()) {
full_filename = output_dir;
if (*output_dir.rbegin() != '/') {
full_filename.push_back('/');
}
absl::StrAppend(&full_filename, test_name_txt);
}
std::ofstream os{std::string(full_filename)};
if (os) {
os << octal_serialized;
return full_filename;
} else {
ABSL_LOG(INFO) << "Failed to open file for debugging: " << full_filename
<< "\n";
return "";
}
}
// Removes all newlines.
static void Normalize(std::string& input) {
input.erase(std::remove(input.begin(), input.end(), '\n'), input.end());
@ -376,7 +433,10 @@ void ConformanceTestSuite::RunValidBinaryInputTest(
const std::string& equivalent_wire_format, bool require_same_wire_format) {
const ConformanceRequest& request = setting.GetRequest();
ConformanceResponse response;
RunTest(setting.GetTestName(), request, &response);
if (!RunTest(setting.GetTestName(), request, &response)) {
return;
}
VerifyResponse(setting, equivalent_wire_format, response, true,
require_same_wire_format);
}
@ -451,7 +511,7 @@ void ConformanceTestSuite::VerifyResponse(
}
}
void ConformanceTestSuite::RunTest(const std::string& test_name,
bool ConformanceTestSuite::RunTest(const std::string& test_name,
const ConformanceRequest& request,
ConformanceResponse* response) {
if (test_names_.insert(test_name).second == false) {
@ -462,7 +522,43 @@ void ConformanceTestSuite::RunTest(const std::string& test_name,
std::string serialized_response;
request.SerializeToString(&serialized_request);
runner_->RunTest(test_name, serialized_request, &serialized_response);
uint32_t len = internal::little_endian::FromHost(
static_cast<uint32_t>(serialized_request.size()));
if (!debug_) { // Not in debug mode. Continue.
} else if (debug_test_names_->erase(test_name) == 1) {
std::string octal = ProduceOctalSerialized(serialized_request, len);
std::string full_filename = WriteToFile(octal, output_dir_, test_name);
if (!full_filename.empty()) {
absl::StrAppendFormat(
&output_, "Produced octal serialized request file for test %s\n",
test_name);
absl::StrAppendFormat(
&output_,
" To pipe the "
"serialized request directly to "
"the "
"testee run from the root of your workspace:\n printf $("
"<\"%s\") | "
"./bazel-bin/google/protobuf/conformance/%s\n\n",
full_filename, testee_);
absl::StrAppendFormat(
&output_,
" To inspect the wire format of the serialized request run "
"(Disclaimer: This may not work properly on non-Linux platforms):\n "
" "
"contents=$(<\"%s\"); sub=$(cut -d \\\\ -f 6- <<< "
"$contents) ; printf \"\\\\${sub}\" | protoscope \n\n\n",
full_filename);
}
} else { // Test is not ran, as it was not asked to be debugged.
expected_to_fail_.erase(test_name);
return false;
}
response->set_protobuf_payload(serialized_request);
runner_->RunTest(test_name, len, serialized_request, &serialized_response);
if (!response->ParseFromString(serialized_response)) {
response->Clear();
@ -475,6 +571,7 @@ void ConformanceTestSuite::RunTest(const std::string& test_name,
test_name, TruncateRequest(request).ShortDebugString(),
TruncateResponse(*response).ShortDebugString());
}
return true;
}
std::string ConformanceTestSuite::WireFormatToString(WireFormat wire_format) {
@ -511,7 +608,10 @@ bool ConformanceTestSuite::RunSuite(ConformanceTestRunner* runner,
unexpected_failing_tests_.clear();
unexpected_succeeding_tests_.clear();
output_ = "\nCONFORMANCE TEST BEGIN ====================================\n\n";
std::string mode = debug_ ? "DEBUG" : "TEST";
absl::StrAppendFormat(
&output_, "CONFORMANCE %s BEGIN ====================================\n\n",
mode);
failure_list_filename_ = filename;
expected_to_fail_.clear();
@ -604,11 +704,9 @@ bool ConformanceTestSuite::RunSuite(ConformanceTestRunner* runner,
absl::StrAppendFormat(&output_,
"CONFORMANCE SUITE %s: %d successes, %zu skipped, "
"%d expected failures, %zu unexpected failures, %zu "
"unexpected_failure_messages.\n",
"%d expected failures, %zu unexpected failures.\n",
ok ? "PASSED" : "FAILED", successes_, skipped_.size(),
expected_failures_, unexpected_failing_tests_.size(),
unexpected_failure_messages_.size());
expected_failures_, unexpected_failing_tests_.size());
absl::StrAppendFormat(&output_, "\n");
output->assign(output_);

@ -15,6 +15,7 @@
#define CONFORMANCE_CONFORMANCE_TEST_H
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
@ -48,13 +49,14 @@ class ConformanceTestRunner {
// Call to run a single conformance test.
//
// "len" is the byte length of a serialized conformance.ConformanceRequest.
// "input" is a serialized conformance.ConformanceRequest.
// "output" should be set to a serialized conformance.ConformanceResponse.
//
// If there is any error in running the test itself, set "runtime_error" in
// the response.
virtual void RunTest(const std::string& test_name, const std::string& input,
std::string* output) = 0;
virtual void RunTest(const std::string& test_name, uint32_t len,
const std::string& input, std::string* output) = 0;
};
// Test runner that spawns the process being tested and communicates with it
@ -78,8 +80,8 @@ class ForkPipeRunner : public ConformanceTestRunner {
~ForkPipeRunner() override = default;
void RunTest(const std::string& test_name, const std::string& request,
std::string* response) override;
void RunTest(const std::string& test_name, uint32_t len,
const std::string& request, std::string* response) override;
private:
void SpawnTestProgram();
@ -127,11 +129,14 @@ class ForkPipeRunner : public ConformanceTestRunner {
class ConformanceTestSuite {
public:
ConformanceTestSuite()
: verbose_(false),
: testee_(""),
verbose_(false),
performance_(false),
enforce_recommended_(false),
maximum_edition_(Edition::EDITION_PROTO3),
failure_list_flag_name_("--failure_list") {}
failure_list_flag_name_("--failure_list"),
debug_test_names_(nullptr),
debug_(false) {}
virtual ~ConformanceTestSuite() = default;
void SetPerformance(bool performance) { performance_ = performance; }
@ -159,7 +164,18 @@ class ConformanceTestSuite {
}
// Sets the path of the output directory.
void SetOutputDir(const char* output_dir) { output_dir_ = output_dir; }
void SetOutputDir(const std::string& output_dir) { output_dir_ = output_dir; }
// Sets if we are running the test in debug mode.
void SetDebug(bool debug) { debug_ = debug; }
// Sets the testee name
void SetTestee(const std::string& testee) { testee_ = testee; }
// Sets the debug test names
void SetDebugTestNames(absl::flat_hash_set<std::string>& debug_test_names) {
debug_test_names_ = &debug_test_names;
}
// Run all the conformance tests against the given test runner.
// Test output will be stored in "output".
@ -168,6 +184,9 @@ class ConformanceTestSuite {
// failure list.
// The filename here is *only* used to create/format useful error messages for
// how to update the failure list. We do NOT read this file at all.
// "debug_test_names" holds the list of test names that the user requested to
// debug. If this is empty, we will run all the tests.
bool RunSuite(ConformanceTestRunner* runner, std::string* output,
const std::string& filename,
conformance::FailureSet* failure_list);
@ -271,7 +290,8 @@ class ConformanceTestSuite {
const std::string& equivalent_wire_format,
bool require_same_wire_format = false);
void RunTest(const std::string& test_name,
// Returns true if our runner_ ran the test and false if it did not.
bool RunTest(const std::string& test_name,
const conformance::ConformanceRequest& request,
conformance::ConformanceResponse* response);
@ -280,6 +300,7 @@ class ConformanceTestSuite {
virtual void RunSuiteImpl() = 0;
ConformanceTestRunner* runner_;
std::string testee_;
int successes_;
int expected_failures_;
bool verbose_;
@ -290,6 +311,8 @@ class ConformanceTestSuite {
std::string output_dir_;
std::string failure_list_flag_name_;
std::string failure_list_filename_;
absl::flat_hash_set<std::string>* debug_test_names_;
bool debug_;
// The set of test names that are expected to fail in this run, but haven't
// failed yet.

@ -32,6 +32,7 @@
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
@ -47,9 +48,11 @@
#include <string>
#include <vector>
#include "absl/container/flat_hash_set.h"
#include "absl/log/absl_log.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "conformance/conformance.pb.h"
#include "conformance/conformance.pb.h"
#include "conformance_test.h"
@ -122,7 +125,7 @@ void UsageError() {
fprintf(stderr,
" should contain one test name per\n");
fprintf(stderr,
" line. Use '#' for comments.\n");
" line. Use '#' for comments.\n\n");
fprintf(stderr,
" --text_format_failure_list <filename> Use to specify list \n");
fprintf(stderr,
@ -133,7 +136,7 @@ void UsageError() {
fprintf(stderr,
" File should contain one test name \n");
fprintf(stderr,
" per line. Use '#' for comments.\n");
" per line. Use '#' for comments.\n\n");
fprintf(stderr,
" --enforce_recommended Enforce that recommended test\n");
@ -143,19 +146,30 @@ void UsageError() {
" this flag if you want to be\n");
fprintf(stderr,
" strictly conforming to protobuf\n");
fprintf(stderr, " spec.\n");
fprintf(stderr, " spec.\n\n");
fprintf(stderr,
" --maximum_edition <edition> Only run conformance tests up\n");
" --maximum_edition <edition> Only run conformance tests up to\n");
fprintf(stderr,
" to and including the specified\n");
fprintf(stderr, " edition.\n");
" and including the specified\n");
fprintf(stderr, " edition.\n\n");
fprintf(stderr,
" --output_dir <dirname> Directory to write\n"
" output files.\n");
" output files.\n\n");
fprintf(stderr,
" --debug <test_name1> <test_name2> ... <test_nameN> Debug the \n");
fprintf(stderr, " specified tests by running\n");
fprintf(stderr,
" them in isolation and producing\n");
fprintf(stderr,
" serialized request data for piping\n");
fprintf(stderr, " directly to the testee.\n\n");
fprintf(stderr, " --performance Boolean option\n");
fprintf(stderr, " for enabling run of\n");
fprintf(stderr, " performance tests.\n");
exit(1);
}
void ForkPipeRunner::RunTest(const std::string &test_name,
void ForkPipeRunner::RunTest(const std::string &test_name, uint32_t len,
const std::string &request,
std::string *response) {
if (child_pid_ < 0) {
@ -163,8 +177,6 @@ void ForkPipeRunner::RunTest(const std::string &test_name,
}
current_test_name_ = test_name;
uint32_t len =
internal::little_endian::FromHost(static_cast<uint32_t>(request.size()));
CheckedWrite(write_fd_, &len, sizeof(uint32_t));
CheckedWrite(write_fd_, request.c_str(), request.size());
@ -210,57 +222,97 @@ int ForkPipeRunner::Run(int argc, char *argv[],
fprintf(stderr, "No test suites found.\n");
return EXIT_FAILURE;
}
string program;
string testee;
std::vector<string> program_args;
bool performance = false;
bool debug = false;
absl::flat_hash_set<string> debug_test_names;
bool enforce_recommended = false;
Edition maximum_edition = EDITION_UNKNOWN;
std::string output_dir;
bool verbose = false;
for (int arg = 1; arg < argc; ++arg) {
if (strcmp(argv[arg], "--performance") == 0) {
performance = true;
} else if (strcmp(argv[arg], "--verbose") == 0) {
verbose = true;
} else if (strcmp(argv[arg], "--enforce_recommended") == 0) {
enforce_recommended = true;
} else if (strcmp(argv[arg], "--maximum_edition") == 0) {
if (++arg == argc) UsageError();
Edition edition = EDITION_UNKNOWN;
if (!Edition_Parse(absl::StrCat("EDITION_", argv[arg]), &edition)) {
fprintf(stderr, "Unknown edition: %s\n", argv[arg]);
UsageError();
}
maximum_edition = edition;
} else if (strcmp(argv[arg], "--output_dir") == 0) {
if (++arg == argc) UsageError();
output_dir = argv[arg];
} else if (strcmp(argv[arg], "--debug") == 0) {
if (++arg == argc) UsageError();
for (int debug_arg = arg; debug_arg < argc; ++debug_arg) {
// Stop when we either find another flag or we reach the last arg
// (program arg)
if (argv[debug_arg][0] == '-' || debug_arg == argc - 1) {
arg = debug_arg - 1;
break;
}
debug_test_names.insert(argv[debug_arg]);
}
} else if (argv[arg][0] == '-') {
bool recognized_flag = false;
for (ConformanceTestSuite *suite : suites) {
if (strcmp(argv[arg], suite->GetFailureListFlagName().c_str()) == 0) {
if (++arg == argc) UsageError();
recognized_flag = true;
}
}
if (!recognized_flag) {
fprintf(stderr, "Unknown option: %s\n", argv[arg]);
UsageError();
}
} else {
program += argv[arg++];
while (arg < argc) {
program_args.push_back(argv[arg]);
arg++;
}
}
}
if (!debug_test_names.empty()) {
debug = true;
}
auto last_slash = program.find_last_of('/');
if (last_slash != string::npos) {
testee = program.substr(last_slash + 1);
}
bool all_ok = true;
for (ConformanceTestSuite *suite : suites) {
string program;
std::vector<string> program_args;
string failure_list_filename;
conformance::FailureSet failure_list;
bool performance = false;
for (int arg = 1; arg < argc; ++arg) {
if (strcmp(argv[arg], suite->GetFailureListFlagName().c_str()) == 0) {
if (++arg == argc) UsageError();
failure_list_filename = argv[arg];
ParseFailureList(argv[arg], &failure_list);
} else if (strcmp(argv[arg], "--performance") == 0) {
performance = true;
suite->SetPerformance(true);
} else if (strcmp(argv[arg], "--verbose") == 0) {
suite->SetVerbose(true);
} else if (strcmp(argv[arg], "--enforce_recommended") == 0) {
suite->SetEnforceRecommended(true);
} else if (strcmp(argv[arg], "--maximum_edition") == 0) {
if (++arg == argc) UsageError();
Edition edition = EDITION_UNKNOWN;
if (!Edition_Parse(absl::StrCat("EDITION_", argv[arg]), &edition)) {
fprintf(stderr, "Unknown edition: %s\n", argv[arg]);
UsageError();
}
suite->SetMaximumEdition(edition);
} else if (strcmp(argv[arg], "--output_dir") == 0) {
if (++arg == argc) UsageError();
suite->SetOutputDir(argv[arg]);
} else if (argv[arg][0] == '-') {
bool recognized_flag = false;
for (ConformanceTestSuite *suite : suites) {
if (strcmp(argv[arg], suite->GetFailureListFlagName().c_str()) == 0) {
if (++arg == argc) UsageError();
recognized_flag = true;
}
}
if (!recognized_flag) {
fprintf(stderr, "Unknown option: %s\n", argv[arg]);
UsageError();
}
} else {
program += argv[arg++];
while (arg < argc) {
program_args.push_back(argv[arg]);
arg++;
}
}
}
suite->SetPerformance(performance);
suite->SetVerbose(verbose);
suite->SetEnforceRecommended(enforce_recommended);
suite->SetMaximumEdition(maximum_edition);
suite->SetOutputDir(output_dir);
suite->SetDebug(debug);
suite->SetDebugTestNames(debug_test_names);
suite->SetTestee(testee);
ForkPipeRunner runner(program, program_args, performance);
@ -270,6 +322,16 @@ int ForkPipeRunner::Run(int argc, char *argv[],
fwrite(output.c_str(), 1, output.size(), stderr);
}
if (!debug_test_names.empty()) {
fprintf(stderr,
"These tests were requested to be debugged, but they do "
"not exist. Revise the test names:\n\n");
for (const string &test_name : debug_test_names) {
fprintf(stderr, " %s\n", test_name.c_str());
}
fprintf(stderr, "\n\n");
}
return all_ok ? EXIT_SUCCESS : EXIT_FAILURE;
}

@ -175,7 +175,10 @@ void TextFormatConformanceTestSuiteImpl<MessageType>::ExpectParseFailure(
setting.ConformanceLevelToString(level), ".",
setting.GetSyntaxIdentifier(), ".TextFormatInput.", test_name);
suite_.RunTest(effective_test_name, request, &response);
if (!suite_.RunTest(effective_test_name, request, &response)) {
return;
}
TestStatus test;
test.set_name(effective_test_name);
if (response.result_case() == ConformanceResponse::kParseError) {

Loading…
Cancel
Save