// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "test/multiprocess_exec.h"

#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "test/test_paths.h"
#include "util/file/file_io.h"

namespace crashpad {
namespace test {
namespace {

class TestMultiprocessExec final : public MultiprocessExec {
 public:
  TestMultiprocessExec() : MultiprocessExec() {}

  TestMultiprocessExec(const TestMultiprocessExec&) = delete;
  TestMultiprocessExec& operator=(const TestMultiprocessExec&) = delete;

  ~TestMultiprocessExec() {}

 private:
  void MultiprocessParent() override {
    // Use Logging*File() instead of Checked*File() so that the test can fail
    // gracefully with a Google Test assertion if the child does not execute
    // properly.

    char c = 'z';
    ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &c, 1));

    ASSERT_TRUE(LoggingReadFileExactly(ReadPipeHandle(), &c, 1));
    EXPECT_EQ(c, 'Z');
  }
};

TEST(MultiprocessExec, MultiprocessExec) {
  TestMultiprocessExec multiprocess_exec;
  base::FilePath child_test_executable = TestPaths::BuildArtifact(
      FILE_PATH_LITERAL("test"),
      FILE_PATH_LITERAL("multiprocess_exec_test_child"),
      TestPaths::FileType::kExecutable);
  multiprocess_exec.SetChildCommand(child_test_executable, nullptr);
  multiprocess_exec.Run();
}


CRASHPAD_CHILD_TEST_MAIN(SimpleMultiprocess) {
  char c;
  CheckedReadFileExactly(StdioFileHandle(StdioStream::kStandardInput), &c, 1);
  LOG_IF(FATAL, c != 'z');

  c = 'Z';
  CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput), &c, 1);
  return 0;
}

TEST(MultiprocessExec, MultiprocessExecSimpleChild) {
  TestMultiprocessExec exec;
  exec.SetChildTestMainFunction("SimpleMultiprocess");
  exec.Run();
}

CRASHPAD_CHILD_TEST_MAIN(SimpleMultiprocessReturnsNonZero) {
  return 123;
}

class TestMultiprocessExecEmpty final : public MultiprocessExec {
 public:
  TestMultiprocessExecEmpty() = default;

  TestMultiprocessExecEmpty(const TestMultiprocessExecEmpty&) = delete;
  TestMultiprocessExecEmpty& operator=(const TestMultiprocessExecEmpty&) =
      delete;

  ~TestMultiprocessExecEmpty() = default;

 private:
  void MultiprocessParent() override {}
};

TEST(MultiprocessExec, MultiprocessExecSimpleChildReturnsNonZero) {
  TestMultiprocessExecEmpty exec;
  exec.SetChildTestMainFunction("SimpleMultiprocessReturnsNonZero");
  exec.SetExpectedChildTermination(
      Multiprocess::TerminationReason::kTerminationNormal, 123);
  exec.Run();
}

#if !defined(OS_WIN)

CRASHPAD_CHILD_TEST_MAIN(BuiltinTrapChild) {
  __builtin_trap();
  return EXIT_SUCCESS;
}

class TestBuiltinTrapTermination final : public MultiprocessExec {
 public:
  TestBuiltinTrapTermination() {
    SetChildTestMainFunction("BuiltinTrapChild");
    SetExpectedChildTerminationBuiltinTrap();
  }

  TestBuiltinTrapTermination(const TestBuiltinTrapTermination&) = delete;
  TestBuiltinTrapTermination& operator=(const TestBuiltinTrapTermination&) =
      delete;

  ~TestBuiltinTrapTermination() = default;

 private:
  void MultiprocessParent() override {}
};

TEST(MultiprocessExec, BuiltinTrapTermination) {
  TestBuiltinTrapTermination test;
  test.Run();
}

#endif  // !OS_WIN

}  // namespace
}  // namespace test
}  // namespace crashpad
