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 |