diff --git a/c/t/01-toolchain/01a-cc.c b/c/t/01-toolchain/01a-cc.t.c similarity index 97% rename from c/t/01-toolchain/01a-cc.c rename to c/t/01-toolchain/01a-cc.t.c index 380935c..9bc8928 100644 --- a/c/t/01-toolchain/01a-cc.c +++ b/c/t/01-toolchain/01a-cc.t.c @@ -181,7 +181,11 @@ create_lib() #else status = system("gcc -c -fPIC -o testlib.o testlib.c"); /* Unix */ if (status==0) { - status = system("cc -shared -s -o testlib.so testlib.o"); + #if defined( __APPLE__ ) + status = system("cc -shared -o testlib.so testlib.o"); + #else + status = system("cc -shared -s -o testlib.so testlib.o"); + #endif } if (status==0) { status = system("rm testlib.o"); diff --git a/c/t/01-toolchain/01b-timing.c b/c/t/01-toolchain/01b-timing.t.c similarity index 93% rename from c/t/01-toolchain/01b-timing.c rename to c/t/01-toolchain/01b-timing.t.c index 7166deb..cf838de 100644 --- a/c/t/01-toolchain/01b-timing.c +++ b/c/t/01-toolchain/01b-timing.t.c @@ -1,5 +1,9 @@ /* 01b-timing.c */ +/* TODO: try these functions on Win32 for better precision: */ +/* _ftime usleep QueryPerformanceCounter QueryPerformanceFrequency */ + + #include /* sprintf */ #ifdef _WIN32 #include @@ -18,13 +22,13 @@ seconds_microseconds_sleep() int microseconds1, microseconds2, microseconds3, microseconds4, microseconds5; char message[80]; - /* Read the clock twice in quick succession, sleep for 1 second, */ - /* then read the clock a third time. Report the differences */ - /* between the times and verify that the sleep was about 1 sec. */ + /* Read the clock multiple times with various delays in between. */ + /* Check that the time differences are within reason */ #ifdef _WIN32 FILETIME time1, time2, time3, time4, time5; GetSystemTimeAsFileTime(&time1); /* * 100ns since 1601-01-01 */ GetSystemTimeAsFileTime(&time2); + _ftime(); // TODO Sleep(0); /* 0 milliseconds */ GetSystemTimeAsFileTime(&time3); Sleep(1); /* 1 millisecond */ diff --git a/c/t/01-toolchain/01c-osthreads.c b/c/t/01-toolchain/01c-osthreads.t.c similarity index 100% rename from c/t/01-toolchain/01c-osthreads.c rename to c/t/01-toolchain/01c-osthreads.t.c diff --git a/c/t/02-components/02a-threads.c b/c/t/02-components/02a-threads.t.c similarity index 100% rename from c/t/02-components/02a-threads.c rename to c/t/02-components/02a-threads.t.c diff --git a/c/t/02-components/02b-hashtable.c b/c/t/02-components/02b-hashtable.t.c similarity index 100% rename from c/t/02-components/02b-hashtable.c rename to c/t/02-components/02b-hashtable.t.c diff --git a/c/t/Test.h b/c/t/Test.h index 1be28ae..9c5fd00 100644 --- a/c/t/Test.h +++ b/c/t/Test.h @@ -35,27 +35,40 @@ int _test_number=0; /* yes, namespace pollution. patches welcome ;-) */ #define \ ok(flag,desc) \ printf("%sok %d - %s\n", \ - flag?"":"not ",++_test_number,desc) + flag?"":"not ",++_test_number,desc); \ + fflush(stdout) #define \ is_ii(got,expected,desc) \ printf("%sok %d - %s\n", \ got==expected?"":"not ",++_test_number,desc); \ if(got!=expected) \ - printf("# got : %d\n# expected : %d\n", got, expected) + printf("# got : %d\n# expected : %d\n", got, expected); \ + fflush(stdout) + +#define \ +is_ll(got,expected,desc) \ + printf("%sok %d - %s\n", \ + got==expected?"":"not ",++_test_number,desc); \ + if(got!=expected) \ + printf("# got : %ld\n# expected : %ld\n", got, expected); \ + fflush(stdout) #define \ is_ss(got,expected,desc) \ printf("%sok %d - %s\n", \ - strcmp(got,expected)?"not ":"",++_test_number,desc) + strcmp(got,expected)?"not ":"",++_test_number,desc); \ + fflush(stdout) #define \ isnt_pp(got,expected,desc) \ printf("%sok %d - %s\n", \ - (got!=expected)?"":"not ", ++_test_number, desc) + (got!=expected)?"":"not ", ++_test_number, desc); \ + fflush(stdout) #define \ diag(message) \ - printf("# %s\n", message) + printf("# %s\n", message); \ + fflush(stdout) /* end of Test.h */ diff --git a/c/tools/build/Configure.c b/c/tools/build/Configure.c index 07ec8df..a55ac9d 100644 --- a/c/tools/build/Configure.c +++ b/c/tools/build/Configure.c @@ -1,27 +1,28 @@ /* Configure.c */ -/* Compiled and run by 6model/c/Configure.(sh|bat) */ +/* Compiled and run by 6model/c/Configure.{sh,bat} */ /* This program (Configure) uses environment variables and C macros */ /* to autodetect the operating system, compiler and other utilities */ /* that can be used to build your software. It then creates your */ -/* Makefile based on a template (Makefile.in). To work also with */ -/* non-GNU systems such as Microsoft Visual C++, it follows the */ -/* style of Automake and Autoconf, but is written in only C and does */ -/* not rely on other tools such as M4. */ - -/* This work will never be finished. Reliable autodetection is hard. */ -/* There are always newer environments and tools to try out. */ -/* Systems usually predefine some variables and macros. Users might */ -/* make the same definitions. */ - -/* Currently works with: +/* Makefile based on a template (Makefile.in). To also work with */ +/* non-GNU systems such as Microsoft C/C++, it follows the style of */ +/* Automake and Autoconf, but is written only in C and does not rely */ +/* on other tools such as M4. */ + +/* This work will never be complete, because autodetection is hard. */ +/* New software emerges, environments and tools evolve, users make */ +/* unforeseen choices. Monitor the changes through regular testing. */ + +/* Currently verified to work with: + * GNU C compiler on Linux, OS X and Windows (as MinGW). + * * MinGW * http://mingw.org/ * Currently based on GCC 4.5.2, 85MB disk. * Targets Win32 libraries, no Posix emulation or dlopen. * (older version bundled with Git full install) - * Microsoft Visual C++ Express Edition 1-2GB RAM, 3GB disk + * Microsoft Visual C++ Express Edition 1-2GB RAM * Registration required to avoid de-activation after 30 days. Downloads a 3.2MB web installer. * http://www.microsoft.com/express/vc/ do not need optional SQL express. * Also installs Windows Installer 4.5, .NET Framework 4, SQL Server Compact 3.5, Help Viewer 1.0 @@ -66,7 +67,7 @@ void detection(void) { int processors = 0; - #if defined( _WIN32 ) + #if defined( _WIN32 ) && ! defined( _OPENMP ) SYSTEM_INFO sysinfo; /* declare up here because MSC hates it lower */ #endif printf("Configure detects the following:\n"); @@ -224,7 +225,7 @@ makefile_convert(char * programfilename, char * templatefilename, trans(&makefiletext, "t/02-components", "t\\02-components"); #endif #if defined( _MSC_VER ) - trans(&makefiletext, "$(O) ", "$(O)"); + trans(&makefiletext, "$(OUTFILE) ", "$(OUTFILE)"); #endif printf(" %s: writing to %s\n", programfilename, outputfilename); squirt(makefiletext, outputfilename); @@ -341,16 +342,16 @@ main(int argc, char * argv[]) /* * TODO - - * Explore more Win32 C compilers and toolchains + * + * Explore more C compilers and toolchains * http://www.thefreecountry.com/compilers/cpp.shtml * lcc-win32 http://www.cs.virginia.edu/~lcc-win32/ * Borland (Registration required) * Tiny C Compiler http://bellard.org/tcc/ * OpenWatcom http://www.openwatcom.org/index.php/Download * Digital Mars http://www.digitalmars.com/download/freecompiler.html - -*/ + * + */ /* See also: */ diff --git a/c/tools/build/Makefile.in b/c/tools/build/Makefile.in index 17fb886..35066e0 100644 --- a/c/tools/build/Makefile.in +++ b/c/tools/build/Makefile.in @@ -20,32 +20,32 @@ OPENMP = @openmp@ all: test # Recipes to build executables -t/01-toolchain/01a-cc.exe: t/01-toolchain/01a-cc.c t/Test.h - $(CC) $(OUTFILE) t/01-toolchain/01a-cc.exe t/01-toolchain/01a-cc.c - -$(RM_RF) 01a-cc.obj +t/01-toolchain/01a-cc.t.exe: t/01-toolchain/01a-cc.t.c t/Test.h + $(CC) $(OUTFILE) t/01-toolchain/01a-cc.t.exe t/01-toolchain/01a-cc.t.c + -$(RM_RF) 01a-cc.t.obj -t/01-toolchain/01b-timing.exe: t/01-toolchain/01b-timing.c t/Test.h - $(CC) $(OUTFILE) t/01-toolchain/01b-timing.exe t/01-toolchain/01b-timing.c - -$(RM_RF) 01b-timing.obj +t/01-toolchain/01b-timing.t.exe: t/01-toolchain/01b-timing.t.c t/Test.h + $(CC) $(OUTFILE) t/01-toolchain/01b-timing.t.exe t/01-toolchain/01b-timing.t.c + -$(RM_RF) 01b-timing.t.obj -t/01-toolchain/01c-osthreads.exe: t/01-toolchain/01c-osthreads.c t/Test.h - $(CC) $(THREADS) $(OPENMP) $(OUTFILE) t/01-toolchain/01c-osthreads.exe t/01-toolchain/01c-osthreads.c - -$(RM_RF) 01c-osthreads.obj +t/01-toolchain/01c-osthreads.t.exe: t/01-toolchain/01c-osthreads.t.c t/Test.h + $(CC) $(THREADS) $(OPENMP) $(OUTFILE) t/01-toolchain/01c-osthreads.t.exe t/01-toolchain/01c-osthreads.t.c + -$(RM_RF) 01c-osthreads.t.obj -t/02-components/02a-threads.exe: t/02-components/02a-threads.c \ +t/02-components/02a-threads.t.exe: t/02-components/02a-threads.t.c \ src/threads.h src/threads.c src/timing.c src/timing.h t/Test.h - $(CC) $(THREADS) $(OUTFILE) t/02-components/02a-threads.exe src/threads.c src/timing.c t/02-components/02a-threads.c - -$(RM_RF) threads.obj 02a-threads.obj + $(CC) $(THREADS) $(OUTFILE) t/02-components/02a-threads.t.exe src/threads.c src/timing.c t/02-components/02a-threads.t.c + -$(RM_RF) threads.obj 02a-threads.t.obj -t/02-components/02b-hashtable.exe: t/02-components/02b-hashtable.c \ +t/02-components/02b-hashtable.t.exe: t/02-components/02b-hashtable.t.c \ src/hashtable.h src/hashtable.c t/Test.h - $(CC) $(OUTFILE) t/02-components/02b-hashtable.exe src/hashtable.c t/02-components/02b-hashtable.c - -$(RM_RF) hashtable.obj 02b-hashtable.obj + $(CC) $(OUTFILE) t/02-components/02b-hashtable.t.exe src/hashtable.c t/02-components/02b-hashtable.t.c + -$(RM_RF) hashtable.obj 02b-hashtable.t.obj -t/02-components/02c-heapmanager.exe: t/02-components/02c-heapmanager.c \ - src/heapmanager.h src/heapmanager.c t/Test.h - $(CC) $(OUTFILE) t/02-components/02c-heapmanager.exe src/heapmanager.c t/02-components/02c-heapmanager.c - -$(RM_RF) heapmanager.obj 02b-heapmanager.obj +t/02-components/02c-mem.t.exe: t/02-components/02c-mem.t.c \ + src/mem.h src/mem.c t/Test.h + $(CC) $(OUTFILE) t/02-components/02c-mem.t.exe src/mem.c t/02-components/02c-mem.t.c + -$(RM_RF) mem.obj 02b-mem.t.obj tools/build/prove$(EXE): tools/build/prove.c $(CC) $(OUTFILE) tools/build/prove$(EXE) tools/build/prove.c @@ -58,13 +58,13 @@ test: test01 test02 # The test01 target checks that the C compiler and so on perform the # functions needed by the rest of the code. It is generally unnecessary # to run this test, but is useful when troubleshooting. -test01: t/01-toolchain/01a-cc.exe t/01-toolchain/01b-timing.exe \ - t/01-toolchain/01c-osthreads.exe tools/build/prove$(EXE) +test01: t/01-toolchain/01a-cc.t.exe t/01-toolchain/01b-timing.t.exe \ + t/01-toolchain/01c-osthreads.t.exe tools/build/prove$(EXE) tools/build/prove -e "" --ext ".exe" t/01-toolchain # The test02 target validates the internal libraries of 6model/c -test02: t/02-components/02a-threads.exe \ - t/02-components/02b-hashtable.exe tools/build/prove$(EXE) +test02: t/02-components/02a-threads.t.exe t/02-components/02b-hashtable.t.exe \ + tools/build/prove$(EXE) tools/build/prove -e "" --ext ".exe" t/02-components # Miscellaneous targets @@ -81,7 +81,7 @@ help: @echo In this 6model/c directory you can make the following targets: @echo "test - general test as far as 6model has been developed" @echo "test01 - test the toolchain, eg C compiler, threads, ICU etc" - @echo "test02 - test 6model components, eg hashtable, heapmanager etc" + @echo "test02 - test 6model components, eg hashtable, mem etc" @echo "clean - remove all generated files except this Makefile" @echo "realclean - remove all generated files including this Makefile" @echo "help - you already found this" diff --git a/c/tools/build/prove.c b/c/tools/build/prove.c index a6c31b3..c648723 100644 --- a/c/tools/build/prove.c +++ b/c/tools/build/prove.c @@ -1,26 +1,34 @@ /* prove.c */ -/* Lightweight TAP (Test Anything Protocol) harness */ +/* Lightweight C version of a TAP (Test Anything Protocol) harness */ -/* TODO: parse the test script output looking for 'ok', 'not ok' etc */ -/* and output a summary instead of every test result. */ +/* TODO: support for TODO tests (that are expected to fail) */ +/* TODO: ensure the '1..n' (plan) output is either first or last line */ +/* TODO: check exit status from child process */ + +/* Functions are placed in this file at the point just before they */ +/* are needed, but all global variables are declared at the start. */ #include /* assert */ #include /* FILE fprintf printf stderr */ -#include /* exit free malloc qsort realloc */ +#include /* atoi exit free malloc qsort realloc */ #include /* strcat strcpy strlen */ #if defined( _WIN32 ) #include #define pclose _pclose #define popen _popen + #define DIRECTORY_SEPARATOR "\\" #else - #include /* opendir readdir */ + #include /* opendir readdir */ + #define DIRECTORY_SEPARATOR "/" #endif #define LINEBUFFERSIZE 128 +/* Global variables */ char * program_name; char * executable_program; char * filename_extension; +int tests_passed=0, tests_failed=0, todos_passed=0, total_files=0; /* options */ @@ -35,7 +43,10 @@ options(int argc, char * argv[]) executable_program = NULL; /* should be "perl6" ;-) */ filename_extension = NULL; /* should be ".t" */ if (argc < 2) { - fprintf(stderr, "Usage: %s test_directory\n", argv[0]); + fprintf(stderr, + "Usage: %s [-e \"cmd\"] [--ext \"extension\"] test_directory\n", + argv[0] + ); exit(1); } scanning_args = 1; @@ -85,6 +96,51 @@ qx(char * command) } +/* split_str */ +int +split_str(char * str, char * delim, char *** substrings) +{ + int substringcount = 0, substringlen, delimiterlen; + char * p1, * p2, * substr; + delimiterlen = strlen(delim); + assert( delimiterlen>0 ); + p1 = str; + while ((p2=strstr(p1, delim)) != NULL) { + substringlen = p2 - p1; + /* Create or extend the list of pointers to strings */ + if (substringcount++ == 0) { + * substrings = (char **) malloc(sizeof(char **)); + } + else { + * substrings = (char **) realloc(* substrings, substringcount * sizeof(char **)); + } + /* Push the substring onto the end of the list */ + substr = (char *) malloc(substringlen+1); + strncpy(substr, p1, substringlen); + substr[substringlen] = '\0'; + (* substrings)[substringcount-1] = substr; + /* Move the search pointer past the delimiter */ + p1 += substringlen + delimiterlen; + } + /* There is often another substring after the last delimiter */ + if ((substringlen=strlen(p1))>0) { + /* Create or extend the list of pointers to strings */ + if (substringcount++ == 0) { + * substrings = (char **) malloc(sizeof(char **)); + } + else { + * substrings = (char **) realloc(* substrings, substringcount * sizeof(char **)); + } + /* Push the substring onto the end of the list */ + substr = (char *) malloc(substringlen+1); + strncpy(substr, p1, substringlen); + substr[substringlen] = '\0'; + (* substrings)[substringcount-1] = substr; + } + return substringcount; +} + + #if ! defined( _WIN32 ) /* scandirectory_comparenames */ int @@ -122,7 +178,6 @@ scandirectory(char * dirname, char *** filenamelist) /* yes, triple pointer */ strcat(filename, "\\*"); if (filename_extension != NULL) strcat(filename, filename_extension); - printf("%s\n", filename); hFind = FindFirstFile(filename, &dir); found_a_file = (hFind != INVALID_HANDLE_VALUE); #else @@ -134,7 +189,6 @@ scandirectory(char * dirname, char *** filenamelist) /* yes, triple pointer */ } direntry = readdir(dir); found_a_file = (direntry != NULL); - printf("scandirectory %s\n", dirname); #endif while (found_a_file) { #if defined( _WIN32 ) @@ -184,7 +238,8 @@ scandirectory(char * dirname, char *** filenamelist) /* yes, triple pointer */ } -/* filecollection - a list of directory names containing file names */ +/* filecollection */ +/* a list of directory names containing file names */ struct filecollection { int dircount; struct filecollection_dir { @@ -196,15 +251,14 @@ struct filecollection { /* filecollection_free */ +/* Frees all the memory allocated to a filecollection */ void filecollection_free(struct filecollection * coll) { int i, j; for (i=0; idircount; ++i) { -// printf("Freeing %s has %d files\n", coll->dirs[i].dirname, coll->dirs[i].filecount); free(coll->dirs[i].dirname); for (j=0; jdirs[i].filecount; ++j) { -// printf(" Freeing %s\n", coll->dirs[i].filenames[j]); free(coll->dirs[i].filenames[j]); } free(coll->dirs[i].filenames); @@ -213,7 +267,11 @@ filecollection_free(struct filecollection * coll) free(coll); } + /* scandirectories */ +/* Takes a list of directory names and returns a filecollection which */ +/* is a list of those directory names and also either all the files */ +/* in each directory or those that have a specified extension. */ struct filecollection * scandirectories(int argc, char * argv[]) { @@ -266,7 +324,6 @@ scandirectories(int argc, char * argv[]) filedir->filenames[filedir->filecount-1] = (char *) malloc(strlen(filenamelist[j])+1); strcpy(filedir->filenames[filedir->filecount-1], filenamelist[j]); -// printf(" %s\n", filenamelist[j]); free(filenamelist[j]); } } @@ -276,15 +333,94 @@ scandirectories(int argc, char * argv[]) } -/* runtests */ +/* TAP_Parser */ void -runtests(struct filecollection * coll) +TAP_Parser(FILE * program_output) +{ + int planned=0, passed=0, failed=0, testnumber, testnumber_expected; + int len; + char line[LINEBUFFERSIZE]; + + testnumber_expected = 1; + while (fgets(line, LINEBUFFERSIZE, program_output)) { + if (strncmp(line, "1..", 3)==0) { /* plan() output */ + planned = atoi(line+3); + } + else { + if (strncmp(line, "ok ", 3)==0) { /* passed test */ + testnumber = atoi(line+3); + if (testnumber == testnumber_expected) { + ++testnumber_expected; + } + else { + fprintf(stderr, + "tap_parser expected test number %d but got %d\n", + testnumber_expected, testnumber ); + testnumber_expected = testnumber + 1; + } + ++passed; + } + else { + if (strncmp(line, "not ok ", 7)==0) { /* failed test */ + testnumber = atoi(line+7); + if (testnumber == testnumber_expected) { + ++testnumber_expected; + } + else { + fprintf(stderr, + "tap_parser expected test number %d but got %d\n", + testnumber_expected, testnumber ); + testnumber_expected = testnumber + 1; + } + ++failed; + } + else { + if (strncmp(line, "#", 1)==0) { /* comment */ + ; + } + else { + fprintf(stderr, "tap_parser: unexpected line: %s\n", line); + } + } + } + } + /* Display a running X/Y count of results as they arrive */ + len = printf(" %d/%d", passed, planned ? planned : passed+failed); + while (len--) + printf("\b"); + fflush(stdout); + } + printf("%d/%d %sok", passed, passed+failed, + (passed!=planned || failed!=0) ? "*NOT* " : ""); + if (planned != passed + failed ) { + printf(" (%d planned)", planned); + } +} + + +/* runtest */ +void +runtest(char * command) +{ + FILE * childprocess; + + /* Run the test and capture its standard output */ + childprocess = popen(command, "r"); + /* Analyze the output for planned number of tests, passes and fails */ + TAP_Parser(childprocess); + /* Clean up */ + pclose(childprocess); +} + + +/* runtestdirs */ +void +runtestdirs(struct filecollection * coll) { int i, j, commandlen; char * command, * tap_output; for (i=0; idircount; ++i) { for (j=0; jdirs[i].filecount; ++j) { - printf("runtest %s / %s\n", coll->dirs[i].dirname, coll->dirs[i].filenames[j]); commandlen = (executable_program ? strlen(executable_program) + 1 : 0) + strlen(coll->dirs[i].dirname) + 1 + strlen(coll->dirs[i].filenames[j]) + 1; @@ -295,41 +431,44 @@ runtests(struct filecollection * coll) strcat(command, " "); } strcat(command, coll->dirs[i].dirname); - #if defined( _WIN32 ) - strcat(command, "\\"); - #else - strcat(command, "/"); - #endif + strcat(command, DIRECTORY_SEPARATOR ); strcat(command, coll->dirs[i].filenames[j]); - - tap_output = qx(command); - printf("%s\n", tap_output); + printf("%s%s%s ", coll->dirs[i].dirname, + DIRECTORY_SEPARATOR, coll->dirs[i].filenames[j]); + fflush(stdout); + /* Run each test and parse its output */ + runtest(command); + printf("\n"); free(command); - free(tap_output); } } } + /* main */ int main(int argc, char * argv[]) { int argi; - struct filecollection * files; + struct filecollection * dirs_and_files; /* Get command line options and process them */ argi = options(argc, argv); /* Scan the remaining non-option arguments as directory names */ - files = scandirectories(argc-argi, argv+argi); /* argi hides what options() saw */ + dirs_and_files = scandirectories(argc-argi, argv+argi); + /* argi hides what options() took from argv */ /* Perform each test and parse its TAP output */ - runtests(files); + runtestdirs(dirs_and_files); /* Clean up when finished */ - filecollection_free(files); + filecollection_free(dirs_and_files); return 0; } +/* See also: */ +/* perldoc prove, TAP::Harness, TAP::Parser::Aggregator */ +/* TAP::Parser::Grammar, TAP::Formatter::Console */ /* end of prove.c */