// Copyright 2010 Google LLC
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// crash_generation_server_test.cc
// Unit tests for CrashGenerationServer

#ifdef HAVE_CONFIG_H
#include <config.h>  // Must come first
#endif

#include <dirent.h>
#include <glob.h>
#include <stdint.h>
#include <sys/wait.h>
#include <unistd.h>

#include <string>

#include "breakpad_googletest_includes.h"
#include "client/mac/crash_generation/client_info.h"
#include "client/mac/crash_generation/crash_generation_client.h"
#include "client/mac/crash_generation/crash_generation_server.h"
#include "client/mac/handler/exception_handler.h"
#include "client/mac/tests/spawn_child_process.h"
#include "common/tests/auto_tempdir.h"
#include "google_breakpad/processor/minidump.h"

namespace google_breakpad {
// This acts as the log sink for INFO logging from the processor
// logging code. The logging output confuses XCode and makes it think
// there are unit test failures. testlogging.h handles the overriding.
std::ostringstream info_log;
}

namespace {
using std::string;
using google_breakpad::AutoTempDir;
using google_breakpad::ClientInfo;
using google_breakpad::CrashGenerationClient;
using google_breakpad::CrashGenerationServer;
using google_breakpad::ExceptionHandler;
using google_breakpad::Minidump;
using google_breakpad::MinidumpContext;
using google_breakpad::MinidumpException;
using google_breakpad::MinidumpModule;
using google_breakpad::MinidumpModuleList;
using google_breakpad::MinidumpSystemInfo;
using google_breakpad::MinidumpThread;
using google_breakpad::MinidumpThreadList;
using testing::Test;
using namespace google_breakpad_test;

class CrashGenerationServerTest : public Test {
public:
  // The port name to receive messages on
  char mach_port_name[128];
  // Filename of the last dump that was generated
  string last_dump_name;
  // PID of the child process
  pid_t child_pid;
  // A temp dir
  AutoTempDir temp_dir;
  // Counter just to ensure that we don't hit the same port again
  static int i;
  bool filter_callback_called;

