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