1 /*****************************************************************************
2 * This file is part of gfxprim library. *
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. *
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. *
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 *
19 * Copyright (C) 2009-2012 Cyril Hrubis <metan@ucw.cz> *
21 *****************************************************************************/
30 #include <sys/types.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
);
64 //TODO: Cleaner solution?
68 snprintf(buf
, sizeof(buf
), "rm -rf '%s'", path
);
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
)
84 /* Fix any funny characters in the test name */
85 snprintf(tmp
, sizeof(tmp
), "%s", name
);
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
));
104 * Copy resources if needed
106 * If resource is directory, copy only it's content.
108 if (res_path
!= NULL
) {
112 if (stat(res_path
, &st
)) {
113 tst_warn("failed to stat resource '%s': %s",
114 res_path
, strerror(errno
));
119 if (S_ISDIR(st
.st_mode
))
122 snprintf(tmp
, sizeof(tmp
), "cp -r '%s'%s '%s'",
123 res_path
, p
, template);
128 tst_warn("failed to copy resource '%s'", res_path
);
135 if (chdir(template)) {
136 tst_warn("chdir(%s) failed: %s", template, strerror(errno
));
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
)];
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
)
166 ret
= read(job
->pipefd
, time
, sizeof(*time
));
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
));
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
)
189 ret
= vsnprintf(buf
+3, sizeof(buf
) - 3, fmt
, va
);
191 ssize_t size
= ret
> 255 ? 255 : ret
+ 1;
195 ((unsigned char*)buf
)[2] = size
;
198 if (write(my_job
->pipefd
, buf
, size
+ 3) != size
+ 3)
199 tst_warn("Failed to write msg to pipe.");
201 tst_warn("tst_report() called from parent, msg: '%s'",
208 int tst_report(int level
, const char *fmt
, ...)
214 ret
= tst_vreport(level
, fmt
, va
);
220 int tst_msg(const char *fmt
, ...)
228 return tst_vreport(TST_MSG
, fmt
, va
);
230 fprintf(stderr
, "MSG: ");
231 ret
= vfprintf(stderr
, fmt
, va
);
237 int tst_warn(const char *fmt
, ...)
245 return tst_vreport(TST_WARN
, fmt
, va
);
247 fprintf(stderr
, "WARN: ");
248 ret
= vfprintf(stderr
, fmt
, va
);
254 int tst_err(const char *fmt
, ...)
262 return tst_vreport(TST_ERR
, fmt
, va
);
264 fprintf(stderr
, "ERR: ");
265 ret
= vfprintf(stderr
, fmt
, va
);
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
;
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};
302 /* Collect the data */
303 for (i
= 0; i
< iter
; i
++) {
304 clock_gettime(CLOCK_PROCESS_CPUTIME_ID
, &cputime_start
);
311 clock_gettime(CLOCK_PROCESS_CPUTIME_ID
, &cputime_stop
);
313 timespec_sub(&cputime_stop
, &cputime_start
, &bench
[i
]);
315 timespec_add(&bench
[i
], &sum
);
319 timespec_div(&sum
, iter
);
321 double sum_d
= timespec_to_double(&sum
);
324 /* And standard deviation */
325 for (i
= 0; i
< iter
; i
++) {
326 double b
= timespec_to_double(&bench
[i
]);
337 double_to_timespec(dev_d
, &dev
);
339 /* Send data to parent */
340 write_timespec(job
, 'M', &sum
);
341 write_timespec(job
, 'V', &dev
);
346 void tst_job_run(struct tst_job
*job
)
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
;
362 tst_warn("pipefd() failed: %s", strerror(errno
));
364 job
->result
= TST_INTERR
;
372 tst_warn("fork() failed: %s", strerror(errno
));
374 job
->result
= TST_INTERR
;
378 job
->pipefd
= pipefd
[1];
383 job
->pipefd
= pipefd
[0];
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();
416 if (job
->test
->bench_iter
) {
417 ret
= tst_job_benchmark(job
);
419 if (job
->test
->flags
& TST_MALLOC_CANARIES
) {
420 tst_malloc_canaries_set(MALLOC_CANARY_BEGIN
);
424 tst_malloc_canaries_set(MALLOC_CANARY_END
);
428 tst_malloc_canaries_set(MALLOC_CANARY_OFF
);
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
)
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);
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",
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
)
492 tst_warn("job_read: Job %s (pid %i) not in running state",
493 job
->test
->name
, job
->pid
);
497 ret
= read(job
->pipefd
, &ch
, 1);
500 tst_warn("job_read: read() failed: %s", strerror(errno
));
503 //TODO: kill the process?
508 /* Child exited => read returns end of file */
510 if (errno
== EAGAIN
) {
511 tst_warn("job_read: read() returned EAGAIN");
521 /* test exited normally */
525 /* cpu consumed time */
527 read_timespec(job
, &job
->cpu_time
);
530 read_timespec(job
, &job
->cpu_time
);
534 read_timespec(job
, &job
->bench_mean
);
537 read_timespec(job
, &job
->bench_var
);
539 /* test message as generated by tst_report() */
541 parent_read_msg(job
);
545 parent_read(job
, &job
->malloc_stats
,
546 sizeof(job
->malloc_stats
));
549 tst_warn("parent: Invalid characters received");
554 void tst_job_collect(struct tst_job
*job
)
558 /* collect the test return status */
559 waitpid(job
->pid
, &status
, 0);
565 if (WIFEXITED(status
)) {
566 job
->result
= WEXITSTATUS(status
);
568 switch (WTERMSIG(status
)) {
570 job
->result
= TST_SIGSEGV
;
573 job
->result
= TST_TIMEOUT
;
576 * Floating point exception, most likely
577 * division by zero (including integer division)
580 job
->result
= TST_FPE
;
583 * abort() called most likely double free or malloc data
587 job
->result
= TST_ABORTED
;
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
)
604 tst_job_collect(job
);