view dstress.c @ 630:ecb96b2bf5cb

fixed module declaration
author thomask
date Sun, 14 Aug 2005 08:38:17 +0000
parents 7acb8b34c87a
children 653cbe20c878
line wrap: on
line source

/*
 * core test tool for the DStress test suite
 * http://dstress.kuehne.cn
 *
 * Copyright (C) 2005 Thomas Kuehne <thomas@kuehne.cn>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * 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
 *
 * $HeadURL$
 * $Date$
 * $Author$
 *
 */

/* Beware: the code doesn't care about freeing allocated memory etc. . .. */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#define OBJ		"-odobj "
#define TLOG		"log.tmp"
#define CRASH_RUN	"./crashRun__"
#define GDB_SCRIPTER	"./gdb.txt"

#define RUN		1
#define NORUN		2
#define COMPILE		4
#define NOCOMPILE	8

/* secure malloc */
void *xmalloc(size_t size){
	void *p;
	if (p < 0){
		fprintf(stderr,"Failed to allocate %zd bytes!\n", size);
		exit(EXIT_FAILURE);
	}
	p = malloc(size);
	if (p == NULL){
		fprintf(stderr,"Failed to allocate %zd bytes!\n", size);
		exit(EXIT_FAILURE);
	}
	return p;
}
#define malloc xmalloc

#if defined(__GNU_LIBRARY__) || defined(__GLIBC__)
#define USE_POSIX
#endif

#ifdef linux
#define USE_POSIX
#endif

#if defined(__APPLE__) && defined(__MACH__)
#define USE_POSIX
#endif

#ifdef __FreeBSD__
#define USE_POSIX
#endif


#ifdef USE_POSIX

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <regex.h>

#else
#ifdef WIN32

#include <windows.h>

#endif /* WIN 32 */
#endif /* USE_POSIX else */

char* errorMsg(int good_error){
	return (good_error) ? ("") : " [bad error message]";
}

char* gdbMsg(int good_gdb){
	return (good_gdb) ? ("") : " [bad debugger message]";
}

char* strip(char* buffer){
	if(buffer!=NULL){
		while(isspace(buffer[0])){
			buffer++;
		}

		char* tmp;
		for(tmp=buffer+strlen(buffer)-1; isspace(tmp[0]); tmp=buffer+strlen(buffer)-1){
			tmp[0]='\x00';
		}
	}
	return buffer;
}

/* cleanup "/" versus "\" in filenames */
char* cleanPathSeperator(char* filename){
	char* pos;
#ifdef USE_POSIX
	for(pos=strchr(filename, '\\'); pos; pos=strchr(filename, '\\')){
		*pos='/';
	}
#else
#if WIN32
	for(pos=strchr(filename, '/'); pos; pos=strchr(filename, '/')){
		*pos='\\';
	}
#else

#error no cleanPathSeperator available for this system

#endif /* WIN32 else */
#endif /* USE_POSIX else */
	return filename;
}

char* loadFile(char* filename){
#ifdef USE_POSIX
	char* back = NULL;
	struct stat fileInfo;
	int file = open(filename, O_RDONLY);
	if(errno == 0 && file != 0 && file != -1){
		if(0==fstat(file, &fileInfo)){
			back=malloc(fileInfo.st_size+1);
			fileInfo.st_size = read(file, back, fileInfo.st_size);
			if(fileInfo.st_size>=0){
				*(back+fileInfo.st_size+1) = '\x00';
			}else{
				back = NULL;
			}
		}
		close(file);
	}

	if(back){
		return back;
	}
	if(0==strcmp(filename, TLOG)){
		return calloc(1,sizeof(char));
	}

	fprintf(stderr, "File not found \"%s\" (%s)\n", filename, strerror(errno));
	exit(EXIT_FAILURE);
#else /* USE_POSIX */
#ifdef WIN32

	char* back=NULL;
	DWORD size, numread;
	HANDLE file=CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL,
		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
	if (file != INVALID_HANDLE_VALUE){
		size = GetFileSize(file, NULL);
		if (size != INVALID_FILE_SIZE){
			back=malloc((size+1)*sizeof(char));
			if (ReadFile(file,back,size,&numread,NULL) == 1){
				if (numread==size){
					*(back+size+1) = '\x00';
				}else{
					back = NULL;
				}
			}else{
				back = NULL;
			}
		}
		CloseHandle(file);
	}
	if(back){
		errno = 0;
		return back;
	}

	fprintf(stderr, "File not found \"%s\"\n", filename);
	exit(EXIT_FAILURE);
#else /* WIN32 */

#error "no loadFile implementation present"

#endif /* WIN32 else */
#endif /* USE_POSIX else */
}

