| 1 | # Core function to execute C unit tests (or just show functions names) |
| 2 | .pkgdev.ctest = function(path, prefix, show, cc) { |
| 3 | |
| 4 | # Initial step: list every potential unit test under path/src/tests. |
| 5 | allFuncNames = .parseCunitTests(file.path(path,"src","tests")) |
| 6 | |
| 7 | # Filter functions names matching prefix |
| 8 | funcNames = grep( paste("^test_",prefix,sep=''), allFuncNames, value=TRUE ) |
| 9 | if (length(funcNames) == 0) return #shortcut: nothing to do... |
| 10 | |
| 11 | # If show==TRUE, display every potential test starting with prefix, and exit |
| 12 | if (show) { |
| 13 | #display in alphabetic order |
| 14 | return (paste(sort(funcNames), sep='\n')) |
| 15 | } |
| 16 | |
| 17 | # Get package name from path |
| 18 | pathTokens = strsplit(path, c(.Platform$file.sep))[[1]] |
| 19 | pkgName = pathTokens[length(pathTokens)] |
| 20 | |
| 21 | # Generate main.c to run exactly the tests asked by user |
| 22 | .generateMainCall(funcNames, pkgName) |
| 23 | |
| 24 | # Get all C source files (src/tests/*, src/sources/* ...) |
| 25 | cFilesUser = c( |
| 26 | list.files(file.path(path,"src","tests"),pattern="\\.[cChH]$", |
| 27 | full.names=TRUE,recursive=TRUE), |
| 28 | list.files(file.path(path,"src","sources"),pattern="\\.[cChH]$", |
| 29 | full.names=TRUE,recursive=TRUE)) |
| 30 | pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev") |
| 31 | cFilesPkdev = c( |
| 32 | "main.c", #above generated main.c |
| 33 | file.path(pkdev_path,"src","tests","unitTestsMacros.c")) #C unit tests macros |
| 34 | |
| 35 | # Now generate appropriate Makefile based on C sources and headers |
| 36 | pathTokens = strsplit(path, c(.Platform$file.sep))[[1]] |
| 37 | pkgName = pathTokens[length(pathTokens)] |
| 38 | .generateMakefileTest(path, cFilesUser, cFilesPkdev, pkgName, cc) |
| 39 | |
| 40 | # Run selected tests (after 'funcs' filter applied) |
| 41 | pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev") |
| 42 | save_wd = getwd() |
| 43 | setwd( file.path(pkdev_path,"pkgs",pkgName,"src","tests") ) |
| 44 | library(parallel) |
| 45 | system( paste( Sys.getenv("MAKE"), "depend", sep=' ') ) |
| 46 | system( paste( Sys.getenv("MAKE"), "-j", detectCores(), "all", sep=' ') ) |
| 47 | system("./runTests") |
| 48 | setwd(save_wd) |
| 49 | } |
| 50 | |
| 51 | # Recursively explore initial path to parse source files for unit tests. |
| 52 | .parseCunitTests = function(path) { |
| 53 | |
| 54 | # Unit test names to return |
| 55 | funcNames = c() |
| 56 | |
| 57 | # For each file in current folder |
| 58 | for (fileName in list.files(path, full.names=TRUE, recursive=TRUE)) { |
| 59 | |
| 60 | # If the file is not a source, skip |
| 61 | if ( length( grep("\\.[CcHh]$", fileName) ) == 0) next |
| 62 | |
| 63 | # Every test function has a name starting with "test_" |
| 64 | matches = grep( |
| 65 | "^[ \t]*void[ \t]*test_[a-zA-Z0-9_]*[ \t]*\\(.*", |
| 66 | scan(fileName, what="character", sep='\n'), |
| 67 | value = TRUE) |
| 68 | |
| 69 | # We matched more to be 100% sure we got test functions, but need to strip now |
| 70 | funcNames = c(funcNames, sub( |
| 71 | "^[ \t]*void[ \t]*(test_[a-zA-Z0-9_]*)[ \t]*\\(.*", |
| 72 | "\\1", |
| 73 | matches)) |
| 74 | } |
| 75 | |
| 76 | return (funcNames) |
| 77 | } |
| 78 | |
| 79 | # Generate main.c file under R_HOME_USER/pkgdev/pkgs/pkgName/src/tests |
| 80 | .generateMainCall = function(funcNames, pkgName) { |
| 81 | |
| 82 | # Build text file main.c |
| 83 | mainDotC = ' |
| 84 | #include <stdlib.h> |
| 85 | #include <stdio.h> |
| 86 | #include <time.h> // to print timings |
| 87 | |
| 88 | void main() { |
| 89 | clock_t start, end; |
| 90 | ' |
| 91 | for (funcName in funcNames) { |
| 92 | mainDotC = paste(mainDotC, "printf(\">>> Running ",funcName,"\\n\");\n",sep='') |
| 93 | mainDotC = paste(mainDotC, "start = clock();\n", sep='') |
| 94 | mainDotC = paste(mainDotC, funcName, "();\n", sep='') |
| 95 | mainDotC = paste(mainDotC, "end = clock();\n", sep='') |
| 96 | mainDotC = paste(mainDotC, "printf(\">>> ... completed in %.3fs.\\n\",((double) (end - start)) / CLOCKS_PER_SEC);\n", sep='') |
| 97 | } |
| 98 | mainDotC = paste(mainDotC, "}\n", sep='') |
| 99 | |
| 100 | # Write it on disk |
| 101 | pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev") |
| 102 | dir.create(file.path(pkdev_path,"pkgs",pkgName,"src","tests"), recursive=TRUE, showWarnings=FALSE) |
| 103 | writeLines(mainDotC, file.path(pkdev_path,"pkgs",pkgName,"src","tests","main.c")) |
| 104 | } |
| 105 | |
| 106 | # Generate appropriate Makefile under R_HOME_USER/pkgdev/pkgs/pkgName/src/tests |
| 107 | .generateMakefileTest = function(path, cFilesUser, cFilesPkdev, pkgName, cc) { |
| 108 | |
| 109 | # Preparation: separate cFiles into codes and headers |
| 110 | codeFilesUser = grep(".*(c|C)$", cFilesUser, value=TRUE) |
| 111 | codeFilesPkdev = grep(".*(c|C)$", cFilesPkdev, value=TRUE) |
| 112 | headerFiles = grep(".*(h|H)$", c(cFilesUser,cFilesPkdev), value=TRUE) |
| 113 | |
| 114 | # objectFiles = all .o files in current folder, duplicating file structure under path/src/ |
| 115 | basePathFrom = file.path(path, "src") |
| 116 | pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev") |
| 117 | basePathTo = file.path(pkdev_path,"pkgs",pkgName,"src","tests") |
| 118 | for (fileOrDir in list.files(basePathFrom, recursive=TRUE, include.dirs=TRUE)) { |
| 119 | if (file.info(file.path(basePathFrom,fileOrDir))$isdir) { |
| 120 | # Process folders only |
| 121 | dir.create(file.path(basePathTo,fileOrDir),showWarnings=FALSE,recursive=TRUE) |
| 122 | } |
| 123 | } |
| 124 | objectFiles = c() |
| 125 | for (codeFileUser in codeFilesUser) { |
| 126 | objectFiles = c( |
| 127 | objectFiles, |
| 128 | sub("(.*)\\.(c|C)$","\\1\\.o", sub(basePathFrom,basePathTo,codeFileUser,fixed=TRUE))) |
| 129 | } |
| 130 | for (codeFilePkdev in codeFilesPkdev) { |
| 131 | objectFiles = c( |
| 132 | objectFiles, |
| 133 | sub("(.*)\\.(c|C)$","\\1\\.o", codeFilePkdev)) |
| 134 | } |
| 135 | |
| 136 | # Build Makefile |
| 137 | makefile = paste(' |
| 138 | CC = ', cc, ' |
| 139 | INCLUDES = |
| 140 | LIBRARIES = -lm |
| 141 | CFLAGS = -g |
| 142 | LDFLAGS = |
| 143 | EXEC = runTests |
| 144 | SRCS = ', paste( |
| 145 | paste(codeFilesUser,sep='',collapse=' '), |
| 146 | paste(codeFilesPkdev,sep='',collapse=' '), |
| 147 | sep=' '), ' |
| 148 | HEDS = ', paste(headerFiles, sep='', collapse=' '), ' |
| 149 | OBJS = ', paste(objectFiles, sep='', collapse= ' '), ' |
| 150 | all: $(EXEC) |
| 151 | $(EXEC) : $(OBJS) |
| 152 | $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $(EXEC) $(LIBRARIES)', sep='') |
| 153 | compileObjects = "" |
| 154 | lengthCodeFilesUser = length(codeFilesUser) |
| 155 | for (i in 1:lengthCodeFilesUser) { |
| 156 | compileObjects = paste(compileObjects, ' |
| 157 | ', objectFiles[i], ' : ', codeFilesUser[i], ' |
| 158 | $(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@', sep='') |
| 159 | } |
| 160 | for (i in 1:length(codeFilesPkdev)) { |
| 161 | compileObjects = paste(compileObjects, ' |
| 162 | ', objectFiles[i+lengthCodeFilesUser], ' : ', codeFilesPkdev[i], ' |
| 163 | $(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@', sep='') |
| 164 | } |
| 165 | makefile = paste(makefile, compileObjects, ' |
| 166 | .PHONY: clean delex depend |
| 167 | clean: |
| 168 | rm -f $(OBJS) ./.depend |
| 169 | delex: |
| 170 | rm -f $(EXEC) |
| 171 | depend: .depend |
| 172 | .depend: $(SRCS) $(HEDS) |
| 173 | rm -f ./.depend |
| 174 | $(CC) -MM $^ > ./.depend |
| 175 | include .depend |
| 176 | ', sep='') |
| 177 | |
| 178 | # Write it to disk |
| 179 | writeLines(makefile, file.path(pkdev_path,"pkgs",pkgName,"src","tests","Makefile")) |
| 180 | } |