• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 
8 // This file contains a program for running the test suite in a separate
9 // process.  The other alternative is to run the suite in-process.  See
10 // conformance.proto for pros/cons of these two options.
11 //
12 // This program will fork the process under test and communicate with it over
13 // its stdin/stdout:
14 //
15 //     +--------+   pipe   +----------+
16 //     | tester | <------> | testee   |
17 //     |        |          |          |
18 //     |  C++   |          | any lang |
19 //     +--------+          +----------+
20 //
21 // The tester contains all of the test cases and their expected output.
22 // The testee is a simple program written in the target language that reads
23 // each test case and attempts to produce acceptable output for it.
24 //
25 // Every test consists of a ConformanceRequest/ConformanceResponse
26 // request/reply pair.  The protocol on the pipe is simply:
27 //
28 //   1. tester sends 4-byte length N (little endian)
29 //   2. tester sends N bytes representing a ConformanceRequest proto
30 //   3. testee sends 4-byte length M (little endian)
31 //   4. testee sends M bytes representing a ConformanceResponse proto
32 
33 #include <errno.h>
34 #include <signal.h>
35 #include <stdio.h>
36 #include <sys/types.h>
37 #include <sys/wait.h>
38 #include <unistd.h>
39 
40 #include <algorithm>
41 #include <cctype>
42 #include <cstdint>
43 #include <cstdio>
44 #include <cstdlib>
45 #include <cstring>
46 #include <fstream>
47 #include <future>
48 #include <memory>
49 #include <string>
50 #include <vector>
51 
52 #include "absl/container/flat_hash_set.h"
53 #include "absl/log/absl_log.h"
54 #include "absl/strings/ascii.h"
55 #include "absl/strings/str_cat.h"
56 #include "absl/strings/str_format.h"
57 #include "conformance/conformance.pb.h"
58 #include "conformance_test.h"
59 #include "google/protobuf/endian.h"
60 
61 using google::protobuf::ConformanceTestSuite;
62 using std::string;
63 using std::vector;
64 
65 #define STRINGIFY(x) #x
66 #define TOSTRING(x) STRINGIFY(x)
67 #define CHECK_SYSCALL(call)                            \
68   if (call < 0) {                                      \
69     perror(#call " " __FILE__ ":" TOSTRING(__LINE__)); \
70     exit(1);                                           \
71   }
72 
73 namespace google {
74 namespace protobuf {
75 
ParseFailureList(const char * filename,conformance::FailureSet * failure_list)76 void ParseFailureList(const char *filename,
77                       conformance::FailureSet *failure_list) {
78   std::ifstream infile(filename);
79 
80   if (!infile.is_open()) {
81     fprintf(stderr, "Couldn't open failure list file: %s\n", filename);
82     exit(1);
83   }
84 
85   for (string line; std::getline(infile, line);) {
86     // Remove comments.
87     string test_name = line.substr(0, line.find('#'));
88 
89     test_name.erase(
90         std::remove_if(test_name.begin(), test_name.end(), ::isspace),
91         test_name.end());
92 
93     if (test_name.empty()) {  // Skip empty lines.
94       continue;
95     }
96 
97     // If we remove whitespace from the beginning of a line, and what we have
98     // left at first is a '#', then we have a comment.
99     if (test_name[0] != '#') {
100       // Find our failure message if it exists. Will be set to an empty string
101       // if no message is found. Empty failure messages also pass our tests.
102       size_t check_message = line.find('#');
103       string message;
104       if (check_message != std::string::npos) {
105         message = line.substr(check_message + 1);  // +1 to skip the delimiter
106         // If we had only whitespace after the delimiter, we will have an empty
107         // failure message and the test will still pass.
108         message = std::string(absl::StripAsciiWhitespace(message));
109       }
110       conformance::TestStatus *test = failure_list->add_test();
111       test->set_name(test_name);
112       test->set_failure_message(message);
113     }
114   }
115 }
116 
UsageError()117 void UsageError() {
118   fprintf(stderr, "Usage: conformance-test-runner [options] <test-program>\n");
119   fprintf(stderr, "\n");
120   fprintf(stderr, "Options:\n");
121   fprintf(stderr,
122           "  --failure_list <filename>   Use to specify list of tests\n");
123   fprintf(stderr,
124           "                              that are expected to fail.  File\n");
125   fprintf(stderr,
126           "                              should contain one test name per\n");
127   fprintf(stderr,
128           "                              line.  Use '#' for comments.\n\n");
129   fprintf(stderr,
130           "  --text_format_failure_list <filename>   Use to specify list \n");
131   fprintf(stderr,
132           "                              of tests that are expected to \n");
133   fprintf(stderr, "                              fail in the \n");
134   fprintf(stderr,
135           "                              text_format_conformance_suite.  \n");
136   fprintf(stderr,
137           "                              File should contain one test name \n");
138   fprintf(stderr,
139           "                              per line.  Use '#' for comments.\n\n");
140 
141   fprintf(stderr,
142           "  --enforce_recommended       Enforce that recommended test\n");
143   fprintf(stderr,
144           "                              cases are also passing. Specify\n");
145   fprintf(stderr,
146           "                              this flag if you want to be\n");
147   fprintf(stderr,
148           "                              strictly conforming to protobuf\n");
149   fprintf(stderr, "                              spec.\n\n");
150   fprintf(stderr,
151           "  --maximum_edition <edition> Only run conformance tests up to\n");
152   fprintf(stderr,
153           "                              and including the specified\n");
154   fprintf(stderr, "                              edition.\n\n");
155   fprintf(stderr,
156           "  --output_dir                <dirname> Directory to write\n"
157           "                              output files.\n\n");
158   fprintf(stderr, "  --test <test_name>          Only run\n");
159   fprintf(stderr,
160           "                              the specified test. Multiple tests\n"
161           "                              can be specified by repeating the \n"
162           "                              flag.\n\n");
163   fprintf(stderr,
164           "  --debug                     Enable debug mode\n"
165           "                              to produce octal serialized\n"
166           "                              ConformanceRequest for the tests\n"
167           "                              passed to --test (required)\n\n");
168   fprintf(stderr, "  --performance               Boolean option\n");
169   fprintf(stderr, "                              for enabling run of\n");
170   fprintf(stderr, "                              performance tests.\n");
171   exit(1);
172 }
173 
RunTest(const std::string & test_name,uint32_t len,const std::string & request,std::string * response)174 void ForkPipeRunner::RunTest(const std::string &test_name, uint32_t len,
175                              const std::string &request,
176                              std::string *response) {
177   if (child_pid_ < 0) {
178     SpawnTestProgram();
179   }
180   current_test_name_ = test_name;
181 
182   CheckedWrite(write_fd_, &len, sizeof(uint32_t));
183   CheckedWrite(write_fd_, request.c_str(), request.size());
184 
185   if (!TryRead(read_fd_, &len, sizeof(uint32_t))) {
186     // We failed to read from the child, assume a crash and try to reap.
187     ABSL_LOG(INFO) << "Trying to reap child, pid=" << child_pid_;
188 
189     int status = 0;
190     waitpid(child_pid_, &status, WEXITED);
191 
192     string error_msg;
193     conformance::ConformanceResponse response_obj;
194     if (WIFEXITED(status)) {
195       if (WEXITSTATUS(status) == 0) {
196         absl::StrAppendFormat(&error_msg,
197                               "child timed out, killed by signal %d",
198                               WTERMSIG(status));
199         response_obj.set_timeout_error(error_msg);
200       } else {
201         absl::StrAppendFormat(&error_msg, "child exited, status=%d",
202                               WEXITSTATUS(status));
203         response_obj.set_runtime_error(error_msg);
204       }
205     } else if (WIFSIGNALED(status)) {
206       absl::StrAppendFormat(&error_msg, "child killed by signal %d",
207                             WTERMSIG(status));
208     }
209     ABSL_LOG(INFO) << error_msg;
210     child_pid_ = -1;
211 
212     response_obj.SerializeToString(response);
213     return;
214   }
215 
216   len = internal::little_endian::ToHost(len);
217   response->resize(len);
218   CheckedRead(read_fd_, (void *)response->c_str(), len);
219 }
220 
Run(int argc,char * argv[],const std::vector<ConformanceTestSuite * > & suites)221 int ForkPipeRunner::Run(int argc, char *argv[],
222                         const std::vector<ConformanceTestSuite *> &suites) {
223   if (suites.empty()) {
224     fprintf(stderr, "No test suites found.\n");
225     return EXIT_FAILURE;
226   }
227 
228   string program;
229   string testee;
230   std::vector<string> program_args;
231   bool performance = false;
232   bool debug = false;
233   absl::flat_hash_set<string> names_to_test;
234   bool enforce_recommended = false;
235   Edition maximum_edition = EDITION_UNKNOWN;
236   std::string output_dir;
237   bool verbose = false;
238   bool isolated = false;
239 
240   for (int arg = 1; arg < argc; ++arg) {
241     if (strcmp(argv[arg], "--performance") == 0) {
242       performance = true;
243     } else if (strcmp(argv[arg], "--debug") == 0) {
244       debug = true;
245     } else if (strcmp(argv[arg], "--verbose") == 0) {
246       verbose = true;
247     } else if (strcmp(argv[arg], "--enforce_recommended") == 0) {
248       enforce_recommended = true;
249     } else if (strcmp(argv[arg], "--maximum_edition") == 0) {
250       if (++arg == argc) UsageError();
251       Edition edition = EDITION_UNKNOWN;
252       if (!Edition_Parse(absl::StrCat("EDITION_", argv[arg]), &edition)) {
253         fprintf(stderr, "Unknown edition: %s\n", argv[arg]);
254         UsageError();
255       }
256       maximum_edition = edition;
257     } else if (strcmp(argv[arg], "--output_dir") == 0) {
258       if (++arg == argc) UsageError();
259       output_dir = argv[arg];
260 
261     } else if (strcmp(argv[arg], "--test") == 0) {
262       if (++arg == argc) UsageError();
263       names_to_test.insert(argv[arg]);
264 
265     } else if (argv[arg][0] == '-') {
266       bool recognized_flag = false;
267       for (ConformanceTestSuite *suite : suites) {
268         if (strcmp(argv[arg], suite->GetFailureListFlagName().c_str()) == 0) {
269           if (++arg == argc) UsageError();
270           recognized_flag = true;
271         }
272       }
273       if (!recognized_flag) {
274         fprintf(stderr, "Unknown option: %s\n", argv[arg]);
275         UsageError();
276       }
277     } else {
278       program += argv[arg++];
279       while (arg < argc) {
280         program_args.push_back(argv[arg]);
281         arg++;
282       }
283     }
284   }
285 
286   if (debug && names_to_test.empty()) {
287     UsageError();
288   }
289 
290   if (!names_to_test.empty()) {
291     isolated = true;
292   }
293 
294   bool all_ok = true;
295   for (ConformanceTestSuite *suite : suites) {
296     string failure_list_filename;
297     conformance::FailureSet failure_list;
298     for (int arg = 1; arg < argc; ++arg) {
299       if (strcmp(argv[arg], suite->GetFailureListFlagName().c_str()) == 0) {
300         if (++arg == argc) UsageError();
301         failure_list_filename = argv[arg];
302         ParseFailureList(argv[arg], &failure_list);
303       }
304     }
305     suite->SetPerformance(performance);
306     suite->SetVerbose(verbose);
307     suite->SetEnforceRecommended(enforce_recommended);
308     suite->SetMaximumEdition(maximum_edition);
309     suite->SetOutputDir(output_dir);
310     suite->SetDebug(debug);
311     suite->SetNamesToTest(names_to_test);
312     suite->SetTestee(program);
313     suite->SetIsolated(isolated);
314 
315     ForkPipeRunner runner(program, program_args, performance);
316 
317     std::string output;
318     all_ok = all_ok && suite->RunSuite(&runner, &output, failure_list_filename,
319                                        &failure_list);
320 
321     names_to_test = suite->GetExpectedTestsNotRun();
322     fwrite(output.c_str(), 1, output.size(), stderr);
323   }
324 
325   if (!names_to_test.empty()) {
326     fprintf(stderr,
327             "These tests were requested to be ran isolated, but they do "
328             "not exist. Revise the test names:\n\n");
329     for (const string &test_name : names_to_test) {
330       fprintf(stderr, "  %s\n", test_name.c_str());
331     }
332     fprintf(stderr, "\n\n");
333   }
334   return all_ok ? EXIT_SUCCESS : EXIT_FAILURE;
335 }
336 
337 // TODO: make this work on Windows, instead of using these
338 // UNIX-specific APIs.
339 //
340 // There is a platform-agnostic API in
341 //    src/google/protobuf/compiler/subprocess.h
342 //
343 // However that API only supports sending a single message to the subprocess.
344 // We really want to be able to send messages and receive responses one at a
345 // time:
346 //
347 // 1. Spawning a new process for each test would take way too long for thousands
348 //    of tests and subprocesses like java that can take 100ms or more to start
349 //    up.
350 //
351 // 2. Sending all the tests in one big message and receiving all results in one
352 //    big message would take away our visibility about which test(s) caused a
353 //    crash or other fatal error.  It would also give us only a single failure
354 //    instead of all of them.
SpawnTestProgram()355 void ForkPipeRunner::SpawnTestProgram() {
356   int toproc_pipe_fd[2];
357   int fromproc_pipe_fd[2];
358   if (pipe(toproc_pipe_fd) < 0 || pipe(fromproc_pipe_fd) < 0) {
359     perror("pipe");
360     exit(1);
361   }
362 
363   pid_t pid = fork();
364   if (pid < 0) {
365     perror("fork");
366     exit(1);
367   }
368 
369   if (pid) {
370     // Parent.
371     CHECK_SYSCALL(close(toproc_pipe_fd[0]));
372     CHECK_SYSCALL(close(fromproc_pipe_fd[1]));
373     write_fd_ = toproc_pipe_fd[1];
374     read_fd_ = fromproc_pipe_fd[0];
375     child_pid_ = pid;
376   } else {
377     // Child.
378     CHECK_SYSCALL(close(STDIN_FILENO));
379     CHECK_SYSCALL(close(STDOUT_FILENO));
380     CHECK_SYSCALL(dup2(toproc_pipe_fd[0], STDIN_FILENO));
381     CHECK_SYSCALL(dup2(fromproc_pipe_fd[1], STDOUT_FILENO));
382 
383     CHECK_SYSCALL(close(toproc_pipe_fd[0]));
384     CHECK_SYSCALL(close(fromproc_pipe_fd[1]));
385     CHECK_SYSCALL(close(toproc_pipe_fd[1]));
386     CHECK_SYSCALL(close(fromproc_pipe_fd[0]));
387 
388     std::unique_ptr<char[]> executable(new char[executable_.size() + 1]);
389     memcpy(executable.get(), executable_.c_str(), executable_.size());
390     executable[executable_.size()] = '\0';
391 
392     std::vector<const char *> argv;
393     argv.push_back(executable.get());
394     ABSL_LOG(INFO) << argv[0];
395     for (size_t i = 0; i < executable_args_.size(); ++i) {
396       argv.push_back(executable_args_[i].c_str());
397       ABSL_LOG(INFO) << executable_args_[i];
398     }
399     argv.push_back(nullptr);
400     // Never returns.
401     CHECK_SYSCALL(execv(executable.get(), const_cast<char **>(argv.data())));
402   }
403 }
404 
CheckedWrite(int fd,const void * buf,size_t len)405 void ForkPipeRunner::CheckedWrite(int fd, const void *buf, size_t len) {
406   if (static_cast<size_t>(write(fd, buf, len)) != len) {
407     ABSL_LOG(FATAL) << current_test_name_
408                     << ": error writing to test program: " << strerror(errno);
409   }
410 }
411 
TryRead(int fd,void * buf,size_t len)412 bool ForkPipeRunner::TryRead(int fd, void *buf, size_t len) {
413   size_t ofs = 0;
414   while (len > 0) {
415     std::future<ssize_t> future = std::async(
416         std::launch::async,
417         [](int fd, void *buf, size_t ofs, size_t len) {
418           return read(fd, (char *)buf + ofs, len);
419         },
420         fd, buf, ofs, len);
421     std::future_status status;
422     if (performance_) {
423       status = future.wait_for(std::chrono::seconds(5));
424       if (status == std::future_status::timeout) {
425         ABSL_LOG(ERROR) << current_test_name_ << ": timeout from test program";
426         kill(child_pid_, SIGQUIT);
427         // TODO: Only log in flag-guarded mode, since reading output
428         // from SIGQUIT is slow and verbose.
429         std::vector<char> err;
430         err.resize(5000);
431         ssize_t err_bytes_read;
432         size_t err_ofs = 0;
433         do {
434           err_bytes_read =
435               read(fd, (void *)&err[err_ofs], err.size() - err_ofs);
436           err_ofs += err_bytes_read;
437         } while (err_bytes_read > 0 && err_ofs < err.size());
438         ABSL_LOG(ERROR) << "child_pid_=" << child_pid_ << " SIGQUIT: \n"
439                         << &err[0];
440         return false;
441       }
442     } else {
443       future.wait();
444     }
445     ssize_t bytes_read = future.get();
446     if (bytes_read == 0) {
447       ABSL_LOG(ERROR) << current_test_name_
448                       << ": unexpected EOF from test program";
449       return false;
450     } else if (bytes_read < 0) {
451       ABSL_LOG(ERROR) << current_test_name_
452                       << ": error reading from test program: "
453                       << strerror(errno);
454       return false;
455     }
456 
457     len -= bytes_read;
458     ofs += bytes_read;
459   }
460 
461   return true;
462 }
463 
CheckedRead(int fd,void * buf,size_t len)464 void ForkPipeRunner::CheckedRead(int fd, void *buf, size_t len) {
465   if (!TryRead(fd, buf, len)) {
466     ABSL_LOG(FATAL) << current_test_name_
467                     << ": error reading from test program: " << strerror(errno);
468   }
469 }
470 
471 }  // namespace protobuf
472 }  // namespace google
473