[yocto] [ptest-runner 5/8] Add parallelism to TC execution
Jiwei Sun
jiwei.sun at windriver.com
Thu Sep 28 19:09:37 PDT 2017
Kinda only makes sense with JUnit test output.
Signed-off-by: Jiwei Sun <jiwei.sun at windriver.com>
---
ptest_list.c | 16 +--
ptest_list.h | 14 ++-
tests/data/glibc/ptest/run-pptest | 3 +
tests/data/parallel/ptest/run-pptest | 3 +
tests/ptest_list.c | 10 +-
tests/utils.c | 13 ++-
utils.c | 183 +++++++++++++++++++++++++++++++----
utils.h | 2 +-
8 files changed, 207 insertions(+), 37 deletions(-)
create mode 100755 tests/data/glibc/ptest/run-pptest
create mode 100755 tests/data/parallel/ptest/run-pptest
diff --git a/ptest_list.c b/ptest_list.c
index 2e1aa30..8a59383 100644
--- a/ptest_list.c
+++ b/ptest_list.c
@@ -27,8 +27,8 @@
#include "utils.h"
#include "ptest_list.h"
-#define VALIDATE_PTR_RINT(ptr) if (ptr == NULL) { errno = EINVAL; return -1; }
-#define VALIDATE_PTR_RNULL(ptr) if (ptr == NULL) { errno = EINVAL; return NULL; }
+#define VALIDATE_PTR_RINT(ptr) if (ptr == NULL) { errno = EINVAL; return -1; }
+#define VALIDATE_PTR_RNULL(ptr) if (ptr == NULL) { errno = EINVAL; return NULL; }
struct ptest_list *
ptest_list_alloc()
@@ -98,7 +98,7 @@ ptest_list_search(struct ptest_list *head, char *ptest)
VALIDATE_PTR_RNULL(ptest);
for (p = head; p != NULL; p = p->next) {
- if (p->ptest == NULL)
+ if (p->ptest == NULL)
continue;
if (strcmp(p->ptest, ptest) == 0) {
@@ -111,9 +111,12 @@ ptest_list_search(struct ptest_list *head, char *ptest)
}
struct ptest_list *
-ptest_list_add(struct ptest_list *head, char *ptest, char *run_ptest)
+ptest_list_add(struct ptest_list *head,
+ char *ptest,
+ char *run_ptest,
+ int parallel)
{
- struct ptest_list *n, *p;
+ struct ptest_list *n, *p;
VALIDATE_PTR_RNULL(head);
VALIDATE_PTR_RNULL(ptest);
@@ -124,6 +127,7 @@ ptest_list_add(struct ptest_list *head, char *ptest, char *run_ptest)
n->ptest = ptest;
n->run_ptest = run_ptest;
+ n->parallel = parallel;
n->prev = NULL;
n->next = NULL;
@@ -139,7 +143,7 @@ ptest_list_add(struct ptest_list *head, char *ptest, char *run_ptest)
struct ptest_list *
ptest_list_remove(struct ptest_list *head, char *ptest, int free)
{
- struct ptest_list *p;
+ struct ptest_list *p;
struct ptest_list *q, *r;
VALIDATE_PTR_RNULL(head);
diff --git a/ptest_list.h b/ptest_list.h
index 8b39485..e627921 100644
--- a/ptest_list.h
+++ b/ptest_list.h
@@ -25,12 +25,19 @@
#define PTEST_LIST_FREE_CLEAN(x) { ptest_list_free(x); x = NULL; }
#define PTEST_LIST_FREE_ALL_CLEAN(x) { ptest_list_free_all(x); x = NULL; }
-#define PTEST_LIST_ITERATE_START(head, p) for (p = head->next; p != NULL; p = p->next) {
+#define PTEST_LIST_ITERATE_START(head, p) for (p = head->next; p != NULL; p = p->next) {
#define PTEST_LIST_ITERATE_END }
+struct ptest_entry {
+ char *ptest;
+ char *run_ptest;
+ int parallel;
+};
+
struct ptest_list {
char *ptest;
char *run_ptest;
+ int parallel;
struct ptest_list *next;
struct ptest_list *prev;
@@ -42,7 +49,10 @@ extern int ptest_list_free_all(struct ptest_list *);
extern int ptest_list_length(struct ptest_list *);
extern struct ptest_list *ptest_list_search(struct ptest_list *, char *);
-extern struct ptest_list *ptest_list_add(struct ptest_list *, char *, char *);
+extern struct ptest_list *ptest_list_add(struct ptest_list *,
+ char *,
+ char *,
+ int);
extern struct ptest_list *ptest_list_remove(struct ptest_list *, char *, int);
#endif // _PTEST_RUNNER_LIST_H_
diff --git a/tests/data/glibc/ptest/run-pptest b/tests/data/glibc/ptest/run-pptest
new file mode 100755
index 0000000..f7f0316
--- /dev/null
+++ b/tests/data/glibc/ptest/run-pptest
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo "glibc"
diff --git a/tests/data/parallel/ptest/run-pptest b/tests/data/parallel/ptest/run-pptest
new file mode 100755
index 0000000..23a0fff
--- /dev/null
+++ b/tests/data/parallel/ptest/run-pptest
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo "parallel"
diff --git a/tests/ptest_list.c b/tests/ptest_list.c
index 2641620..a02af0e 100644
--- a/tests/ptest_list.c
+++ b/tests/ptest_list.c
@@ -49,7 +49,7 @@ END_TEST
START_TEST(test_add)
{
struct ptest_list *head = ptest_list_alloc();
- ck_assert(ptest_list_add(head, strdup("perl"), NULL) != NULL);
+ ck_assert(ptest_list_add(head, strdup("perl"), NULL, 0) != NULL);
ptest_list_free_all(head);
}
END_TEST
@@ -64,7 +64,7 @@ START_TEST(test_free_all)
head = ptest_list_alloc();
for (i = 0; i < ptests_num; i++)
- ptest_list_add(head, strdup(ptest_names[i]), NULL);
+ ptest_list_add(head, strdup(ptest_names[i]), NULL, 0);
ptest_list_free_all(head);
}
@@ -81,7 +81,7 @@ START_TEST(test_length)
head = ptest_list_alloc();
for (i = 0; i < ptests_num; i++)
- ptest_list_add(head, strdup(ptest_names[i]), NULL);
+ ptest_list_add(head, strdup(ptest_names[i]), NULL, 0);
ck_assert_int_eq(ptest_list_length(head), ptests_num);
ptest_list_free_all(head);
@@ -100,7 +100,7 @@ START_TEST(test_search)
head = ptest_list_alloc();
for (i = 0; i < ptests_num; i++) {
ptest = strdup(ptest_names[i]);
- ptest_list_add(head, ptest, NULL);
+ ptest_list_add(head, ptest, NULL, 0);
}
for (i = ptests_num - 1; i >= 0; i--)
@@ -119,7 +119,7 @@ START_TEST(test_remove)
for (i = 0; i < ptests_num; i++) {
ptest = strdup(ptest_names[i]);
- ptest_list_add(head, ptest, NULL);
+ ptest_list_add(head, ptest, NULL, 0);
}
/* Remove node free'ing */
diff --git a/tests/utils.c b/tests/utils.c
index ecf3e8a..9b1f442 100644
--- a/tests/utils.c
+++ b/tests/utils.c
@@ -40,11 +40,13 @@ static char *ptests_found[] = {
"fail",
"gcc",
"glibc",
+ "glibc",
"hang",
+ "parallel",
"python",
NULL
};
-static int ptests_found_length = 6;
+static int ptests_found_length = 8;
static char *ptests_not_found[] = {
"busybox",
"perl",
@@ -254,10 +256,15 @@ filecmp(FILE *fp1, FILE *fp2)
START_TEST(test_xml_pass)
FILE *xp;
+ struct ptest_entry entry;
+
xp = xml_create(2, "./test.xml");
ck_assert(xp != NULL);
- xml_add_case(xp, 0,"test1");
- xml_add_case(xp, 1,"test2");
+ entry.ptest = "test1";
+ entry.run_ptest = "run-ptest";
+ xml_add_case(xp, 0, &entry);
+ entry.ptest = "test2";
+ xml_add_case(xp, 1, &entry);
xml_finish(xp);
FILE *fp, *fr;
diff --git a/utils.c b/utils.c
index a07faec..056d7b7 100644
--- a/utils.c
+++ b/utils.c
@@ -42,7 +42,7 @@
#define GET_STIME_BUF_SIZE 1024
#define WAIT_CHILD_POLL_TIMEOUT_MS 200
-#define WAIT_CHILD_BUF_MAX_SIZE 1024
+#define WAIT_CHILD_BUF_MAX_SIZE 4096
static inline char *
get_stime(char *stime, size_t size)
@@ -108,7 +108,6 @@ get_available_ptests(const char *dir)
fail = 0;
for (i = 0; i < n; i++) {
char *run_ptest;
-
char *d_name = strdup(namelist[i]->d_name);
CHECK_ALLOCATION(d_name, sizeof(namelist[i]->d_name), 0);
if (d_name == NULL) {
@@ -124,7 +123,7 @@ get_available_ptests(const char *dir)
}
if (asprintf(&run_ptest, "%s/%s/ptest/run-ptest",
- dir, d_name) == -1) {
+ dir, d_name) == -1) {
fail = 1;
saved_errno = errno;
free(d_name);
@@ -133,18 +132,52 @@ get_available_ptests(const char *dir)
if (stat(run_ptest, &st_buf) == -1) {
free(run_ptest);
+ goto parallel;
+ }
+
+ if (!S_ISREG(st_buf.st_mode)) {
+ free(run_ptest);
+ goto parallel;
+ }
+
+ struct ptest_list *p = ptest_list_add(head,
+ d_name,
+ run_ptest,
+ 0);
+
+ CHECK_ALLOCATION(p, sizeof(struct ptest_list *), 0);
+ if (p == NULL) {
+ fail = 1;
+ saved_errno = errno;
+ free(run_ptest);
+ free(d_name);
+ break;
+ }
+
+ parallel:
+ if (asprintf(&run_ptest, "%s/%s/ptest/run-pptest",
+ dir, d_name) == -1) {
+ fail = 1;
+ saved_errno = errno;
free(d_name);
+ break;
+ }
+
+ if (stat(run_ptest, &st_buf) == -1) {
+ free(run_ptest);
continue;
}
if (!S_ISREG(st_buf.st_mode)) {
free(run_ptest);
- free(d_name);
continue;
}
- struct ptest_list *p = ptest_list_add(head,
- d_name, run_ptest);
+ p = ptest_list_add(head,
+ strdup(d_name),
+ run_ptest,
+ 1);
+
CHECK_ALLOCATION(p, sizeof(struct ptest_list *), 0);
if (p == NULL) {
fail = 1;
@@ -162,7 +195,7 @@ get_available_ptests(const char *dir)
if (fail) {
PTEST_LIST_FREE_ALL_CLEAN(head);
errno = saved_errno;
- break;
+ return NULL;
}
} while (0);
@@ -220,7 +253,10 @@ filter_ptests(struct ptest_list *head, char **ptests, int ptest_num)
break;
}
- if (ptest_list_add(head_new, ptest, run_ptest) == NULL) {
+ if (ptest_list_add(head_new,
+ ptest,
+ run_ptest,
+ n->parallel) == NULL) {
saved_errno = errno;
fail = 1;
break;
@@ -288,7 +324,7 @@ wait_child(const char *ptest_dir, const char *run_ptest, pid_t pid,
fwrite(buf, n, 1, fps[1]);
}
- sentinel = time(NULL);
+ sentinel = time(NULL);
} else if (timeout >= 0 && ((time(NULL) - sentinel) > timeout)) {
timeouted = 1;
kill(pid, SIGKILL);
@@ -303,11 +339,11 @@ wait_child(const char *ptest_dir, const char *run_ptest, pid_t pid,
if (timeouted)
fprintf(fps[0], "TIMEOUT: %s ", ptest_dir);
- if(WIFEXITED(status)) {
+ if(WIFEXITED(status) && pid > 0) {
fprintf(fps[0], "\nERROR: Exit status is %d\n", WEXITSTATUS(status));
return WEXITSTATUS(status);
}
- else if(WIFSIGNALED(status)) {
+ else if(WIFSIGNALED(status) && pid > 0) {
fprintf(fps[0], " Killed by signal\n");
return 127;
}
@@ -328,7 +364,7 @@ run_ptests(struct ptest_list *head, const struct ptest_options opts,
struct ptest_list *p;
char stime[GET_STIME_BUF_SIZE];
- pid_t child;
+ pid_t child, pid;
int pipefd_stdout[2];
int pipefd_stderr[2];
@@ -336,6 +372,7 @@ run_ptests(struct ptest_list *head, const struct ptest_options opts,
xh = xml_create(ptest_list_length(head), opts.xml_filename);
if (!xh)
exit(EXIT_FAILURE);
+ fflush(xh);
}
do
@@ -349,9 +386,90 @@ run_ptests(struct ptest_list *head, const struct ptest_options opts,
break;
}
+ fprintf(fp, "START PARALLEL: %s\n", progname);
+ PTEST_LIST_ITERATE_START(head, p);
+ char *ptest_dir;
+ int master;
+
+ if (!p->parallel)
+ continue;
+
+ master = fork();
+ if (master)
+ continue;
+
+ ptest_dir = strdup(p->run_ptest);
+ if (ptest_dir == NULL) {
+ rc = -1;
+ break;
+ }
+ dirname(ptest_dir);
+
+ child = fork();
+ if (child == -1) {
+ fprintf(fp, "ERROR: Fork %s\n", strerror(errno));
+ rc = -1;
+ break;
+ } else if (child == 0) {
+ run_child(p->run_ptest, pipefd_stdout[1], pipefd_stderr[1]);
+ } else {
+ int status;
+ int fds[2]; fds[0] = pipefd_stdout[0]; fds[1] = pipefd_stderr[0];
+ FILE *fps[2]; fps[0] = fp; fps[1] = fp_stderr;
+
+// fprintf(fp, "%s\n", get_stime(stime, GET_STIME_BUF_SIZE));
+ fprintf(fp, "BEGIN: %s\n", ptest_dir);
+
+ status = wait_child(ptest_dir, p->run_ptest, child,
+ opts.timeout, fds, fps);
+ if (status)
+ rc += 1;
+
+ if (opts.xml_filename)
+ xml_add_case(xh,
+ status,
+ (struct ptest_entry *)p);
+
+// fprintf(fp, "END: %s\n", ptest_dir);
+// fprintf(fp, "%s\n", get_stime(stime, GET_STIME_BUF_SIZE));
+ /* Let non-master gracefully terminate */
+ exit(0);
+ }
+ PTEST_LIST_ITERATE_END;
+
+ /* Reap all children before continuing */
+ while (1) {
+ int status;
+ pid = waitpid((pid_t)(-1), &status, WNOHANG);
+
+ /* Child reaped */
+ if (pid > 0) {
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) == 0)
+ continue;
+ }
+ fprintf(fp, "One child failed to do its job pid:%u", pid);
+ exit(-1);
+ }
+
+ /* Still children alive */
+ if (pid == 0) {
+ usleep(100000); /* 100ms */
+ continue;
+ }
+
+ /* No more children to reap */
+ if (pid < 0)
+ break;
+ }
+
fprintf(fp, "START: %s\n", progname);
PTEST_LIST_ITERATE_START(head, p);
- char *ptest_dir = strdup(p->run_ptest);
+ char *ptest_dir;
+ if (p->parallel)
+ continue;
+
+ ptest_dir = strdup(p->run_ptest);
if (ptest_dir == NULL) {
rc = -1;
break;
@@ -379,7 +497,9 @@ run_ptests(struct ptest_list *head, const struct ptest_options opts,
rc += 1;
if (opts.xml_filename)
- xml_add_case(xh, status, ptest_dir);
+ xml_add_case(xh,
+ status,
+ (struct ptest_entry *)p);
fprintf(fp, "END: %s\n", ptest_dir);
fprintf(fp, "%s\n", get_stime(stime, GET_STIME_BUF_SIZE));
@@ -418,17 +538,40 @@ xml_create(int test_count, char *xml_filename)
}
void
-xml_add_case(FILE *xh, int status, const char *ptest_dir)
+xml_add_case(FILE *xh, int status, struct ptest_entry *ptest)
{
- fprintf(xh, "\t<testcase classname='%s' name='run-ptest'>\n", ptest_dir);
+ struct flock lock;
+ int fd;
+ char *bname;
+ fd = fileno(xh);
+ memset (&lock, 0, sizeof(lock));
+ lock.l_type = F_WRLCK;
+ fcntl (fd, F_SETLKW, &lock);
+ bname = strdup(ptest->run_ptest);
+
+ /* fprintf should guarantee atomicity for fprintfs within the same process */
if (status != 0) {
- fprintf(xh, "\t\t<failure type='exit_code'");
- fprintf(xh, " message='run-ptest exited with code: %d'>", status);
- fprintf(xh, "</failure>\n");
+ fprintf(xh, "\t<testcase classname='%s' name='%s'>\n" \
+ "\t\t<failure type='exit_code'" \
+ " message='run-ptest exited with code: %d'>" \
+ "</failure>\n" \
+ "\t</testcase>\n",
+ ptest->ptest,
+ basename(bname),
+ status);
+ }
+ else {
+ fprintf(xh, "\t<testcase classname='%s' name='%s'>\n" \
+ "\t</testcase>\n",
+ ptest->ptest,
+ basename(bname));
}
- fprintf(xh, "\t</testcase>\n");
+ fflush(xh);
+ lock.l_type = F_UNLCK;
+ fcntl (fd, F_SETLKW, &lock);
+ free(bname);
}
void
diff --git a/utils.h b/utils.h
index 8fa20a8..d0ef735 100644
--- a/utils.h
+++ b/utils.h
@@ -47,7 +47,7 @@ extern int run_ptests(struct ptest_list *, const struct ptest_options,
const char *, FILE *, FILE *);
extern FILE *xml_create(int, char *);
-extern void xml_add_case(FILE *, int, const char *);
+extern void xml_add_case(FILE *, int, struct ptest_entry *);
extern void xml_finish(FILE *);
#endif
--
1.8.3.1
More information about the yocto
mailing list