# Core function to execute C unit tests (or just show functions names) .pkgdev.ctest = function(path, prefix, show, cc) { # Initial step: list every potential unit test under path/src/tests. allFuncNames = .parseCunitTests(file.path(path,"src","tests")) # Filter functions names matching prefix funcNames = grep( paste("^test_",prefix,sep=''), allFuncNames, value=TRUE ) if (length(funcNames) == 0) return #shortcut: nothing to do... # If show==TRUE, display every potential test starting with prefix, and exit if (show) { #display in alphabetic order return (paste(sort(funcNames), sep='\n')) } # Get package name from path pathTokens = strsplit(path, c(.Platform$file.sep))[[1]] pkgName = pathTokens[length(pathTokens)] # Generate main.c to run exactly the tests asked by user .generateMainCall(funcNames, pkgName) # Get all C source files (src/tests/*, src/sources/* ...) cFilesUser = c( list.files(file.path(path,"src","tests"),pattern="\\.[cChH]$", full.names=TRUE,recursive=TRUE), list.files(file.path(path,"src","sources"),pattern="\\.[cChH]$", full.names=TRUE,recursive=TRUE)) pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev") cFilesPkdev = c( "main.c", #above generated main.c file.path(pkdev_path,"src","tests","unitTestsMacros.c")) #C unit tests macros # Now generate appropriate Makefile based on C sources and headers pathTokens = strsplit(path, c(.Platform$file.sep))[[1]] pkgName = pathTokens[length(pathTokens)] .generateMakefileTest(path, cFilesUser, cFilesPkdev, pkgName, cc) # Run selected tests (after 'funcs' filter applied) pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev") save_wd = getwd() setwd( file.path(pkdev_path,"pkgs",pkgName,"src","tests") ) library(parallel) system( paste( Sys.getenv("MAKE"), "depend", sep=' ') ) system( paste( Sys.getenv("MAKE"), "-j", detectCores(), "all", sep=' ') ) system("./runTests") setwd(save_wd) } # Recursively explore initial path to parse source files for unit tests. .parseCunitTests = function(path) { # Unit test names to return funcNames = c() # For each file in current folder for (fileName in list.files(path, full.names=TRUE, recursive=TRUE)) { # If the file is not a source, skip if ( length( grep("\\.[CcHh]$", fileName) ) == 0) next # Every test function has a name starting with "test_" matches = grep( "^[ \t]*void[ \t]*test_[a-zA-Z0-9_]*[ \t]*\\(.*", scan(fileName, what="character", sep='\n'), value = TRUE) # We matched more to be 100% sure we got test functions, but need to strip now funcNames = c(funcNames, sub( "^[ \t]*void[ \t]*(test_[a-zA-Z0-9_]*)[ \t]*\\(.*", "\\1", matches)) } return (funcNames) } # Generate main.c file under R_HOME_USER/pkgdev/pkgs/pkgName/src/tests .generateMainCall = function(funcNames, pkgName) { # Build text file main.c mainDotC = ' #include #include #include // to print timings void main() { clock_t start, end; ' for (funcName in funcNames) { mainDotC = paste(mainDotC, "printf(\">>> Running ",funcName,"\\n\");\n",sep='') mainDotC = paste(mainDotC, "start = clock();\n", sep='') mainDotC = paste(mainDotC, funcName, "();\n", sep='') mainDotC = paste(mainDotC, "end = clock();\n", sep='') mainDotC = paste(mainDotC, "printf(\">>> ... completed in %.3fs.\\n\",((double) (end - start)) / CLOCKS_PER_SEC);\n", sep='') } mainDotC = paste(mainDotC, "}\n", sep='') # Write it on disk pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev") dir.create(file.path(pkdev_path,"pkgs",pkgName,"src","tests"), recursive=TRUE, showWarnings=FALSE) writeLines(mainDotC, file.path(pkdev_path,"pkgs",pkgName,"src","tests","main.c")) } # Generate appropriate Makefile under R_HOME_USER/pkgdev/pkgs/pkgName/src/tests .generateMakefileTest = function(path, cFilesUser, cFilesPkdev, pkgName, cc) { # Preparation: separate cFiles into codes and headers codeFilesUser = grep(".*(c|C)$", cFilesUser, value=TRUE) codeFilesPkdev = grep(".*(c|C)$", cFilesPkdev, value=TRUE) headerFiles = grep(".*(h|H)$", c(cFilesUser,cFilesPkdev), value=TRUE) # objectFiles = all .o files in current folder, duplicating file structure under path/src/ basePathFrom = file.path(path, "src") pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev") basePathTo = file.path(pkdev_path,"pkgs",pkgName,"src","tests") for (fileOrDir in list.files(basePathFrom, recursive=TRUE, include.dirs=TRUE)) { if (file.info(file.path(basePathFrom,fileOrDir))$isdir) { # Process folders only dir.create(file.path(basePathTo,fileOrDir),showWarnings=FALSE,recursive=TRUE) } } objectFiles = c() for (codeFileUser in codeFilesUser) { objectFiles = c( objectFiles, sub("(.*)\\.(c|C)$","\\1\\.o", sub(basePathFrom,basePathTo,codeFileUser,fixed=TRUE))) } for (codeFilePkdev in codeFilesPkdev) { objectFiles = c( objectFiles, sub("(.*)\\.(c|C)$","\\1\\.o", codeFilePkdev)) } # Build Makefile makefile = paste(' CC = ', cc, ' INCLUDES = LIBRARIES = -lm CFLAGS = -g LDFLAGS = EXEC = runTests SRCS = ', paste( paste(codeFilesUser,sep='',collapse=' '), paste(codeFilesPkdev,sep='',collapse=' '), sep=' '), ' HEDS = ', paste(headerFiles, sep='', collapse=' '), ' OBJS = ', paste(objectFiles, sep='', collapse= ' '), ' all: $(EXEC) $(EXEC) : $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $(EXEC) $(LIBRARIES)', sep='') compileObjects = "" lengthCodeFilesUser = length(codeFilesUser) for (i in 1:lengthCodeFilesUser) { compileObjects = paste(compileObjects, ' ', objectFiles[i], ' : ', codeFilesUser[i], ' $(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@', sep='') } for (i in 1:length(codeFilesPkdev)) { compileObjects = paste(compileObjects, ' ', objectFiles[i+lengthCodeFilesUser], ' : ', codeFilesPkdev[i], ' $(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@', sep='') } makefile = paste(makefile, compileObjects, ' .PHONY: clean delex depend clean: rm -f $(OBJS) ./.depend delex: rm -f $(EXEC) depend: .depend .depend: $(SRCS) $(HEDS) rm -f ./.depend $(CC) -MM $^ > ./.depend include .depend ', sep='') # Write it to disk writeLines(makefile, file.path(pkdev_path,"pkgs",pkgName,"src","tests","Makefile")) }