  void SetUp() {
    sprintf(mach_port_name,
            "com.google.breakpad.ServerTest.%d.%d", getpid(),
            CrashGenerationServerTest::i++);
    child_pid = (pid_t)-1;
    filter_callback_called = false;
  }
};
int CrashGenerationServerTest::i = 0;

// Test that starting and stopping a server works
TEST_F(CrashGenerationServerTest, testStartStopServer) {
  CrashGenerationServer server(mach_port_name,
                               NULL,  // filter callback
                               NULL,  // filter context
                               NULL,  // dump callback
                               NULL,  // dump context
                               NULL,  // exit callback
                               NULL,  // exit context
                               false, // generate dumps
                               ""); // dump path
  ASSERT_TRUE(server.Start());
  ASSERT_TRUE(server.Stop());
}

// Test that requesting a dump via CrashGenerationClient works
// Test without actually dumping
TEST_F(CrashGenerationServerTest, testRequestDumpNoDump) {
  CrashGenerationServer server(mach_port_name,
                               NULL,  // filter callback
                               NULL,  // filter context
                               NULL,  // dump callback
                               NULL,  // dump context
                               NULL,  // exit callback
                               NULL,  // exit context
                               false, // don't generate dumps
                               temp_dir.path()); // dump path
  ASSERT_TRUE(server.Start());

  pid_t pid = fork();
  ASSERT_NE(-1, pid);
  if (pid == 0) {
    CrashGenerationClient client(mach_port_name);
    bool result = client.RequestDump();
    exit(result ? 0 : 1);
  }

  int ret;
  ASSERT_EQ(pid, waitpid(pid, &ret, 0));
  EXPECT_TRUE(WIFEXITED(ret));
  EXPECT_EQ(0, WEXITSTATUS(ret));
  EXPECT_TRUE(server.Stop());
  // check that no minidump was written
  string pattern = temp_dir.path() + "/*";
  glob_t dirContents;
  ret = glob(pattern.c_str(), GLOB_NOSORT, NULL, &dirContents);
  EXPECT_EQ(GLOB_NOMATCH, ret);
  if (ret != GLOB_NOMATCH)
    globfree(&dirContents);
}

void dumpCallback(void* context, const ClientInfo& client_info,
                  const std::string& file_path) {
  if (context) {
    CrashGenerationServerTest* self =
        reinterpret_cast<CrashGenerationServerTest*>(context);
    if (!file_path.empty())
      self->last_dump_name = file_path;
    self->child_pid = client_info.pid();
  }
}

void* RequestDump(void* context) {
  CrashGenerationClient client((const char*)context);
  bool result = client.RequestDump();
  return (void*)(result ? 0 : 1);
}

// Test that actually writing a minidump works
TEST_F(CrashGenerationServerTest, testRequestDump) {
  CrashGenerationServer server(mach_port_name,
                               NULL,  // filter callback
                               NULL,  // filter context
                               dumpCallback,  // dump callback
                               this,  // dump context
                               NULL,  // exit callback
                               NULL,  // exit context
                               true, //  generate dumps
                               temp_dir.path()); // dump path
  ASSERT_TRUE(server.Start());

  pid_t pid = fork();
  ASSERT_NE(-1, pid);
  if (pid == 0) {
    // Have to spawn off a separate thread to request the dump,
    // because MinidumpGenerator assumes the handler thread is not
    // the only thread
    pthread_t thread;
    if (pthread_create(&thread, NULL, RequestDump, (void*)mach_port_name) != 0)
      exit(1);
    void* result;
    pthread_join(thread, &result);
    exit(reinterpret_cast<intptr_t>(result));
  }

  int ret;
  ASSERT_EQ(pid, waitpid(pid, &ret, 0));
  EXPECT_TRUE(WIFEXITED(ret));
  EXPECT_EQ(0, WEXITSTATUS(ret));
  EXPECT_TRUE(server.Stop());
  // check that minidump was written
  ASSERT_FALSE(last_dump_name.empty());
  struct stat st;
  EXPECT_EQ(0, stat(last_dump_name.c_str(), &st));
  EXPECT_LT(0, st.st_size);
  // check client's PID
  ASSERT_EQ(pid, child_pid);
}

static void Crasher() {
  int* a = (int*)0x42;

  fprintf(stdout, "Going to crash...\n");
  fprintf(stdout, "A = %d", *a);
}

// Test that crashing a child process with an OOP ExceptionHandler installed
// results in a minidump being written by the CrashGenerationServer in
// the parent.
TEST_F(CrashGenerationServerTest, testChildProcessCrash) {
  CrashGenerationServer server(mach_port_name,
                               NULL,  // filter callback
                               NULL,  // filter context
                               dumpCallback,  // dump callback
                               this,  // dump context
                               NULL,  // exit callback
                               NULL,  // exit context
                               true, //  generate dumps
                               temp_dir.path()); // dump path
  ASSERT_TRUE(server.Start());

  pid_t pid = fork();
  ASSERT_NE(-1, pid);
  if (pid == 0) {
    // Instantiate an OOP exception handler.
    ExceptionHandler eh("", NULL, NULL, NULL, true, mach_port_name);
    Crasher();
    // not reached
    exit(0);
  }

  int ret;
  ASSERT_EQ(pid, waitpid(pid, &ret, 0));
  EXPECT_FALSE(WIFEXITED(ret));
  EXPECT_TRUE(server.Stop());
  // check that minidump was written
  ASSERT_FALSE(last_dump_name.empty());
  struct stat st;
  EXPECT_EQ(0, stat(last_dump_name.c_str(), &st));
  EXPECT_LT(0, st.st_size);

  // Read the minidump, sanity check some data.
  Minidump minidump(last_dump_name.c_str());
  ASSERT_TRUE(minidump.Read());

  MinidumpSystemInfo* system_info = minidump.GetSystemInfo();
  ASSERT_TRUE(system_info);
  const MDRawSystemInfo* raw_info = system_info->system_info();
  ASSERT_TRUE(raw_info);
  EXPECT_EQ(kNativeArchitecture, raw_info->processor_architecture);

  MinidumpThreadList* thread_list = minidump.GetThreadList();
  ASSERT_TRUE(thread_list);
  ASSERT_EQ((unsigned int)1, thread_list->thread_count());

  MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0);
  ASSERT_TRUE(main_thread);
  MinidumpContext* context = main_thread->GetContext();
  ASSERT_TRUE(context);
  EXPECT_EQ(kNativeContext, context->GetContextCPU());

  MinidumpModuleList* module_list = minidump.GetModuleList();
  ASSERT_TRUE(module_list);
  const MinidumpModule* main_module = module_list->GetMainModule();
  ASSERT_TRUE(main_module);
  EXPECT_EQ(GetExecutablePath(), main_module->code_file());
}

