blob: 62aa3c4998077750ce61eb3e0de8ea5605f34598 [file]
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef GOOGLE_PROTOBUF_CONFORMANCE_FORK_PIPE_RUNNER_H__
#define GOOGLE_PROTOBUF_CONFORMANCE_FORK_PIPE_RUNNER_H__
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "absl/types/optional.h"
#include "absl/types/span.h"
#include "conformance/conformance.pb.h"
#include "conformance/test_runner.h"
namespace google {
namespace protobuf {
namespace conformance {
// Knobs of ForkPipeRunner. The defaults suit real testees. Tests shorten
// them.
struct ForkPipeRunnerOptions {
// How long the testee has to produce each response before the test is
// failed as timed out.
absl::Duration read_timeout = absl::Seconds(30);
// How long the testee has to exit after its stdin is closed before it is
// killed.
absl::Duration shutdown_grace_period = absl::Seconds(5);
};
// Test runner that spawns the process being tested and communicates with it
// over a pipe.
//
// The testee is spawned by the first RunTest() and lives until the runner is
// destroyed. The destructor closes the pipes, gives the testee a few seconds
// to exit, and kills it if it hasn't. Every conformance testee exits on EOF
// on its stdin. Leaving it to the end of the process is not an option.
// Several runners can be created in one process, one per test phase, and some
// testees busy-poll stdin while idle.
//
// On POSIX the testee is made the leader of its own process group, so that
// the SIGKILL reaches anything it forked, for example the real testee behind
// a wrapper script that does not exec it. The same goes for the diagnostic
// SIGQUIT sent when the testee stops answering. Once the testee has exited,
// whatever it left behind in the group is killed too. This has two
// trade-offs. The testee is not in the terminal's foreground process group,
// so an interactive Ctrl-C reaches the runner but not the testee, which then
// only learns that the run is over from EOF on its stdin. And a wrapper
// script that does not exec the testee is terminated by the SIGQUIT, which is
// acceptable since the testee is shut down right after. On Windows the
// testee runs in a job object that is terminated as a whole.
class ForkPipeRunner : public ConformanceTestRunner {
public:
// The testee, `executable` run with `executable_args`, is spawned by the
// first RunTest().
ForkPipeRunner(absl::string_view executable,
absl::Span<const std::string> executable_args,
ForkPipeRunnerOptions options = {});
explicit ForkPipeRunner(absl::string_view executable);
~ForkPipeRunner() override;
ForkPipeRunner(const ForkPipeRunner&) = delete;
ForkPipeRunner& operator=(const ForkPipeRunner&) = delete;
std::string RunTest(absl::string_view test_name,
absl::string_view request) override;
private:
// Outcome of an attempt to read a fixed number of bytes from the testee.
enum class ReadResult {
kOk, // All requested bytes were read.
kEof, // The testee closed its end of the pipe (it exited or crashed).
kError, // The read failed.
kTimeout, // The testee produced nothing for the whole read timeout.
};
struct ShutdownResult {
// How the testee ended: its wait status (POSIX) or exit code (Windows).
// nullopt if there was nothing to reap, or if how it ended is unknown.
absl::optional<int> wait_status;
// True if the testee outlived the grace period and the runner killed it.
bool killed = false;
// True if something other than the runner reaped the testee, so how it
// ended is unknown. The kernel does that if this process ignores SIGCHLD.
bool reaped_elsewhere = false;
};
// The process and pipe handles of the test program. Defined by the
// platform's implementation file, fork_pipe_runner_posix.cc or
// fork_pipe_runner_win32.cc, along with the methods below, the constructor
// that creates it and the destructor.
struct State;
void SpawnTestProgram();
bool IsTestProgramRunning() const;
// Closes the pipes to the testee, waits up to `grace_period` for it to exit,
// kills it if it hasn't, and reaps it. Anything it spawned is killed with
// it (its process group on POSIX, its job object on Windows). A no-op if
// there is no testee.
ShutdownResult Shutdown(absl::Duration grace_period);
// Shuts the testee down after a failed exchange and returns `what_failed`
// (e.g. "child timed out") with how the testee ended appended, if known.
// The next RunTest() call spawns a fresh testee.
std::string GetTestProgramFailure(absl::string_view what_failed);
// Shuts the testee down after a failed exchange and returns the serialized
// ConformanceResponse reporting it: a timeout_error if `timed_out`, a
// runtime_error otherwise.
std::string ReportFailure(bool timed_out, absl::string_view what_failed);
// ReportFailure() for a TryRead() that did not return kOk.
std::string ReportReadFailure(ReadResult read_result);
// Writes all `len` bytes of `buf` to the testee. Returns false if the write
// failed, in particular because the testee has exited. That shows as EPIPE
// on POSIX and as a broken-pipe error on Windows. On POSIX, SIGPIPE is
// blocked on the calling thread for the duration of the write, so that a
// testee that dies between two requests is reported instead of killing the
// runner.
bool TryWrite(const void* buf, size_t len);
// Reads exactly `len` bytes from the testee, giving up if it produces
// nothing for `options_.read_timeout`. Never blocks indefinitely.
ReadResult TryRead(void* buf, size_t len);
std::string executable_;
const std::vector<std::string> executable_args_;
const ForkPipeRunnerOptions options_;
std::string current_test_name_;
std::unique_ptr<State> state_;
};
} // namespace conformance
} // namespace protobuf
} // namespace google
#endif // GOOGLE_PROTOBUF_CONFORMANCE_FORK_PIPE_RUNNER_H__