void writeFile(const char* filename, const char* content){
	size_t len = strlen(content);
	FILE* file = fopen(filename, "w+");

	if(errno == 0 && file != NULL){
		if((fwrite(content, sizeof(char), len, file) != len) || (errno != 0)){
		    fprintf(stderr, "failed to write file \"%s\" (%s)\n", filename, strerror(errno));
		    exit(EXIT_FAILURE);
		}
		if(fclose(file) || (errno != 0)){
		    fprintf(stderr, "failed to close file \"%s\" (%s)\n", filename, strerror(errno));
		    exit(EXIT_FAILURE);
		}
		return;
	}

	fprintf(stderr, "couldn't open file \"%s\" for writing (%s)\n", filename, strerror(errno));
	exit(EXIT_FAILURE);
}

/* query the environment for the compiler name */
char* getCompiler(){
	char* back = getenv("DMD");
	if(back == NULL){
		back = getenv("dmd");
		if(back==NULL){
			back = "dmd";
		}
	}
	return strip(cleanPathSeperator(back));
}

/* query the environment for the debugger name */
char* getGDB(){
	char* back = getenv("GDB");
	if(back == NULL){
		back = getenv("gdb");
		if(back == NULL){
			back = "gdb";
		}
	}
	return strip(cleanPathSeperator(back));
}

/* query the environment for general flags */
char* getGeneralFlags(){
	char* back = getenv("DFLAGS");
	if(back == NULL){
		back = getenv("dflags");
		if(back==NULL){
			 back = calloc(1,1);
		}
	}
	return strip(cleanPathSeperator(back));
}


/* extract the FIRST occurance of a given TAG until the next linebreak */
char* getCaseFlag(const char* data, const char* tag){
	char* begin;
	char* end1;
	char* end2;
	char* back;

	if(data!=NULL && tag!=NULL){
		begin = strstr(data, tag);
		if(begin!=NULL){
			begin = begin+strlen(tag);
			end1 = strstr(begin, "\n");
			end2 = strstr(begin, "\r");
			if(end2!=NULL && ((end1!=NULL && end2<end1) || end1==NULL)){
				end1=end2;
			}
			if(end1==NULL){
				end1 = begin + strlen(begin);
			}
			back = malloc(end1-begin+1);
			strncpy(back, begin, end1-begin);
			back[end1-begin+1]='\x00';
			return strip(cleanPathSeperator(back));
		}
	}

	return calloc(1,1);
}

/* check compile-time error messages */
int checkErrorMessage(const char* file_, const char* line_, const char* buffer){

	char* file;
	char* line;
	char* dmd;
	char* gdc;

	int back=0;

	/* clean arguments */
	if(strcmp(file_, "")!=0){
		file = malloc(strlen(file_)+1);
		strcpy(file, file_);
	}else{
		file=NULL;
	}

	if(strcmp(line_, "")!=0){
		line = malloc(strlen(line_)+1);
		strcpy(line, line_);
	}else{
		line=NULL;
	}

	/* gen patterns*/
	if(file!=NULL){
		if(line!=NULL){
			dmd = malloc(strlen(file)+strlen(line)+5);
			sprintf(dmd, "%s(%s)", file, line);

			gdc = malloc(strlen(file)+strlen(line)+4);
			sprintf(gdc, "%s:%s: ", file, line);
		}else{
			dmd = malloc(strlen(file)+2);
			sprintf(dmd, "%s(", file);

			gdc = malloc(strlen(file)+2);
			sprintf(gdc, "%s:", file);
		}
	}else if(line!=NULL){
		dmd = malloc(strlen(line)+5);
		sprintf(dmd, "(%s): ", line);

		gdc = malloc(strlen(line)+4);
		sprintf(gdc, ":%s: ", line);
	}else{
		return 1;
	}

	/* specific error messages */

	if( (dmd!=NULL && strstr(buffer, dmd))
			|| (gdc!=NULL && strstr(buffer, gdc))
			|| (dmd==NULL && gdc==NULL)){
		back=1;
	}

	return back;
}