#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) && \
  (defined(__x86_64__) || defined(__i386__))
// Test that crashing a child process of a different architecture
// produces a valid minidump.
TEST_F(CrashGenerationServerTest, testChildProcessCrashCrossArchitecture) {
  CrashGenerationServer server(mach_port_name,
                               NULL,  // filter callback
                               NULL,  // filter context
                               dumpCallback,  // dump callback
                               this,  // dump context
                               NULL,  // exit callback
                               NULL,  // exit context
                               true, //  generate dumps
                               temp_dir.path()); // dump path
  ASSERT_TRUE(server.Start());

  // Spawn a child process
  string helper_path = GetHelperPath();
  const char* argv[] = {
    helper_path.c_str(),
    "crash",
    mach_port_name,
    NULL
  };
  pid_t pid = spawn_child_process(argv);
  ASSERT_NE(-1, pid);

  int ret;
  ASSERT_EQ(pid, waitpid(pid, &ret, 0));
  EXPECT_FALSE(WIFEXITED(ret));
  EXPECT_TRUE(server.Stop());
  // check that minidump was written
  ASSERT_FALSE(last_dump_name.empty());
  struct stat st;
  EXPECT_EQ(0, stat(last_dump_name.c_str(), &st));
  EXPECT_LT(0, st.st_size);

const MDCPUArchitecture kExpectedArchitecture =
#if defined(__x86_64__)
  MD_CPU_ARCHITECTURE_X86
#elif defined(__i386__)
  MD_CPU_ARCHITECTURE_AMD64
#endif
  ;
const uint32_t kExpectedContext =
#if defined(__i386__)
  MD_CONTEXT_AMD64
#elif defined(__x86_64__)
  MD_CONTEXT_X86
#endif
  ;

  // Read the minidump, sanity check some data.
  Minidump minidump(last_dump_name.c_str());
  ASSERT_TRUE(minidump.Read());

  MinidumpSystemInfo* system_info = minidump.GetSystemInfo();
  ASSERT_TRUE(system_info);
  const MDRawSystemInfo* raw_info = system_info->system_info();
  ASSERT_TRUE(raw_info);
  EXPECT_EQ(kExpectedArchitecture, raw_info->processor_architecture);

  MinidumpThreadList* thread_list = minidump.GetThreadList();
  ASSERT_TRUE(thread_list);
  ASSERT_EQ((unsigned int)1, thread_list->thread_count());

  MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0);
  ASSERT_TRUE(main_thread);
  MinidumpContext* context = main_thread->GetContext();
  ASSERT_TRUE(context);
  EXPECT_EQ(kExpectedContext, context->GetContextCPU());

  MinidumpModuleList* module_list = minidump.GetModuleList();
  ASSERT_TRUE(module_list);
  const MinidumpModule* main_module = module_list->GetMainModule();
  ASSERT_TRUE(main_module);
  EXPECT_EQ(helper_path, main_module->code_file());
}
#endif

bool filter_callback(void* context) {
  CrashGenerationServerTest* self =
    reinterpret_cast<CrashGenerationServerTest*>(context);
  self->filter_callback_called = true;
  // veto dump generation
  return false;
}

// Test that a filter callback can veto minidump writing.
TEST_F(CrashGenerationServerTest, testFilter) {
  CrashGenerationServer server(mach_port_name,
                               filter_callback,  // filter callback
                               this,            // filter context
                               dumpCallback,  // dump callback
                               this,  // dump context
                               NULL,  // exit callback
                               NULL,  // exit context
                               true, //  generate dumps
                               temp_dir.path()); // dump path
  ASSERT_TRUE(server.Start());

  pid_t pid = fork();
  ASSERT_NE(-1, pid);
  if (pid == 0) {
    // Instantiate an OOP exception handler.
    ExceptionHandler eh("", NULL, NULL, NULL, true, mach_port_name);
    Crasher();
    // not reached
    exit(0);
  }

  int ret;
  ASSERT_EQ(pid, waitpid(pid, &ret, 0));
  EXPECT_FALSE(WIFEXITED(ret));
  EXPECT_TRUE(server.Stop());

  // check that no minidump was written
  EXPECT_TRUE(last_dump_name.empty());
  EXPECT_TRUE(filter_callback_called);
}

}  // namespace
