diff mbox series

[31/51] tests/qtest: Support libqtest to build and run on Windows

Message ID 20220824094029.1634519-32-bmeng.cn@gmail.com (mailing list archive)
State New, archived
Headers show
Series tests/qtest: Enable running qtest on Windows | expand

Commit Message

Bin Meng Aug. 24, 2022, 9:40 a.m. UTC
From: Bin Meng <bin.meng@windriver.com>

At present the libqtest codes were written to depend on several
POSIX APIs, including fork(), kill() and waitpid(). Unfortunately
these APIs are not available on Windows.

This commit implements the corresponding functionalities using
win32 native APIs. With this change, all qtest cases can build
successfully on a Windows host, and we can start qtest testing
on Windows now.

Signed-off-by: Xuzhou Cheng <xuzhou.cheng@windriver.com>
Signed-off-by: Bin Meng <bin.meng@windriver.com>
---

 tests/qtest/libqtest.c  | 101 +++++++++++++++++++++++++++++++++++++++-
 tests/qtest/meson.build |   5 +-
 2 files changed, 101 insertions(+), 5 deletions(-)

Comments

Marc-André Lureau Aug. 31, 2022, 4:28 p.m. UTC | #1
Hi

On Wed, Aug 24, 2022 at 2:46 PM Bin Meng <bmeng.cn@gmail.com> wrote:

