| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 | // 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.// This file contains a program for running the test suite in a separate// process.  The other alternative is to run the suite in-process.  See// conformance.proto for pros/cons of these two options.//// This program will fork the process under test and communicate with it over// its stdin/stdout:////     +--------+   pipe   +----------+//     | tester | <------> | testee   |//     |        |          |          |//     |  C++   |          | any lang |//     +--------+          +----------+//// The tester contains all of the test cases and their expected output.// The testee is a simple program written in the target language that reads// each test case and attempts to produce acceptable output for it.//// Every test consists of a ConformanceRequest/ConformanceResponse// request/reply pair.  The protocol on the pipe is simply:////   1. tester sends 4-byte length N (little endian)//   2. tester sends N bytes representing a ConformanceRequest proto//   3. testee sends 4-byte length M (little endian)//   4. testee sends M bytes representing a ConformanceResponse proto#include <algorithm>#include <errno.h>#include <fstream>#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>#include <vector>#include <google/protobuf/stubs/stringprintf.h>#include "conformance.pb.h"#include "conformance_test.h"using conformance::ConformanceResponse;using google::protobuf::StringAppendF;using google::protobuf::ConformanceTestSuite;using std::string;using std::vector;#define STRINGIFY(x) #x#define TOSTRING(x) STRINGIFY(x)#define CHECK_SYSCALL(call) \  if (call < 0) { \    perror(#call " " __FILE__ ":" TOSTRING(__LINE__)); \    exit(1); \  }// Test runner that spawns the process being tested and communicates with it// over a pipe.class ForkPipeRunner : public google::protobuf::ConformanceTestRunner { public:  ForkPipeRunner(const std::string &executable)      : child_pid_(-1), executable_(executable) {}  virtual ~ForkPipeRunner() {}  void RunTest(const std::string& test_name,               const std::string& request,               std::string* response) {    if (child_pid_ < 0) {      SpawnTestProgram();    }    current_test_name_ = test_name;    uint32_t len = request.size();    CheckedWrite(write_fd_, &len, sizeof(uint32_t));    CheckedWrite(write_fd_, request.c_str(), request.size());    if (!TryRead(read_fd_, &len, sizeof(uint32_t))) {      // We failed to read from the child, assume a crash and try to reap.      GOOGLE_LOG(INFO) << "Trying to reap child, pid=" << child_pid_;      int status;      waitpid(child_pid_, &status, WEXITED);      string error_msg;      if (WIFEXITED(status)) {        StringAppendF(&error_msg,                      "child exited, status=%d", WEXITSTATUS(status));      } else if (WIFSIGNALED(status)) {        StringAppendF(&error_msg,                      "child killed by signal %d", WTERMSIG(status));      }      GOOGLE_LOG(INFO) << error_msg;      child_pid_ = -1;      conformance::ConformanceResponse response_obj;      response_obj.set_runtime_error(error_msg);      response_obj.SerializeToString(response);      return;    }    response->resize(len);    CheckedRead(read_fd_, (void*)response->c_str(), len);  } private:  // TODO(haberman): make this work on Windows, instead of using these  // UNIX-specific APIs.  //  // There is a platform-agnostic API in  //    src/google/protobuf/compiler/subprocess.h  //  // However that API only supports sending a single message to the subprocess.  // We really want to be able to send messages and receive responses one at a  // time:  //  // 1. Spawning a new process for each test would take way too long for thousands  //    of tests and subprocesses like java that can take 100ms or more to start  //    up.  //  // 2. Sending all the tests in one big message and receiving all results in one  //    big message would take away our visibility about which test(s) caused a  //    crash or other fatal error.  It would also give us only a single failure  //    instead of all of them.  void SpawnTestProgram() {    int toproc_pipe_fd[2];    int fromproc_pipe_fd[2];    if (pipe(toproc_pipe_fd) < 0 || pipe(fromproc_pipe_fd) < 0) {      perror("pipe");      exit(1);    }    pid_t pid = fork();    if (pid < 0) {      perror("fork");      exit(1);    }    if (pid) {      // Parent.      CHECK_SYSCALL(close(toproc_pipe_fd[0]));      CHECK_SYSCALL(close(fromproc_pipe_fd[1]));      write_fd_ = toproc_pipe_fd[1];      read_fd_ = fromproc_pipe_fd[0];      child_pid_ = pid;    } else {      // Child.      CHECK_SYSCALL(close(STDIN_FILENO));      CHECK_SYSCALL(close(STDOUT_FILENO));      CHECK_SYSCALL(dup2(toproc_pipe_fd[0], STDIN_FILENO));      CHECK_SYSCALL(dup2(fromproc_pipe_fd[1], STDOUT_FILENO));      CHECK_SYSCALL(close(toproc_pipe_fd[0]));      CHECK_SYSCALL(close(fromproc_pipe_fd[1]));      CHECK_SYSCALL(close(toproc_pipe_fd[1]));      CHECK_SYSCALL(close(fromproc_pipe_fd[0]));      std::unique_ptr<char[]> executable(new char[executable_.size() + 1]);      memcpy(executable.get(), executable_.c_str(), executable_.size());      executable[executable_.size()] = '\0';      char *const argv[] = {executable.get(), NULL};      CHECK_SYSCALL(execv(executable.get(), argv));  // Never returns.    }  }  void CheckedWrite(int fd, const void *buf, size_t len) {    if (write(fd, buf, len) != len) {      GOOGLE_LOG(FATAL) << current_test_name_                        << ": error writing to test program: "                        << strerror(errno);    }  }  bool TryRead(int fd, void *buf, size_t len) {    size_t ofs = 0;    while (len > 0) {      ssize_t bytes_read = read(fd, (char*)buf + ofs, len);      if (bytes_read == 0) {        GOOGLE_LOG(ERROR) << current_test_name_                          << ": unexpected EOF from test program";        return false;      } else if (bytes_read < 0) {        GOOGLE_LOG(ERROR) << current_test_name_                          << ": error reading from test program: "                          << strerror(errno);        return false;      }      len -= bytes_read;      ofs += bytes_read;    }    return true;  }  void CheckedRead(int fd, void *buf, size_t len) {    if (!TryRead(fd, buf, len)) {      GOOGLE_LOG(FATAL) << current_test_name_                        << ": error reading from test program: "                        << strerror(errno);    }  }  int write_fd_;  int read_fd_;  pid_t child_pid_;  std::string executable_;  std::string current_test_name_;};void UsageError() {  fprintf(stderr,          "Usage: conformance-test-runner [options] <test-program>\n");  fprintf(stderr, "\n");  fprintf(stderr, "Options:\n");  fprintf(stderr,          "  --failure_list <filename>   Use to specify list of tests\n");  fprintf(stderr,          "                              that are expected to fail.  File\n");  fprintf(stderr,          "                              should contain one test name per\n");  fprintf(stderr,          "                              line.  Use '#' for comments.\n");  fprintf(stderr,          "  --enforce_recommended       Enforce that recommended test\n");  fprintf(stderr,          "                              cases are also passing. Specify\n");  fprintf(stderr,          "                              this flag if you want to be\n");  fprintf(stderr,          "                              strictly conforming to protobuf\n");  fprintf(stderr,          "                              spec.\n");  exit(1);}void ParseFailureList(const char *filename, std::vector<string>* failure_list) {  std::ifstream infile(filename);  if (!infile.is_open()) {    fprintf(stderr, "Couldn't open failure list file: %s\n", filename);    exit(1);  }  for (string line; getline(infile, line);) {    // Remove whitespace.    line.erase(std::remove_if(line.begin(), line.end(), ::isspace),               line.end());    // Remove comments.    line = line.substr(0, line.find("#"));    if (!line.empty()) {      failure_list->push_back(line);    }  }}int main(int argc, char *argv[]) {  char *program;  const std::set<ConformanceTestSuite*>& test_suite_set =      ::google::protobuf::GetTestSuiteSet();  string failure_list_filename;  std::vector<string> failure_list;  for (int arg = 1; arg < argc; ++arg) {    if (strcmp(argv[arg], "--failure_list") == 0) {      if (++arg == argc) UsageError();      failure_list_filename = argv[arg];      ParseFailureList(argv[arg], &failure_list);    } else if (strcmp(argv[arg], "--verbose") == 0) {      for (auto *suite : test_suite_set) {        suite->SetVerbose(true);      }    } else if (strcmp(argv[arg], "--enforce_recommended") == 0) {      for (auto suite : test_suite_set) {        suite->SetEnforceRecommended(true);      }    } else if (argv[arg][0] == '-') {      fprintf(stderr, "Unknown option: %s\n", argv[arg]);      UsageError();    } else {      if (arg != argc - 1) {        fprintf(stderr, "Too many arguments.\n");        UsageError();      }      program = argv[arg];    }  }  for (auto suite : test_suite_set) {    suite->SetFailureList(failure_list_filename, failure_list);  }  ForkPipeRunner runner(program);  std::string output;  bool ok = true;  for (auto suite : test_suite_set) {    ok &= suite->RunSuite(&runner, &output);  }  fwrite(output.c_str(), 1, output.size(), stderr);  return ok ? EXIT_SUCCESS : EXIT_FAILURE;}
 |