#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>

pid_t pid;
int fds[2];

#define MAX 1024

struct stats {
	int nfailed;
	int nskipped;
	int npassed;
	int nwarned;
	int status[MAX];
};

struct stats s;

char *readbuf = NULL;
int readbuf_sz = 0, readbuf_used = 0;

int die = 0;

#define PASSED 0
#define SKIPPED 1
#define FAILED 2
#define WARNED 3

void handler( int s ) {
	signal( s, SIG_DFL );
	kill( pid, s );
	die = s;
}

void dump() {
	fwrite(readbuf, 1, readbuf_used, stdout);
}

void clear() {
	readbuf_used = 0;
}

void drain() {
	int sz;
	char buf[2048];
	while (1) {
		sz = read(fds[1], buf, 2048);
		if (sz <= 0)
			return;
		if (readbuf_used + sz >= readbuf_sz) {
			readbuf_sz = readbuf_sz ? 2 * readbuf_sz : 4096;
			readbuf = realloc(readbuf, readbuf_sz);
		}
		if (!readbuf)
			exit(205);
		memcpy(readbuf + readbuf_used, buf, sz);
		readbuf_used += sz;
		readbuf[readbuf_used] = 0;
	}
}

void passed(int i, char *f) {
	if (strstr(readbuf, "TEST WARNING")) {
		++s.nwarned;
		s.status[i] = WARNED;
		printf("warnings\n");
	} else {
		++ s.npassed;
		s.status[i] = PASSED;
		printf("passed.\n");
	}
}

void skipped(int i, char *f) {
	++ s.nskipped;
	s.status[i] = SKIPPED;
	printf("skipped.\n");
}

void failed(int i, char *f, int st) {
	++ s.nfailed;
	s.status[i] = FAILED;
	if(die == 2) {
		printf("interrupted.\n");
		return;
	}
	printf("FAILED.\n");
	printf("-- FAILED %s ------------------------------------\n", f);
	dump();
	printf("-- FAILED %s (end) ------------------------------\n", f);
}

void run(int i, char *f) {
	pid = fork();
	if (pid < 0) {
		perror("Fork failed.");
		exit(201);
	} else if (pid == 0) {
		close(0);
		dup2(fds[0], 1);
		dup2(fds[0], 2);
		execlp("bash", "bash", f, NULL);
		perror("execlp");
		fflush(stderr);
		_exit(202);
	} else {
		char buf[128];
		snprintf(buf, 128, "%s ...", f);
		buf[127] = 0;
		printf("Running %-40s ", buf);
		fflush(stdout);
		int st, w;
		while ((w = waitpid(pid, &st, WNOHANG)) == 0) {
			drain();
			usleep(20000);
		}
		if (w != pid) {
			perror("waitpid");
			exit(206);
		}
		drain();
		if (WIFEXITED(st)) {
			if (WEXITSTATUS(st) == 0) {
				passed(i, f);
			} else if (WEXITSTATUS(st) == 200) {
				skipped(i, f);
			} else {
				failed(i, f, st);
			}
		} else {
			failed(i, f, st);
		}
		clear();
	}
}

int main(int argc, char **argv) {
	int i;

	if (argc >= MAX) {
		fprintf(stderr, "Sorry, my head exploded. Please increase MAX.\n");
		exit(1);
	}

	s.nwarned = s.nfailed = s.npassed = s.nskipped = 0;

	char *config = getenv("LVM_TEST_CONFIG"),
	     *config_debug;
	config = config ? config : "";
	asprintf(&config_debug, "%s\n%s\n", config, "log { verbose=4 }");

	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; i < argc; ++ i) {
		run(i, argv[i]);
		if (die)
			break;
	}

	printf("\n## %d tests: %d OK, %d warnings, %d failures; %d skipped\n",
	       s.nwarned + s.npassed + s.nfailed + s.nskipped,
	       s.npassed, s.nwarned, s.nfailed, s.nskipped);

	/* print out a summary */
	if (s.nfailed || s.nskipped) {
		for (i = 1; i < argc; ++ i) {
			switch (s.status[i]) {
			case FAILED:
				printf("FAILED: %s\n", argv[i]);
				break;
			case SKIPPED:
				printf("skipped: %s\n", argv[i]);
				break;
			}
		}
		printf("\n");
		return s.nfailed > 0 || die;
	}
	return die;
}
