// Copyright (c) 2010, Google Inc. // All rights reserved. // // 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 Inc. 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 #include #include #include #include #include #include #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/auto_tempdir.h" 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 testing::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; void SetUp() { sprintf(mach_port_name, "com.google.breakpad.ServerTest.%d.%d", getpid(), CrashGenerationServerTest::i++); child_pid = (pid_t)-1; } }; int CrashGenerationServerTest::i = 0; // Test that starting and stopping a server works TEST_F(CrashGenerationServerTest, testStartStopServer) { CrashGenerationServer server(mach_port_name, 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, // 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(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, 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(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, 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); } } // namespace