int checkRuntimeErrorMessage(const char* file_, const char* line_, const char* buffer){
	/* PhobosLong	dir/file.d(2)
	 * Phobos	package.module(2)
	 */

	char* file;
	char* line;
	char* phobos;
	char* phobosLong;

	char* begin;
	char* end;

	int back=0;

	/* clean arguments */
	if(strcmp(file_, "")!=0){
		file = malloc(strlen(file_)+1);
		strcpy(file, file_);
	}else{
		file=NULL;
	}

	if(strcmp(line_, "")!=0){
		line = malloc(strlen(line_)+1);
		strcpy(line, line_);
	}else{
		line=NULL;
	}

	/* gen patterns*/
	if(file!=NULL){
		if(line!=NULL){
			phobos = malloc(strlen(file)+strlen(line)+5);
			phobos[0]='\x00';
			begin=strrchr(file,'/');
			if(begin){
				begin++;
			}else{
				begin=strrchr(file,'\\');
				if(begin){
					begin++;
				}else{
					begin=file;
				}
			}
			end=strrchr(file,'.');
			strncat(phobos, begin, end-begin);
			strcat(phobos, "(");
			strcat(phobos, line);
			strcat(phobos, ")");

			phobosLong = malloc(strlen(file)+strlen(line)+5);
			sprintf(phobosLong, "%s(%s)", file, line);
		}else{
			phobos = malloc(strlen(file)+2);
			phobos[0]='\x00';
			begin=strrchr(file,'/');
			if(begin){
				begin++;
			}else{
				begin=strrchr(file,'\\');
				if(begin){
					begin++;
				}else{
					begin=file;
				}
			}
			end=strrchr(file,'.');
			strncat(phobos, begin, end-begin);
			strcat(phobos, "(");

			phobosLong = malloc(strlen(file)+2);
			sprintf(phobosLong, "%s(", file);
		}
	}else if(line!=NULL){
		phobos = malloc(strlen(line)+3);
		sprintf(phobos, "(%s)", line);

		phobosLong=NULL;
	}else{
		return 1;
	}

	/* specific error messages */

	if( (phobos && strstr(buffer, phobos))
		|| (phobosLong && strstr(buffer, phobosLong)))
	{
		back=1;
	}

	return back;
}

int hadExecCrash(const char* buffer){
	if(strstr(buffer, "Segmentation fault")
			|| strstr(buffer, "Internal error")
			|| strstr(buffer, "gcc.gnu.org/bugs")
			|| strstr(buffer, "EXIT CODE: signal"))
	{
		return 1;
	}
	return 0;
}

/* system call with time-out */
int crashRun(const char* cmd){
#ifdef USE_POSIX
	char* buffer=malloc(4+strlen(CRASH_RUN)+strlen(cmd));
	sprintf(buffer, "\"%s\" %s", CRASH_RUN, cmd);
	system(buffer);
	buffer=loadFile(TLOG);

	if(strstr(buffer, "EXIT CODE: 0")){
		return EXIT_SUCCESS;
	}else if(strstr(buffer, "EXIT CODE: 256")
			|| strstr(buffer, "EXIT CODE: timeout"))
	{
		return EXIT_FAILURE;
	}else{
		return RAND_MAX;
	}
#else

//#error comment me out, if your test cases produce neither eternal loops nor Access Violations
	return system(cmd);

#endif /* USE_POSIX else */
}


