1 # Core function to execute C unit tests (or just show functions names)
2 .pkgdev.ctest = function(path, prefix, show, cc) {
4 # Initial step: list every potential unit test under path/src/tests.
5 allFuncNames = .parseCunitTests(file.path(path,"src","tests"))
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...
11 # If show==TRUE, display every potential test starting with prefix, and exit
13 #display in alphabetic order
14 return (paste(sort(funcNames), sep='\n'))
17 # Get package name from path
18 pathTokens = strsplit(path, c(.Platform$file.sep))[[1]]
19 pkgName = pathTokens[length(pathTokens)]
21 # Generate main.c to run exactly the tests asked by user
22 .generateMainCall(funcNames, pkgName)
24 # Get all C source files (src/tests/*, src/sources/* ...)
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")
32 "main.c", #above generated main.c
33 file.path(pkdev_path,"src","tests","unitTestsMacros.c")) #C unit tests macros
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)
40 # Run selected tests (after 'funcs' filter applied)
41 pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev")
43 setwd( file.path(pkdev_path,"pkgs",pkgName,"src","tests") )
45 system( paste( Sys.getenv("MAKE"), "depend", sep=' ') )
46 system( paste( Sys.getenv("MAKE"), "-j", detectCores(), "all", sep=' ') )
51 # Recursively explore initial path to parse source files for unit tests.
52 .parseCunitTests = function(path) {
54 # Unit test names to return
57 # For each file in current folder
58 for (fileName in list.files(path, full.names=TRUE, recursive=TRUE)) {
60 # If the file is not a source, skip
61 if ( length( grep("\\.[CcHh]$", fileName) ) == 0) next
63 # Every test function has a name starting with "test_"
65 "^[ \t]*void[ \t]*test_[a-zA-Z0-9_]*[ \t]*\\(.*",
66 scan(fileName, what="character", sep='\n'),
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]*\\(.*",
79 # Generate main.c file under R_HOME_USER/pkgdev/pkgs/pkgName/src/tests
80 .generateMainCall = function(funcNames, pkgName) {
82 # Build text file main.c
86 #include <time.h> // to print timings
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='')
98 mainDotC = paste(mainDotC, "}\n", sep='')
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"))
106 # Generate appropriate Makefile under R_HOME_USER/pkgdev/pkgs/pkgName/src/tests
107 .generateMakefileTest = function(path, cFilesUser, cFilesPkdev, pkgName, cc) {
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)
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)
125 for (codeFileUser in codeFilesUser) {
128 sub("(.*)\\.(c|C)$","\\1\\.o", sub(basePathFrom,basePathTo,codeFileUser,fixed=TRUE)))
130 for (codeFilePkdev in codeFilesPkdev) {
133 sub("(.*)\\.(c|C)$","\\1\\.o", codeFilePkdev))
145 paste(codeFilesUser,sep='',collapse=' '),
146 paste(codeFilesPkdev,sep='',collapse=' '),
148 HEDS = ', paste(headerFiles, sep='', collapse=' '), '
149 OBJS = ', paste(objectFiles, sep='', collapse= ' '), '
152 $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $(EXEC) $(LIBRARIES)', sep='')
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='')
160 for (i in 1:length(codeFilesPkdev)) {
161 compileObjects = paste(compileObjects, '
162 ', objectFiles[i+lengthCodeFilesUser], ' : ', codeFilesPkdev[i], '
163 $(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@', sep='')
165 makefile = paste(makefile, compileObjects, '
166 .PHONY: clean delex depend
168 rm -f $(OBJS) ./.depend
172 .depend: $(SRCS) $(HEDS)
174 $(CC) -MM $^ > ./.depend
179 writeLines(makefile, file.path(pkdev_path,"pkgs",pkgName,"src","tests","Makefile"))