OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include <signal.h> |
| 6 |
| 7 #include <string> |
| 8 #include <vector> |
| 9 |
| 10 #include "base/message_loop/message_loop.h" |
| 11 #include "mojo/public/cpp/bindings/interface_request.h" |
| 12 #include "mojo/public/cpp/bindings/type_converter.h" |
| 13 #include "mojo/services/files/public/cpp/input_stream_file.h" |
| 14 #include "mojo/services/files/public/cpp/output_stream_file.h" |
| 15 #include "mojo/services/files/public/interfaces/types.mojom.h" |
| 16 #include "services/native_support/process_test_base.h" |
| 17 |
| 18 namespace native_support { |
| 19 namespace { |
| 20 |
| 21 using ProcessControllerImplTest = ProcessTestBase; |
| 22 |
| 23 void QuitMessageLoop() { |
| 24 base::MessageLoop::current()->QuitWhenIdle(); |
| 25 } |
| 26 |
| 27 void RunMessageLoop() { |
| 28 base::MessageLoop::current()->Run(); |
| 29 } |
| 30 |
| 31 // Note: We already tested success (zero) versus failure (non-zero) exit |
| 32 // statuses in |ProcessControllerTest.Spawn|. |
| 33 TEST_F(ProcessControllerImplTest, Wait) { |
| 34 mojo::files::Error error; |
| 35 |
| 36 { |
| 37 ProcessControllerPtr process_controller; |
| 38 error = mojo::files::ERROR_INTERNAL; |
| 39 const char kPath[] = "/bin/sh"; |
| 40 mojo::Array<mojo::String> argv; |
| 41 argv.push_back(kPath); |
| 42 argv.push_back("-c"); |
| 43 argv.push_back("exit 42"); |
| 44 process()->Spawn(kPath, argv.Pass(), mojo::Array<mojo::String>(), nullptr, |
| 45 nullptr, nullptr, GetProxy(&process_controller), |
| 46 Capture(&error)); |
| 47 ASSERT_TRUE(process().WaitForIncomingResponse()); |
| 48 EXPECT_EQ(mojo::files::ERROR_OK, error); |
| 49 |
| 50 error = mojo::files::ERROR_INTERNAL; |
| 51 int32_t exit_status = 0; |
| 52 process_controller->Wait(Capture(&error, &exit_status)); |
| 53 ASSERT_TRUE(process_controller.WaitForIncomingResponse()); |
| 54 EXPECT_EQ(mojo::files::ERROR_OK, error); |
| 55 EXPECT_EQ(42, exit_status); |
| 56 } |
| 57 } |
| 58 |
| 59 // An output file stream that captures output and quits when "ready" is seen. |
| 60 class QuitOnReadyFile : public files_impl::OutputStreamFile::Client { |
| 61 public: |
| 62 explicit QuitOnReadyFile(mojo::InterfaceRequest<mojo::files::File> request) |
| 63 : impl_(files_impl::OutputStreamFile::Create(this, request.Pass())) {} |
| 64 ~QuitOnReadyFile() override {} |
| 65 |
| 66 bool got_ready() const { return got_ready_; } |
| 67 |
| 68 private: |
| 69 // |files_impl::OutputStreamFile::Client|: |
| 70 void OnDataReceived(const void* bytes, size_t num_bytes) override { |
| 71 output_.append(static_cast<const char*>(bytes), num_bytes); |
| 72 if (output_.find("ready") != std::string::npos) { |
| 73 got_ready_ = true; |
| 74 QuitMessageLoop(); |
| 75 } |
| 76 } |
| 77 void OnClosed() override { QuitMessageLoop(); } |
| 78 |
| 79 std::string output_; |
| 80 bool got_ready_ = false; |
| 81 std::unique_ptr<files_impl::OutputStreamFile> impl_; |
| 82 |
| 83 MOJO_DISALLOW_COPY_AND_ASSIGN(QuitOnReadyFile); |
| 84 }; |
| 85 |
| 86 // An input file stream that ignores reads (never completing them). |
| 87 class IgnoreReadsFile : public files_impl::InputStreamFile::Client { |
| 88 public: |
| 89 explicit IgnoreReadsFile(mojo::InterfaceRequest<mojo::files::File> request) |
| 90 : impl_(files_impl::InputStreamFile::Create(this, request.Pass())) {} |
| 91 ~IgnoreReadsFile() override {} |
| 92 |
| 93 private: |
| 94 // |files_impl::InputStreamFile::Client|: |
| 95 bool RequestData(size_t max_num_bytes, |
| 96 mojo::files::Error* error, |
| 97 mojo::Array<uint8_t>* data, |
| 98 const RequestDataCallback& callback) override { |
| 99 // Don't let |callback| die, because we probably have assertions "ensuring" |
| 100 // that response callbacks get called. |
| 101 callbacks_.push_back(callback); |
| 102 return false; |
| 103 } |
| 104 void OnClosed() override { QuitMessageLoop(); } |
| 105 |
| 106 std::vector<RequestDataCallback> callbacks_; |
| 107 std::unique_ptr<files_impl::InputStreamFile> impl_; |
| 108 |
| 109 MOJO_DISALLOW_COPY_AND_ASSIGN(IgnoreReadsFile); |
| 110 }; |
| 111 |
| 112 TEST_F(ProcessControllerImplTest, Kill) { |
| 113 // We want to run bash and have it set a trap for SIGINT, which it'll handle |
| 114 // by quitting with code 42. We need to make sure that bash started and set |
| 115 // the trap before we kill our child. Thus we have bash echo "ready" and wait |
| 116 // for it to do so (and thus we need to capture/examine stdout). |
| 117 // |
| 118 // We want bash to pause, so that we can kill it before it quits. We can't use |
| 119 // "sleep" since it's not a builtin (so bash would wait for it before dying). |
| 120 // So instead we use "read" with a timeout (note that "-t" is a bash |
| 121 // extension, and not specified by POSIX for /bin/sh). Thus we have to give |
| 122 // something for stdin (otherwise, it'd come from /dev/null and the read would |
| 123 // be completed immediately). |
| 124 mojo::files::FilePtr ifile; |
| 125 IgnoreReadsFile ifile_impl(GetProxy(&ifile)); |
| 126 mojo::files::FilePtr ofile; |
| 127 QuitOnReadyFile ofile_impl(GetProxy(&ofile)); |
| 128 |
| 129 ProcessControllerPtr process_controller; |
| 130 mojo::files::Error error = mojo::files::ERROR_INTERNAL; |
| 131 const char kPath[] = "/bin/bash"; |
| 132 mojo::Array<mojo::String> argv; |
| 133 argv.push_back(kPath); |
| 134 argv.push_back("-c"); |
| 135 argv.push_back("trap 'exit 42' INT; echo ready; read -t30; exit 1"); |
| 136 process()->Spawn(kPath, argv.Pass(), mojo::Array<mojo::String>(), |
| 137 ifile.Pass(), ofile.Pass(), nullptr, |
| 138 GetProxy(&process_controller), Capture(&error)); |
| 139 ASSERT_TRUE(process().WaitForIncomingResponse()); |
| 140 EXPECT_EQ(mojo::files::ERROR_OK, error); |
| 141 |
| 142 // |ofile_impl| will quit the message loop once it sees "ready". |
| 143 RunMessageLoop(); |
| 144 ASSERT_TRUE(ofile_impl.got_ready()); |
| 145 |
| 146 // Send SIGINT. |
| 147 error = mojo::files::ERROR_INTERNAL; |
| 148 process_controller->Kill(static_cast<int32_t>(SIGINT), Capture(&error)); |
| 149 ASSERT_TRUE(process_controller.WaitForIncomingResponse()); |
| 150 EXPECT_EQ(mojo::files::ERROR_OK, error); |
| 151 |
| 152 error = mojo::files::ERROR_INTERNAL; |
| 153 int32_t exit_status = 0; |
| 154 process_controller->Wait(Capture(&error, &exit_status)); |
| 155 ASSERT_TRUE(process_controller.WaitForIncomingResponse()); |
| 156 EXPECT_EQ(mojo::files::ERROR_OK, error); |
| 157 EXPECT_EQ(42, exit_status); |
| 158 } |
| 159 |
| 160 TEST_F(ProcessControllerImplTest, DestroyingControllerKills) { |
| 161 // We want to make sure that we've exec-ed before killing, so we do what we do |
| 162 // in |ProcessControllerImplTest.Kill| (without the trap). |
| 163 { |
| 164 mojo::files::FilePtr ifile; |
| 165 IgnoreReadsFile ifile_impl(GetProxy(&ifile)); |
| 166 mojo::files::FilePtr ofile; |
| 167 QuitOnReadyFile ofile_impl(GetProxy(&ofile)); |
| 168 |
| 169 ProcessControllerPtr process_controller; |
| 170 mojo::files::Error error = mojo::files::ERROR_INTERNAL; |
| 171 const char kPath[] = "/bin/bash"; |
| 172 mojo::Array<mojo::String> argv; |
| 173 argv.push_back(kPath); |
| 174 argv.push_back("-c"); |
| 175 argv.push_back("echo ready; read -t30"); |
| 176 process()->Spawn(kPath, argv.Pass(), mojo::Array<mojo::String>(), |
| 177 ifile.Pass(), ofile.Pass(), nullptr, |
| 178 GetProxy(&process_controller), Capture(&error)); |
| 179 ASSERT_TRUE(process().WaitForIncomingResponse()); |
| 180 EXPECT_EQ(mojo::files::ERROR_OK, error); |
| 181 |
| 182 // |ofile_impl| will quit the message loop once it sees "ready". |
| 183 RunMessageLoop(); |
| 184 ASSERT_TRUE(ofile_impl.got_ready()); |
| 185 } |
| 186 |
| 187 // The child should be killed. |
| 188 // TODO(vtl): It's pretty hard to verify that the child process was actually |
| 189 // killed. This could be done, e.g., by having the child trap SIGTERM and |
| 190 // writing something to a file, and then separately checking for that file. |
| 191 // For now now, just be happy if it doesn't crash. (I've actually verified it |
| 192 // "manually", but automation is hard.) |
| 193 } |
| 194 |
| 195 } // namespace |
| 196 } // namespace native_support |
OLD | NEW |