> -----Original Message-----
> From: Martin Sebor [mailto:[EMAIL PROTECTED]
> Sent: Friday, July 28, 2006 4:02 AM
> To: [email protected]
> Subject: Re: testsuite process helpers (was: RE: string
> methods thread safety)
[...]
Martin, I have been updated the files rw_process.h, process.cpp and
0.process.cpp. The new files are attached.
ChangeLog:
* rw_process.h (rw_pid_t): The type long changed to intptr_t
(on Windows) and pid_t (on other platforms).
(rw_process_error_report_mode): New variable to enable/disable
rw_error() outputs within defined functions.
(rw_wait_pid): Added the timeout parameter.
(rw_process_kill): New function to kill the specified process.
* process.cpp: Ditto.
* 0.process.cpp: New test exercising the rw_process_create(),
rw_process_kill() and rw_waitpid() functions.
> I think it's important to exercise this functionality. We'll
> need to come up with a way to silence the errors in these
> tests. Maybe via some global variable or something like that.
I added the extern variable to the rw_process.h to handle this.
// if == 0 - report about errors using rw_error() (default)
// if != 0 - do not report about errors
_TEST_EXPORT extern int
rw_process_error_report_mode;
Maybe will be better to implement a function
rw_set_process_error_report_mode() which sets a new value and returns
the previous one?
> There is another problem with the test: we cannot assume that
> 1234 (or any other number we pick out of a hat) is an invalid
> process id.
To resolve this I used the pid from succeeded test of the
rw_process_create() instead of 1234.
> If there is a process with that pid our test might never
> finish (or fail with a false negative). Which brings up
> another point: we need to be able to kill the (child) process
> in case it hangs, and we need to exercise the ability to
> correctly report the signal the child process exited with, or
> more generally, its exit status.
To resolve this I added the additional parameter --timeout for the
test (the default timeout is 5 seconds).
> Also, as a general comment on the test, it helps to give each
> test function a descriptive name (rather than test1, test2,
> etc. It's also nice to print out some information (using
> rw_info()) about what's being tested.
Done.
Farid.
/************************************************************************
*
* 0.process.cpp - test exercising the rw_process_create(),
* rw_process_kill() and rw_waitpid() functions
*
* $Id: 0.process.cpp $
*
************************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you 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 <string.h> // for strcmp()
#include <errno.h> // for errno
#include <rw_process.h> // for rw_process_create(), rw_waitpid()
#include <driver.h> // for rw_test()
static int _rw_child = 0;
static int _rw_timeout = 5;
static char arg1[] = "--child=1";
static char arg2[] = "--no-stdout";
static char* args [] = { 0, arg1, arg2, 0 };
static const int nargs = sizeof (args) / sizeof (*args) - 1;
static rw_pid_t _rw_pid = -1;
static int join_test (rw_pid_t pid, bool should_hang)
{
int result = 0;
const rw_pid_t ret = rw_waitpid (pid, &result, _rw_timeout);
rw_assert (-1 != ret, __FILE__, __LINE__,
"rw_waitpid() failed, errno = %{#m}");
if (-1 == ret)
return 1;
if (0 == ret) {
// time_out elapsed, kill the process
rw_process_kill (pid);
// save error output mode
const int prev_mode = rw_process_error_report_mode;
// disable rw_error() within functions from rw_process.h
rw_process_error_report_mode = 1;
// remove the zombie process if present
rw_waitpid (pid, 0);
// restore error output mode
rw_process_error_report_mode = prev_mode;
rw_assert (should_hang, __FILE__, __LINE__,
"The child process unexpectedly deadlocked");
return should_hang ? 0 : 1;
}
rw_assert (!should_hang, __FILE__, __LINE__,
"Expected the deadlocked process, but process exited "
"with code: %d",
result);
if (!should_hang)
rw_assert (0 == result, __FILE__, __LINE__,
"Process exit code: expected 0, got %d", result);
return result;
}
static int test_process_create1 ()
{
rw_info (0, 0, 0,
"Exercising the rw_process_create "
"(const char*, char* const []) overload");
const rw_pid_t pid = rw_process_create (args [0], args);
rw_assert (-1 != pid, __FILE__, __LINE__,
"rw_process_create() failed, errno = %{#m}");
if (-1 == pid)
return 1;
// save the pid for test exercising rw_waitpid() fail
if (-1 == _rw_pid)
_rw_pid = pid;
return join_test (pid, false);
}
static int test_process_create2 ()
{
rw_info (0, 0, 0,
"Exercising the rw_process_create (const char*, ...) overload");
const rw_pid_t pid = rw_process_create ("\"%s\" %s %s",
args[0], args[1], args[2]);
rw_assert (-1 != pid, __FILE__, __LINE__,
"rw_process_create() failed, errno = %{#m}");
if (-1 == pid)
return 1;
// save the pid for test exercising rw_waitpid() fail
if (-1 == _rw_pid)
_rw_pid = pid;
return join_test (pid, false);
}
static int test_process_deadlocked ()
{
rw_info (0, 0, 0,
"Exercising the rw_waitpid() with timeout and deadlocked process");
const rw_pid_t pid = rw_process_create ("\"%s\" --child=2 %s",
args[0], args[2]);
rw_assert (-1 != pid, __FILE__, __LINE__,
"rw_process_create() failed, errno = %{#m}");
return -1 == pid ? 1 : join_test (pid, true);
}
static int test_process_create_fail ()
{
rw_info (0, 0, 0,
"Exercising the rw_process_create() behavior "
"when invalid path specified");
const rw_pid_t pid = rw_process_create ("/\\/\\/\\", args);
rw_assert (-1 == pid, __FILE__, __LINE__,
"rw_process_create returns %ld, expected -1",
long (pid));
if (-1 != pid) {
rw_waitpid (pid, 0);
return 1;
}
rw_assert (ENOENT == errno, __FILE__, __LINE__,
"errno: expected ENOENT, got %{#m}");
return ENOENT == errno ? 0 : 1;
}
static int test_waitpid_fail ()
{
if (-1 == _rw_pid) {
rw_info (0, 0, 0,
"The test, exercising the rw_waitpid() behavior "
"when invalid pid specified is disabled");
return 0;
}
rw_info (0, 0, 0,
"Exercising the rw_waitpid() behavior "
"when invalid pid specified");
const rw_pid_t pid = rw_waitpid (_rw_pid, 0);
rw_assert (-1 == pid, __FILE__, __LINE__,
"rw_waitpid returns %ld, expected -1",
long (pid));
if (-1 != pid)
return 1;
rw_assert (ECHILD == errno, __FILE__, __LINE__,
"errno: expected ECHILD, got %{#m}");
return ECHILD == errno ? 0 : 1;
}
static int test_process_kill_fail ()
{
if (-1 == _rw_pid) {
rw_info (0, 0, 0,
"The test, exercising the rw_process_kill() behavior "
"when invalid pid specified is disabled");
return 0;
}
rw_info (0, 0, 0,
"Exercising the rw_process_kill() behavior "
"when invalid pid specified");
const int res = rw_process_kill (_rw_pid);
rw_assert (-1 == res, __FILE__, __LINE__,
"rw_process_kill returns %ld, expected -1",
long (res));
if (-1 != res)
return 1;
rw_assert (ESRCH == errno, __FILE__, __LINE__,
"errno: expected ESRCH, got %{#m}");
return ESRCH == errno ? 0 : 1;
}
static int
run_test (int argc, char** argv)
{
if (_rw_child) {
if (2 == _rw_child) {
// simulate the deadlock
while (true) ;
}
// compare number of parameters with expected
if (nargs != argc)
return nargs;
// compare the parameters with expected
for (int i = 1; i < argc; ++i) {
if (0 != strcmp (argv [i], args [i]))
return i;
}
return 0;
}
args [0] = argv [0];
int fails = 0;
if (test_process_create1 ())
++fails;
if (test_process_create2 ())
++fails;
if (test_process_deadlocked ())
++fails;
// disable rw_error() within functions from rw_process.h
rw_process_error_report_mode = 1;
if (test_process_create_fail ())
++fails;
if (test_waitpid_fail ())
++fails;
if (test_process_kill_fail ())
++fails;
return fails;
}
int main (int argc, char *argv[])
{
return rw_test (argc, argv, __FILE__,
"0.process",
"", run_test,
"|-child#0 "
"|-timeout#",
&_rw_child,
&_rw_timeout,
0 /*sentinel*/);
}
/************************************************************************
*
* process.cpp - definitions of testsuite process helpers
*
* $Id: process.cpp $
*
************************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you 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.
*
**************************************************************************/
// expand _TEST_EXPORT macros
#define _RWSTD_TEST_SRC
#include <rw_process.h>
#include <stddef.h> // for size_t
#include <stdarg.h> // for va_copy, va_list, ...
#include <stdlib.h> // for free(), exit()
#include <string.h> // for strchr()
#include <ctype.h> // for isspace()
#include <errno.h> // for errno
#include <driver.h> // for rw_note(), ...
#include <rw_printf.h> // for rw_fprintf()
#ifndef ENOMEM
# define ENOMEM 12 // e.g., Linux, Solaris
#endif
#ifdef __CYGWIN__
// use the Windows API on Cygwin
# define _WIN32
#endif
/**************************************************************************/
_TEST_EXPORT int
rw_process_error_report_mode = 0;
#define RW_ERROR(arg) \
if (0 == rw_process_error_report_mode) \
rw_error arg
/**************************************************************************/
_TEST_EXPORT int
rw_vasnprintf (char**, size_t*, const char*, va_list);
/**************************************************************************/
#if defined (_WIN32) || defined (_WIN64)
# include <process.h> // for spawnvp(), cwait()
# include <windows.h> // for WaitForSingleObject(), ...
extern "C" void _dosmaperr(unsigned long);
#else
# include <sys/types.h>
# include <sys/wait.h> // for waitpid()
# include <unistd.h> // for fork(), execv(), access()
# include <setjmp.h> // for setjmp, longjmp
# include <signal.h> // for signal
#endif // _WIN{32,64}
/**************************************************************************/
static int
_rw_vsystem (const char *cmd, va_list va)
{
RW_ASSERT (0 != cmd);
char buffer [256];
char *buf = buffer;
size_t bufsize = sizeof buffer;
rw_vasnprintf (&buf, &bufsize, cmd, va);
rw_note (0, "file:" __FILE__, __LINE__, "executing \"%s\"", buf);
// avoid using const in order to prevent gcc warning on Linux
// issued for WIFSIGNALED() et al: cast from `const int*' to
// `int*' discards qualifiers from pointer target type:
// see http://sourceware.org/bugzilla/show_bug.cgi?id=1392
/* const */ int ret = system (buf);
if (ret) {
#if !defined (_WIN32) && !defined (_WIN64)
if (-1 == ret) {
// system() failed, e.g., because fork() failed
RW_ERROR ((0, __FILE__, __LINE__,
"system (\"%s\") failed: errno = %{#m} (%{m})",
buf));
}
else if (WIFSIGNALED (ret)) {
// command exited with a signal
const int signo = WTERMSIG (ret);
RW_ERROR ((0, __FILE__, __LINE__,
"the command \"%s\" exited with signal %d (%{K})",
buf, signo, signo));
}
else {
// command exited with a non-zero status
const int status = WEXITSTATUS (ret);
RW_ERROR ((0, __FILE__, __LINE__,
"the command \"%s\" exited with status %d",
buf, status));
}
#else // if defined (_WIN32) || defined (_WIN64)
// FIXME: make this more descriptive
RW_ERROR ((0, __FILE__, __LINE__,
"the command \"%s\" failed with code %d",
buf, ret));
#endif // _WIN32
}
if (buf != buffer)
free (buf);
return ret;
}
/**************************************************************************/
_TEST_EXPORT int
rw_system (const char *cmd, ...)
{
va_list va;
va_start (va, cmd);
const int ret = _rw_vsystem (cmd, va);
va_end (va);
return ret;
}
/**************************************************************************/
// splits command line to the array of parameters
// note: modifies the cmd string
// returns the number of parameters in cmd
// if argv != 0 fills argv up to size elements
static size_t
_rw_split_cmd (char* cmd, char** argv, size_t size)
{
RW_ASSERT (0 != cmd);
size_t ret = 0;
for (char* end = cmd + strlen (cmd); cmd != end; /*do nothing*/) {
// skip the leading spaces
while (isspace (*cmd))
++cmd;
if (end == cmd)
break;
if (argv && ret < size)
argv [ret] = cmd;
++ret;
if ('\'' == *cmd || '\"' == *cmd) {
char* const cmd1 = cmd + 1;
// search the closing quote
if (char* pos = strchr (cmd1, *cmd)) {
// found, remove the quotes
// remove the opening quote
memmove (cmd, cmd1, pos - cmd1);
// remove the closing quote
cmd = pos - 1;
memmove (cmd, pos + 1, end - pos);
end -= 2;
} else {
// not found
break;
}
}
// search the space
while (*cmd && !isspace (*cmd))
++cmd;
if (cmd != end)
// found, replace to '\0'
*cmd++ = '\0';
}
return ret;
}
/**************************************************************************/
static rw_pid_t
_rw_vprocess_create (const char* cmd, va_list va)
{
RW_ASSERT (0 != cmd);
char buffer [256];
char *buf = buffer;
size_t bufsize = sizeof (buffer);
rw_vasnprintf (&buf, &bufsize, cmd, va);
const size_t MAX_PARAMS = 63;
char* argv [MAX_PARAMS + 1] = { 0 };
rw_pid_t ret;
size_t argc = _rw_split_cmd (buf, argv, MAX_PARAMS);
if (0 < argc && MAX_PARAMS >= argc)
ret = rw_process_create (argv [0], argv);
else {
ret = -1;
errno = ENOMEM;
}
if (buf != buffer)
free (buf);
return ret;
}
/**************************************************************************/
_TEST_EXPORT rw_pid_t
rw_process_create (const char* cmd, ...)
{
va_list va;
va_start (va, cmd);
const rw_pid_t ret = _rw_vprocess_create (cmd, va);
va_end (va);
return ret;
}
/**************************************************************************/
_TEST_EXPORT rw_pid_t
rw_process_create (const char* path, char* const argv[])
{
#if defined (_WIN32) || defined (_WIN64)
const rw_pid_t child_pid = spawnvp (P_NOWAIT, path, argv);
if (-1 == child_pid)
RW_ERROR ((0, __FILE__, __LINE__,
"spawnp (P_NOWAIT, %#s, %{As}) failed: errno = %{#m} (%{m})",
path, argv));
#else
rw_pid_t child_pid = -1;
const int res = access (path, X_OK);
if (0 == res) {
child_pid = fork ();
if (0 == child_pid) {
// the child process
execvp (path, argv);
// the execvp returns only if an error occurs
rw_fprintf (rw_stderr, "%s:%d execvp (%#s, %{As}) failed: "
"errno = %{#m} (%{m})\n", path, argv);
exit (1);
}
else if (-1 == child_pid)
RW_ERROR ((0, __FILE__, __LINE__,
"fork () failed: errno = %{#m} (%{m})"));
}
else
RW_ERROR ((0, __FILE__, __LINE__,
"access (%#s, X_OK) failed: errno = %{#m} (%{m})",
path));
#endif // #if defined (_WIN32) || defined (_WIN64)
return child_pid;
}
/**************************************************************************/
#if !defined (_WIN32) && !defined (_WIN64)
#ifdef sigsetjmp
#define SETJMP(env) sigsetjmp (env, 1)
#else
#define SETJMP(env) setjmp (env)
#endif
static jmp_buf mark;
extern "C" {
static void
sig_handler (int)
{
longjmp (mark, -1);
}
}
#endif // #if !defined (_WIN32) && !defined (_WIN64)
_TEST_EXPORT rw_pid_t
rw_waitpid (rw_pid_t pid, int* result, int timeout/* = -1*/)
{
#if defined (_WIN32) || defined (_WIN64)
/* Explicitly check for process_id being -1 or -2. In Windows NT,
* -1 is a handle on the current process, -2 is a handle on the
* current thread, and it is perfectly legal to to wait (forever)
* on either */
if (-1 == pid || -2 == pid) {
errno = ECHILD;
return -1;
}
rw_pid_t ret = pid;
/* wait for child process, then fetch its exit code */
const HANDLE hProcess = HANDLE (pid);
const DWORD dwMilliseconds = 0 > timeout ? INFINITE : DWORD (timeout *
1000);
const DWORD dwRet = WaitForSingleObject (hProcess, dwMilliseconds);
DWORD dwErr = ERROR_SUCCESS;
if (WAIT_OBJECT_0 == dwRet) {
DWORD dwExitCode;
if (GetExitCodeProcess (hProcess, &dwExitCode)) {
CloseHandle (hProcess);
if (dwExitCode)
RW_ERROR ((0, __FILE__, __LINE__,
"the process (pid=%ld) exited with return code %d",
long (pid), int (dwExitCode)));
if (result)
*result = int (dwExitCode);
}
else {
dwErr = GetLastError ();
RW_ERROR ((0, __FILE__, __LINE__,
"GetExitCodeProcess (%li, %#p) failed: "
"GetLastError() = %zu",
pid, &dwExitCode, size_t (dwErr)));
}
}
else if (WAIT_FAILED == dwRet) {
dwErr = GetLastError ();
RW_ERROR ((0, __FILE__, __LINE__,
"WaitForSingleObject (%li, %{?}%s%{:}%zu%{;}) failed: "
"GetLastError() = %zu",
pid, INFINITE == dwMilliseconds, "INFINITE",
size_t (dwMilliseconds), size_t (dwErr)));
}
else {
// time-out elapsed
RW_ASSERT (WAIT_TIMEOUT == dwRet);
return 0;
}
if (ERROR_SUCCESS != dwErr) {
ret = -1;
if (ERROR_INVALID_HANDLE == dwErr)
errno = ECHILD;
else
_dosmaperr (dwErr);
}
#else // #if !defined (_WIN32) || defined (_WIN64)
void (* old_handler)(int);
if (0 < timeout) {
old_handler = signal (SIGALRM, sig_handler);
alarm (timeout);
}
int status = 0;
const int options = timeout ? 0 : WNOHANG;
rw_pid_t ret = 0;
if (0 >= timeout || 0 == SETJMP (mark))
ret = waitpid (pid, &status, options);
if (0 < timeout) {
// restore the previous handler
signal (SIGALRM, old_handler);
if (0 == ret)
return 0; // the time-out interval elapsed
}
if (ret == pid) {
if (WIFSIGNALED (status)) {
// process exited with a signal
const int signo = WTERMSIG (status);
RW_ERROR ((0, __FILE__, __LINE__,
"the process (pid=%ld) exited with signal %d (%{K})",
long (ret), signo, signo));
if (result)
*result = signo;
}
else if (WIFEXITED (status)) {
// process exited with a status
const int retcode = WEXITSTATUS (status);
if (retcode)
RW_ERROR ((0, __FILE__, __LINE__,
"the process (pid=%ld) exited with return code %d",
long (ret), retcode));
if (result)
*result = retcode;
}
else if (result)
*result = -1;
}
if (-1 == ret)
RW_ERROR ((0, __FILE__, __LINE__,
"waitpid (%li, %#p, %i) failed: errno = %{#m} (%{m})",
pid, &status, options));
#endif // #if defined (_WIN32) || defined (_WIN64)
return ret;
}
_TEST_EXPORT int
rw_process_kill (rw_pid_t pid)
{
#if defined (_WIN32) || defined (_WIN64)
int ret = 0;
if (!TerminateProcess (HANDLE (pid), DWORD (-1))) {
ret = -1;
const DWORD dwErr = GetLastError ();
if (ERROR_INVALID_HANDLE == dwErr)
errno = ESRCH;
else if (ERROR_ACCESS_DENIED == dwErr)
errno = EPERM;
else
_dosmaperr (dwErr);
RW_ERROR ((0, __FILE__, __LINE__,
"TerminateProcess (%li, -1) failed: GetLastError() = %zu",
pid, size_t (dwErr)));
}
#else // #if !defined (_WIN32) || defined (_WIN64)
const int ret = kill (pid, SIGKILL);
if (-1 == ret)
RW_ERROR ((0, __FILE__, __LINE__,
"kill (%li, SIGKILL) failed: errno = %{#m} (%{m})",
pid));
#endif // #if defined (_WIN32) || defined (_WIN64)
return ret;
}
/************************************************************************
*
* rw_process.h - declarations of testsuite process helpers
*
* $Id: rw_process.h $
*
************************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you 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.
*
**************************************************************************/
#ifndef RW_PROCESS_H_INCLUDED
#define RW_PROCESS_H_INCLUDED
#include <testdefs.h>
#if defined (_WIN32) || defined (_WIN64) || defined (__CYGWIN__)
#include <stddef.h> // for intptr_t
typedef intptr_t rw_pid_t;
#else
#include <sys/types.h>
typedef pid_t rw_pid_t;
#endif
// if == 0 - report about errors using rw_error() (default)
// if != 0 - do not report about errors
_TEST_EXPORT extern int
rw_process_error_report_mode;
_TEST_EXPORT int
rw_system (const char*, ...);
// returns pid of the created process or -1 on error
// (in which case errno is set to an appropriate value)
_TEST_EXPORT rw_pid_t
rw_process_create (const char*, ...);
// note: argv[0] should be equal to path
// returns pid of the created process or -1 on error
// (in which case errno is set to an appropriate value)
_TEST_EXPORT rw_pid_t
rw_process_create (const char* /*path*/, char* const /*argv*/[]);
// result is a pointer to a buffer where the result code
// of the specified process will be stored, or NULL
// the function suspends execution of the current process
// until a child has exited or specified timeout is reached
// returns:
// pid of the specified process
// 0 when process still active
// -1 on error (in which case errno is set to an appropriate value)
// timeout is timeout interval in seconds.
// if timeout < 0 the function returns if the interval elapses
// if timeout == 0 the function returns immediately
// if timeout < 0 the function's time-out interval never elapses
// Errors:
// ECHILD: no specified process exists
_TEST_EXPORT rw_pid_t
rw_waitpid (rw_pid_t /*pid*/, int* /*result*/, int /*timeout*/ = -1);
// returns:
// 0 when process terminated successfully
// -1 on error (in which case errno is set to an appropriate value)
// Errors:
// ESRCH: the pid does not exist
// EPERM: the calling process does not have permission
// to terminate the specified process
_TEST_EXPORT int
rw_process_kill (rw_pid_t);
#endif // RW_PROCESS_H_INCLUDED