diff options
Diffstat (limited to 'sandbox/linux/tests')
-rw-r--r-- | sandbox/linux/tests/main.cc | 82 | ||||
-rw-r--r-- | sandbox/linux/tests/sandbox_test_runner.cc | 19 | ||||
-rw-r--r-- | sandbox/linux/tests/sandbox_test_runner.h | 30 | ||||
-rw-r--r-- | sandbox/linux/tests/sandbox_test_runner_function_pointer.cc | 25 | ||||
-rw-r--r-- | sandbox/linux/tests/sandbox_test_runner_function_pointer.h | 26 | ||||
-rw-r--r-- | sandbox/linux/tests/scoped_temporary_file.cc | 35 | ||||
-rw-r--r-- | sandbox/linux/tests/scoped_temporary_file.h | 30 | ||||
-rw-r--r-- | sandbox/linux/tests/scoped_temporary_file_unittest.cc | 76 | ||||
-rw-r--r-- | sandbox/linux/tests/test_utils.cc | 42 | ||||
-rw-r--r-- | sandbox/linux/tests/test_utils.h | 29 | ||||
-rw-r--r-- | sandbox/linux/tests/test_utils_unittest.cc | 24 | ||||
-rw-r--r-- | sandbox/linux/tests/unit_tests.cc | 354 | ||||
-rw-r--r-- | sandbox/linux/tests/unit_tests.h | 201 | ||||
-rw-r--r-- | sandbox/linux/tests/unit_tests_unittest.cc | 62 |
14 files changed, 1035 insertions, 0 deletions
diff --git a/sandbox/linux/tests/main.cc b/sandbox/linux/tests/main.cc new file mode 100644 index 0000000000..caeddee32c --- /dev/null +++ b/sandbox/linux/tests/main.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2012 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 "base/at_exit.h" +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/test/test_suite.h" +#include "build/build_config.h" +#include "sandbox/linux/tests/test_utils.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +namespace sandbox { +namespace { + +// Check for leaks in our tests. +void RunPostTestsChecks(const base::FilePath& orig_cwd) { + if (TestUtils::CurrentProcessHasChildren()) { + LOG(FATAL) << "One of the tests created a child that was not waited for. " + << "Please, clean up after your tests!"; + } + + base::FilePath cwd; + CHECK(GetCurrentDirectory(&cwd)); + if (orig_cwd != cwd) { + LOG(FATAL) << "One of the tests changed the current working directory. " + << "Please, clean up after your tests!"; + } +} + +} // namespace +} // namespace sandbox + +#if !defined(SANDBOX_USES_BASE_TEST_SUITE) +void UnitTestAssertHandler(const std::string& str) { + _exit(1); +} +#endif + +int main(int argc, char* argv[]) { + base::CommandLine::Init(argc, argv); + std::string client_func; +#if defined(SANDBOX_USES_BASE_TEST_SUITE) + client_func = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kTestChildProcess); +#endif + if (!client_func.empty()) { + base::AtExitManager exit_manager; + return multi_process_function_list::InvokeChildProcessTest(client_func); + } + + base::FilePath orig_cwd; + CHECK(GetCurrentDirectory(&orig_cwd)); + +#if !defined(SANDBOX_USES_BASE_TEST_SUITE) + // The use of Callbacks requires an AtExitManager. + base::AtExitManager exit_manager; + testing::InitGoogleTest(&argc, argv); + // Death tests rely on LOG(FATAL) triggering an exit (the default behavior is + // SIGABRT). The normal test launcher does this at initialization, but since + // we still do not use this on Android, we must install the handler ourselves. + logging::SetLogAssertHandler(UnitTestAssertHandler); +#endif + // Always go through re-execution for death tests. + // This makes gtest only marginally slower for us and has the + // additional side effect of getting rid of gtest warnings about fork() + // safety. + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; +#if !defined(SANDBOX_USES_BASE_TEST_SUITE) + int tests_result = RUN_ALL_TESTS(); +#else + int tests_result = base::RunUnitTestsUsingBaseTestSuite(argc, argv); +#endif + + sandbox::RunPostTestsChecks(orig_cwd); + return tests_result; +} diff --git a/sandbox/linux/tests/sandbox_test_runner.cc b/sandbox/linux/tests/sandbox_test_runner.cc new file mode 100644 index 0000000000..b099b97289 --- /dev/null +++ b/sandbox/linux/tests/sandbox_test_runner.cc @@ -0,0 +1,19 @@ +// Copyright 2014 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 "sandbox/linux/tests/sandbox_test_runner.h" + +namespace sandbox { + +SandboxTestRunner::SandboxTestRunner() { +} + +SandboxTestRunner::~SandboxTestRunner() { +} + +bool SandboxTestRunner::ShouldCheckForLeaks() const { + return true; +} + +} // namespace sandbox diff --git a/sandbox/linux/tests/sandbox_test_runner.h b/sandbox/linux/tests/sandbox_test_runner.h new file mode 100644 index 0000000000..3155b74008 --- /dev/null +++ b/sandbox/linux/tests/sandbox_test_runner.h @@ -0,0 +1,30 @@ +// Copyright 2014 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. + +#ifndef SANDBOX_LINUX_TESTS_SANDBOX_TEST_RUNNER_H_ +#define SANDBOX_LINUX_TESTS_SANDBOX_TEST_RUNNER_H_ + +#include "base/macros.h" + +namespace sandbox { + +// A simple "runner" class to implement tests. +class SandboxTestRunner { + public: + SandboxTestRunner(); + virtual ~SandboxTestRunner(); + + virtual void Run() = 0; + + // Override to decide whether or not to check for leaks with LSAN + // (if built with LSAN and LSAN is enabled). + virtual bool ShouldCheckForLeaks() const; + + private: + DISALLOW_COPY_AND_ASSIGN(SandboxTestRunner); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_TESTS_SANDBOX_TEST_RUNNER_H_ diff --git a/sandbox/linux/tests/sandbox_test_runner_function_pointer.cc b/sandbox/linux/tests/sandbox_test_runner_function_pointer.cc new file mode 100644 index 0000000000..69e05ac4e0 --- /dev/null +++ b/sandbox/linux/tests/sandbox_test_runner_function_pointer.cc @@ -0,0 +1,25 @@ +// Copyright 2014 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 "sandbox/linux/tests/sandbox_test_runner_function_pointer.h" + +#include "base/logging.h" +#include "build/build_config.h" + +namespace sandbox { + +SandboxTestRunnerFunctionPointer::SandboxTestRunnerFunctionPointer( + void (*function_to_run)(void)) + : function_to_run_(function_to_run) { +} + +SandboxTestRunnerFunctionPointer::~SandboxTestRunnerFunctionPointer() { +} + +void SandboxTestRunnerFunctionPointer::Run() { + DCHECK(function_to_run_); + function_to_run_(); +} + +} // namespace sandbox diff --git a/sandbox/linux/tests/sandbox_test_runner_function_pointer.h b/sandbox/linux/tests/sandbox_test_runner_function_pointer.h new file mode 100644 index 0000000000..cadd07c248 --- /dev/null +++ b/sandbox/linux/tests/sandbox_test_runner_function_pointer.h @@ -0,0 +1,26 @@ +// Copyright 2014 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. + +#ifndef SANDBOX_LINUX_TESTS_SANDBOX_TEST_RUNNER_FUNCTION_POINTER_H_ +#define SANDBOX_LINUX_TESTS_SANDBOX_TEST_RUNNER_FUNCTION_POINTER_H_ + +#include "base/macros.h" +#include "sandbox/linux/tests/sandbox_test_runner.h" + +namespace sandbox { + +class SandboxTestRunnerFunctionPointer : public SandboxTestRunner { + public: + SandboxTestRunnerFunctionPointer(void (*function_to_run)(void)); + ~SandboxTestRunnerFunctionPointer() override; + void Run() override; + + private: + void (*function_to_run_)(void); + DISALLOW_COPY_AND_ASSIGN(SandboxTestRunnerFunctionPointer); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_TESTS_SANDBOX_TEST_RUNNER__FUNCTION_POINTER_H_ diff --git a/sandbox/linux/tests/scoped_temporary_file.cc b/sandbox/linux/tests/scoped_temporary_file.cc new file mode 100644 index 0000000000..1f2d66fd6b --- /dev/null +++ b/sandbox/linux/tests/scoped_temporary_file.cc @@ -0,0 +1,35 @@ +// Copyright 2014 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 "sandbox/linux/tests/scoped_temporary_file.h" + +#include <stdlib.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" + +namespace sandbox { + +ScopedTemporaryFile::ScopedTemporaryFile() : fd_(-1) { +#if defined(OS_ANDROID) + static const char file_template[] = "/data/local/tmp/ScopedTempFileXXXXXX"; +#else + static const char file_template[] = "/tmp/ScopedTempFileXXXXXX"; +#endif // defined(OS_ANDROID) + static_assert(sizeof(full_file_name_) >= sizeof(file_template), + "full_file_name is not large enough"); + memcpy(full_file_name_, file_template, sizeof(file_template)); + fd_ = mkstemp(full_file_name_); + CHECK_LE(0, fd_); +} + +ScopedTemporaryFile::~ScopedTemporaryFile() { + CHECK_EQ(0, unlink(full_file_name_)); + CHECK_EQ(0, IGNORE_EINTR(close(fd_))); +} + +} // namespace sandbox diff --git a/sandbox/linux/tests/scoped_temporary_file.h b/sandbox/linux/tests/scoped_temporary_file.h new file mode 100644 index 0000000000..0734130055 --- /dev/null +++ b/sandbox/linux/tests/scoped_temporary_file.h @@ -0,0 +1,30 @@ +// Copyright 2014 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. + +#ifndef SANDBOX_LINUX_TESTS_SCOPED_TEMPORARY_FILE_H_ +#define SANDBOX_LINUX_TESTS_SCOPED_TEMPORARY_FILE_H_ + +#include "base/macros.h" + +namespace sandbox { +// Creates and open a temporary file on creation and closes +// and removes it on destruction. +// Unlike base/ helpers, this does not require JNI on Android. +class ScopedTemporaryFile { + public: + ScopedTemporaryFile(); + ~ScopedTemporaryFile(); + + int fd() const { return fd_; } + const char* full_file_name() const { return full_file_name_; } + + private: + int fd_; + char full_file_name_[128]; + DISALLOW_COPY_AND_ASSIGN(ScopedTemporaryFile); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_TESTS_SCOPED_TEMPORARY_FILE_H_ diff --git a/sandbox/linux/tests/scoped_temporary_file_unittest.cc b/sandbox/linux/tests/scoped_temporary_file_unittest.cc new file mode 100644 index 0000000000..44a2ecb1ae --- /dev/null +++ b/sandbox/linux/tests/scoped_temporary_file_unittest.cc @@ -0,0 +1,76 @@ +// Copyright 2014 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 "sandbox/linux/tests/scoped_temporary_file.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> + +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +bool FullWrite(int fd, const char* buffer, size_t count) { + while (count > 0) { + const ssize_t transfered = HANDLE_EINTR(write(fd, buffer, count)); + if (transfered <= 0 || static_cast<size_t>(transfered) > count) { + return false; + } + count -= transfered; + buffer += transfered; + } + return true; +} + +bool FullRead(int fd, char* buffer, size_t count) { + while (count > 0) { + const ssize_t transfered = HANDLE_EINTR(read(fd, buffer, count)); + if (transfered <= 0 || static_cast<size_t>(transfered) > count) { + return false; + } + count -= transfered; + buffer += transfered; + } + return true; +} + +TEST(ScopedTemporaryFile, Basics) { + std::string temp_file_name; + { + ScopedTemporaryFile temp_file_1; + const char kTestString[] = "This is a test"; + ASSERT_LE(0, temp_file_1.fd()); + + temp_file_name = temp_file_1.full_file_name(); + base::ScopedFD temp_file_2(open(temp_file_1.full_file_name(), O_RDONLY)); + ASSERT_TRUE(temp_file_2.is_valid()); + + ASSERT_TRUE(FullWrite(temp_file_1.fd(), kTestString, sizeof(kTestString))); + + char test_string_read[sizeof(kTestString)] = {0}; + ASSERT_TRUE(FullRead( + temp_file_2.get(), test_string_read, sizeof(test_string_read))); + ASSERT_EQ(0, memcmp(kTestString, test_string_read, sizeof(kTestString))); + } + + errno = 0; + struct stat buf; + ASSERT_EQ(-1, stat(temp_file_name.c_str(), &buf)); + ASSERT_EQ(ENOENT, errno); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/tests/test_utils.cc b/sandbox/linux/tests/test_utils.cc new file mode 100644 index 0000000000..747bad27a5 --- /dev/null +++ b/sandbox/linux/tests/test_utils.cc @@ -0,0 +1,42 @@ +// Copyright 2014 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 "sandbox/linux/tests/test_utils.h" + +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +namespace sandbox { + +bool TestUtils::CurrentProcessHasChildren() { + siginfo_t process_info; + int ret = HANDLE_EINTR( + waitid(P_ALL, 0, &process_info, WEXITED | WNOHANG | WNOWAIT)); + if (-1 == ret) { + PCHECK(ECHILD == errno); + return false; + } else { + return true; + } +} + +void TestUtils::HandlePostForkReturn(pid_t pid) { + const int kChildExitCode = 1; + if (pid > 0) { + int status = 0; + PCHECK(pid == HANDLE_EINTR(waitpid(pid, &status, 0))); + CHECK(WIFEXITED(status)); + CHECK_EQ(kChildExitCode, WEXITSTATUS(status)); + } else if (pid == 0) { + _exit(kChildExitCode); + } +} + +} // namespace sandbox diff --git a/sandbox/linux/tests/test_utils.h b/sandbox/linux/tests/test_utils.h new file mode 100644 index 0000000000..7cf9749fe4 --- /dev/null +++ b/sandbox/linux/tests/test_utils.h @@ -0,0 +1,29 @@ +// Copyright 2014 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. + +#ifndef SANDBOX_LINUX_TESTS_TEST_UTILS_H_ +#define SANDBOX_LINUX_TESTS_TEST_UTILS_H_ + +#include <sys/types.h> + +#include "base/macros.h" + +namespace sandbox { + +// This class provide small helpers to help writing tests. +class TestUtils { + public: + static bool CurrentProcessHasChildren(); + // |pid| is the return value of a fork()-like call. This + // makes sure that if fork() succeeded the child exits + // and the parent waits for it. + static void HandlePostForkReturn(pid_t pid); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(TestUtils); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_TESTS_TEST_UTILS_H_ diff --git a/sandbox/linux/tests/test_utils_unittest.cc b/sandbox/linux/tests/test_utils_unittest.cc new file mode 100644 index 0000000000..0f86e616e9 --- /dev/null +++ b/sandbox/linux/tests/test_utils_unittest.cc @@ -0,0 +1,24 @@ +// Copyright 2014 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 "sandbox/linux/tests/test_utils.h" + +#include <sys/types.h> +#include <unistd.h> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +// Check that HandlePostForkReturn works. +TEST(TestUtils, HandlePostForkReturn) { + pid_t pid = fork(); + TestUtils::HandlePostForkReturn(pid); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/tests/unit_tests.cc b/sandbox/linux/tests/unit_tests.cc new file mode 100644 index 0000000000..4973c41fbd --- /dev/null +++ b/sandbox/linux/tests/unit_tests.cc @@ -0,0 +1,354 @@ +// Copyright (c) 2012 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 <fcntl.h> +#include <poll.h> +#include <signal.h> +#include <stdio.h> +#include <sys/resource.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <time.h> +#include <unistd.h> + +#include "base/debug/leak_annotations.h" +#include "base/files/file_util.h" +#include "base/posix/eintr_wrapper.h" +#include "base/third_party/valgrind/valgrind.h" +#include "build/build_config.h" +#include "sandbox/linux/tests/unit_tests.h" + +// Specifically, PNaCl toolchain does not have this flag. +#if !defined(POLLRDHUP) +#define POLLRDHUP 0x2000 +#endif + +namespace { +std::string TestFailedMessage(const std::string& msg) { + return msg.empty() ? std::string() : "Actual test failure: " + msg; +} + +int GetSubProcessTimeoutTimeInSeconds() { + // Previously 10s, but that timed out (just) on Chromecast. + return 12; +} + +// Returns the number of threads of the current process or -1. +int CountThreads() { + struct stat task_stat; + int task_d = stat("/proc/self/task", &task_stat); + // task_stat.st_nlink should be the number of tasks + 2 (accounting for + // "." and "..". + if (task_d != 0 || task_stat.st_nlink < 3) + return -1; + const int num_threads = task_stat.st_nlink - 2; + return num_threads; +} + +} // namespace + +namespace sandbox { + +bool IsAndroid() { +#if defined(OS_ANDROID) + return true; +#else + return false; +#endif +} + +bool IsArchitectureArm() { +#if defined(ARCH_CPU_ARM_FAMILY) + return true; +#else + return false; +#endif +} + +// TODO(jln): figure out why base/.../dynamic_annotations.h's +// RunningOnValgrind() cannot link. +bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; } + +static const int kExpectedValue = 42; +static const int kIgnoreThisTest = 43; +static const int kExitWithAssertionFailure = 1; +#if !defined(OS_NACL_NONSFI) +static const int kExitForTimeout = 2; +#endif + +#if defined(SANDBOX_USES_BASE_TEST_SUITE) +// This is due to StackDumpSignalHandler() performing _exit(1). +// TODO(jln): get rid of the collision with kExitWithAssertionFailure. +const int kExitAfterSIGSEGV = 1; +#endif + +// PNaCl toolchain's signal ABIs are incompatible with Linux's. +// So, for simplicity, just drop the "timeout" feature from unittest framework +// with relying on the buildbot's timeout feature. +#if !defined(OS_NACL_NONSFI) +static void SigAlrmHandler(int) { + const char failure_message[] = "Timeout reached!\n"; + // Make sure that we never block here. + if (!fcntl(2, F_SETFL, O_NONBLOCK)) { + ignore_result(write(2, failure_message, sizeof(failure_message) - 1)); + } + _exit(kExitForTimeout); +} + +// Set a timeout with a handler that will automatically fail the +// test. +static void SetProcessTimeout(int time_in_seconds) { + struct sigaction act = {}; + act.sa_handler = SigAlrmHandler; + SANDBOX_ASSERT(sigemptyset(&act.sa_mask) == 0); + act.sa_flags = 0; + + struct sigaction old_act; + SANDBOX_ASSERT(sigaction(SIGALRM, &act, &old_act) == 0); + + // We don't implemenet signal chaining, so make sure that nothing else + // is expecting to handle SIGALRM. + SANDBOX_ASSERT((old_act.sa_flags & SA_SIGINFO) == 0); + SANDBOX_ASSERT(old_act.sa_handler == SIG_DFL); + sigset_t sigalrm_set; + SANDBOX_ASSERT(sigemptyset(&sigalrm_set) == 0); + SANDBOX_ASSERT(sigaddset(&sigalrm_set, SIGALRM) == 0); + SANDBOX_ASSERT(sigprocmask(SIG_UNBLOCK, &sigalrm_set, NULL) == 0); + SANDBOX_ASSERT(alarm(time_in_seconds) == 0); // There should be no previous + // alarm. +} +#endif // !defined(OS_NACL_NONSFI) + +// Runs a test in a sub-process. This is necessary for most of the code +// in the BPF sandbox, as it potentially makes global state changes and as +// it also tends to raise fatal errors, if the code has been used in an +// insecure manner. +void UnitTests::RunTestInProcess(SandboxTestRunner* test_runner, + DeathCheck death, + const void* death_aux) { + CHECK(test_runner); + // We need to fork(), so we can't be multi-threaded, as threads could hold + // locks. + int num_threads = CountThreads(); +#if !defined(THREAD_SANITIZER) + const int kNumExpectedThreads = 1; +#else + // Under TSAN, there is a special helper thread. It should be completely + // invisible to our testing, so we ignore it. It should be ok to fork() + // with this thread. It's currently buggy, but it's the best we can do until + // there is a way to delay the start of the thread + // (https://code.google.com/p/thread-sanitizer/issues/detail?id=19). + const int kNumExpectedThreads = 2; +#endif + + // The kernel is at liberty to wake a thread id futex before updating /proc. + // If another test running in the same process has stopped a thread, it may + // appear as still running in /proc. + // We poll /proc, with an exponential back-off. At most, we'll sleep around + // 2^iterations nanoseconds in nanosleep(). + for (unsigned int iteration = 0; iteration < 30; iteration++) { + struct timespec ts = {0, 1L << iteration /* nanoseconds */}; + PCHECK(0 == HANDLE_EINTR(nanosleep(&ts, &ts))); + num_threads = CountThreads(); + if (kNumExpectedThreads == num_threads) + break; + } + + ASSERT_EQ(kNumExpectedThreads, num_threads) + << "Running sandbox tests with multiple threads " + << "is not supported and will make the tests flaky."; + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + // Check that our pipe is not on one of the standard file descriptor. + SANDBOX_ASSERT(fds[0] > 2 && fds[1] > 2); + + pid_t pid; + ASSERT_LE(0, (pid = fork())); + if (!pid) { + // In child process + // Redirect stderr to our pipe. This way, we can capture all error + // messages, if we decide we want to do so in our tests. + SANDBOX_ASSERT(dup2(fds[1], 2) == 2); + SANDBOX_ASSERT(!close(fds[0])); + SANDBOX_ASSERT(!close(fds[1])); + + // Don't set a timeout if running on Valgrind, since it's generally much + // slower. + if (!IsRunningOnValgrind()) { +#if !defined(OS_NACL_NONSFI) + SetProcessTimeout(GetSubProcessTimeoutTimeInSeconds()); +#endif + } + + // Disable core files. They are not very useful for our individual test + // cases. + struct rlimit no_core = {0}; + setrlimit(RLIMIT_CORE, &no_core); + + test_runner->Run(); + if (test_runner->ShouldCheckForLeaks()) { +#if defined(LEAK_SANITIZER) + __lsan_do_leak_check(); +#endif + } + _exit(kExpectedValue); + } + + close(fds[1]); + std::vector<char> msg_buf; + ssize_t rc; + + // Make sure read() will never block as we'll use poll() to + // block with a timeout instead. + const int fcntl_ret = fcntl(fds[0], F_SETFL, O_NONBLOCK); + ASSERT_EQ(0, fcntl_ret); + struct pollfd poll_fd = {fds[0], POLLIN | POLLRDHUP, 0}; + + int poll_ret; + // We prefer the SIGALRM timeout to trigger in the child than this timeout + // so we double the common value here. + int poll_timeout = GetSubProcessTimeoutTimeInSeconds() * 2 * 1000; + while ((poll_ret = poll(&poll_fd, 1, poll_timeout) > 0)) { + const size_t kCapacity = 256; + const size_t len = msg_buf.size(); + msg_buf.resize(len + kCapacity); + rc = HANDLE_EINTR(read(fds[0], &msg_buf[len], kCapacity)); + msg_buf.resize(len + std::max(rc, static_cast<ssize_t>(0))); + if (rc <= 0) + break; + } + ASSERT_NE(poll_ret, -1) << "poll() failed"; + ASSERT_NE(poll_ret, 0) << "Timeout while reading child state"; + close(fds[0]); + std::string msg(msg_buf.begin(), msg_buf.end()); + + int status = 0; + int waitpid_returned = HANDLE_EINTR(waitpid(pid, &status, 0)); + ASSERT_EQ(pid, waitpid_returned) << TestFailedMessage(msg); + + // At run-time, we sometimes decide that a test shouldn't actually + // run (e.g. when testing sandbox features on a kernel that doesn't + // have sandboxing support). When that happens, don't attempt to + // call the "death" function, as it might be looking for a + // death-test condition that would never have triggered. + if (!WIFEXITED(status) || WEXITSTATUS(status) != kIgnoreThisTest || + !msg.empty()) { + // We use gtest's ASSERT_XXX() macros instead of the DeathCheck + // functions. This means, on failure, "return" is called. This + // only works correctly, if the call of the "death" callback is + // the very last thing in our function. + death(status, msg, death_aux); + } +} + +void UnitTests::DeathSuccess(int status, const std::string& msg, const void*) { + std::string details(TestFailedMessage(msg)); + + bool subprocess_terminated_normally = WIFEXITED(status); + ASSERT_TRUE(subprocess_terminated_normally) << details; + int subprocess_exit_status = WEXITSTATUS(status); + ASSERT_EQ(kExpectedValue, subprocess_exit_status) << details; + bool subprocess_exited_but_printed_messages = !msg.empty(); + EXPECT_FALSE(subprocess_exited_but_printed_messages) << details; +} + +void UnitTests::DeathSuccessAllowNoise(int status, + const std::string& msg, + const void*) { + std::string details(TestFailedMessage(msg)); + + bool subprocess_terminated_normally = WIFEXITED(status); + ASSERT_TRUE(subprocess_terminated_normally) << details; + int subprocess_exit_status = WEXITSTATUS(status); + ASSERT_EQ(kExpectedValue, subprocess_exit_status) << details; +} + +void UnitTests::DeathMessage(int status, + const std::string& msg, + const void* aux) { + std::string details(TestFailedMessage(msg)); + const char* expected_msg = static_cast<const char*>(aux); + + bool subprocess_terminated_normally = WIFEXITED(status); + ASSERT_TRUE(subprocess_terminated_normally) << "Exit status: " << status + << " " << details; + int subprocess_exit_status = WEXITSTATUS(status); + ASSERT_EQ(1, subprocess_exit_status) << details; + + bool subprocess_exited_without_matching_message = + msg.find(expected_msg) == std::string::npos; + +// In official builds CHECK messages are dropped, so look for SIGABRT. +// See https://code.google.com/p/chromium/issues/detail?id=437312 +#if defined(OFFICIAL_BUILD) && defined(NDEBUG) && !defined(OS_ANDROID) + if (subprocess_exited_without_matching_message) { + static const char kSigAbortMessage[] = "Received signal 6"; + subprocess_exited_without_matching_message = + msg.find(kSigAbortMessage) == std::string::npos; + } +#endif + EXPECT_FALSE(subprocess_exited_without_matching_message) << details; +} + +void UnitTests::DeathSEGVMessage(int status, + const std::string& msg, + const void* aux) { + std::string details(TestFailedMessage(msg)); + const char* expected_msg = static_cast<const char*>(aux); + +#if !defined(SANDBOX_USES_BASE_TEST_SUITE) + const bool subprocess_got_sigsegv = + WIFSIGNALED(status) && (SIGSEGV == WTERMSIG(status)); +#else + // This hack is required when a signal handler is installed + // for SEGV that will _exit(1). + const bool subprocess_got_sigsegv = + WIFEXITED(status) && (kExitAfterSIGSEGV == WEXITSTATUS(status)); +#endif + + ASSERT_TRUE(subprocess_got_sigsegv) << "Exit status: " << status + << " " << details; + + bool subprocess_exited_without_matching_message = + msg.find(expected_msg) == std::string::npos; + EXPECT_FALSE(subprocess_exited_without_matching_message) << details; +} + +void UnitTests::DeathExitCode(int status, + const std::string& msg, + const void* aux) { + int expected_exit_code = static_cast<int>(reinterpret_cast<intptr_t>(aux)); + std::string details(TestFailedMessage(msg)); + + bool subprocess_terminated_normally = WIFEXITED(status); + ASSERT_TRUE(subprocess_terminated_normally) << details; + int subprocess_exit_status = WEXITSTATUS(status); + ASSERT_EQ(expected_exit_code, subprocess_exit_status) << details; +} + +void UnitTests::DeathBySignal(int status, + const std::string& msg, + const void* aux) { + int expected_signo = static_cast<int>(reinterpret_cast<intptr_t>(aux)); + std::string details(TestFailedMessage(msg)); + + bool subprocess_terminated_by_signal = WIFSIGNALED(status); + ASSERT_TRUE(subprocess_terminated_by_signal) << details; + int subprocess_signal_number = WTERMSIG(status); + ASSERT_EQ(expected_signo, subprocess_signal_number) << details; +} + +void UnitTests::AssertionFailure(const char* expr, const char* file, int line) { + fprintf(stderr, "%s:%d:%s", file, line, expr); + fflush(stderr); + _exit(kExitWithAssertionFailure); +} + +void UnitTests::IgnoreThisTest() { + fflush(stderr); + _exit(kIgnoreThisTest); +} + +} // namespace diff --git a/sandbox/linux/tests/unit_tests.h b/sandbox/linux/tests/unit_tests.h new file mode 100644 index 0000000000..5a7116e932 --- /dev/null +++ b/sandbox/linux/tests/unit_tests.h @@ -0,0 +1,201 @@ +// Copyright (c) 2012 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. + +#ifndef SANDBOX_LINUX_TESTS_UNIT_TESTS_H_ +#define SANDBOX_LINUX_TESTS_UNIT_TESTS_H_ + +#include "base/macros.h" +#include "build/build_config.h" +#include "sandbox/linux/tests/sandbox_test_runner_function_pointer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Has this been compiled to run on Android? +bool IsAndroid(); + +bool IsArchitectureArm(); + +// Is Valgrind currently being used? +bool IsRunningOnValgrind(); + +#if defined(ADDRESS_SANITIZER) +#define DISABLE_ON_ASAN(test_name) DISABLED_##test_name +#else +#define DISABLE_ON_ASAN(test_name) test_name +#endif // defined(ADDRESS_SANITIZER) + +#if defined(LEAK_SANITIZER) +#define DISABLE_ON_LSAN(test_name) DISABLED_##test_name +#else +#define DISABLE_ON_LSAN(test_name) test_name +#endif + +#if defined(THREAD_SANITIZER) +#define DISABLE_ON_TSAN(test_name) DISABLED_##test_name +#else +#define DISABLE_ON_TSAN(test_name) test_name +#endif // defined(THREAD_SANITIZER) + +#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ + defined(THREAD_SANITIZER) || defined(LEAK_SANITIZER) || \ + defined(UNDEFINED_SANITIZER) || defined(SANITIZER_COVERAGE) +#define DISABLE_ON_SANITIZERS(test_name) DISABLED_##test_name +#else +#define DISABLE_ON_SANITIZERS(test_name) test_name +#endif + +#if defined(OS_ANDROID) +#define DISABLE_ON_ANDROID(test_name) DISABLED_##test_name +#else +#define DISABLE_ON_ANDROID(test_name) test_name +#endif + +// While it is perfectly OK for a complex test to provide its own DeathCheck +// function. Most death tests have very simple requirements. These tests should +// use one of the predefined DEATH_XXX macros as an argument to +// SANDBOX_DEATH_TEST(). You can check for a (sub-)string in the output of the +// test, for a particular exit code, or for a particular death signal. +// NOTE: If you do decide to write your own DeathCheck, make sure to use +// gtests's ASSERT_XXX() macros instead of SANDBOX_ASSERT(). See +// unit_tests.cc for examples. +#define DEATH_SUCCESS() sandbox::UnitTests::DeathSuccess, NULL +#define DEATH_SUCCESS_ALLOW_NOISE() \ + sandbox::UnitTests::DeathSuccessAllowNoise, NULL +#define DEATH_MESSAGE(msg) \ + sandbox::UnitTests::DeathMessage, \ + static_cast<const void*>(static_cast<const char*>(msg)) +#define DEATH_SEGV_MESSAGE(msg) \ + sandbox::UnitTests::DeathSEGVMessage, \ + static_cast<const void*>(static_cast<const char*>(msg)) +#define DEATH_EXIT_CODE(rc) \ + sandbox::UnitTests::DeathExitCode, \ + reinterpret_cast<void*>(static_cast<intptr_t>(rc)) +#define DEATH_BY_SIGNAL(s) \ + sandbox::UnitTests::DeathBySignal, \ + reinterpret_cast<void*>(static_cast<intptr_t>(s)) + +// A SANDBOX_DEATH_TEST is just like a SANDBOX_TEST (see below), but it assumes +// that the test actually dies. The death test only passes if the death occurs +// in the expected fashion, as specified by "death" and "death_aux". These two +// parameters are typically set to one of the DEATH_XXX() macros. +#define SANDBOX_DEATH_TEST(test_case_name, test_name, death) \ + void TEST_##test_name(void); \ + TEST(test_case_name, test_name) { \ + SandboxTestRunnerFunctionPointer sandbox_test_runner(TEST_##test_name); \ + sandbox::UnitTests::RunTestInProcess(&sandbox_test_runner, death); \ + } \ + void TEST_##test_name(void) + +// Define a new test case that runs inside of a GTest death test. This is +// necessary, as most of our tests by definition make global and irreversible +// changes to the system (i.e. they install a sandbox). GTest provides death +// tests as a tool to isolate global changes from the rest of the tests. +#define SANDBOX_TEST(test_case_name, test_name) \ + SANDBOX_DEATH_TEST(test_case_name, test_name, DEATH_SUCCESS()) + +// SANDBOX_TEST_ALLOW_NOISE is just like SANDBOX_TEST, except it does not +// consider log error messages printed by the test to be test failures. +#define SANDBOX_TEST_ALLOW_NOISE(test_case_name, test_name) \ + SANDBOX_DEATH_TEST(test_case_name, test_name, DEATH_SUCCESS_ALLOW_NOISE()) + +// Simple assertion macro that is compatible with running inside of a death +// test. We unfortunately cannot use any of the GTest macros. +#define SANDBOX_STR(x) #x +#define SANDBOX_ASSERT(expr) \ + ((expr) ? static_cast<void>(0) : sandbox::UnitTests::AssertionFailure( \ + SANDBOX_STR(expr), __FILE__, __LINE__)) + +#define SANDBOX_ASSERT_EQ(x, y) SANDBOX_ASSERT((x) == (y)) +#define SANDBOX_ASSERT_NE(x, y) SANDBOX_ASSERT((x) != (y)) +#define SANDBOX_ASSERT_LT(x, y) SANDBOX_ASSERT((x) < (y)) +#define SANDBOX_ASSERT_GT(x, y) SANDBOX_ASSERT((x) > (y)) +#define SANDBOX_ASSERT_LE(x, y) SANDBOX_ASSERT((x) <= (y)) +#define SANDBOX_ASSERT_GE(x, y) SANDBOX_ASSERT((x) >= (y)) + +// This class allows to run unittests in their own process. The main method is +// RunTestInProcess(). +class UnitTests { + public: + typedef void (*DeathCheck)(int status, + const std::string& msg, + const void* aux); + + // Runs a test inside a short-lived process. Do not call this function + // directly. It is automatically invoked by SANDBOX_TEST(). Most sandboxing + // functions make global irreversible changes to the execution environment + // and must therefore execute in their own isolated process. + // |test_runner| must implement the SandboxTestRunner interface and will run + // in a subprocess. + // Note: since the child process (created with fork()) will never return from + // RunTestInProcess(), |test_runner| is guaranteed to exist for the lifetime + // of the child process. + static void RunTestInProcess(SandboxTestRunner* test_runner, + DeathCheck death, + const void* death_aux); + + // Report a useful error message and terminate the current SANDBOX_TEST(). + // Calling this function from outside a SANDBOX_TEST() is unlikely to do + // anything useful. + static void AssertionFailure(const char* expr, const char* file, int line); + + // Sometimes we determine at run-time that a test should be disabled. + // Call this method if we want to return from a test and completely + // ignore its results. + // You should not call this method, if the test already ran any test-relevant + // code. Most notably, you should not call it, you already wrote any messages + // to stderr. + static void IgnoreThisTest(); + + // A DeathCheck method that verifies that the test completed succcessfully. + // This is the default test mode for SANDBOX_TEST(). The "aux" parameter + // of this DeathCheck is unused (and thus unnamed) + static void DeathSuccess(int status, const std::string& msg, const void*); + + // A DeathCheck method that verifies that the test completed succcessfully + // allowing for log error messages. + static void DeathSuccessAllowNoise(int status, + const std::string& msg, + const void*); + + // A DeathCheck method that verifies that the test completed with error + // code "1" and printed a message containing a particular substring. The + // "aux" pointer should point to a C-string containing the expected error + // message. This method is useful for checking assertion failures such as + // in SANDBOX_ASSERT() and/or SANDBOX_DIE(). + static void DeathMessage(int status, const std::string& msg, const void* aux); + + // Like DeathMessage() but the process must be terminated with a segmentation + // fault. + // Implementation detail: On Linux (but not on Android), this does check for + // the return value of our default signal handler rather than for the actual + // reception of a SIGSEGV. + // TODO(jln): make this more robust. + static void DeathSEGVMessage(int status, + const std::string& msg, + const void* aux); + + // A DeathCheck method that verifies that the test completed with a + // particular exit code. If the test output any messages to stderr, they are + // silently ignored. The expected exit code should be passed in by + // casting the its "int" value to a "void *", which is then used for "aux". + static void DeathExitCode(int status, + const std::string& msg, + const void* aux); + + // A DeathCheck method that verifies that the test was terminated by a + // particular signal. If the test output any messages to stderr, they are + // silently ignore. The expected signal number should be passed in by + // casting the its "int" value to a "void *", which is then used for "aux". + static void DeathBySignal(int status, + const std::string& msg, + const void* aux); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(UnitTests); +}; + +} // namespace + +#endif // SANDBOX_LINUX_TESTS_UNIT_TESTS_H_ diff --git a/sandbox/linux/tests/unit_tests_unittest.cc b/sandbox/linux/tests/unit_tests_unittest.cc new file mode 100644 index 0000000000..57799b14c0 --- /dev/null +++ b/sandbox/linux/tests/unit_tests_unittest.cc @@ -0,0 +1,62 @@ +// Copyright 2014 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 <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "sandbox/linux/tests/unit_tests.h" + +namespace sandbox { + +namespace { + +// Let's not use any of the "magic" values used internally in unit_tests.cc, +// such as kExpectedValue. +const int kExpectedExitCode = 100; + +SANDBOX_DEATH_TEST(UnitTests, + DeathExitCode, + DEATH_EXIT_CODE(kExpectedExitCode)) { + _exit(kExpectedExitCode); +} + +const int kExpectedSignalNumber = SIGKILL; + +SANDBOX_DEATH_TEST(UnitTests, + DeathBySignal, + DEATH_BY_SIGNAL(kExpectedSignalNumber)) { + raise(kExpectedSignalNumber); +} + +SANDBOX_DEATH_TEST(UnitTests, + DeathWithMessage, + DEATH_MESSAGE("Hello")) { + LOG(ERROR) << "Hello"; + _exit(1); +} + +SANDBOX_DEATH_TEST(UnitTests, + SEGVDeathWithMessage, + DEATH_SEGV_MESSAGE("Hello")) { + LOG(ERROR) << "Hello"; + while (1) { + volatile char* addr = reinterpret_cast<volatile char*>(NULL); + *addr = '\0'; + } + + _exit(2); +} + +SANDBOX_TEST_ALLOW_NOISE(UnitTests, NoisyTest) { + LOG(ERROR) << "The cow says moo!"; +} + +} // namespace + +} // namespace sandbox |