text: // 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.h>
  
  #include <string>
  #include <vector>
  
  #include "base/message_loop/message_loop.h"
  #include "mojo/public/cpp/bindings/interface_request.h"
  #include "mojo/services/files/cpp/input_stream_file.h"
  #include "mojo/services/files/cpp/output_stream_file.h"
  #include "mojo/services/files/interfaces/types.mojom.h"
  #include "services/native_support/process_test_base.h"
  
  namespace native_support {
  namespace {
  
  using ProcessControllerImplTest = ProcessTestBase;
  
  mojo::Array<uint8_t> ToByteArray(const std::string& s) {
    auto rv = mojo::Array<uint8_t>::New(s.size());
    memcpy(rv.data(), s.data(), s.size());
    return rv;
  }
  
  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::Array<uint8_t>> argv;
      argv.push_back(ToByteArray(kPath));
      argv.push_back(ToByteArray("-c"));
      argv.push_back(ToByteArray("exit 42"));
      process()->Spawn(ToByteArray(kPath), argv.Pass(), nullptr, 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::Array<uint8_t>> argv;
    argv.push_back(ToByteArray(kPath));
    argv.push_back(ToByteArray("-c"));
    argv.push_back(
        ToByteArray("trap 'exit 42' INT; echo ready; read -t30; exit 1"));
    process()->Spawn(ToByteArray(kPath), argv.Pass(), nullptr, 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::Array<uint8_t>> argv;
      argv.push_back(ToByteArray(kPath));
      argv.push_back(ToByteArray("-c"));
      argv.push_back(ToByteArray("echo ready; read -t30"));
      process()->Spawn(ToByteArray(kPath), argv.Pass(), nullptr, 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
  