> From: Bin Meng <bin.meng@windriver.com>
>
> At present the libqtest codes were written to depend on several
> POSIX APIs, including fork(), kill() and waitpid(). Unfortunately
> these APIs are not available on Windows.
>
> This commit implements the corresponding functionalities using
> win32 native APIs. With this change, all qtest cases can build
> successfully on a Windows host, and we can start qtest testing
> on Windows now.
>
> Signed-off-by: Xuzhou Cheng <xuzhou.cheng@windriver.com>
> Signed-off-by: Bin Meng <bin.meng@windriver.com>
> ---
>
>  tests/qtest/libqtest.c  | 101 +++++++++++++++++++++++++++++++++++++++-
>  tests/qtest/meson.build |   5 +-
>  2 files changed, 101 insertions(+), 5 deletions(-)
>
> diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
> index 70d7578740..99e52ff571 100644
> --- a/tests/qtest/libqtest.c
> +++ b/tests/qtest/libqtest.c
> @@ -16,9 +16,11 @@
>
>  #include "qemu/osdep.h"
>
> +#ifndef _WIN32
>  #include <sys/socket.h>
>  #include <sys/wait.h>
>  #include <sys/un.h>
> +#endif /* _WIN32 */
>  #ifdef __linux__
>  #include <sys/prctl.h>
>  #endif /* __linux__ */
> @@ -27,6 +29,7 @@
>  #include "libqmp.h"
>  #include "qemu/ctype.h"
>  #include "qemu/cutils.h"
> +#include "qemu/sockets.h"
>  #include "qapi/qmp/qdict.h"
>  #include "qapi/qmp/qjson.h"
>  #include "qapi/qmp/qlist.h"
> @@ -35,6 +38,16 @@
>  #define MAX_IRQ 256
>  #define SOCKET_TIMEOUT 50
>
> +#ifndef _WIN32
> +# define CMD_EXEC   "exec "
> +# define DEV_STDERR "/dev/fd/2"
> +# define DEV_NULL   "/dev/null"
> +#else
> +# define CMD_EXEC   ""
> +# define DEV_STDERR "2"
> +# define DEV_NULL   "nul"
> +#endif
> +
>  typedef void (*QTestSendFn)(QTestState *s, const char *buf);
>  typedef void (*ExternalSendFn)(void *s, const char *buf);
>  typedef GString* (*QTestRecvFn)(QTestState *);
> @@ -68,6 +81,9 @@ struct QTestState
>  QTestState *global_qtest;
>
>  static GHookList abrt_hooks;
> +#ifdef _WIN32
> +typedef void (*sighandler_t)(int);
> +#endif
>  static sighandler_t sighandler_old;
>
>  static int qtest_query_target_endianness(QTestState *s);
> @@ -120,10 +136,18 @@ bool qtest_probe_child(QTestState *s)
>      pid_t pid = s->qemu_pid;
>
>      if (pid != -1) {
> +#ifndef _WIN32
>          pid = waitpid(pid, &s->wstatus, WNOHANG);
>          if (pid == 0) {
>              return true;
>          }
> +#else
> +        DWORD exit_code;
> +        GetExitCodeProcess((HANDLE)pid, &exit_code);
> +        if (exit_code == STILL_ACTIVE) {
> +            return true;
> +        }
> +#endif
>          s->qemu_pid = -1;
>      }
>      return false;
> @@ -137,13 +161,23 @@ void qtest_set_expected_status(QTestState *s, int
> status)
>  void qtest_kill_qemu(QTestState *s)
>  {
>      pid_t pid = s->qemu_pid;
> +#ifndef _WIN32
>      int wstatus;
> +#else
> +    DWORD ret, exit_code;
> +#endif
>
>      /* Skip wait if qtest_probe_child already reaped.  */
>      if (pid != -1) {
> +#ifndef _WIN32
>          kill(pid, SIGTERM);
>          TFR(pid = waitpid(s->qemu_pid, &s->wstatus, 0));
>          assert(pid == s->qemu_pid);
> +#else
> +        TerminateProcess((HANDLE)pid, s->expected_status);
> +        ret = WaitForSingleObject((HANDLE)pid, INFINITE);
> +        assert(ret == WAIT_OBJECT_0);
> +#endif
>          s->qemu_pid = -1;
>      }
>
> @@ -151,6 +185,7 @@ void qtest_kill_qemu(QTestState *s)
>       * Check whether qemu exited with expected exit status; anything else
> is
>       * fishy and should be logged with as much detail as possible.
>       */
> +#ifndef _WIN32
>      wstatus = s->wstatus;
>      if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) != s->expected_status)
> {
>          fprintf(stderr, "%s:%d: kill_qemu() tried to terminate QEMU "
> @@ -167,6 +202,16 @@ void qtest_kill_qemu(QTestState *s)
>                  __FILE__, __LINE__, sig, signame, dump);
>          abort();
>      }
> +#else
> +    GetExitCodeProcess((HANDLE)pid, &exit_code);
> +    CloseHandle((HANDLE)pid);
> +    if (exit_code != s->expected_status) {
> +        fprintf(stderr, "%s:%d: kill_qemu() tried to terminate QEMU "
> +                "process but encountered exit status %ld (expected %d)\n",
> +                __FILE__, __LINE__, exit_code, s->expected_status);
> +        abort();
> +    }
> +#endif
>  }
>
>  static void kill_qemu_hook_func(void *s)
> @@ -245,6 +290,38 @@ static const char *qtest_qemu_binary(void)
>      return qemu_bin;
>  }
>
> +#ifdef _WIN32
> +static pid_t qtest_create_process(char *cmd)
> +{
> +    STARTUPINFO si;
> +    PROCESS_INFORMATION pi;
> +    BOOL ret;
> +
> +    ZeroMemory(&si, sizeof(si));
> +    si.cb = sizeof(si);
> +    ZeroMemory(&pi, sizeof(pi));
> +
> +    ret = CreateProcess(NULL,   /* module name */
> +                        cmd,    /* command line */
> +                        NULL,   /* process handle not inheritable */
> +                        NULL,   /* thread handle not inheritable */
> +                        FALSE,  /* set handle inheritance to FALSE */
> +                        0,      /* No creation flags */
> +                        NULL,   /* use parent's environment block */
> +                        NULL,   /* use parent's starting directory */
> +                        &si,    /* pointer to STARTUPINFO structure */
> +                        &pi     /* pointer to PROCESS_INFORMATION
> structure */
> +                        );
> +    if (ret == 0) {
> +        fprintf(stderr, "%s:%d: unable to create a new process (%s)\n",
> +                __FILE__, __LINE__, strerror(GetLastError()));
> +        abort();
> +    }
> +
> +    return (pid_t)pi.hProcess;
> +}
> +#endif /* _WIN32 */
> +
>  QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
>  {
>      QTestState *s;
> @@ -272,6 +349,9 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
>      unlink(socket_path);
>      unlink(qmp_socket_path);
>
> +#ifdef _WIN32
> +    socket_init();
> +#endif
>

You can call this unconditionally, afaict


>      sock = init_socket(socket_path);
>      qmpsock = init_socket(qmp_socket_path);
>
> @@ -280,7 +360,7 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
>
>      qtest_add_abrt_handler(kill_qemu_hook_func, s);
>
> -    command = g_strdup_printf("exec %s %s"
> +    command = g_strdup_printf(CMD_EXEC "%s %s"
>                                "-qtest unix:%s "
>                                "-qtest-log %s "
>                                "-chardev socket,path=%s,id=char0 "
> @@ -289,7 +369,7 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
>                                "%s"
>                                " -accel qtest",
>                                qemu_binary, tracearg, socket_path,
> -                              getenv("QTEST_LOG") ? "/dev/fd/2" :
> "/dev/null",
> +                              getenv("QTEST_LOG") ? DEV_STDERR : DEV_NULL,
>                                qmp_socket_path,
>                                extra_args ?: "");
>
> @@ -298,6 +378,7 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
>      s->pending_events = NULL;
>      s->wstatus = 0;
>      s->expected_status = 0;
> +#ifndef _WIN32
>      s->qemu_pid = fork();
>      if (s->qemu_pid == 0) {
>  #ifdef __linux__
> @@ -320,6 +401,9 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
>          execlp("/bin/sh", "sh", "-c", command, NULL);
>          exit(1);
>      }
> +#else
> +    s->qemu_pid = qtest_create_process(command);
>

Why not replace the fork/exec with g_spawn_async() ?


> +#endif /* _WIN32 */
>
>      g_free(command);
>      s->fd = socket_accept(sock);
> @@ -338,9 +422,19 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
>          s->irq_level[i] = false;
>      }
>
> +    /*
> +     * Stopping QEMU for debugging is not supported on Windows.
> +     *
> +     * Using DebugActiveProcess() API can suspend the QEMU process,
> +     * but gdb cannot attach to the process. Using the undocumented
> +     * NtSuspendProcess() can suspend the QEMU process and gdb can
> +     * attach to the process, but gdb cannot resume it.
> +     */
> +#ifndef _WIN32
>      if (getenv("QTEST_STOP")) {
>          kill(s->qemu_pid, SIGSTOP);
>      }
> +#endif
>
>      /* ask endianness of the target */
>
> @@ -393,6 +487,9 @@ QTestState *qtest_init_with_serial(const char
> *extra_args, int *sock_fd)
>      g_assert_true(g_mkdtemp(sock_dir) != NULL);
>      sock_path = g_strdup_printf("%s/sock", sock_dir);
>
> +#ifdef _WIN32
> +    socket_init();
> +#endif
>

same


>      sock_fd_init = init_socket(sock_path);
>
>      qts = qtest_initf("-chardev socket,id=s0,path=%s -serial chardev:s0
> %s",
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 0291b3966c..6d469a1822 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -1,6 +1,5 @@
> -# All QTests for now are POSIX-only, but the dependencies are
> -# really in libqtest, not in the testcases themselves.
> -if not config_host.has_key('CONFIG_POSIX')
> +# Build all QTests for POSIX and Windows
> +if not config_host.has_key('CONFIG_POSIX') and not
> config_host.has_key('CONFIG_WIN32')
>    subdir_done()
>  endif
>
> --
> 2.34.1
>
>
>
lgtm otherwise
Marc-André Lureau Sept. 1, 2022, 11:38 a.m. UTC | #2
Hi

On Wed, Aug 24, 2022 at 2:46 PM Bin Meng <bmeng.cn@gmail.com> wrote:

> From: Bin Meng <bin.meng@windriver.com>
>
> At present the libqtest codes were written to depend on several
> POSIX APIs, including fork(), kill() and waitpid(). Unfortunately
> these APIs are not available on Windows.
>
> This commit implements the corresponding functionalities using
> win32 native APIs. With this change, all qtest cases can build
> successfully on a Windows host, and we can start qtest testing
> on Windows now.
>
> Signed-off-by: Xuzhou Cheng <xuzhou.cheng@windriver.com>
> Signed-off-by: Bin Meng <bin.meng@windriver.com>
> ---
>
>  tests/qtest/libqtest.c  | 101 +++++++++++++++++++++++++++++++++++++++-
>  tests/qtest/meson.build |   5 +-
>  2 files changed, 101 insertions(+), 5 deletions(-)
>
> diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
> index 70d7578740..99e52ff571 100644
> --- a/tests/qtest/libqtest.c
> +++ b/tests/qtest/libqtest.c
> @@ -16,9 +16,11 @@
>
>  #include "qemu/osdep.h"
>
> +#ifndef _WIN32
>  #include <sys/socket.h>
>  #include <sys/wait.h>
>  #include <sys/un.h>
> +#endif /* _WIN32 */
>  #ifdef __linux__
>  #include <sys/prctl.h>
>  #endif /* __linux__ */
> @@ -27,6 +29,7 @@
>  #include "libqmp.h"
>  #include "qemu/ctype.h"
>  #include "qemu/cutils.h"
> +#include "qemu/sockets.h"
>  #include "qapi/qmp/qdict.h"
>  #include "qapi/qmp/qjson.h"
>  #include "qapi/qmp/qlist.h"
> @@ -35,6 +38,16 @@
>  #define MAX_IRQ 256
>  #define SOCKET_TIMEOUT 50
>
> +#ifndef _WIN32
> +# define CMD_EXEC   "exec "
> +# define DEV_STDERR "/dev/fd/2"
> +# define DEV_NULL   "/dev/null"
> +#else
> +# define CMD_EXEC   ""
> +# define DEV_STDERR "2"
> +# define DEV_NULL   "nul"
> +#endif
> +
>  typedef void (*QTestSendFn)(QTestState *s, const char *buf);
>  typedef void (*ExternalSendFn)(void *s, const char *buf);
>  typedef GString* (*QTestRecvFn)(QTestState *);
> @@ -68,6 +81,9 @@ struct QTestState
>  QTestState *global_qtest;
>
>  static GHookList abrt_hooks;
> +#ifdef _WIN32
> +typedef void (*sighandler_t)(int);
> +#endif
>  static sighandler_t sighandler_old;
>
>  static int qtest_query_target_endianness(QTestState *s);
> @@ -120,10 +136,18 @@ bool qtest_probe_child(QTestState *s)
>      pid_t pid = s->qemu_pid;
>
>      if (pid != -1) {
> +#ifndef _WIN32
>          pid = waitpid(pid, &s->wstatus, WNOHANG);
>          if (pid == 0) {
>              return true;
>          }
> +#else
> +        DWORD exit_code;
> +        GetExitCodeProcess((HANDLE)pid, &exit_code);
> +        if (exit_code == STILL_ACTIVE) {
> +            return true;
> +        }
>

Missing a CloseHandle((HANDLE)pid) here.


> +#endif
>          s->qemu_pid = -1;
>      }
>      return false;
> @@ -137,13 +161,23 @@ void qtest_set_expected_status(QTestState *s, int
> status)
>  void qtest_kill_qemu(QTestState *s)
>  {
>      pid_t pid = s->qemu_pid;
> +#ifndef _WIN32
>      int wstatus;
> +#else
> +    DWORD ret, exit_code;
> +#endif
>
>      /* Skip wait if qtest_probe_child already reaped.  */
>      if (pid != -1) {
> +#ifndef _WIN32
>          kill(pid, SIGTERM);
>          TFR(pid = waitpid(s->qemu_pid, &s->wstatus, 0));
>          assert(pid == s->qemu_pid);
> +#else
> +        TerminateProcess((HANDLE)pid, s->expected_status);
> +        ret = WaitForSingleObject((HANDLE)pid, INFINITE);
> +        assert(ret == WAIT_OBJECT_0);
> +#endif
>          s->qemu_pid = -1;
>      }
>
> @@ -151,6 +185,7 @@ void qtest_kill_qemu(QTestState *s)
>       * Check whether qemu exited with expected exit status; anything else
> is
>       * fishy and should be logged with as much detail as possible.
>       */
> +#ifndef _WIN32
>      wstatus = s->wstatus;
>      if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) != s->expected_status)
> {
>          fprintf(stderr, "%s:%d: kill_qemu() tried to terminate QEMU "
> @@ -167,6 +202,16 @@ void qtest_kill_qemu(QTestState *s)
>                  __FILE__, __LINE__, sig, signame, dump);
>          abort();
>      }
> +#else
> +    GetExitCodeProcess((HANDLE)pid, &exit_code);
> +    CloseHandle((HANDLE)pid);
> +    if (exit_code != s->expected_status) {
> +        fprintf(stderr, "%s:%d: kill_qemu() tried to terminate QEMU "
> +                "process but encountered exit status %ld (expected %d)\n",
> +                __FILE__, __LINE__, exit_code, s->expected_status);
> +        abort();
> +    }
> +#endif
>  }
>
>  static void kill_qemu_hook_func(void *s)
> @@ -245,6 +290,38 @@ static const char *qtest_qemu_binary(void)
>      return qemu_bin;
>  }
>
> +#ifdef _WIN32
> +static pid_t qtest_create_process(char *cmd)
> +{
> +    STARTUPINFO si;
> +    PROCESS_INFORMATION pi;
> +    BOOL ret;
> +
> +    ZeroMemory(&si, sizeof(si));
> +    si.cb = sizeof(si);
> +    ZeroMemory(&pi, sizeof(pi));
> +
> +    ret = CreateProcess(NULL,   /* module name */
> +                        cmd,    /* command line */
> +                        NULL,   /* process handle not inheritable */
> +                        NULL,   /* thread handle not inheritable */
> +                        FALSE,  /* set handle inheritance to FALSE */
> +                        0,      /* No creation flags */
> +                        NULL,   /* use parent's environment block */
> +                        NULL,   /* use parent's starting directory */
> +                        &si,    /* pointer to STARTUPINFO structure */
> +                        &pi     /* pointer to PROCESS_INFORMATION
> structure */
> +                        );
> +    if (ret == 0) {
> +        fprintf(stderr, "%s:%d: unable to create a new process (%s)\n",
> +                __FILE__, __LINE__, strerror(GetLastError()));
> +        abort();
> +    }
> +
> +    return (pid_t)pi.hProcess;
> +}
> +#endif /* _WIN32 */
> +
>  QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
>  {
>      QTestState *s;
> @@ -272,6 +349,9 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
>      unlink(socket_path);
>      unlink(qmp_socket_path);
>
> +#ifdef _WIN32
> +    socket_init();
> +#endif
>      sock = init_socket(socket_path);
>      qmpsock = init_socket(qmp_socket_path);
>
> @@ -280,7 +360,7 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
>
>      qtest_add_abrt_handler(kill_qemu_hook_func, s);
>
> -    command = g_strdup_printf("exec %s %s"
> +    command = g_strdup_printf(CMD_EXEC "%s %s"
>                                "-qtest unix:%s "
>                                "-qtest-log %s "
>                                "-chardev socket,path=%s,id=char0 "
> @@ -289,7 +369,7 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
>                                "%s"
>                                " -accel qtest",
>                                qemu_binary, tracearg, socket_path,
> -                              getenv("QTEST_LOG") ? "/dev/fd/2" :
> "/dev/null",
> +                              getenv("QTEST_LOG") ? DEV_STDERR : DEV_NULL,
>                                qmp_socket_path,
>                                extra_args ?: "");
>
> @@ -298,6 +378,7 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
>      s->pending_events = NULL;
>      s->wstatus = 0;
>      s->expected_status = 0;
> +#ifndef _WIN32
>      s->qemu_pid = fork();
>      if (s->qemu_pid == 0) {
>  #ifdef __linux__
> @@ -320,6 +401,9 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
>          execlp("/bin/sh", "sh", "-c", command, NULL);
>          exit(1);
>      }
> +#else
> +    s->qemu_pid = qtest_create_process(command);
> +#endif /* _WIN32 */
>
>      g_free(command);
>      s->fd = socket_accept(sock);
> @@ -338,9 +422,19 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
>          s->irq_level[i] = false;
>      }
>
> +    /*
> +     * Stopping QEMU for debugging is not supported on Windows.
> +     *
> +     * Using DebugActiveProcess() API can suspend the QEMU process,
> +     * but gdb cannot attach to the process. Using the undocumented
> +     * NtSuspendProcess() can suspend the QEMU process and gdb can
> +     * attach to the process, but gdb cannot resume it.
> +     */
> +#ifndef _WIN32
>      if (getenv("QTEST_STOP")) {
>          kill(s->qemu_pid, SIGSTOP);
>      }
> +#endif
>
>      /* ask endianness of the target */
>
> @@ -393,6 +487,9 @@ QTestState *qtest_init_with_serial(const char
> *extra_args, int *sock_fd)
>      g_assert_true(g_mkdtemp(sock_dir) != NULL);
>      sock_path = g_strdup_printf("%s/sock", sock_dir);
>
> +#ifdef _WIN32
> +    socket_init();
> +#endif
>      sock_fd_init = init_socket(sock_path);
>
>      qts = qtest_initf("-chardev socket,id=s0,path=%s -serial chardev:s0
> %s",
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 0291b3966c..6d469a1822 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -1,6 +1,5 @@
> -# All QTests for now are POSIX-only, but the dependencies are
> -# really in libqtest, not in the testcases themselves.
> -if not config_host.has_key('CONFIG_POSIX')
> +# Build all QTests for POSIX and Windows
> +if not config_host.has_key('CONFIG_POSIX') and not
> config_host.has_key('CONFIG_WIN32')
>    subdir_done()
>  endif
>
> --
> 2.34.1
>
>
>
Bin Meng Sept. 19, 2022, 10 a.m. UTC | #3
Hi,

On Thu, Sep 1, 2022 at 12:29 AM Marc-André Lureau
<marcandre.lureau@gmail.com> wrote:
>
> Hi
>
> On Wed, Aug 24, 2022 at 2:46 PM Bin Meng <bmeng.cn@gmail.com> wrote:
>>
>> From: Bin Meng <bin.meng@windriver.com>
>>
>> At present the libqtest codes were written to depend on several
>> POSIX APIs, including fork(), kill() and waitpid(). Unfortunately
>> these APIs are not available on Windows.
>>
>> This commit implements the corresponding functionalities using
>> win32 native APIs. With this change, all qtest cases can build
>> successfully on a Windows host, and we can start qtest testing
>> on Windows now.
>>
>> Signed-off-by: Xuzhou Cheng <xuzhou.cheng@windriver.com>
>> Signed-off-by: Bin Meng <bin.meng@windriver.com>
>> ---
>>
>>  tests/qtest/libqtest.c  | 101 +++++++++++++++++++++++++++++++++++++++-
>>  tests/qtest/meson.build |   5 +-
>>  2 files changed, 101 insertions(+), 5 deletions(-)
>>
>> diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
>> index 70d7578740..99e52ff571 100644
>> --- a/tests/qtest/libqtest.c
>> +++ b/tests/qtest/libqtest.c
>> @@ -16,9 +16,11 @@
>>
>>  #include "qemu/osdep.h"
>>
>> +#ifndef _WIN32
>>  #include <sys/socket.h>
>>  #include <sys/wait.h>
>>  #include <sys/un.h>
>> +#endif /* _WIN32 */
>>  #ifdef __linux__
>>  #include <sys/prctl.h>
>>  #endif /* __linux__ */
>> @@ -27,6 +29,7 @@
>>  #include "libqmp.h"
>>  #include "qemu/ctype.h"
>>  #include "qemu/cutils.h"
>> +#include "qemu/sockets.h"
>>  #include "qapi/qmp/qdict.h"
>>  #include "qapi/qmp/qjson.h"
>>  #include "qapi/qmp/qlist.h"
>> @@ -35,6 +38,16 @@
>>  #define MAX_IRQ 256
>>  #define SOCKET_TIMEOUT 50
>>
>> +#ifndef _WIN32
>> +# define CMD_EXEC   "exec "
>> +# define DEV_STDERR "/dev/fd/2"
>> +# define DEV_NULL   "/dev/null"
>> +#else
>> +# define CMD_EXEC   ""
>> +# define DEV_STDERR "2"
>> +# define DEV_NULL   "nul"
>> +#endif
>> +
>>  typedef void (*QTestSendFn)(QTestState *s, const char *buf);
>>  typedef void (*ExternalSendFn)(void *s, const char *buf);
>>  typedef GString* (*QTestRecvFn)(QTestState *);
>> @@ -68,6 +81,9 @@ struct QTestState
>>  QTestState *global_qtest;
>>
>>  static GHookList abrt_hooks;
>> +#ifdef _WIN32
>> +typedef void (*sighandler_t)(int);
>> +#endif
>>  static sighandler_t sighandler_old;
>>
>>  static int qtest_query_target_endianness(QTestState *s);
>> @@ -120,10 +136,18 @@ bool qtest_probe_child(QTestState *s)
>>      pid_t pid = s->qemu_pid;
>>
>>      if (pid != -1) {
>> +#ifndef _WIN32
>>          pid = waitpid(pid, &s->wstatus, WNOHANG);
>>          if (pid == 0) {
>>              return true;
>>          }
>> +#else
>> +        DWORD exit_code;
>> +        GetExitCodeProcess((HANDLE)pid, &exit_code);
>> +        if (exit_code == STILL_ACTIVE) {
>> +            return true;
>> +        }
>> +#endif
>>          s->qemu_pid = -1;
>>      }
>>      return false;
>> @@ -137,13 +161,23 @@ void qtest_set_expected_status(QTestState *s, int status)
>>  void qtest_kill_qemu(QTestState *s)
>>  {
>>      pid_t pid = s->qemu_pid;
>> +#ifndef _WIN32
>>      int wstatus;
>> +#else
>> +    DWORD ret, exit_code;
>> +#endif
>>
>>      /* Skip wait if qtest_probe_child already reaped.  */
>>      if (pid != -1) {
>> +#ifndef _WIN32
>>          kill(pid, SIGTERM);
>>          TFR(pid = waitpid(s->qemu_pid, &s->wstatus, 0));
>>          assert(pid == s->qemu_pid);
>> +#else
>> +        TerminateProcess((HANDLE)pid, s->expected_status);
>> +        ret = WaitForSingleObject((HANDLE)pid, INFINITE);
>> +        assert(ret == WAIT_OBJECT_0);
>> +#endif
>>          s->qemu_pid = -1;
>>      }
>>
>> @@ -151,6 +185,7 @@ void qtest_kill_qemu(QTestState *s)
>>       * Check whether qemu exited with expected exit status; anything else is
>>       * fishy and should be logged with as much detail as possible.
>>       */
>> +#ifndef _WIN32
>>      wstatus = s->wstatus;
>>      if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) != s->expected_status) {
>>          fprintf(stderr, "%s:%d: kill_qemu() tried to terminate QEMU "
>> @@ -167,6 +202,16 @@ void qtest_kill_qemu(QTestState *s)
>>                  __FILE__, __LINE__, sig, signame, dump);
>>          abort();
>>      }
>> +#else
>> +    GetExitCodeProcess((HANDLE)pid, &exit_code);
>> +    CloseHandle((HANDLE)pid);
>> +    if (exit_code != s->expected_status) {
>> +        fprintf(stderr, "%s:%d: kill_qemu() tried to terminate QEMU "
>> +                "process but encountered exit status %ld (expected %d)\n",
>> +                __FILE__, __LINE__, exit_code, s->expected_status);
>> +        abort();
>> +    }
>> +#endif
>>  }
>>
>>  static void kill_qemu_hook_func(void *s)
>> @@ -245,6 +290,38 @@ static const char *qtest_qemu_binary(void)
>>      return qemu_bin;
>>  }
>>
>> +#ifdef _WIN32
>> +static pid_t qtest_create_process(char *cmd)
>> +{
>> +    STARTUPINFO si;
>> +    PROCESS_INFORMATION pi;
>> +    BOOL ret;
>> +
>> +    ZeroMemory(&si, sizeof(si));
>> +    si.cb = sizeof(si);
>> +    ZeroMemory(&pi, sizeof(pi));
>> +
>> +    ret = CreateProcess(NULL,   /* module name */
>> +                        cmd,    /* command line */
>> +                        NULL,   /* process handle not inheritable */
>> +                        NULL,   /* thread handle not inheritable */
>> +                        FALSE,  /* set handle inheritance to FALSE */
>> +                        0,      /* No creation flags */
>> +                        NULL,   /* use parent's environment block */
>> +                        NULL,   /* use parent's starting directory */
>> +                        &si,    /* pointer to STARTUPINFO structure */
>> +                        &pi     /* pointer to PROCESS_INFORMATION structure */
>> +                        );
>> +    if (ret == 0) {
>> +        fprintf(stderr, "%s:%d: unable to create a new process (%s)\n",
>> +                __FILE__, __LINE__, strerror(GetLastError()));
>> +        abort();
>> +    }
>> +
>> +    return (pid_t)pi.hProcess;
>> +}
>> +#endif /* _WIN32 */
>> +
>>  QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
>>  {
>>      QTestState *s;
>> @@ -272,6 +349,9 @@ QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
>>      unlink(socket_path);
>>      unlink(qmp_socket_path);
>>
>> +#ifdef _WIN32
>> +    socket_init();
>> +#endif
>
>
> You can call this unconditionally, afaict
>

Will fix in v2.

>>
>>      sock = init_socket(socket_path);
>>      qmpsock = init_socket(qmp_socket_path);
>>
>> @@ -280,7 +360,7 @@ QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
>>
>>      qtest_add_abrt_handler(kill_qemu_hook_func, s);
>>
>> -    command = g_strdup_printf("exec %s %s"
>> +    command = g_strdup_printf(CMD_EXEC "%s %s"
>>                                "-qtest unix:%s "
>>                                "-qtest-log %s "
>>                                "-chardev socket,path=%s,id=char0 "
>> @@ -289,7 +369,7 @@ QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
>>                                "%s"
>>                                " -accel qtest",
>>                                qemu_binary, tracearg, socket_path,
>> -                              getenv("QTEST_LOG") ? "/dev/fd/2" : "/dev/null",
>> +                              getenv("QTEST_LOG") ? DEV_STDERR : DEV_NULL,
>>                                qmp_socket_path,
>>                                extra_args ?: "");
>>
>> @@ -298,6 +378,7 @@ QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
>>      s->pending_events = NULL;
>>      s->wstatus = 0;
>>      s->expected_status = 0;
>> +#ifndef _WIN32
>>      s->qemu_pid = fork();
>>      if (s->qemu_pid == 0) {
>>  #ifdef __linux__
>> @@ -320,6 +401,9 @@ QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
>>          execlp("/bin/sh", "sh", "-c", command, NULL);
>>          exit(1);
>>      }
>> +#else
>> +    s->qemu_pid = qtest_create_process(command);
>
>
> Why not replace the fork/exec with g_spawn_async() ?
>

I tried g_spawn_async(), but I am getting in trouble creating the
argument vector of the QEMU command lines, to pass in g_spawn_async().

g_strsplit() with SPACE as the delimiter does not work, as there might
be space in the command line surrounded by quotes.

g_shell_parse_argv() does not work either, as it just ate all Windows
path separator "\", so a patch name like
"D:\msys64\tmp/qtest-4220.qmp" becomes "D:msys64tmp/qtest-4220.qmp".

Do you have any suggestions?

>>
>> +#endif /* _WIN32 */
>>
>>      g_free(command);
>>      s->fd = socket_accept(sock);
>> @@ -338,9 +422,19 @@ QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
>>          s->irq_level[i] = false;
>>      }
>>
>> +    /*
>> +     * Stopping QEMU for debugging is not supported on Windows.
>> +     *
>> +     * Using DebugActiveProcess() API can suspend the QEMU process,
>> +     * but gdb cannot attach to the process. Using the undocumented
>> +     * NtSuspendProcess() can suspend the QEMU process and gdb can
>> +     * attach to the process, but gdb cannot resume it.
>> +     */
>> +#ifndef _WIN32
>>      if (getenv("QTEST_STOP")) {
>>          kill(s->qemu_pid, SIGSTOP);
>>      }
>> +#endif
>>
>>      /* ask endianness of the target */
>>
>> @@ -393,6 +487,9 @@ QTestState *qtest_init_with_serial(const char *extra_args, int *sock_fd)
>>      g_assert_true(g_mkdtemp(sock_dir) != NULL);
>>      sock_path = g_strdup_printf("%s/sock", sock_dir);
>>
>> +#ifdef _WIN32
>> +    socket_init();
>> +#endif
>
>
> same
>
>>
>>      sock_fd_init = init_socket(sock_path);
>>
>>      qts = qtest_initf("-chardev socket,id=s0,path=%s -serial chardev:s0 %s",
>> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
>> index 0291b3966c..6d469a1822 100644
>> --- a/tests/qtest/meson.build
>> +++ b/tests/qtest/meson.build
>> @@ -1,6 +1,5 @@
>> -# All QTests for now are POSIX-only, but the dependencies are
>> -# really in libqtest, not in the testcases themselves.
>> -if not config_host.has_key('CONFIG_POSIX')
>> +# Build all QTests for POSIX and Windows
>> +if not config_host.has_key('CONFIG_POSIX') and not config_host.has_key('CONFIG_WIN32')
>>    subdir_done()
>>  endif
>>

Regards,
Bin
diff mbox series

Patch

diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
index 70d7578740..99e52ff571 100644
--- a/tests/qtest/libqtest.c
+++ b/tests/qtest/libqtest.c
@@ -16,9 +16,11 @@ 
 
 #include "qemu/osdep.h"
 
+#ifndef _WIN32
 #include <sys/socket.h>
 #include <sys/wait.h>
 #include <sys/un.h>
+#endif /* _WIN32 */
 #ifdef __linux__
 #include <sys/prctl.h>
 #endif /* __linux__ */
@@ -27,6 +29,7 @@ 
 #include "libqmp.h"
 #include "qemu/ctype.h"
 #include "qemu/cutils.h"
+#include "qemu/sockets.h"
 #include "qapi/qmp/qdict.h"
 #include "qapi/qmp/qjson.h"
 #include "qapi/qmp/qlist.h"
@@ -35,6 +38,16 @@ 
 #define MAX_IRQ 256
 #define SOCKET_TIMEOUT 50
 
+#ifndef _WIN32
+# define CMD_EXEC   "exec "
+# define DEV_STDERR "/dev/fd/2"
+# define DEV_NULL   "/dev/null"
+#else
+# define CMD_EXEC   ""
+# define DEV_STDERR "2"
+# define DEV_NULL   "nul"
+#endif
+
 typedef void (*QTestSendFn)(QTestState *s, const char *buf);
 typedef void (*ExternalSendFn)(void *s, const char *buf);
 typedef GString* (*QTestRecvFn)(QTestState *);
@@ -68,6 +81,9 @@  struct QTestState
 QTestState *global_qtest;
 
 static GHookList abrt_hooks;
+#ifdef _WIN32
+typedef void (*sighandler_t)(int);
+#endif
 static sighandler_t sighandler_old;
 
 static int qtest_query_target_endianness(QTestState *s);
@@ -120,10 +136,18 @@  bool qtest_probe_child(QTestState *s)
     pid_t pid = s->qemu_pid;
 
     if (pid != -1) {
+#ifndef _WIN32
         pid = waitpid(pid, &s->wstatus, WNOHANG);
         if (pid == 0) {
             return true;
         }
+#else
+        DWORD exit_code;
+        GetExitCodeProcess((HANDLE)pid, &exit_code);
+        if (exit_code == STILL_ACTIVE) {
+            return true;
+        }
+#endif
         s->qemu_pid = -1;
     }
     return false;
@@ -137,13 +161,23 @@  void qtest_set_expected_status(QTestState *s, int status)
 void qtest_kill_qemu(QTestState *s)
 {
     pid_t pid = s->qemu_pid;
+#ifndef _WIN32
     int wstatus;
+#else
+    DWORD ret, exit_code;
+#endif
 
     /* Skip wait if qtest_probe_child already reaped.  */
     if (pid != -1) {
+#ifndef _WIN32
         kill(pid, SIGTERM);
         TFR(pid = waitpid(s->qemu_pid, &s->wstatus, 0));
         assert(pid == s->qemu_pid);
+#else
+        TerminateProcess((HANDLE)pid, s->expected_status);
+        ret = WaitForSingleObject((HANDLE)pid, INFINITE);
+        assert(ret == WAIT_OBJECT_0);
+#endif
         s->qemu_pid = -1;
     }
 
@@ -151,6 +185,7 @@  void qtest_kill_qemu(QTestState *s)
      * Check whether qemu exited with expected exit status; anything else is
      * fishy and should be logged with as much detail as possible.
      */
+#ifndef _WIN32
     wstatus = s->wstatus;
     if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) != s->expected_status) {
         fprintf(stderr, "%s:%d: kill_qemu() tried to terminate QEMU "
@@ -167,6 +202,16 @@  void qtest_kill_qemu(QTestState *s)
                 __FILE__, __LINE__, sig, signame, dump);
         abort();
     }
+#else
+    GetExitCodeProcess((HANDLE)pid, &exit_code);
+    CloseHandle((HANDLE)pid);
+    if (exit_code != s->expected_status) {
+        fprintf(stderr, "%s:%d: kill_qemu() tried to terminate QEMU "
+                "process but encountered exit status %ld (expected %d)\n",
+                __FILE__, __LINE__, exit_code, s->expected_status);
+        abort();
+    }
+#endif
 }
 
 static void kill_qemu_hook_func(void *s)
