1
0
mirror of git://sourceware.org/git/lvm2.git synced 2025-01-06 17:18:29 +03:00
lvm2/test/lib/harness.c
Zdenek Kabelac 4937df4aed tests: extend harness
Add limit for buffer so if the test is running in some loop
and generating lots of output, the output log gets too large
for browser to display  (at least Firefox is quite confused
to display 300MB logs).

For now limit max output of one test to 32MB.
If there is need to see full log set LVM_TEST_UNLIMITED to
disable interruption of test.

harness now also prints max memory used during test,
it user and system time and amount of read/write operations.
2013-06-11 14:07:51 +02:00

497 lines
12 KiB
C

/*
* Copyright (C) 2010-2013 Red Hat, Inc. All rights reserved.
*
* This file is part of LVM2.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License v.2.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#define _GNU_SOURCE
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h> /* rusage */
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
static pid_t pid;
static int fds[2];
#define MAX 1024
#define MAX_LOG_SIZE (32*1024*1024) /* Default max size of test log */
struct stats {
int nfailed;
int nskipped;
int npassed;
int nknownfail;
int nwarned;
int ninterrupted;
int status[MAX];
};
static struct stats s;
static char *readbuf = NULL;
static size_t readbuf_sz = 0, readbuf_used = 0;
static int die = 0;
static int verbose = 0; /* >1 with timestamps */
static int interactive = 0; /* disable all redirections */
static const char *results;
static unsigned fullbuffer = 0;
static FILE *outfile = NULL;
struct subst {
const char *key;
char *value;
};
static struct subst subst[2];
enum {
UNKNOWN,
PASSED,
SKIPPED,
FAILED,
WARNED,
KNOWNFAIL,
INTERRUPTED,
};
static void handler( int sig ) {
signal( sig, SIG_DFL );
kill( pid, sig );
die = sig;
}
static int outline(FILE *out, char *buf, int start, int force) {
char *from = buf + start;
char *next = strchr(buf + start, '\n');
if (!next && !force) /* not a complete line yet... */
return start;
if (!next)
next = from + strlen(from);
else
++next;
if (!strncmp(from, "@TESTDIR=", 9)) {
subst[0].key = "@TESTDIR@";
subst[0].value = strndup(from + 9, next - from - 9 - 1);
} else if (!strncmp(from, "@PREFIX=", 8)) {
subst[1].key = "@PREFIX@";
subst[1].value = strndup(from + 8, next - from - 8 - 1);
} else {
char *line = strndup(from, next - from);
char *a = line, *b;
do {
int idx = -1;
int i;
b = line + strlen(line);
for ( i = 0; i < 2; ++i ) {
if (subst[i].key) {
// printf("trying: %s -> %s\n", subst[i].value, subst[i].key);
char *stop = strstr(a, subst[i].value);
if (stop && stop < b) {
idx = i;
b = stop;
}
}
}
fwrite(a, 1, b - a, out);
a = b;
if ( idx >= 0 ) {
fprintf(out, "%s", subst[idx].key);
a += strlen(subst[idx].value);
}
} while (b < line + strlen(line));
free(line);
}
return next - buf + (force ? 0 : 1);
}
static void dump(void) {
int counter_last = -1, counter = 0;
while ((counter < (int) readbuf_used) && (counter != counter_last)) {
counter_last = counter;
counter = outline( stdout, readbuf, counter, 1 );
}
}
static void trickle(FILE *out, int *last, int *counter) {
if (*last > (int) readbuf_used) {
*last = -1;
*counter = 0;
}
while ((*counter < (int) readbuf_used) && (*counter != *last)) {
*last = *counter;
*counter = outline( out, readbuf, *counter, 1 );
}
}
static void clear(void) {
readbuf_used = 0;
}
static int64_t _get_time_us(void)
{
struct timeval tv;
(void) gettimeofday(&tv, 0);
return (int64_t) tv.tv_sec * 1000000 + (int64_t) tv.tv_usec;
}
static void _append_buf(const char *buf, size_t len)
{
if ((readbuf_used + len) >= readbuf_sz) {
if ((readbuf_sz >= MAX_LOG_SIZE) &&
!getenv("LVM_TEST_UNLIMITED")) {
if (fullbuffer++ == 0)
kill(pid, SIGINT);
return;
}
readbuf_sz = readbuf_sz ? 2 * readbuf_sz : 4096;
readbuf = realloc(readbuf, readbuf_sz);
}
if (!readbuf)
exit(205);
memcpy(readbuf + readbuf_used, buf, len);
readbuf_used += len;
}
static const char *_append_with_stamp(const char *buf, int stamp)
{
static const char spaces[] = " ";
static int64_t t_last;
static int64_t t_start = 0;
int64_t t_now;
char stamp_buf[32]; /* Bigger to always fit both numbers */
const char *be;
const char *bb = buf;
size_t len;
while ((be = strchr(bb, '\n'))) {
if (stamp++ == 0) {
t_now = _get_time_us();
if (!t_start)
t_start = t_last = t_now;
len = snprintf(stamp_buf, sizeof(stamp_buf),
"%8.3f%8.4f ",
(t_now - t_start) / 1000000.f,
(t_now - t_last) / 1000000.f);
_append_buf(stamp_buf, (len < (sizeof(spaces) - 1)) ?
len : (sizeof(spaces) - 1));
t_last = t_now;
}
_append_buf(bb, be + 1 - bb);
bb = be + 1;
if (stamp > 0 && bb[0])
_append_buf(spaces, sizeof(spaces) - 1);
}
return bb;
}
static void drain(void) {
char buf[4096];
const char *bp;
int stamp = 0;
int sz;
static int stdout_last = -1, stdout_counter = 0;
static int outfile_last = -1, outfile_counter = 0;
while ((sz = read(fds[1], buf, sizeof(buf) - 1)) > 0) {
if (fullbuffer)
continue;
buf[sz] = '\0';
bp = (verbose < 2) ? buf : _append_with_stamp(buf, stamp);
if (sz > (bp - buf)) {
_append_buf(bp, sz - (bp - buf));
stamp = -1; /* unfinished line */
} else
stamp = 0;
readbuf[readbuf_used] = 0;
if (verbose)
trickle(stdout, &stdout_last, &stdout_counter);
if (outfile)
trickle(outfile, &outfile_last, &outfile_counter);
}
}
static const char *duration(time_t start, const struct rusage *usage)
{
static char buf[100];
int t = (int)(time(NULL) - start);
int p = sprintf(buf, "%2d:%02d", t / 60, t % 60);
if (usage)
sprintf(buf + p, " %2ld:%02ld.%03ld/%ld:%02ld.%03ld%5ld%8ld/%ld",
usage->ru_utime.tv_sec / 60, usage->ru_utime.tv_sec % 60,
usage->ru_utime.tv_usec / 1000,
usage->ru_stime.tv_sec / 60, usage->ru_stime.tv_sec % 60,
usage->ru_stime.tv_usec / 1000,
usage->ru_maxrss / 1024,
usage->ru_inblock, usage->ru_oublock);
return buf;
}
static void passed(int i, char *f, time_t t, const struct rusage *usage) {
if (readbuf && strstr(readbuf, "TEST EXPECT FAIL")) {
++ s.npassed;
s.status[i] = PASSED;
printf("passed (UNEXPECTED). %s\n", duration(t, usage));
} else if (readbuf && strstr(readbuf, "TEST WARNING")) {
++s.nwarned;
s.status[i] = WARNED;
printf("warnings %s\n", duration(t, usage));
} else {
++ s.npassed;
s.status[i] = PASSED;
printf("passed. %s\n", duration(t, usage));
}
}
static void interrupted(int i, char *f) {
++ s.ninterrupted;
s.status[i] = INTERRUPTED;
printf("\ninterrupted.\n");
if (!verbose && fullbuffer) {
printf("-- Interrupted %s ------------------------------------\n", f);
dump();
printf("\n-- Interrupted %s (end) ------------------------------\n", f);
}
}
static void skipped(int i, char *f) {
++ s.nskipped;
s.status[i] = SKIPPED;
printf("skipped.\n");
}
static void failed(int i, char *f, int st) {
if (readbuf && strstr(readbuf, "TEST EXPECT FAIL")) {
printf("FAILED (expected).\n");
s.status[i] = KNOWNFAIL;
++ s.nknownfail;
return;
}
++ s.nfailed;
s.status[i] = FAILED;
printf("FAILED.\n");
if (!verbose && readbuf) {
printf("-- FAILED %s ------------------------------------\n", f);
dump();
printf("-- FAILED %s (end) ------------------------------\n", f);
}
}
static void run(int i, char *f) {
struct rusage usage;
char flavour[512], script[512];
pid = fork();
if (pid < 0) {
perror("Fork failed.");
exit(201);
} else if (pid == 0) {
if (!interactive) {
close(0);
dup2(fds[0], 1);
dup2(fds[0], 2);
close(fds[0]);
close(fds[1]);
}
if (strchr(f, ':')) {
strcpy(flavour, f);
*strchr(flavour, ':') = 0;
setenv("LVM_TEST_FLAVOUR", flavour, 1);
strcpy(script, strchr(f, ':') + 1);
} else {
strcpy(script, f);
}
execlp("bash", "bash", "-noprofile", "-norc", script, NULL);
perror("execlp");
fflush(stderr);
_exit(202);
} else {
int st = -1, w;
time_t start = time(NULL);
char buf[128];
char outpath[PATH_MAX];
char *c = outpath + strlen(results) + 1;
struct timeval timeout;
fd_set master_set, copy_set;
snprintf(buf, sizeof(buf), "%s ...", f);
printf("Running %-60s ", buf);
fflush(stdout);
snprintf(outpath, sizeof(outpath), "%s/%s.txt", results, f);
while ((c = strchr(c, '/')))
*c = '_';
if (!(outfile = fopen(outpath, "w")))
perror("fopen");
FD_ZERO(&master_set);
FD_SET(fds[1], &master_set);
while ((w = wait4(pid, &st, WNOHANG, &usage)) == 0) {
if (fullbuffer && fullbuffer++ == 8000)
/* Output doesn't won't to stop... */
kill(pid, SIGKILL);
memcpy(&copy_set, &master_set, sizeof(master_set));
timeout.tv_sec = 0;
timeout.tv_usec = 500000;
if (select(fds[1] + 1, &copy_set, NULL, NULL, &timeout) <= 0)
continue;
drain();
}
if (w != pid) {
perror("waitpid");
exit(206);
}
drain();
if (fullbuffer || (die == 2))
interrupted(i, f);
else if (WIFEXITED(st)) {
if (WEXITSTATUS(st) == 0)
passed(i, f, start, &usage);
else if (WEXITSTATUS(st) == 200)
skipped(i, f);
else
failed(i, f, st);
} else
failed(i, f, st);
clear();
if (outfile)
fclose(outfile);
if (fullbuffer)
printf("\nTest was interrupted, output has got too large (>%u) (loop:%u)\n"
"Set LVM_TEST_UNLIMITED=1 for unlimited log.\n",
(unsigned) readbuf_sz, fullbuffer);
}
}
int main(int argc, char **argv) {
char results_list[PATH_MAX];
const char *result;
const char *be_verbose = getenv("VERBOSE"),
*be_interactive = getenv("INTERACTIVE");
time_t start = time(NULL);
int i;
FILE *list;
if (argc >= MAX) {
fprintf(stderr, "Sorry, my head exploded. Please increase MAX.\n");
exit(1);
}
if (be_verbose)
verbose = atoi(be_verbose);
if (be_interactive)
interactive = atoi(be_interactive);
results = getenv("LVM_TEST_RESULTS") ? : "results";
(void) snprintf(results_list, sizeof(results_list), "%s/list", results);
if (socketpair(PF_UNIX, SOCK_STREAM, 0, fds)) {
perror("socketpair");
return 201;
}
if (fcntl(fds[1], F_SETFL, O_NONBLOCK ) == -1) {
perror("fcntl on socket");
return 202;
}
/* set up signal handlers */
for (i = 0; i <= 32; ++i) {
if (i == SIGCHLD || i == SIGWINCH || i == SIGURG)
continue;
signal(i, handler);
}
/* run the tests */
for (i = 1; !die && i < argc; ++i)
run(i, argv[i]);
printf("\n## %d tests %s : %d OK, %d warnings, %d failures, %d known failures; "
"%d skipped, %d interrupted\n",
s.nwarned + s.npassed + s.nfailed + s.nskipped + s.ninterrupted,
duration(start, NULL),
s.npassed, s.nwarned, s.nfailed, s.nknownfail,
s.nskipped, s.ninterrupted);
/* dump a list to results */
if ((list = fopen(results_list, "w"))) {
for (i = 1; i < argc; ++ i) {
switch (s.status[i]) {
case PASSED: result = "passed"; break;
case FAILED: result = "failed"; break;
case SKIPPED: result = "skipped"; break;
case WARNED: result = "warnings"; break;
case INTERRUPTED: result = "interrupted"; break;
default: result = "unknown"; break;
}
fprintf(list, "%s %s\n", argv[i], result);
}
fclose(list);
} else
perror("fopen result");
/* print out a summary */
if (s.nfailed || s.nskipped || s.nknownfail || s.ninterrupted) {
for (i = 1; i < argc; ++ i) {
switch (s.status[i]) {
case FAILED:
printf("FAILED: %s\n", argv[i]);
break;
case KNOWNFAIL:
printf("FAILED (expected): %s\n", argv[i]);
break;
case SKIPPED:
printf("skipped: %s\n", argv[i]);
break;
case INTERRUPTED:
printf("interrupted: %s\n", argv[i]);
break;
default: /* do nothing */ ;
}
}
printf("\n");
return (s.nfailed > 0) || (s.ninterrupted > 0) || die;
}
free(readbuf);
return die;
}