| Index: services/native_support/process_controller_impl_unittest.cc
|
| diff --git a/services/native_support/process_controller_impl_unittest.cc b/services/native_support/process_controller_impl_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..123c266d9a21566165a6388fc5e213e6c4b31703
|
| --- /dev/null
|
| +++ b/services/native_support/process_controller_impl_unittest.cc
|
| @@ -0,0 +1,196 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include <signal.h>
|
| +
|
| +#include <string>
|
| +#include <vector>
|
| +
|
| +#include "base/message_loop/message_loop.h"
|
| +#include "mojo/public/cpp/bindings/interface_request.h"
|
| +#include "mojo/public/cpp/bindings/type_converter.h"
|
| +#include "mojo/services/files/public/cpp/input_stream_file.h"
|
| +#include "mojo/services/files/public/cpp/output_stream_file.h"
|
| +#include "mojo/services/files/public/interfaces/types.mojom.h"
|
| +#include "services/native_support/process_test_base.h"
|
| +
|
| +namespace native_support {
|
| +namespace {
|
| +
|
| +using ProcessControllerImplTest = ProcessTestBase;
|
| +
|
| +void QuitMessageLoop() {
|
| + base::MessageLoop::current()->QuitWhenIdle();
|
| +}
|
| +
|
| +void RunMessageLoop() {
|
| + base::MessageLoop::current()->Run();
|
| +}
|
| +
|
| +// Note: We already tested success (zero) versus failure (non-zero) exit
|
| +// statuses in |ProcessControllerTest.Spawn|.
|
| +TEST_F(ProcessControllerImplTest, Wait) {
|
| + mojo::files::Error error;
|
| +
|
| + {
|
| + ProcessControllerPtr process_controller;
|
| + error = mojo::files::ERROR_INTERNAL;
|
| + const char kPath[] = "/bin/sh";
|
| + mojo::Array<mojo::String> argv;
|
| + argv.push_back(kPath);
|
| + argv.push_back("-c");
|
| + argv.push_back("exit 42");
|
| + process()->Spawn(kPath, argv.Pass(), mojo::Array<mojo::String>(), nullptr,
|
| + nullptr, nullptr, GetProxy(&process_controller),
|
| + Capture(&error));
|
| + ASSERT_TRUE(process().WaitForIncomingResponse());
|
| + EXPECT_EQ(mojo::files::ERROR_OK, error);
|
| +
|
| + error = mojo::files::ERROR_INTERNAL;
|
| + int32_t exit_status = 0;
|
| + process_controller->Wait(Capture(&error, &exit_status));
|
| + ASSERT_TRUE(process_controller.WaitForIncomingResponse());
|
| + EXPECT_EQ(mojo::files::ERROR_OK, error);
|
| + EXPECT_EQ(42, exit_status);
|
| + }
|
| +}
|
| +
|
| +// An output file stream that captures output and quits when "ready" is seen.
|
| +class QuitOnReadyFile : public files_impl::OutputStreamFile::Client {
|
| + public:
|
| + explicit QuitOnReadyFile(mojo::InterfaceRequest<mojo::files::File> request)
|
| + : impl_(files_impl::OutputStreamFile::Create(this, request.Pass())) {}
|
| + ~QuitOnReadyFile() override {}
|
| +
|
| + bool got_ready() const { return got_ready_; }
|
| +
|
| + private:
|
| + // |files_impl::OutputStreamFile::Client|:
|
| + void OnDataReceived(const void* bytes, size_t num_bytes) override {
|
| + output_.append(static_cast<const char*>(bytes), num_bytes);
|
| + if (output_.find("ready") != std::string::npos) {
|
| + got_ready_ = true;
|
| + QuitMessageLoop();
|
| + }
|
| + }
|
| + void OnClosed() override { QuitMessageLoop(); }
|
| +
|
| + std::string output_;
|
| + bool got_ready_ = false;
|
| + std::unique_ptr<files_impl::OutputStreamFile> impl_;
|
| +
|
| + MOJO_DISALLOW_COPY_AND_ASSIGN(QuitOnReadyFile);
|
| +};
|
| +
|
| +// An input file stream that ignores reads (never completing them).
|
| +class IgnoreReadsFile : public files_impl::InputStreamFile::Client {
|
| + public:
|
| + explicit IgnoreReadsFile(mojo::InterfaceRequest<mojo::files::File> request)
|
| + : impl_(files_impl::InputStreamFile::Create(this, request.Pass())) {}
|
| + ~IgnoreReadsFile() override {}
|
| +
|
| + private:
|
| + // |files_impl::InputStreamFile::Client|:
|
| + bool RequestData(size_t max_num_bytes,
|
| + mojo::files::Error* error,
|
| + mojo::Array<uint8_t>* data,
|
| + const RequestDataCallback& callback) override {
|
| + // Don't let |callback| die, because we probably have assertions "ensuring"
|
| + // that response callbacks get called.
|
| + callbacks_.push_back(callback);
|
| + return false;
|
| + }
|
| + void OnClosed() override { QuitMessageLoop(); }
|
| +
|
| + std::vector<RequestDataCallback> callbacks_;
|
| + std::unique_ptr<files_impl::InputStreamFile> impl_;
|
| +
|
| + MOJO_DISALLOW_COPY_AND_ASSIGN(IgnoreReadsFile);
|
| +};
|
| +
|
| +TEST_F(ProcessControllerImplTest, Kill) {
|
| + // We want to run bash and have it set a trap for SIGINT, which it'll handle
|
| + // by quitting with code 42. We need to make sure that bash started and set
|
| + // the trap before we kill our child. Thus we have bash echo "ready" and wait
|
| + // for it to do so (and thus we need to capture/examine stdout).
|
| + //
|
| + // We want bash to pause, so that we can kill it before it quits. We can't use
|
| + // "sleep" since it's not a builtin (so bash would wait for it before dying).
|
| + // So instead we use "read" with a timeout (note that "-t" is a bash
|
| + // extension, and not specified by POSIX for /bin/sh). Thus we have to give
|
| + // something for stdin (otherwise, it'd come from /dev/null and the read would
|
| + // be completed immediately).
|
| + mojo::files::FilePtr ifile;
|
| + IgnoreReadsFile ifile_impl(GetProxy(&ifile));
|
| + mojo::files::FilePtr ofile;
|
| + QuitOnReadyFile ofile_impl(GetProxy(&ofile));
|
| +
|
| + ProcessControllerPtr process_controller;
|
| + mojo::files::Error error = mojo::files::ERROR_INTERNAL;
|
| + const char kPath[] = "/bin/bash";
|
| + mojo::Array<mojo::String> argv;
|
| + argv.push_back(kPath);
|
| + argv.push_back("-c");
|
| + argv.push_back("trap 'exit 42' INT; echo ready; read -t30; exit 1");
|
| + process()->Spawn(kPath, argv.Pass(), mojo::Array<mojo::String>(),
|
| + ifile.Pass(), ofile.Pass(), nullptr,
|
| + GetProxy(&process_controller), Capture(&error));
|
| + ASSERT_TRUE(process().WaitForIncomingResponse());
|
| + EXPECT_EQ(mojo::files::ERROR_OK, error);
|
| +
|
| + // |ofile_impl| will quit the message loop once it sees "ready".
|
| + RunMessageLoop();
|
| + ASSERT_TRUE(ofile_impl.got_ready());
|
| +
|
| + // Send SIGINT.
|
| + error = mojo::files::ERROR_INTERNAL;
|
| + process_controller->Kill(static_cast<int32_t>(SIGINT), Capture(&error));
|
| + ASSERT_TRUE(process_controller.WaitForIncomingResponse());
|
| + EXPECT_EQ(mojo::files::ERROR_OK, error);
|
| +
|
| + error = mojo::files::ERROR_INTERNAL;
|
| + int32_t exit_status = 0;
|
| + process_controller->Wait(Capture(&error, &exit_status));
|
| + ASSERT_TRUE(process_controller.WaitForIncomingResponse());
|
| + EXPECT_EQ(mojo::files::ERROR_OK, error);
|
| + EXPECT_EQ(42, exit_status);
|
| +}
|
| +
|
| +TEST_F(ProcessControllerImplTest, DestroyingControllerKills) {
|
| + // We want to make sure that we've exec-ed before killing, so we do what we do
|
| + // in |ProcessControllerImplTest.Kill| (without the trap).
|
| + {
|
| + mojo::files::FilePtr ifile;
|
| + IgnoreReadsFile ifile_impl(GetProxy(&ifile));
|
| + mojo::files::FilePtr ofile;
|
| + QuitOnReadyFile ofile_impl(GetProxy(&ofile));
|
| +
|
| + ProcessControllerPtr process_controller;
|
| + mojo::files::Error error = mojo::files::ERROR_INTERNAL;
|
| + const char kPath[] = "/bin/bash";
|
| + mojo::Array<mojo::String> argv;
|
| + argv.push_back(kPath);
|
| + argv.push_back("-c");
|
| + argv.push_back("echo ready; read -t30");
|
| + process()->Spawn(kPath, argv.Pass(), mojo::Array<mojo::String>(),
|
| + ifile.Pass(), ofile.Pass(), nullptr,
|
| + GetProxy(&process_controller), Capture(&error));
|
| + ASSERT_TRUE(process().WaitForIncomingResponse());
|
| + EXPECT_EQ(mojo::files::ERROR_OK, error);
|
| +
|
| + // |ofile_impl| will quit the message loop once it sees "ready".
|
| + RunMessageLoop();
|
| + ASSERT_TRUE(ofile_impl.got_ready());
|
| + }
|
| +
|
| + // The child should be killed.
|
| + // TODO(vtl): It's pretty hard to verify that the child process was actually
|
| + // killed. This could be done, e.g., by having the child trap SIGTERM and
|
| + // writing something to a file, and then separately checking for that file.
|
| + // For now now, just be happy if it doesn't crash. (I've actually verified it
|
| + // "manually", but automation is hard.)
|
| +}
|
| +
|
| +} // namespace
|
| +} // namespace native_support
|
|
|