From 91975649d12f44150bc93c51dfde209010eb098e Mon Sep 17 00:00:00 2001 From: Lukasz Skalski Date: Tue, 27 Sep 2016 14:24:15 +0200 Subject: [PATCH] gdbus: add support for 'dbus-integration-tests' framework Change-Id: Icd3e73b5221996945b0b9e6e34813aba2ebf0ab5 --- packaging/glib2.spec | 29 +++ packaging/test-runner.c | 524 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 553 insertions(+) create mode 100644 packaging/test-runner.c diff --git a/packaging/glib2.spec b/packaging/glib2.spec index 65768bd..d9a7960 100644 --- a/packaging/glib2.spec +++ b/packaging/glib2.spec @@ -14,6 +14,7 @@ Url: http://www.gtk.org/ Source: http://download.gnome.org/sources/glib/%{baseline}/%{name}-%{version}.tar.xz Source1: glib2.sh Source2: glib2.csh +Source3: test-runner.c # Not upstream file. Only proposes upstream packages: Source4: glib2-upstream-gnome_defaults.conf Source6: macros.glib2 @@ -175,8 +176,18 @@ a main loop abstraction, and so on. The GObject library provides an object-oriented framework for C. +%package tests +License: LGPL-2.0+ and Apache-2.0 +Summary: Set of tests for gdbus component +Requires: %{name} = %{version} + +%description tests +This package is part of 'dbus-integratnion-tests' framework and contains set of tests +for gdbus component. + %prep %setup -q -n %{name}-%{version} +cp %{SOURCE3} . cp %{SOURCE1001} . cp -a %{S:1} %{S:2} . cp -a %{S:4} gnome_defaults.conf @@ -195,10 +206,14 @@ export CFLAGS+=" -D_TIZEN_DBUS_TOUCH" %if %{with dbuspolicy} --enable-libdbuspolicy \ %endif + --enable-always-build-tests \ + --enable-installed-tests \ --with-pcre=system %{__make} %{?_smp_mflags} V=1 +# compile test-runner for 'dbus-integration-test' framework +%__cc %{_builddir}/%{name}-%{version}/test-runner.c -o %{_builddir}/%{name}-%{version}/glib-tests %install %make_install @@ -223,6 +238,15 @@ touch %{buildroot}%{_libdir}/gio/modules/giomodule.cache touch %{buildroot}%{_datadir}/glib-2.0/schemas/gschemas.compiled # remove files we don't care about find %{buildroot}%{_libdir} -name '*.la' -type f -delete -print +# preapre tests for 'dbus-integration-test' framework +install -D -m 755 %{_builddir}/%{name}-%{version}/glib-tests %{buildroot}%{_prefix}/lib/dbus-tests/runner/glib-tests +mkdir -p %{buildroot}%{_prefix}/lib/dbus-tests/test-suites/glib-tests/ +mv %{buildroot}%{_prefix}/libexec/installed-tests/glib/*gdbus-* %{buildroot}%{_prefix}/lib/dbus-tests/test-suites/glib-tests/ +# workaround for UNIX fd passing test (gdbus-peer) +echo "Testfile - check for UNIX fd passing" > %{buildroot}%{_prefix}/lib/dbus-tests/test-suites/glib-tests/file.c +rm %{buildroot}%{_prefix}/lib/dbus-tests/test-suites/glib-tests/gdbus-testserver +rm -Rf %{buildroot}%{_prefix}/libexec/installed-tests/glib/ +rm -Rf %{buildroot}%{_prefix}/share/installed-tests/glib/ # Install rpm macros mkdir -p %{buildroot}%{_sysconfdir}/rpm cp %{S:6} %{buildroot}%{_sysconfdir}/rpm @@ -359,4 +383,9 @@ cp %{S:6} %{buildroot}%{_sysconfdir}/rpm %defattr(-,root,root) %{_libdir}/lib*.a +%files tests +%manifest %{name}.manifest +%{_prefix}/lib/dbus-tests/test-suites/glib-tests/ +%{_prefix}/lib/dbus-tests/runner/glib-tests + %changelog diff --git a/packaging/test-runner.c b/packaging/test-runner.c new file mode 100644 index 0000000..bf395e4 --- /dev/null +++ b/packaging/test-runner.c @@ -0,0 +1,524 @@ +/* + * This file contains standalone test-runner. + * This file is NOT part of GLib project. + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved + * Author: Kazimierz Krosman + * + * Licensed 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_TC_NUM 1024 +#define MAX_BUFFER (64*1024) +#define MAX_COMMENT 1024 + +enum { + INIT_TEST, + NEW_STDOUT, + NEW_STDERR, + RESULT_CODE, + RESULT_SIGNAL, + RESULT_ERROR, + RESULT_TIMEOUT +}; + +struct test_result { + bool is_positive; + char comment[MAX_COMMENT]; + char result[MAX_COMMENT]; + char name[MAX_COMMENT]; +}; + +struct test_case { + const char* name; + const char* description; +}; + +struct binary { + const char* path; + const char* name; + struct test_case* test_cases; + int timeout; + + char** (*prepare_args) (const struct binary* b, const char* test_name); + void (*parse) (const struct binary* b, const char* test_name, char* buffer, int state_change, int state_option); + int (*init)(void); + int (*clean)(void); +}; + +char* get_test_id(char* dest, const struct binary* b, const char* test_name); +void add_test_result(const char* test_id, const char* result, const char* comment, int res); + +enum { + PIPE_READ, + PIPE_WRITE, +}; + +void parse_test_state(const struct binary* b, const char* test_name, char* buffer, int state_change, int state_option); +void parse_one_test_one_binary(const struct binary* b, const char* test_name, char* buffer, int state_change, int state_option); +char** prepare_args_for_binary(const struct binary* b, const char* test_name); + +static struct test_case test_case_desc_01[] = { + {"", "Checks if the library supports format of addresses described in the D-Bus specification (key/value pairs for each transport are valid)"}, + {NULL, NULL} +}; + +static struct test_case test_case_desc_02[] = { + {"", "Test case for GNOME #662395"}, + {NULL, NULL} +}; + +static struct test_case test_case_desc_03[] = { + {"", "Test flushing connection with g_dbus_connection_flush_sync() function"}, + {NULL, NULL} +}; + +static struct test_case test_case_desc_04[] = { + {"", "Test 'lock' and 'copy' operations on GDBusMessage object"}, + {NULL, NULL} +}; + +static struct test_case test_case_desc_05[] = { + {"", "Test 'non-socket' connections (connection via pipes and GIOStreams objects)"}, + {NULL, NULL} +}; + +static struct test_case test_case_desc_06[] = { + {"", "Test overflowing socket buffer in producer/consumer scenario (UNIX sockets as a transport mechanism)"}, + {NULL, NULL} +}; + +static struct test_case test_case_desc_07[] = { + {"", "Test that peer-to-peer connections work"}, + {NULL, NULL} +}; + +static struct test_case test_case_desc_08[] = { + {"", "Test that peer-to-peer connections work (using GDBusObjectManagerServer and GDBusObjectManager objects)"}, + {NULL, NULL} +}; + +/* This table is used to start binaries */ +struct binary tests[] = { + /*path, name, TC_table, timeout in us, prepare_args_handler, parse_function_handler, init_handler, clean_handler*/ + {"/usr/lib/dbus-tests/test-suites/glib-tests/gdbus-addresses", "gdbus-addresses", test_case_desc_01, 5000*1000, prepare_args_for_binary, parse_one_test_one_binary, NULL, NULL}, + {"/usr/lib/dbus-tests/test-suites/glib-tests/gdbus-connection-flush", "gdbus-connection-flush", test_case_desc_02, 5000*1000, prepare_args_for_binary, parse_one_test_one_binary, NULL, NULL}, + {"/usr/lib/dbus-tests/test-suites/glib-tests/gdbus-connection-flush-helper", "gdbus-connection-flush-helper", test_case_desc_03, 5000*1000, prepare_args_for_binary, parse_one_test_one_binary, NULL, NULL}, + {"/usr/lib/dbus-tests/test-suites/glib-tests/gdbus-message", "gdbus-message", test_case_desc_04, 5000*1000, prepare_args_for_binary, parse_one_test_one_binary, NULL, NULL}, + {"/usr/lib/dbus-tests/test-suites/glib-tests/gdbus-non-socket", "gdbus-non-socket", test_case_desc_05, 5000*1000, prepare_args_for_binary, parse_one_test_one_binary, NULL, NULL}, + {"/usr/lib/dbus-tests/test-suites/glib-tests/gdbus-overflow", "gdbus-overflow", test_case_desc_06, 5000*1000, prepare_args_for_binary, parse_one_test_one_binary, NULL, NULL}, + {"/usr/lib/dbus-tests/test-suites/glib-tests/gdbus-peer", "gdbus-peer", test_case_desc_07, 5000*1000, prepare_args_for_binary, parse_one_test_one_binary, NULL, NULL}, + {"/usr/lib/dbus-tests/test-suites/glib-tests/gdbus-peer-object-manager", "gdbus-peer-object-manager", test_case_desc_08, 5000*1000, prepare_args_for_binary, parse_one_test_one_binary, NULL, NULL}, +}; + +static char* args[3]; +char** prepare_args_for_binary(const struct binary* b, const char* test_name) +{ + args[0] = (char*)b->name; + args[1] = NULL; + return args; +} + +void parse_one_test_one_binary(const struct binary* b, const char* test_name, char* buffer, int state_change, int state_option) +{ + char test_id[MAX_COMMENT]; + + switch(state_change) { + case INIT_TEST: + break; + case NEW_STDOUT: + buffer[state_option] = 0; + get_test_id(test_id, b, test_name); + fprintf(stderr, "[stdout][%s]%s\n",test_id, buffer); + break; + case NEW_STDERR: + buffer[state_option] = 0; + get_test_id(test_id, b, test_name); + fprintf(stderr, "[stderr][%s]%s\n",test_id, buffer); + break; + case RESULT_CODE: + get_test_id(test_id, b, test_name); + if (state_option == 0) + add_test_result(test_id, "PASS", "", 1); + else if (state_option == 77) + add_test_result(test_id, "SKIP", "", 0); + else + add_test_result(test_id, "FAIL", "", 0); + break; + case RESULT_SIGNAL: + get_test_id(test_id, b, test_name); + add_test_result(test_id, "FAIL", "Finished by SIGNAL", 0); + break; + case RESULT_TIMEOUT: + get_test_id(test_id, b, test_name); + add_test_result(test_id, "FAIL", "Test TIMEOUT", 0); + break; + } +} + +static struct option long_options[] = { + {"list", no_argument, 0, 'l'}, + {"run", required_argument, 0, 'r'}, + {"description", required_argument, 0, 'd'}, + {0, 0, 0, 0 } +}; + +static int stdin_pipe[2]; +static int stdout_pipe[2]; +static int stderr_pipe[2]; +static int gravedigger_pipe[2]; +static char buffer[MAX_BUFFER]; +static const char* requested_tc[MAX_TC_NUM]; + +char* get_test_id(char* dest, const struct binary* b, const char* test_name) +{ + int len = strlen(b->name); + memcpy(dest, b->name, len); + memcpy(dest + len, test_name, strlen(test_name)+1); + return dest; +} + +static void print_description(const char* name, const char* description) +{ + printf("%s;%s\n",name, description); +} + +static void print_list(const char* test_name) +{ + unsigned int i; + char full_name[MAX_COMMENT]; + for (i = 0;i < sizeof(tests)/sizeof(struct binary); i++) { + int j = 0; + int l = strlen(tests[i].name); + memcpy(full_name, tests[i].name, l+1); + if (test_name && strncmp(test_name, full_name, l) != 0) + continue; + + while (tests[i].test_cases[j].name) { + memcpy(full_name + l, tests[i].test_cases[j].name, strlen(tests[i].test_cases[j].name) + 1); + if (!test_name || strncmp(full_name, test_name, sizeof(full_name)) == 0) + print_description(full_name,tests[i].test_cases[j].description); + j++; + } + } +} + + +static void stop_binary(const struct binary* b, pid_t pid, const char* test_name, int w_res) +{ + int status = 0; + int res = 0; + if (w_res == 0) + res = waitpid(pid, &status, WNOHANG); + else + res = waitpid(pid, &status, 0); + + if (res == 0) { + //timeouted + kill(pid, SIGKILL); + res = waitpid(pid, &status, WNOHANG); + b->parse(b, test_name, buffer, RESULT_TIMEOUT, res); + } else if (res < 0) { + //errno check + kill(pid, SIGKILL); + res = waitpid(pid, &status, WNOHANG); + b->parse(b, test_name, buffer, RESULT_ERROR, res); + } else if (res > 0) { + if (WIFEXITED(status)) { + b->parse(b, test_name, buffer, RESULT_CODE, WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + b->parse(b, test_name, buffer, RESULT_SIGNAL, WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + b->parse(b, test_name, buffer, RESULT_SIGNAL, WSTOPSIG(status)); + } else if (WIFCONTINUED(status)) { + kill(pid, SIGKILL); + b->parse(b, test_name, buffer, RESULT_SIGNAL, -1); + } + } +} + +static void parse_output_with_timeout(const struct binary* b, pid_t pid, const char* test_name) +{ + struct timeval tv; + fd_set rfds; + int nfds; + int res; + int w_res = 0; + tv.tv_sec = b->timeout/(1000*1000); + tv.tv_usec = (b->timeout-tv.tv_sec*1000*1000); + while (1) { + FD_ZERO(&rfds); + if (stdout_pipe[PIPE_READ] > -1) { + assert(stdout_pipe[PIPE_READ] > -1); + assert(stdout_pipe[PIPE_READ] < 1024); + FD_SET(stdout_pipe[PIPE_READ], &rfds); + } + if (stderr_pipe[PIPE_READ] > -1) { + assert(stderr_pipe[PIPE_READ] > -1); + assert(stderr_pipe[PIPE_READ] < 1024); + FD_SET(stderr_pipe[PIPE_READ], &rfds); + } + FD_SET(gravedigger_pipe[PIPE_READ], &rfds); + + nfds = select(FD_SETSIZE, &rfds, NULL, NULL, &tv); + if (nfds == -1) { + if (errno != EINTR) { + w_res = 0; + break; + } + } else if (nfds > 0) { + if (stdout_pipe[PIPE_READ] > -1 && FD_ISSET(stdout_pipe[PIPE_READ], &rfds)) { + res = read(stdout_pipe[PIPE_READ], buffer, MAX_BUFFER-1); + if (res == 0 || (res < 0 && errno != EINTR)) { + close (stdout_pipe[PIPE_READ]); + stdout_pipe[PIPE_READ] = -1; + continue; + } else if (res >=0) { + b->parse(b, test_name, buffer, NEW_STDOUT, res); + } + } + + if (stderr_pipe[PIPE_READ] > -1 && FD_ISSET(stderr_pipe[PIPE_READ], &rfds)) { + res = read(stderr_pipe[PIPE_READ], buffer, MAX_BUFFER-1); + if (res == 0 || (res < 0 && errno != EINTR)) { + close (stderr_pipe[PIPE_READ]); + stderr_pipe[PIPE_READ] = -1; + continue; + } + b->parse(b, test_name, buffer, NEW_STDERR, res); + } + + if (FD_ISSET(gravedigger_pipe[PIPE_READ], &rfds)) { + w_res = 1; + break; //it has ended + } + } else { + //timeout + w_res = 0; + break; + } + } + stop_binary(b, pid, test_name, w_res); +} + +static int create_child(const char* path, char* const arguments[]) +{ + int child; + int nResult; + if (pipe(gravedigger_pipe) < 0) { + perror("allocating pipe for gravedigger failed"); + goto error1; + } + + if (pipe(stdin_pipe) < 0) { + perror("allocating pipe for child input redirect failed"); + goto error1; + } + + if (pipe(stdout_pipe) < 0) { + perror("allocating pipe for child output redirect failed"); + goto error2; + } + + if (pipe(stderr_pipe) < 0) { + perror("allocating pipe for child output redirect failed"); + goto error3; + } + + child = fork(); + if (!child) { + char ld_path[512]; + sprintf(ld_path, "/usr/lib/dbus-tests/test-suites/glib-tests/:"); + // redirect stdin + if (dup2(stdin_pipe[PIPE_READ], STDIN_FILENO) == -1) { + perror("redirecting stdin failed"); + return -1; + } + + if (dup2(stdout_pipe[PIPE_WRITE], STDOUT_FILENO) == -1) { + perror("redirecting stdout failed"); + return -1; + } + + if (dup2(stderr_pipe[PIPE_WRITE], STDERR_FILENO) == -1) { + perror("redirecting stderr failed"); + return -1; + } + + close(stdin_pipe[PIPE_READ]); + close(stdin_pipe[PIPE_WRITE]); + close(stdout_pipe[PIPE_READ]); + close(stdout_pipe[PIPE_WRITE]); + close(stderr_pipe[PIPE_READ]); + close(stderr_pipe[PIPE_WRITE]); + close(gravedigger_pipe[PIPE_READ]); + + char* ld_path_b = getenv("LD_LIBRARY_PATH"); + if (ld_path_b != NULL) + memcpy(ld_path + strlen(ld_path), ld_path_b, strlen(ld_path_b)+1); + setenv("LD_LIBRARY_PATH", ld_path, 1); + setenv("G_TEST_SRCDIR", "/usr/lib/dbus-tests/test-suites/glib-tests", 1); + + // run child process image + nResult = execv(path, arguments); + + // if we get here at all, an error occurred, but we are in the child + // process, so just exit + perror("exec of the child process failed"); + exit(nResult); + } else if (child > 0) { + // close unused file descriptors, these are for child only + close(stdin_pipe[PIPE_READ]); + close(stdout_pipe[PIPE_WRITE]); + close(stderr_pipe[PIPE_WRITE]); + close(gravedigger_pipe[PIPE_WRITE]); + } else { + // failed to create child + goto error4; + } + + return child; + +error4: + close(stderr_pipe[PIPE_READ]); + close(stderr_pipe[PIPE_WRITE]); +error3: + close(stdout_pipe[PIPE_READ]); + close(stdout_pipe[PIPE_WRITE]); +error2: + close(stdin_pipe[PIPE_READ]); + close(stdin_pipe[PIPE_WRITE]); +error1: + return -1; +} + +static void run_test(const struct binary* b, const char* test_name) +{ + int res = -1; + char** arg; + char test_id[MAX_COMMENT]; + + assert(b); + assert(b->name); + assert(b->path); + assert(test_name); + + arg = b->prepare_args(b, test_name); + + if (b->init) + if (!b->init()) { + add_test_result(get_test_id(test_id, b, test_name), "ERROR", "Cannot init test", 0); + return; + } + + res = create_child(b->path, arg); + if (res > 0) + parse_output_with_timeout(b, res, test_name); + else + add_test_result(get_test_id(test_id, b, test_name), "ERROR", "Cannot start test", 0); + + if (b->clean) + b->clean(); +} + +static void parse_run_test(const char* tc) { + unsigned int i = 0; + for (i = 0;i < sizeof(tests)/sizeof(struct binary); i++) { + int len = strlen(tests[i].name); + if (strncmp(tc, tests[i].name, len) == 0) { + if (tc[len] == '*' || tc[len] == '\0') + run_test(&tests[i], ""); + else + run_test(&tests[i], tc + len); + } + } +} + +static int parse_option(int argc, char* argv[]) +{ + int ch = 0; + int c = 0; + while ((ch = getopt_long(argc, argv, "lr:d:", long_options, NULL)) != -1) { + switch (ch) { + case 'l': + print_list(NULL); + return 1; + case 'r': + if (c >= MAX_TC_NUM - 1) //NULL at the end + return 0; + + if (optarg) + requested_tc[c++] = optarg; + + break; + case 'd': + print_list(optarg); + return 1; + } + } + return 0; +} + +void add_test_result(const char* test_id, const char* result, const char* comment, int res) +{ + printf("%s;%s;%s\n", test_id, result, comment); + fflush(stdout); +} + +static void prepare_results(void) +{ +} + +int main(int argc, char* argv[]) +{ + unsigned int i; + signal(SIGPIPE, SIG_IGN); + if (parse_option(argc, argv)) + return 0; + + prepare_results(); + + if (!requested_tc[0]) { + for (i = 0;i < sizeof(tests)/sizeof(struct binary); i++) + run_test(&tests[i], ""); + } else { + i = 0; + while(requested_tc[i]) { + parse_run_test(requested_tc[i]); + i++; + } + } + + return 0; +} -- 2.7.4