initial commit
[pkgdev.git] / R / runCtests.R
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 }