static LIST_HEAD(finished_tests);
bool safe_mode = false;
static struct btree *exclude;
+static bool fastmode = false;
static void usage(const char *name)
{
- fprintf(stderr, "Usage: %s [-s] [-n] [-v] [-d <dirname>]\n"
+ fprintf(stderr, "Usage: %s [-s] [-n] [-v] [-t] [-d <dirname>]\n"
" -v: verbose mode\n"
" -s: simply give one line summary\n"
" -d: use this directory instead of the current one\n"
" -n: do not compile anything\n"
" -l: list tests ccanlint performs\n"
- " -x: exclude tests (e.g. -x trailing_whitespace,valgrind)\n",
+ " -x: exclude tests (e.g. -x trailing_whitespace,valgrind)\n"
+ " -t: ignore (terminate) tests that are slow\n",
name);
exit(1);
}
struct manifest *m)
{
void *result;
- unsigned int this_score;
+ unsigned int this_score, timeleft;
const struct dependent *d;
const char *skip;
d->dependent->num_depends--;
skip = should_skip(m, i);
+
if (skip) {
+ skip:
if (verbose)
printf(" %s: skipped (%s)\n", i->name, skip);
return true;
}
- result = i->check(m);
+ timeleft = fastmode ? 1000 : default_timeout_ms;
+ result = i->check(m, &timeleft);
+ if (fastmode && timeleft == 0) {
+ skip = "timeout";
+ goto skip;
+ }
if (!result) {
if (verbose) {
printf(" %s: OK", i->name);
/* I'd love to use long options, but that's not standard. */
/* FIXME: getopt_long ccan package? */
- while ((c = getopt(argc, argv, "sd:vnlx:")) != -1) {
+ while ((c = getopt(argc, argv, "sd:vnlx:t")) != -1) {
switch (c) {
case 'd':
dir = optarg;
for (i = 0; exclude_strs[i]; i++)
btree_insert(exclude, exclude_strs[i]);
} break;
+ case 't':
+ fastmode = true;
+ break;
default:
usage(argv[0]);
}
/* Can we run this test? Return string explaining why, if not. */
const char *(*can_run)(struct manifest *m);
- /* If this returns non-NULL, it means the check failed. */
- void *(*check)(struct manifest *m);
+ /* If this returns non-NULL, it means the check failed.
+ * If timeleft is set to 0, means it timed out. */
+ void *(*check)(struct manifest *m, unsigned int *timeleft);
/* The non-NULL return from check is passed to one of these: */
return list;
}
-static void *do_build(struct manifest *m)
+static void *do_build(struct manifest *m, unsigned int *timeleft)
{
char *filename, *err;
return NULL;
}
-static void *check_objs_build(struct manifest *m)
+static void *check_objs_build(struct manifest *m, unsigned int *timeleft)
{
char *report = NULL;
struct ccan_file *i;
return ret;
}
-static void *check_use_build(struct manifest *m)
+static void *check_use_build(struct manifest *m, unsigned int *timeleft)
{
char *contents;
char *tmpfile, *err;
return has_c_files;
}
-static void *check_depends_built(struct manifest *m)
+static void *check_depends_built(struct manifest *m, unsigned int *timeleft)
{
struct ccan_file *i;
struct stat st;
return sofar;
}
-static void *check_depends_exist(struct manifest *m)
+static void *check_depends_exist(struct manifest *m, unsigned int *timeleft)
{
unsigned int i;
char *report = NULL;
return NULL;
}
-static void *check_includes_build(struct manifest *m)
+static void *check_includes_build(struct manifest *m, unsigned int *timeleft)
{
char *contents;
char *tmpfile, *err;
return err;
}
-static void *do_compile_test_helpers(struct manifest *m)
+static void *do_compile_test_helpers(struct manifest *m, unsigned int *timeleft)
{
char *cmdout = NULL;
struct ccan_file *i;
const char *output;
};
-static void *do_compile_tests(struct manifest *m)
+static void *do_compile_tests(struct manifest *m, unsigned int *timeleft)
{
struct list_head *list = talloc(m, struct list_head);
char *cmdout;
#include <string.h>
#include <ccan/noerr/noerr.h>
-static void *check_has_info(struct manifest *m)
+static void *check_has_info(struct manifest *m, unsigned int *timeleft)
{
if (m->info_file)
return NULL;
#include <ccan/talloc/talloc.h>
#include <ccan/noerr/noerr.h>
-static void *check_has_main_header(struct manifest *m)
+static void *check_has_main_header(struct manifest *m, unsigned int *timeleft)
{
struct ccan_file *f;
static char test_is_not_dir[] = "test is not a directory";
-static void *check_has_tests(struct manifest *m)
+static void *check_has_tests(struct manifest *m, unsigned int *timeleft)
{
struct stat st;
char *test_dir = talloc_asprintf(m, "%s/test", m->dir);
const char *output;
};
-static void *do_run_tests(struct manifest *m)
+static void *do_run_tests(struct manifest *m, unsigned int *timeleft)
{
struct list_head *list = talloc(m, struct list_head);
struct run_tests_result *res;
list_for_each(&m->run_tests, i, list) {
run_tests.total_score++;
- /* FIXME: timeout here */
- cmdout = run_command(m, i->compiled);
+ cmdout = run_command(m, timeleft, i->compiled);
if (cmdout) {
res = talloc(list, struct run_tests_result);
res->file = i;
list_for_each(&m->api_tests, i, list) {
run_tests.total_score++;
- /* FIXME: timeout here */
- cmdout = run_command(m, i->compiled);
+ cmdout = run_command(m, timeleft, i->compiled);
if (cmdout) {
res = talloc(list, struct run_tests_result);
res->file = i;
bool example;
};
-static void *check_has_info_documentation(struct manifest *m)
+static void *check_has_info_documentation(struct manifest *m,
+ unsigned int *timeleft)
{
struct list_head *infodocs = get_ccan_file_docs(m->info_file);
struct doc_section *d;
return sofar;
}
-static void *check_idempotent(struct manifest *m)
+static void *check_idempotent(struct manifest *m, unsigned int *timeleft)
{
struct ccan_file *f;
char *report = NULL;
/* Note: we already test safe_mode in run_tests.c */
static const char *can_run_vg(struct manifest *m)
{
- char *output = run_command(m, "valgrind -q true");
+ unsigned int timeleft = default_timeout_ms;
+ char *output = run_command(m, &timeleft, "valgrind -q true");
if (output)
return talloc_asprintf(m, "No valgrind support: %s", output);
const char *output;
};
-static void *do_run_tests_vg(struct manifest *m)
+static void *do_run_tests_vg(struct manifest *m, unsigned int *timeleft)
{
struct list_head *list = talloc(m, struct list_head);
struct run_tests_result *res;
list_for_each(&m->run_tests, i, list) {
run_tests_vg.total_score++;
- /* FIXME: timeout here */
- cmdout = run_command(m, "valgrind -q %s", i->compiled);
+ cmdout = run_command(m, timeleft,
+ "valgrind -q %s", i->compiled);
if (cmdout) {
res = talloc(list, struct run_tests_result);
res->file = i;
list_for_each(&m->api_tests, i, list) {
run_tests_vg.total_score++;
- /* FIXME: timeout here */
- cmdout = run_command(m, "valgrind -q %s", i->compiled);
+ cmdout = run_command(m, timeleft,
+ "valgrind -q %s", i->compiled);
if (cmdout) {
res = talloc(list, struct run_tests_result);
res->file = i;
return talloc_asprintf(line, "'%s'", line);
}
-static void *check_trailing_whitespace(struct manifest *m)
+static void *check_trailing_whitespace(struct manifest *m,
+ unsigned int *timeleft)
{
char *report;
{
char *file = temp_file(ctx, ".o");
- *errmsg = run_command(ctx, "ld -r -o %s %s", file, objs);
+ *errmsg = run_command(ctx, NULL, "ld -r -o %s %s", file, objs);
if (*errmsg) {
talloc_free(file);
return NULL;
{
char *file = temp_file(ctx, ".o");
- *errmsg = run_command(ctx, "cc " CFLAGS " -I%s -c -o %s %s",
+ *errmsg = run_command(ctx, NULL, "cc " CFLAGS " -I%s -c -o %s %s",
ccandir, file, cfile);
if (*errmsg) {
talloc_free(file);
{
char *file = temp_file(ctx, "");
- *errmsg = run_command(ctx, "cc " CFLAGS " -I%s %s -o %s %s %s %s",
+ *errmsg = run_command(ctx, NULL, "cc " CFLAGS " -I%s %s -o %s %s %s %s",
ccandir, extra_cflags, file, cfile, objs, libs);
if (*errmsg) {
talloc_free(file);
#include <ccan/grab_file/grab_file.h>
#include <ccan/noerr/noerr.h>
#include <ccan/read_write_all/read_write_all.h>
+#include <ccan/noerr/noerr.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
static char *tmpdir = NULL;
static unsigned int count;
+/* Ten minutes. */
+const unsigned int default_timeout_ms = 10 * 60 * 1000;
+
char *talloc_basename(const void *ctx, const char *dir)
{
char *p = strrchr(dir, '/');
return cwd;
}
+static char *run_with_timeout(const void *ctx,
+ const char *cmd,
+ bool *ok,
+ unsigned *timeout_ms)
+{
+ pid_t pid;
+ int p[2];
+ char *ret;
+ int status, ms;
+ struct timeval start, end;
+
+ *ok = false;
+ if (pipe(p) != 0)
+ return talloc_asprintf(ctx, "Failed to create pipe: %s",
+ strerror(errno));
+
+ pid = fork();
+ if (pid == -1) {
+ close_noerr(p[0]);
+ close_noerr(p[1]);
+ return talloc_asprintf(ctx, "Failed to fork: %s",
+ strerror(errno));
+ return NULL;
+ }
+
+ if (pid == 0) {
+ struct itimerval itim;
+
+ if (dup2(p[1], STDOUT_FILENO) != STDOUT_FILENO
+ || dup2(p[1], STDERR_FILENO) != STDERR_FILENO
+ || close(p[0]) != 0
+ || close(STDIN_FILENO) != 0
+ || open("/dev/null", O_RDONLY) != STDIN_FILENO)
+ exit(128);
+
+ itim.it_interval.tv_sec = itim.it_interval.tv_usec = 0;
+ itim.it_value.tv_sec = *timeout_ms / 1000;
+ itim.it_value.tv_usec = (*timeout_ms % 1000) * 1000;
+ setitimer(ITIMER_REAL, &itim, NULL);
+
+ status = system(cmd);
+ if (WIFEXITED(status))
+ exit(WEXITSTATUS(status));
+ /* Here's a hint... */
+ exit(128 + WTERMSIG(status));
+ }
+
+ close(p[1]);
+ gettimeofday(&start, NULL);
+ ret = grab_fd(ctx, p[0], NULL);
+ /* This shouldn't fail... */
+ if (waitpid(pid, &status, 0) != pid)
+ err(1, "Failed to wait for child");
+
+ gettimeofday(&end, NULL);
+ if (WIFSIGNALED(status)) {
+ *timeout_ms = 0;
+ return ret;
+ }
+ if (end.tv_usec < start.tv_usec) {
+ end.tv_usec += 1000000;
+ end.tv_sec--;
+ }
+ ms = (end.tv_sec - start.tv_sec) * 1000
+ + (end.tv_usec - start.tv_usec) / 1000;
+ if (ms > *timeout_ms)
+ *timeout_ms = 0;
+ else
+ *timeout_ms -= ms;
+
+ *ok = (WEXITSTATUS(status) == 0);
+ return ret;
+}
+
/* Returns output if command fails. */
-char *run_command(const void *ctx, const char *fmt, ...)
+char *run_command(const void *ctx, unsigned int *time_ms, const char *fmt, ...)
{
va_list ap;
char *cmd, *contents;
- FILE *pipe;
+ bool ok;
+ unsigned int default_time = default_timeout_ms;
+
+ if (!time_ms)
+ time_ms = &default_time;
va_start(ap, fmt);
cmd = talloc_vasprintf(ctx, fmt, ap);
va_end(ap);
- /* Ensure stderr gets to us too. */
- cmd = talloc_asprintf_append(cmd, " 2>&1");
-
- pipe = popen(cmd, "r");
- if (!pipe)
- return talloc_asprintf(ctx, "Failed to run '%s'", cmd);
-
- contents = grab_fd(cmd, fileno(pipe), NULL);
- if (pclose(pipe) != 0)
- return talloc_asprintf(ctx, "Running '%s':\n%s",
- cmd, contents);
+ contents = run_with_timeout(ctx, cmd, &ok, time_ms);
+ if (ok) {
+ talloc_free(contents);
+ return NULL;
+ }
- talloc_free(cmd);
- return NULL;
+ if (!contents)
+ err(1, "Problem running child");
+ if (*time_ms == 0)
+ talloc_asprintf_append(contents, "\n== TIMED OUT ==\n");
+ return contents;
}
static int unlink_all(char *dir)
char *talloc_basename(const void *ctx, const char *dir);
char *talloc_dirname(const void *ctx, const char *dir);
char *talloc_getcwd(const void *ctx);
-char *run_command(const void *ctx, const char *fmt, ...);
+char *run_command(const void *ctx, unsigned int *time_ms, const char *fmt, ...);
char *temp_file(const void *ctx, const char *extension);
bool move_file(const char *oldname, const char *newname);
char *compile_and_link(const void *ctx, const char *cfile, const char *ccandir,
const char *objs, const char *extra_cflags,
const char *libs, char **errmsg);
+
+/* Default wait for run_command. Should never time out. */
+extern const unsigned int default_timeout_ms;
+
#endif /* CCAN_TOOLS_H */