int main(int argc, char* arg[]){
	char* compiler;		/* the compiler - from enviroment flag "DMD" */
	char* cmd_arg_general;	/* additional arguments - from enviroment flag "DFLAGS" */
	char* cmd_arg_case;	/* additional arguments - from the testcase file */
	char* buffer;		/* general purpose buffer */
	int modus;		/* test modus: RUN NORUN COMPILE NOCOMPILE */
	int res;		/* return code from external executions */
	char* case_file;
	char* error_file;	/* expected sourcefile containing the error */
	char* error_line;	/* expected error line */
	int good_error;		/* error contained file and line and matched the expectations */
	char* gdb;		/* the debugger - from environment flag "GDB" */
	char* gdb_script;	/* gdb command sequence */
	char* gdb_pattern_raw;	/* POSIX regexp expected in GDB's output */
#ifdef REG_EXTENDED
	regex_t* gdb_pattern;
#endif
	int good_gdb;		/* (gdb test and positive) or (no gdb test)*/

	/* check arguments */
	if(argc != 3){
err:
		if(argc!=0)
			fprintf(stderr,"%s <run|norun|compile|nocompile> <source>\n", arg[0]);
		else
			fprintf(stderr,"dstress <run|norun|compile|nocompile> <source>\n");
		exit(EXIT_FAILURE);
	}

	if(0==strcmp(arg[1], "run") || 0==strcmp(arg[1], "RUN")){
		modus = RUN;
	}else if(0==strcmp(arg[1], "norun") || 0==strcmp(arg[1], "NORUN")){
		modus = NORUN;
	}else if(0==strcmp(arg[1], "compile") || 0==strcmp(arg[1], "COMPILE")){
		modus = COMPILE;
	}else if(0==strcmp(arg[1], "nocompile") || 0==strcmp(arg[1], "NOCOMPILE")){
		modus = NOCOMPILE;
	}else{
		goto err;
	}

	/* gen flags */
	case_file = cleanPathSeperator(strdup(arg[2]));
	compiler = getCompiler();
	cmd_arg_general = getGeneralFlags();
	gdb = getGDB();
	buffer = loadFile(case_file);

	cmd_arg_case = getCaseFlag(buffer, "__DSTRESS_DFLAGS__");
	error_line = getCaseFlag(buffer, "__DSTRESS_ELINE__");
	error_file = getCaseFlag(buffer, "__DSTRESS_EFILE__");
	gdb_script = getCaseFlag(buffer, "__GDB_SCRIPT__");
	gdb_pattern_raw = getCaseFlag(buffer, "__GDB_PATTERN__");


	/* set implicit source file */
	if(strcmp(error_line, "")!=0 && strcmp(error_file, "")==0){
		error_file=case_file;
	}

	/* gdb pattern */
#ifdef REG_EXTENDED
	if(gdb_pattern_raw!=NULL && gdb_pattern_raw[0]!='\x00'){

		gdb_pattern = malloc(sizeof(regex_t));
		if(regcomp(gdb_pattern, gdb_pattern_raw, REG_EXTENDED)){
			fprintf(stderr, "failed to compile regular expression:\n\t%s\n", gdb_pattern_raw);
			exit(EXIT_FAILURE);
		}else if(gdb_script==NULL){
			fprintf(stderr, "GDB pattern without GDB script\n");
			exit(EXIT_FAILURE);
		}
	}else{
		gdb_pattern = NULL;
	}

	/* gdb script */
	if(gdb_script!=NULL && gdb_script[0]!='\x00'){
		if(gdb_pattern==NULL){
			fprintf(stderr, "GDB script without GDB pattern\n");
			exit(EXIT_FAILURE);
		}
		buffer=gdb_script;
		for(; *buffer; buffer++){
			if(buffer[0]=='\\'){
				if(buffer[1]=='n'){
					buffer[0]=' ';
					buffer[1]='\n';
				}
				buffer++;
			}
		}

		buffer=malloc(strlen(gdb_script)+10);
		strcpy(buffer, gdb_script);
		gdb_script=strcat(buffer, "\n\nquit\ny\n\n");
		good_gdb = 0;
	}else{
	    good_gdb = 1;
	    gdb_script = NULL;
	}

#else

	if(gdb_script && strlen(gdb_script) && gdb_pattern_raw && strlen(gdb_pattern_raw)){
		fprintf(stderr, "WARNING: regex support inactive\n");
	}else if(gdb_script && strlen(gdb_script)){
		fprintf(stderr, "GDB script without GDB pattern\n");
		exit(EXIT_FAILURE);
	}else if(gdb_pattern_raw && strlen(gdb_pattern_raw)){
		fprintf(stderr, "GDB pattern without GDB script\n");
		exit(EXIT_FAILURE);
	}

#endif /* REG_EXTENDED else */



#ifdef DEBUG
	fprintf(stderr, "case:     \"%s\"\n", case_file);
	fprintf(stderr, "compiler: \"%s\"\n", compiler);
	fprintf(stderr, "DFLAGS G: \"%s\"\n", cmd_arg_general);
	fprintf(stderr, "DFLAGS C: \"%s\"\n", cmd_arg_case);
	fprintf(stderr, "ELINE   : \"%s\"\n", error_line);
	fprintf(stderr, "EFILE   : \"%s\"\n", error_file);
	fprintf(stderr, "GDB Scri: \"%s\"\n", gdb_script);
	fprintf(stderr, "GDB Patt: \"%s\"\n", gdb_pattern_raw);
#endif

	/* start working */
	if(modus==COMPILE || modus==NOCOMPILE){
		/* gen command */
		buffer = malloc(strlen(compiler)+strlen(cmd_arg_general)+strlen(cmd_arg_case)+strlen(OBJ)
			+strlen(case_file)+strlen(TLOG)+64);
		buffer[0]='\x00';
		strcat(buffer, compiler);
		strcat(buffer, " ");
		strcat(buffer, cmd_arg_general);
		strcat(buffer, " ");
		strcat(buffer, cmd_arg_case);
		strcat(buffer, " -c ");
		if(NULL==strstr(buffer, "-od")){
			strcat(buffer, OBJ);
			strcat(buffer, " ");
		}
		strcat(buffer, case_file);
		strcat(buffer, " 1> ");
		strcat(buffer, TLOG);
		strcat(buffer, " 2>&1");

		/* test */
		if(modus==COMPILE){
			fprintf(stderr, "compile: %s\n", buffer);
		}else{
			fprintf(stderr, "nocompile: %s\n", buffer);
		}
		res = crashRun(buffer);

		/* diagnostic */
		buffer = loadFile(TLOG);
		fprintf(stderr, "%s\n", buffer);
		good_error = checkErrorMessage(error_file, error_line, buffer);

		if(hadExecCrash(buffer)){
			printf("ERROR:\t%s [internal compiler error]\n", case_file);
		}else if(modus==COMPILE){
			if(res==EXIT_SUCCESS){
				printf("PASS: \t%s\n", case_file);
			}else if(res==EXIT_FAILURE && good_error){
				if(checkErrorMessage(case_file, "", buffer)){
					printf("FAIL: \t%s\n", case_file);
				}else{
					printf("ERROR:\t%s%s\n", case_file, errorMsg(good_error));
				}
			}else{
				printf("ERROR:\t%s%s\n", case_file, errorMsg(good_error));
			}
		}else{
			if(res==EXIT_FAILURE){
				if(good_error){
					printf("XFAIL:\t%s\n", case_file);
				}else{
					printf("FAIL: \t%s%s\n", case_file, errorMsg(good_error));
				}
			}else if(res==EXIT_SUCCESS){
				printf("XPASS:\t%s\n", case_file);
			}else{
				printf("ERROR:\t%s\n", case_file);
			}
		}
		fprintf(stderr,"--------\n");
	}else if(modus==RUN || modus==NORUN){
		/* gen command */
		buffer = malloc(strlen(compiler)+strlen(cmd_arg_general)+strlen(cmd_arg_case)+strlen(OBJ)
			+strlen(case_file)*2+strlen(TLOG)+64);
		strcpy(buffer, compiler);
		strcat(buffer, " ");
		strcat(buffer, cmd_arg_general);
		strcat(buffer, " ");
		strcat(buffer, cmd_arg_case);
		strcat(buffer, " ");
		if(NULL==strstr(buffer, "-od")){
			strcat(buffer, OBJ);
			strcat(buffer, " ");
		}
		if(NULL==strstr(buffer, "-of")){
			strcat(buffer, "-of");
			strcat(buffer, case_file);
			strcat(buffer, ".exe ");
		}
		strcat(buffer, case_file);
		strcat(buffer, " 1> ");
		strcat(buffer, TLOG);
		strcat(buffer, " 2>&1");

		/* test 1/3 - compile */
		if(modus==RUN){
			fprintf(stderr, "run: %s\n", buffer);
		}else{
			fprintf(stderr, "norun: %s\n", buffer);
		}
		res = crashRun(buffer);

		/* diagnostic 1/3 */
		buffer = loadFile(TLOG);
		fprintf(stderr, "%s", buffer);
		good_error = checkErrorMessage(error_file, error_line, buffer);
		if(hadExecCrash(buffer)){
			printf("ERROR:\t%s [internal compiler error]\n", case_file);
			fprintf(stderr, "\n--------\n");
			return  EXIT_SUCCESS;
		}else if(res==EXIT_FAILURE && good_error){
			printf("FAIL: \t%s\n", case_file);
			fprintf(stderr, "\n--------\n");
			return  EXIT_SUCCESS;
		}else if(res!=EXIT_SUCCESS){
			printf("ERROR:\t%s%s\n", case_file, errorMsg(good_error));
			fprintf(stderr, "\n--------\n");
			return  EXIT_SUCCESS;
		}

		/* test 2/3 - run */
		buffer = malloc(strlen(case_file) + strlen(TLOG) + 30);
		sprintf(buffer, "%s.exe 1> %s 2>&1\x00\n", case_file, TLOG);
		fprintf(stderr, "%s\n", buffer);
		res=crashRun(buffer);

		/* diagnostic 2/3 */
		buffer = loadFile(TLOG);
		fprintf(stderr, "%s\n", buffer);
		good_error = checkRuntimeErrorMessage(error_file, error_line, buffer);

#ifdef REG_EXTENDED
		if(!good_gdb){
			/* test 3/3 - gdb */
			writeFile(GDB_SCRIPTER, gdb_script);
			buffer = malloc(strlen(gdb) + strlen(case_file) + strlen(GDB_SCRIPTER) + strlen(TLOG) + 20);
			sprintf(buffer, "%s %s.exe < %s > %s 2>&1", gdb, case_file, GDB_SCRIPTER, TLOG);
			fprintf(stderr, "%s\n", buffer);
			if(EXIT_SUCCESS==crashRun(buffer)){
				/* diagnostic 3/3 */
				buffer = loadFile(TLOG);
				fprintf(stderr, "%s\n", buffer);
				good_gdb = (regexec(gdb_pattern, buffer, 0, NULL, 0)==0);
			}
		}
#endif /* REG_EXTENDED */

		if(modus==RUN){
			if(hadExecCrash(buffer)){
				printf("ERROR:\t%s [test case crash]%s%s", case_file, errorMsg(good_error), gdbMsg(good_gdb));
			}else if(res==EXIT_SUCCESS && good_gdb){
				printf("PASS: \t%s\n", case_file);
			}else if(res==EXIT_FAILURE && good_error && good_gdb){
				printf("FAIL: \t%s\n", case_file);
			}else{
				printf("ERROR:\t%s%s%s\n", case_file, errorMsg(good_error), gdbMsg(good_gdb));
			}
		}else{
			if(res==EXIT_SUCCESS && good_gdb){
				printf("XPASS:\t%s%s%s\n", case_file, errorMsg(good_error), gdbMsg(good_gdb));
			}else if(good_error && good_gdb){
				printf("XFAIL:\t%s%s%s\n", case_file, errorMsg(good_error), gdbMsg(good_gdb));
			}else{
				printf("FAIL:\t%s%s%s\n", case_file, errorMsg(good_error), gdbMsg(good_gdb));
			}
		}
		fprintf(stderr, "--------\n");
	}else{
		printf("@bug@ %d (%s)\n", modus, case_file);
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}