@@ -245,6 +290,38 @@  static const char *qtest_qemu_binary(void)
     return qemu_bin;
 }
 
+#ifdef _WIN32
+static pid_t qtest_create_process(char *cmd)
+{
+    STARTUPINFO si;
+    PROCESS_INFORMATION pi;
+    BOOL ret;
+
+    ZeroMemory(&si, sizeof(si));
+    si.cb = sizeof(si);
+    ZeroMemory(&pi, sizeof(pi));
+
+    ret = CreateProcess(NULL,   /* module name */
+                        cmd,    /* command line */
+                        NULL,   /* process handle not inheritable */
+                        NULL,   /* thread handle not inheritable */
+                        FALSE,  /* set handle inheritance to FALSE */
+                        0,      /* No creation flags */
+                        NULL,   /* use parent's environment block */
+                        NULL,   /* use parent's starting directory */
+                        &si,    /* pointer to STARTUPINFO structure */
+                        &pi     /* pointer to PROCESS_INFORMATION structure */
+                        );
+    if (ret == 0) {
+        fprintf(stderr, "%s:%d: unable to create a new process (%s)\n",
+                __FILE__, __LINE__, strerror(GetLastError()));
+        abort();
+    }
+
+    return (pid_t)pi.hProcess;
+}
+#endif /* _WIN32 */
+
 QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
 {
     QTestState *s;
@@ -272,6 +349,9 @@  QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
     unlink(socket_path);
     unlink(qmp_socket_path);
 
+#ifdef _WIN32
+    socket_init();
+#endif
     sock = init_socket(socket_path);
     qmpsock = init_socket(qmp_socket_path);
 
@@ -280,7 +360,7 @@  QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
 
     qtest_add_abrt_handler(kill_qemu_hook_func, s);
 
-    command = g_strdup_printf("exec %s %s"
+    command = g_strdup_printf(CMD_EXEC "%s %s"
                               "-qtest unix:%s "
                               "-qtest-log %s "
                               "-chardev socket,path=%s,id=char0 "
@@ -289,7 +369,7 @@  QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
                               "%s"
                               " -accel qtest",
                               qemu_binary, tracearg, socket_path,
-                              getenv("QTEST_LOG") ? "/dev/fd/2" : "/dev/null",
+                              getenv("QTEST_LOG") ? DEV_STDERR : DEV_NULL,
                               qmp_socket_path,
                               extra_args ?: "");
 
@@ -298,6 +378,7 @@  QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
     s->pending_events = NULL;
     s->wstatus = 0;
     s->expected_status = 0;
+#ifndef _WIN32
     s->qemu_pid = fork();
     if (s->qemu_pid == 0) {
 #ifdef __linux__
@@ -320,6 +401,9 @@  QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
         execlp("/bin/sh", "sh", "-c", command, NULL);
         exit(1);
     }
+#else
+    s->qemu_pid = qtest_create_process(command);
+#endif /* _WIN32 */
 
     g_free(command);
     s->fd = socket_accept(sock);
@@ -338,9 +422,19 @@  QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
         s->irq_level[i] = false;
     }
 
+    /*
+     * Stopping QEMU for debugging is not supported on Windows.
+     *
+     * Using DebugActiveProcess() API can suspend the QEMU process,
+     * but gdb cannot attach to the process. Using the undocumented
+     * NtSuspendProcess() can suspend the QEMU process and gdb can
+     * attach to the process, but gdb cannot resume it.
+     */
+#ifndef _WIN32
     if (getenv("QTEST_STOP")) {
         kill(s->qemu_pid, SIGSTOP);
     }
+#endif
 
     /* ask endianness of the target */
 
@@ -393,6 +487,9 @@  QTestState *qtest_init_with_serial(const char *extra_args, int *sock_fd)
     g_assert_true(g_mkdtemp(sock_dir) != NULL);
     sock_path = g_strdup_printf("%s/sock", sock_dir);
 
+#ifdef _WIN32
+    socket_init();
+#endif
     sock_fd_init = init_socket(sock_path);
 
     qts = qtest_initf("-chardev socket,id=s0,path=%s -serial chardev:s0 %s",
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 0291b3966c..6d469a1822 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -1,6 +1,5 @@ 
-# All QTests for now are POSIX-only, but the dependencies are
-# really in libqtest, not in the testcases themselves.
-if not config_host.has_key('CONFIG_POSIX')
+# Build all QTests for POSIX and Windows
+if not config_host.has_key('CONFIG_POSIX') and not config_host.has_key('CONFIG_WIN32')
   subdir_done()
 endif