Commit | Line | Data |
---|---|---|
156f8ca6 BA |
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 | } |