diff options
-rw-r--r-- | base/base_unittest_exported_symbols.version | 5 | ||||
-rw-r--r-- | base/base_unittests.scons | 29 | ||||
-rw-r--r-- | base/multiprocess_test.h | 38 | ||||
-rw-r--r-- | base/port.h | 2 | ||||
-rw-r--r-- | base/process_util.h | 25 | ||||
-rw-r--r-- | base/process_util_linux.cc | 46 | ||||
-rw-r--r-- | base/process_util_unittest.cc | 32 | ||||
-rw-r--r-- | base/process_util_win.cc | 13 | ||||
-rw-r--r-- | base/stats_table_unittest.cc | 63 | ||||
-rw-r--r-- | base/test_suite.h | 20 |
10 files changed, 225 insertions, 48 deletions
diff --git a/base/base_unittest_exported_symbols.version b/base/base_unittest_exported_symbols.version new file mode 100644 index 0000000..357ed6b --- /dev/null +++ b/base/base_unittest_exported_symbols.version @@ -0,0 +1,5 @@ +# list of functions exported from the Linux base_unittests executable. +{ + SimpleChildProcess; + StatsTableMultipleProcessMain; +}; diff --git a/base/base_unittests.scons b/base/base_unittests.scons index fbb7c0c..3fcd89c 100644 --- a/base/base_unittests.scons +++ b/base/base_unittests.scons @@ -59,6 +59,22 @@ if env['PLATFORM'] == 'win32': ], ) +if env['PLATFORM'] == 'posix': + # Explicity list the functions we want to export from the base_unittest + # executable in the following file. + exported_symbols_filename = \ + env.Dir('#').abspath + '/base_unittest_exported_symbols.version' + + env.Append( + LIBS = [ + 'event', + ], + LINKFLAGS = [ + '-Xlinker', + '--dynamic-list=' + exported_symbols_filename, + ], + ) + # These test files work on *all* platforms; tests that don't work # cross-platform live below. input_files = [ @@ -127,8 +143,6 @@ if env['PLATFORM'] in ('posix', 'darwin'): to_be_ported_files = [ 'clipboard_unittest.cc', 'idletimer_unittest.cc', - 'process_util_unittest.cc', - 'stats_table_unittest.cc', 'watchdog_unittest.cc', 'gfx/native_theme_unittest.cc', 'gfx/uniscribe_unittest.cc', @@ -137,6 +151,17 @@ if env['PLATFORM'] in ('posix', 'darwin'): for remove in to_be_ported_files: input_files.remove(remove) +if env['PLATFORM'] == 'darwin': + # Remove files that still need to be ported from the input_files list. + # TODO(port): delete files from this list as they get ported. + to_be_ported_files = [ + 'process_util_unittest.cc', + 'stats_table_unittest.cc', + ] + for remove in to_be_ported_files: + input_files.remove(remove) + + if env['PLATFORM'] == 'win32': # Windows-specific tests. input_files.extend([ diff --git a/base/multiprocess_test.h b/base/multiprocess_test.h index 93bc5e7..15085d9 100644 --- a/base/multiprocess_test.h +++ b/base/multiprocess_test.h @@ -7,8 +7,14 @@ #include "base/command_line.h" #include "base/process_util.h" +#include "base/string_util.h" #include "testing/gtest/include/gtest/gtest.h" +#if defined(OS_POSIX) +#include <sys/types.h> +#include <unistd.h> +#endif + // Command line switch to invoke a child process rather than // to run the normal test suite. static const wchar_t kRunClientProcess[] = L"client"; @@ -24,13 +30,15 @@ static const wchar_t kRunClientProcess[] = L"client"; // 3) Create a mainline function for the child processes // 4) Call SpawnChild("foo"), where "foo" is the name of // the function you wish to run in the child processes. +// 5) On posix, add the function's name to the list in the file +// base_unittests_exported_symbols.version // That's it! // class MultiProcessTest : public testing::Test { public: // Prototype function for a client function. Multi-process // clients must provide a callback with this signature to run. - typedef int (__cdecl *ChildFunctionPtr)(); + typedef int (*ChildFunctionPtr)(); protected: // Run a child process. @@ -44,13 +52,27 @@ class MultiProcessTest : public testing::Test { // } // // Returns the handle to the child, or NULL on failure - HANDLE SpawnChild(const std::wstring& procname) { - std::wstring cl(GetCommandLineW()); - CommandLine::AppendSwitchWithValue(&cl, kRunClientProcess, procname); - // TODO(darin): re-enable this once we have base/debug_util.h - //ProcessDebugFlags(&cl, DebugUtil::UNKNOWN, false); - HANDLE handle = NULL; - process_util::LaunchApp(cl, false, true, &handle); + // + // TODO(darin): re-enable this once we have base/debug_util.h + // ProcessDebugFlags(&cl, DebugUtil::UNKNOWN, false); + ProcessHandle SpawnChild(const std::wstring& procname) { + CommandLine cl; + ProcessHandle handle = static_cast<ProcessHandle>(NULL); + +#if defined(OS_WIN) + std::wstring clstr = cl.command_line_string(); + CommandLine::AppendSwitchWithValue(&clstr, kRunClientProcess, procname); + process_util::LaunchApp(clstr, false, true, &handle); +#elif defined(OS_POSIX) + std::vector<std::string> clvec(cl.argv()); + std::wstring wswitchstr = + CommandLine::PrefixedSwitchStringWithValue(kRunClientProcess, + procname); + std::string switchstr = WideToUTF8(wswitchstr); + clvec.push_back(switchstr.c_str()); + process_util::LaunchApp(clvec, false, &handle); +#endif + return handle; } }; diff --git a/base/port.h b/base/port.h index 102651f..9207192 100644 --- a/base/port.h +++ b/base/port.h @@ -53,8 +53,10 @@ inline void va_copy(va_list& a, va_list& b) { // Define an OS-neutral wrapper for shared library entry points #if defined(OS_WIN) #define API_CALL __stdcall +#define DYNAMIC_EXPORT __declspec(dllexport) #elif defined(OS_POSIX) #define API_CALL +#define DYNAMIC_EXPORT #endif #endif // BASE_PORT_H_ diff --git a/base/process_util.h b/base/process_util.h index 4f33bb0..503c2ec 100644 --- a/base/process_util.h +++ b/base/process_util.h @@ -17,6 +17,7 @@ #include <string> +#include "base/command_line.h" #include "base/process.h" #if defined(OS_WIN) @@ -47,6 +48,7 @@ ProcessHandle GetCurrentProcessHandle(); // Win XP SP1 as well. int GetProcId(ProcessHandle process); +#if defined(OS_WIN) // Runs the given application name with the given command line. Normally, the // first command line argument should be the path to the process, and don't // forget to quote it. @@ -63,6 +65,24 @@ int GetProcId(ProcessHandle process); // that it doesn't leak! bool LaunchApp(const std::wstring& cmdline, bool wait, bool start_hidden, ProcessHandle* process_handle); +#elif defined(OS_POSIX) +// Runs the application specified in argv[0] with the command line argv. +// Both the elements of argv and argv itself must be terminated with a null +// byte. +// +// As above, if wait is true, execute synchronously. The pid will be stored +// in process_handle if that pointer is non-null. +// +// Note that the first argument in argv must point to the filename, +// and must be fully specified. +bool LaunchApp(const std::vector<std::string>& argv, + bool wait, ProcessHandle* process_handle); +#endif + +// Execute the application specified by cl. This function delegates to one +// of the above two platform-specific functions. +bool LaunchApp(const CommandLine& cl, + bool wait, bool start_hidden, ProcessHandle* process_handle); // Used to filter processes by process ID. class ProcessFilter { @@ -106,6 +126,11 @@ bool WaitForProcessesToExit(const std::wstring& executable_name, int wait_milliseconds, const ProcessFilter* filter); +// Wait for a single process to exit. Return true if it exited cleanly within +// the given time limit. +bool WaitForSingleProcess(ProcessHandle handle, + int wait_milliseconds); + // Waits a certain amount of time (can be 0) for all the processes with a given // executable name to exit, then kills off any of them that are still around. // If filter is non-null, then only processes selected by the filter are waited diff --git a/base/process_util_linux.cc b/base/process_util_linux.cc index e5bc5db..f53ba3b 100644 --- a/base/process_util_linux.cc +++ b/base/process_util_linux.cc @@ -5,6 +5,8 @@ #include "base/process_util.h" #include <string> +#include <sys/types.h> +#include <sys/wait.h> #include "base/file_util.h" #include "base/logging.h" @@ -22,6 +24,50 @@ enum ParsingState { namespace process_util { +bool LaunchApp(const std::vector<std::string>& argv, + bool wait, ProcessHandle* process_handle) { + bool retval = true; + + char* argv_copy[argv.size() + 1]; + for (size_t i = 0; i < argv.size(); i++) { + argv_copy[i] = new char[argv[i].size() + 1]; + strcpy(argv_copy[i], argv[i].c_str()); + } + argv_copy[argv.size()] = NULL; + + int pid = vfork(); + if (pid == 0) { + execv(argv_copy[0], argv_copy); + } else if (pid < 0) { + retval = false; + } else { + if (wait) + waitpid(pid, 0, 0); + + if(process_handle) + *process_handle = pid; + } + + for (size_t i = 0; i < argv.size(); i++) + delete[] argv_copy[i]; + + return retval; +} + +bool LaunchApp(const CommandLine& cl, + bool wait, bool start_hidden, ProcessHandle* process_handle) { + return LaunchApp(cl.argv(), wait, process_handle); +} + +bool WaitForSingleProcess(ProcessHandle handle, int wait_milliseconds) { + int status; + waitpid(handle, &status, 0); + return WIFEXITED(status); +} + +/////////////////////////////////////////////////////////////////////////////// +//// ProcessMetrics + // To have /proc/self/io file you must enable CONFIG_TASK_IO_ACCOUNTING // in your kernel configuration. bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) { diff --git a/base/process_util_unittest.cc b/base/process_util_unittest.cc index de51fc2..32595c9 100644 --- a/base/process_util_unittest.cc +++ b/base/process_util_unittest.cc @@ -4,10 +4,35 @@ #define _CRT_SECURE_NO_WARNINGS -#include "testing/gtest/include/gtest/gtest.h" +#include "base/multiprocess_test.h" #include "base/process_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include <windows.h> +#elif defined(OS_LINUX) +#include <dlfcn.h> +#endif + +namespace { +class ProcessUtilTest : public MultiProcessTest { +}; +} + +extern "C" int DYNAMIC_EXPORT SimpleChildProcess() { + return 0; +} + +TEST_F(ProcessUtilTest, SpawnChild) { + ProcessHandle handle = this->SpawnChild(L"SimpleChildProcess"); + + ASSERT_NE(static_cast<ProcessHandle>(NULL), handle); + EXPECT_TRUE(process_util::WaitForSingleProcess(handle, 1000)); +} -TEST(ProcessUtilTest, EnableLFH) { +// TODO(estade): if possible, port these 2 tests. +#if defined(OS_WIN) +TEST_F(ProcessUtilTest, EnableLFH) { ASSERT_TRUE(process_util::EnableLowFragmentationHeap()); if (IsDebuggerPresent()) { // Under these conditions, LFH can't be enabled. There's no point to test @@ -38,7 +63,7 @@ TEST(ProcessUtilTest, EnableLFH) { } } -TEST(ProcessUtilTest, CalcFreeMemory) { +TEST_F(ProcessUtilTest, CalcFreeMemory) { process_util::ProcessMetrics* metrics = process_util::ProcessMetrics::CreateProcessMetrics(::GetCurrentProcess()); ASSERT_TRUE(NULL != metrics); @@ -72,4 +97,5 @@ TEST(ProcessUtilTest, CalcFreeMemory) { delete[] alloc; delete metrics; } +#endif // defined(OS_WIN) diff --git a/base/process_util_win.cc b/base/process_util_win.cc index b464336..b13cbea 100644 --- a/base/process_util_win.cc +++ b/base/process_util_win.cc @@ -144,6 +144,12 @@ bool LaunchApp(const std::wstring& cmdline, return true; } +bool LaunchApp(CommandLine& cl, + bool wait, bool start_hidden, ProcessHandle* process_handle) { + return LaunchApp(cl.command_line_string(), wait, + start_hidden, process_handle); +} + // Attempts to kill the process identified by the given process // entry structure, giving it the specified exit code. // Returns true if this is successful, false otherwise. @@ -316,6 +322,12 @@ bool WaitForProcessesToExit(const std::wstring& executable_name, return result; } +bool WaitForSingleProcess(ProcessHandle handle, int wait_milliseconds) { + bool retval = WaitForSingleObject(handle, wait_milliseconds) == WAIT_OBJECT_0; + CloseHandle(handle); + return retval; +} + bool CleanupProcesses(const std::wstring& executable_name, int wait_milliseconds, int exit_code, @@ -328,7 +340,6 @@ bool CleanupProcesses(const std::wstring& executable_name, return exited_cleanly; } - /////////////////////////////////////////////////////////////////////////////// // ProcesMetrics diff --git a/base/stats_table_unittest.cc b/base/stats_table_unittest.cc index 649fd07..7d5f015 100644 --- a/base/stats_table_unittest.cc +++ b/base/stats_table_unittest.cc @@ -2,15 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include <process.h> -#include <windows.h> - #include "base/multiprocess_test.h" +#include "base/platform_thread.h" #include "base/stats_table.h" #include "base/stats_counters.h" #include "base/string_util.h" #include "testing/gtest/include/gtest/gtest.h" +#if defined(OS_WIN) +#include <process.h> +#include <windows.h> +#endif + namespace { class StatsTableTest : public MultiProcessTest { }; @@ -61,6 +64,8 @@ const std::wstring kCounterMixed = L"CounterMixed"; // The number of thread loops that we will do. const int kThreadLoops = 1000; +// TODO(estade): port this test +#if defined(OS_WIN) unsigned __stdcall StatsTableMultipleThreadMain(void* param) { // Each thread will open the shared memory and set counters // concurrently in a loop. We'll use some pauses to @@ -81,10 +86,11 @@ unsigned __stdcall StatsTableMultipleThreadMain(void* param) { mixed_counter.Decrement(); else mixed_counter.Increment(); - Sleep(index % 10); // short wait + PlatformThread::Sleep(index % 10); // short wait } return 0; } + // Create a few threads and have them poke on their counters. TEST_F(StatsTableTest, MultipleThreads) { // Create a stats table. @@ -140,10 +146,11 @@ TEST_F(StatsTableTest, MultipleThreads) { table.GetCounterValue(name)); EXPECT_EQ(0, table.CountThreadsRegistered()); } +#endif // defined(OS_WIN) const std::wstring kTableName = L"MultipleProcessStatTable"; -extern "C" int __declspec(dllexport) ChildProcessMain() { +extern "C" int DYNAMIC_EXPORT StatsTableMultipleProcessMain() { // Each process will open the shared memory and set counters // concurrently in a loop. We'll use some pauses to // mixup the scheduling. @@ -159,38 +166,38 @@ extern "C" int __declspec(dllexport) ChildProcessMain() { lucky13_counter.Set(1313); increment_counter.Increment(); decrement_counter.Decrement(); - Sleep(index % 10); // short wait + PlatformThread::Sleep(index % 10); // short wait } return 0; } -// Create a few threads and have them poke on their counters. +// Create a few processes and have them poke on their counters. TEST_F(StatsTableTest, MultipleProcesses) { // Create a stats table. const std::wstring kTableName = L"MultipleProcessStatTable"; - const int kMaxThreads = 20; + const int kMaxProcs = 20; const int kMaxCounter = 5; - StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable table(kTableName, kMaxProcs, kMaxCounter); StatsTable::set_current(&table); EXPECT_EQ(0, table.CountThreadsRegistered()); - // Spin up a set of threads to go bang on the various counters. - // After we join the threads, we'll make sure the counters + // Spin up a set of processes to go bang on the various counters. + // After we join the processes, we'll make sure the counters // contain the values we expected. - HANDLE threads[kMaxThreads]; + ProcessHandle procs[kMaxProcs]; // Spawn the processes. - for (int16 index = 0; index < kMaxThreads; index++) { - threads[index] = this->SpawnChild(L"ChildProcessMain"); - EXPECT_NE((HANDLE)NULL, threads[index]); + for (int16 index = 0; index < kMaxProcs; index++) { + procs[index] = this->SpawnChild(L"StatsTableMultipleProcessMain"); + EXPECT_NE(static_cast<ProcessHandle>(NULL), procs[index]); } - // Wait for the threads to finish. - for (int index = 0; index < kMaxThreads; index++) { - DWORD rv = WaitForSingleObject(threads[index], 60 * 1000); - EXPECT_EQ(rv, WAIT_OBJECT_0); // verify all threads finished + // Wait for the processes to finish. + for (int index = 0; index < kMaxProcs; index++) { + EXPECT_TRUE(process_util::WaitForSingleProcess(procs[index], 60 * 1000)); } + StatsCounter zero_counter(kCounterZero); StatsCounter lucky13_counter(kCounter1313); StatsCounter increment_counter(kCounterIncrement); @@ -201,13 +208,13 @@ TEST_F(StatsTableTest, MultipleProcesses) { name = L"c:" + kCounterZero; EXPECT_EQ(0, table.GetCounterValue(name)); name = L"c:" + kCounter1313; - EXPECT_EQ(1313 * kMaxThreads, + EXPECT_EQ(1313 * kMaxProcs, table.GetCounterValue(name)); name = L"c:" + kCounterIncrement; - EXPECT_EQ(kMaxThreads * kThreadLoops, + EXPECT_EQ(kMaxProcs * kThreadLoops, table.GetCounterValue(name)); name = L"c:" + kCounterDecrement; - EXPECT_EQ(-kMaxThreads * kThreadLoops, + EXPECT_EQ(-kMaxProcs * kThreadLoops, table.GetCounterValue(name)); EXPECT_EQ(0, table.CountThreadsRegistered()); } @@ -290,13 +297,13 @@ TEST_F(StatsTableTest, StatsCounterTimer) { // Do some timing. bar.Start(); - Sleep(500); + PlatformThread::Sleep(500); bar.Stop(); EXPECT_LE(500, table.GetCounterValue(L"t:bar")); // Verify that timing again is additive. bar.Start(); - Sleep(500); + PlatformThread::Sleep(500); bar.Stop(); EXPECT_LE(1000, table.GetCounterValue(L"t:bar")); } @@ -319,14 +326,14 @@ TEST_F(StatsTableTest, StatsRate) { // Do some timing. baz.Start(); - Sleep(500); + PlatformThread::Sleep(500); baz.Stop(); EXPECT_EQ(1, table.GetCounterValue(L"c:baz")); EXPECT_LE(500, table.GetCounterValue(L"t:baz")); // Verify that timing again is additive. baz.Start(); - Sleep(500); + PlatformThread::Sleep(500); baz.Stop(); EXPECT_EQ(2, table.GetCounterValue(L"c:baz")); EXPECT_LE(1000, table.GetCounterValue(L"t:baz")); @@ -353,7 +360,7 @@ TEST_F(StatsTableTest, StatsScope) { { StatsScope<StatsCounterTimer> timer(foo); StatsScope<StatsRate> timer2(bar); - Sleep(500); + PlatformThread::Sleep(500); } EXPECT_LE(500, table.GetCounterValue(L"t:foo")); EXPECT_LE(500, table.GetCounterValue(L"t:bar")); @@ -363,7 +370,7 @@ TEST_F(StatsTableTest, StatsScope) { { StatsScope<StatsCounterTimer> timer(foo); StatsScope<StatsRate> timer2(bar); - Sleep(500); + PlatformThread::Sleep(500); } EXPECT_LE(1000, table.GetCounterValue(L"t:foo")); EXPECT_LE(1000, table.GetCounterValue(L"t:bar")); diff --git a/base/test_suite.h b/base/test_suite.h index 6b6dfd5..9104a54 100644 --- a/base/test_suite.h +++ b/base/test_suite.h @@ -14,12 +14,14 @@ #include "base/debug_on_start.h" #include "base/icu_util.h" #include "base/logging.h" +#include "base/multiprocess_test.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(OS_WIN) #include <windows.h> #include "base/multiprocess_test.h" #elif defined(OS_LINUX) +#include <dlfcn.h> #include <gtk/gtk.h> #endif @@ -37,25 +39,31 @@ class TestSuite { int Run() { Initialize(); - -#if defined(OS_WIN) - // Check to see if we are being run as a client process. +#if defined(OS_WIN) || defined(OS_LINUX) std::wstring client_func = CommandLine().GetSwitchValue(kRunClientProcess); + // Check to see if we are being run as a client process. if (!client_func.empty()) { // Convert our function name to a usable string for GetProcAddress. std::string func_name(client_func.begin(), client_func.end()); +#if defined(OS_WIN) // Get our module handle and search for an exported function // which we can use as our client main. MultiProcessTest::ChildFunctionPtr func = reinterpret_cast<MultiProcessTest::ChildFunctionPtr>( GetProcAddress(GetModuleHandle(NULL), func_name.c_str())); +#elif defined(OS_LINUX) + void* exobj = dlopen(0, RTLD_LAZY); + MultiProcessTest::ChildFunctionPtr func = + reinterpret_cast<MultiProcessTest::ChildFunctionPtr>( + dlsym(exobj, func_name.c_str())); +#endif // defined(OS_LINUX) + if (func) - return func(); + return (*func)(); return -1; } -#endif - +#endif // defined(OS_WIN) || defined(OS_LINUX) int result = RUN_ALL_TESTS(); Shutdown(); |