loaders: JPG: Fix bussy loop on corrupted file.
[gfxprim.git] / tests / framework / tst_job.c
blobf0607a83fa03b9feced9e43b3958724f99ab8a4d
1 /*****************************************************************************
2 * This file is part of gfxprim library. *
3 * *
4 * Gfxprim is free software; you can redistribute it and/or *
5 * modify it under the terms of the GNU Lesser General Public *
6 * License as published by the Free Software Foundation; either *
7 * version 2.1 of the License, or (at your option) any later version. *
8 * *
9 * Gfxprim is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
12 * Lesser General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU Lesser General Public *
15 * License along with gfxprim; if not, write to the Free Software *
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, *
17 * Boston, MA 02110-1301 USA *
18 * *
19 * Copyright (C) 2009-2012 Cyril Hrubis <metan@ucw.cz> *
20 * *
21 *****************************************************************************/
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <signal.h>
26 #include <ctype.h>
27 #include <errno.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <sys/types.h>
31 #include <sys/wait.h>
32 #include <sys/stat.h>
33 #include <stdarg.h>
34 #include <math.h>
36 #include "tst_test.h"
37 #include "tst_job.h"
38 #include "tst_timespec.h"
41 * Once we child forks to do a job, this points to its job structure.
43 static struct tst_job *my_job = NULL;
45 static int in_child(void)
47 return my_job != NULL;
51 * Removes recursively temporary directory.
53 static void remove_tmpdir(const char *path)
56 * Assert that we are working in /tmp/
58 if (!strncmp("/tmp/", path, sizeof("/tmp/"))) {
59 tst_warn("Path '%s' doesn't start with /tmp/, "
60 "omitting cleanup", path);
61 return;
64 //TODO: Cleaner solution?
65 char buf[256];
66 int ret;
68 snprintf(buf, sizeof(buf), "rm -rf '%s'", path);
69 ret = system(buf);
71 if (ret)
72 tst_warn("Failed to clean temp dir.");
76 * Create temp directory and cd into it, copy resources if needed
78 static void prepare_tmpdir(const char *name, const char *res_path,
79 char *template, size_t size)
81 char tmp[256];
82 int ret;
84 /* Fix any funny characters in the test name */
85 snprintf(tmp, sizeof(tmp), "%s", name);
87 char *s = tmp;
89 while (*s != '\0') {
90 if (!isalnum(*s))
91 *s = '_';
92 s++;
95 /* Create template from test name */
96 snprintf(template, size, "/tmp/test_%s_XXXXXX", tmp);
98 if (mkdtemp(template) == NULL) {
99 tst_warn("mkdtemp(%s) failed: %s", template, strerror(errno));
100 exit(TST_INTERR);
104 * Copy resources if needed
106 * If resource is directory, copy only it's content.
108 if (res_path != NULL) {
109 struct stat st;
110 char *p = "";
112 if (stat(res_path, &st)) {
113 tst_warn("failed to stat resource '%s': %s",
114 res_path, strerror(errno));
115 rmdir(template);
116 exit(TST_INTERR);
119 if (S_ISDIR(st.st_mode))
120 p = "/*";
122 snprintf(tmp, sizeof(tmp), "cp -r '%s'%s '%s'",
123 res_path, p, template);
125 ret = system(tmp);
127 if (ret) {
128 tst_warn("failed to copy resource '%s'", res_path);
129 rmdir(template);
130 exit(TST_INTERR);
135 if (chdir(template)) {
136 tst_warn("chdir(%s) failed: %s", template, strerror(errno));
137 exit(TST_INTERR);
142 * Writes timespec into pipe
144 static void write_timespec(struct tst_job *job, char type,
145 struct timespec *time)
147 char buf[1 + sizeof(struct timespec)];
148 char *ptr = buf;
150 *(ptr++) = type;
152 memcpy(ptr, time, sizeof(*time));
154 if (write(job->pipefd, buf, sizeof(buf)) != sizeof(buf))
155 tst_warn("write(timespec) failed: %s", strerror(errno));
159 * Reads timespec from pipe
161 static void read_timespec(struct tst_job *job, struct timespec *time)
163 int ret;
165 do {
166 ret = read(job->pipefd, time, sizeof(*time));
167 } while (ret == 0);
169 if (ret < 0 || ret != sizeof(*time))
170 tst_warn("read(timespec) failed: %s", strerror(errno));
173 static void child_write(struct tst_job *job, char ch, void *ptr, ssize_t size)
175 if (write(job->pipefd, &ch, 1) != 1)
176 tst_warn("child write() failed: %s", strerror(errno));
178 if (ptr != NULL) {
179 if (write(job->pipefd, ptr, size) != size)
180 tst_warn("child write() failed: %s", strerror(errno));
184 static int tst_vreport(int level, const char *fmt, va_list va)
186 int ret;
187 char buf[258];
189 ret = vsnprintf(buf+3, sizeof(buf) - 3, fmt, va);
191 ssize_t size = ret > 255 ? 255 : ret + 1;
193 buf[0] = 'm';
194 buf[1] = level;
195 ((unsigned char*)buf)[2] = size;
197 if (in_child()) {
198 if (write(my_job->pipefd, buf, size + 3) != size + 3)
199 tst_warn("Failed to write msg to pipe.");
200 } else {
201 tst_warn("tst_report() called from parent, msg: '%s'",
202 buf + 3);
205 return ret;
208 int tst_report(int level, const char *fmt, ...)
210 va_list va;
211 int ret;
213 va_start(va, fmt);
214 ret = tst_vreport(level, fmt, va);
215 va_end(va);
217 return ret;
220 int tst_msg(const char *fmt, ...)
222 va_list va;
223 int ret;
225 va_start(va, fmt);
227 if (in_child())
228 return tst_vreport(TST_MSG, fmt, va);
230 fprintf(stderr, "MSG: ");
231 ret = vfprintf(stderr, fmt, va);
232 va_end(va);
234 return ret;
237 int tst_warn(const char *fmt, ...)
239 va_list va;
240 int ret;
242 va_start(va, fmt);
244 if (in_child())
245 return tst_vreport(TST_WARN, fmt, va);
247 fprintf(stderr, "WARN: ");
248 ret = vfprintf(stderr, fmt, va);
249 va_end(va);
251 return ret;
254 int tst_err(const char *fmt, ...)
256 va_list va;
257 int ret;
259 va_start(va, fmt);
261 if (in_child())
262 return tst_vreport(TST_ERR, fmt, va);
264 fprintf(stderr, "ERR: ");
265 ret = vfprintf(stderr, fmt, va);
266 va_end(va);
268 return ret;
271 static int job_run(struct tst_job *job)
273 int (*fn1)(void) = job->test->tst_fn;
274 int (*fn2)(void*) = job->test->tst_fn;
275 void *data = job->test->data;
277 if (data)
278 return fn2(data);
280 return fn1();
284 * Run benchmark job and compute result
286 static int tst_job_benchmark(struct tst_job *job)
288 unsigned int i, iter = job->test->bench_iter;
289 struct timespec cputime_start;
290 struct timespec cputime_stop;
291 struct timespec bench[iter];
292 struct timespec sum = {.tv_sec = 0, .tv_nsec = 0};
293 struct timespec dev = {.tv_sec = 0, .tv_nsec = 0};
294 int ret;
296 /* Warm up */
297 ret = job_run(job);
299 if (ret)
300 return ret;
302 /* Collect the data */
303 for (i = 0; i < iter; i++) {
304 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cputime_start);
306 ret = job_run(job);
308 if (ret)
309 return ret;
311 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cputime_stop);
313 timespec_sub(&cputime_stop, &cputime_start, &bench[i]);
315 timespec_add(&bench[i], &sum);
318 /* Compute mean */
319 timespec_div(&sum, iter);
321 double sum_d = timespec_to_double(&sum);
322 double dev_d = 0;
324 /* And standard deviation */
325 for (i = 0; i < iter; i++) {
326 double b = timespec_to_double(&bench[i]);
328 b -= sum_d;
329 b = b * b;
331 dev_d += b;
334 dev_d /= iter;
335 dev_d = sqrt(dev_d);
337 double_to_timespec(dev_d, &dev);
339 /* Send data to parent */
340 write_timespec(job, 'M', &sum);
341 write_timespec(job, 'V', &dev);
343 return TST_SUCCESS;
346 void tst_job_run(struct tst_job *job)
348 int ret;
349 char template[256];
350 int pipefd[2];
352 /* Write down starting time of the test */
353 clock_gettime(CLOCK_MONOTONIC, &job->start_time);
355 /* Prepare the test message store */
356 tst_msg_init(&job->store);
358 /* copy benchmark interation */
359 job->bench_iter = job->test->bench_iter;
361 if (pipe(pipefd)) {
362 tst_warn("pipefd() failed: %s", strerror(errno));
363 job->running = 0;
364 job->result = TST_INTERR;
365 return;
368 job->pid = fork();
370 switch (job->pid) {
371 case -1:
372 tst_warn("fork() failed: %s", strerror(errno));
373 job->running = 0;
374 job->result = TST_INTERR;
375 return;
376 case 0:
377 close(pipefd[0]);
378 job->pipefd = pipefd[1];
379 my_job = job;
380 break;
381 default:
382 close(pipefd[1]);
383 job->pipefd = pipefd[0];
384 job->running = 1;
385 return;
388 /* Redirect stderr/stdout TODO: catch its output */
389 // if (freopen("/dev/null", "w", stderr) == NULL)
390 // tst_warn("freopen(stderr) failed: %s", strerror(errno));
392 // if (freopen("/dev/null", "w", stdout) == NULL)
393 // tst_warn("freopen(stdout) failed: %s", strerror(errno));
395 /* Create directory in /tmp/ and chdir into it. */
396 if (job->test->flags & TST_TMPDIR)
397 prepare_tmpdir(job->test->name, job->test->res_path,
398 template, sizeof(template));
401 * If timeout is specified, setup alarm.
403 * If alarm fires the test will be killed by SIGALRM.
405 if (job->test->timeout)
406 alarm(job->test->timeout);
408 /* Send process cpu time to parent */
409 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &job->cpu_time);
410 write_timespec(job, 'c', &job->cpu_time);
412 if (job->test->flags & TST_CHECK_MALLOC)
413 tst_malloc_check_start();
415 /* Run test */
416 if (job->test->bench_iter) {
417 ret = tst_job_benchmark(job);
418 } else {
419 if (job->test->flags & TST_MALLOC_CANARIES) {
420 tst_malloc_canaries_set(MALLOC_CANARY_BEGIN);
421 ret = job_run(job);
423 if (!ret) {
424 tst_malloc_canaries_set(MALLOC_CANARY_END);
425 ret = job_run(job);
428 tst_malloc_canaries_set(MALLOC_CANARY_OFF);
429 } else {
430 ret = job_run(job);
434 if (job->test->flags & TST_CHECK_MALLOC) {
435 tst_malloc_check_stop();
436 tst_malloc_check_report(&job->malloc_stats);
438 child_write(job, 's', &job->malloc_stats,
439 sizeof(job->malloc_stats));
441 if (job->malloc_stats.lost_chunks != 0 && ret == TST_SUCCESS)
442 ret = TST_MEMLEAK;
445 /* Send process cpu time to parent */
446 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &job->cpu_time);
447 write_timespec(job, 'C', &job->cpu_time);
449 /* Cleanup temporary dir recursively */
450 if (job->test->flags & TST_TMPDIR)
451 remove_tmpdir(template);
453 /* Send the parent we are done */
454 child_write(job, 'x', NULL, 0);
456 close(job->pipefd);
458 exit(ret);
461 static void parent_read_msg(struct tst_job *job)
463 unsigned char header[2];
465 if (read(job->pipefd, header, sizeof(header)) != sizeof(header))
466 tst_warn("parent: read(message header) failed: %s",
467 strerror(errno));
469 char buf[header[1]];
471 if (read(job->pipefd, buf, sizeof(buf)) != (ssize_t)sizeof(buf))
472 tst_warn("parent: read(message) failed: %s", strerror(errno));
474 /* null-terminated the string, to be extra sure */
475 buf[header[1] - 1] = '\0';
477 tst_msg_append(&job->store, header[0], buf);
480 static void parent_read(struct tst_job *job, void *ptr, ssize_t size)
482 if (read(job->pipefd, ptr, size) != size)
483 tst_warn("parent: read(): %s", strerror(errno));
486 void tst_job_read(struct tst_job *job)
488 char ch;
489 int ret;
491 if (!job->running)
492 tst_warn("job_read: Job %s (pid %i) not in running state",
493 job->test->name, job->pid);
495 errno = 0;
497 ret = read(job->pipefd, &ch, 1);
499 if (ret < 0) {
500 tst_warn("job_read: read() failed: %s", strerror(errno));
501 job->running = 0;
503 //TODO: kill the process?
505 return;
508 /* Child exited => read returns end of file */
509 if (ret == 0) {
510 if (errno == EAGAIN) {
511 tst_warn("job_read: read() returned EAGAIN");
512 return;
515 job->running = 0;
517 return;
520 switch (ch) {
521 /* test exited normally */
522 case 'x':
523 job->running = 0;
524 break;
525 /* cpu consumed time */
526 case 'c':
527 read_timespec(job, &job->cpu_time);
528 break;
529 case 'C':
530 read_timespec(job, &job->cpu_time);
531 break;
532 /* benchmark data */
533 case 'M':
534 read_timespec(job, &job->bench_mean);
535 break;
536 case 'V':
537 read_timespec(job, &job->bench_var);
538 break;
539 /* test message as generated by tst_report() */
540 case 'm':
541 parent_read_msg(job);
542 break;
543 /* malloc stats */
544 case 's':
545 parent_read(job, &job->malloc_stats,
546 sizeof(job->malloc_stats));
547 break;
548 default:
549 tst_warn("parent: Invalid characters received");
550 break;
554 void tst_job_collect(struct tst_job *job)
556 int status;
558 /* collect the test return status */
559 waitpid(job->pid, &status, 0);
561 close(job->pipefd);
562 job->pipefd = -1;
563 job->pid = -1;
565 if (WIFEXITED(status)) {
566 job->result = WEXITSTATUS(status);
567 } else {
568 switch (WTERMSIG(status)) {
569 case SIGSEGV:
570 job->result = TST_SIGSEGV;
571 break;
572 case SIGALRM:
573 job->result = TST_TIMEOUT;
574 break;
576 * Floating point exception, most likely
577 * division by zero (including integer division)
579 case SIGFPE:
580 job->result = TST_FPE;
581 break;
583 * abort() called most likely double free or malloc data
584 * corruption
586 case SIGABRT:
587 job->result = TST_ABORTED;
588 break;
589 default:
590 tst_warn("Test signaled with %i\n", WTERMSIG(status));
591 job->result = TST_INTERR;
595 /* Write down stop time */
596 clock_gettime(CLOCK_MONOTONIC, &job->stop_time);
599 void tst_job_wait(struct tst_job *job)
601 while (job->running)
602 tst_job_read(job);
604 tst_job_collect(job);