| 1 |
module d_do_test; |
|---|
| 2 |
|
|---|
| 3 |
import std.algorithm; |
|---|
| 4 |
import std.array; |
|---|
| 5 |
import std.conv; |
|---|
| 6 |
import std.exception; |
|---|
| 7 |
import std.file; |
|---|
| 8 |
import std.format; |
|---|
| 9 |
import std.process; |
|---|
| 10 |
import std.random; |
|---|
| 11 |
import std.stdio; |
|---|
| 12 |
import std.string; |
|---|
| 13 |
import core.sys.posix.sys.wait; |
|---|
| 14 |
|
|---|
| 15 |
void usage() |
|---|
| 16 |
{ |
|---|
| 17 |
write("d_do_test <input_dir> <test_name> <test_extension>\n" |
|---|
| 18 |
"\n" |
|---|
| 19 |
" input_dir: one of: compilable, fail_compilation, runnable\n" |
|---|
| 20 |
" test_name: basename of test case to run\n" |
|---|
| 21 |
" test_extension: one of: d, html, or sh\n" |
|---|
| 22 |
"\n" |
|---|
| 23 |
" example: d_do_test runnable pi d\n" |
|---|
| 24 |
"\n" |
|---|
| 25 |
" relevant environment variables:\n" |
|---|
| 26 |
" ARGS: set to execute all combinations of\n" |
|---|
| 27 |
" DMD: compiler to use, ex: ../src/dmd\n" |
|---|
| 28 |
" OS: win32, linux, freebsd, osx\n" |
|---|
| 29 |
" RESULTS_DIR: base directory for test results\n" |
|---|
| 30 |
" windows vs non-windows portability env vars:\n" |
|---|
| 31 |
" DSEP: \\\\ or /\n" |
|---|
| 32 |
" SEP: \\ or /\n" |
|---|
| 33 |
" OBJ: .obj or .o\n" |
|---|
| 34 |
" EXE: .exe or <null>\n"); |
|---|
| 35 |
} |
|---|
| 36 |
|
|---|
| 37 |
enum TestMode |
|---|
| 38 |
{ |
|---|
| 39 |
COMPILE, |
|---|
| 40 |
FAIL_COMPILE, |
|---|
| 41 |
RUN |
|---|
| 42 |
} |
|---|
| 43 |
|
|---|
| 44 |
struct TestArgs |
|---|
| 45 |
{ |
|---|
| 46 |
TestMode mode; |
|---|
| 47 |
|
|---|
| 48 |
bool compileSeparately; |
|---|
| 49 |
string executeArgs; |
|---|
| 50 |
string[] sources; |
|---|
| 51 |
string permuteArgs; |
|---|
| 52 |
string postScript; |
|---|
| 53 |
string requiredArgs; |
|---|
| 54 |
} |
|---|
| 55 |
|
|---|
| 56 |
struct EnvData |
|---|
| 57 |
{ |
|---|
| 58 |
string all_args; |
|---|
| 59 |
string dmd; |
|---|
| 60 |
string results_dir; |
|---|
| 61 |
string sep; |
|---|
| 62 |
string dsep; |
|---|
| 63 |
string obj; |
|---|
| 64 |
string exe; |
|---|
| 65 |
string os; |
|---|
| 66 |
string model; |
|---|
| 67 |
} |
|---|
| 68 |
|
|---|
| 69 |
bool findTestParameter(string file, string token, ref string result) |
|---|
| 70 |
{ |
|---|
| 71 |
auto tokenStart = std.string.indexOf(file, token); |
|---|
| 72 |
if (tokenStart == -1) return false; |
|---|
| 73 |
|
|---|
| 74 |
auto lineEndR = std.string.indexOf(file[tokenStart .. $], "\r"); |
|---|
| 75 |
auto lineEndN = std.string.indexOf(file[tokenStart .. $], "\n"); |
|---|
| 76 |
auto lineEnd = lineEndR == -1 ? |
|---|
| 77 |
(lineEndN == -1 ? file.length : lineEndN) : |
|---|
| 78 |
(lineEndN == -1 ? lineEndR : min(lineEndR, lineEndN)); |
|---|
| 79 |
|
|---|
| 80 |
//writeln("found ", token, " in line: ", file.length, ", ", tokenStart, ", ", tokenStart+lineEnd); |
|---|
| 81 |
//writeln("found ", token, " in line: '", file[tokenStart .. tokenStart+lineEnd], "'"); |
|---|
| 82 |
|
|---|
| 83 |
result = strip(file[tokenStart+token.length .. tokenStart+lineEnd]); |
|---|
| 84 |
// skips the :, if present |
|---|
| 85 |
if (result.length > 0 && result[0] == ':') |
|---|
| 86 |
result = strip(result[1 .. $]); |
|---|
| 87 |
|
|---|
| 88 |
//writeln("arg: '", result, "'"); |
|---|
| 89 |
|
|---|
| 90 |
return true; |
|---|
| 91 |
} |
|---|
| 92 |
|
|---|
| 93 |
void gatherTestParameters(ref TestArgs testArgs, string input_dir, string input_file, const ref EnvData envData) |
|---|
| 94 |
{ |
|---|
| 95 |
string file = cast(string)std.file.read(input_file); |
|---|
| 96 |
|
|---|
| 97 |
if (findTestParameter(file, "REQUIRED_ARGS", testArgs.requiredArgs) && |
|---|
| 98 |
testArgs.requiredArgs.length > 0) |
|---|
| 99 |
{ |
|---|
| 100 |
testArgs.requiredArgs ~= " "; |
|---|
| 101 |
} |
|---|
| 102 |
|
|---|
| 103 |
// TODO: should the win32 anti -fPIC code be moved out and made unilateral, would eliminate |
|---|
| 104 |
// different ARGS settings in the Makefile |
|---|
| 105 |
if (findTestParameter(file, "PERMUTE_ARGS", testArgs.permuteArgs)) |
|---|
| 106 |
{ |
|---|
| 107 |
if (envData.os == "win32") |
|---|
| 108 |
{ |
|---|
| 109 |
auto index = std.string.indexOf(testArgs.permuteArgs, "-fPIC"); |
|---|
| 110 |
if (index != -1) |
|---|
| 111 |
testArgs.permuteArgs = testArgs.permuteArgs[0 .. index] ~ testArgs.permuteArgs[index+5 .. $]; |
|---|
| 112 |
} |
|---|
| 113 |
} |
|---|
| 114 |
else |
|---|
| 115 |
{ |
|---|
| 116 |
if (testArgs.mode != TestMode.FAIL_COMPILE) |
|---|
| 117 |
testArgs.permuteArgs = envData.all_args; |
|---|
| 118 |
} |
|---|
| 119 |
|
|---|
| 120 |
findTestParameter(file, "EXECUTE_ARGS", testArgs.executeArgs); |
|---|
| 121 |
|
|---|
| 122 |
string extraSourcesStr; |
|---|
| 123 |
findTestParameter(file, "EXTRA_SOURCES", extraSourcesStr); |
|---|
| 124 |
testArgs.sources = [input_file]; |
|---|
| 125 |
// prepend input_dir to each extra source file |
|---|
| 126 |
foreach(s; split(extraSourcesStr, " ")) |
|---|
| 127 |
testArgs.sources ~= input_dir ~ "/" ~ s; |
|---|
| 128 |
|
|---|
| 129 |
// swap / with $SEP |
|---|
| 130 |
if (envData.sep && envData.sep != "/") |
|---|
| 131 |
foreach (ref s; testArgs.sources) |
|---|
| 132 |
s = replace(s, "/", to!string(envData.sep)); |
|---|
| 133 |
//writeln ("sources: ", testArgs.sources); |
|---|
| 134 |
|
|---|
| 135 |
string compileSeparatelyStr; |
|---|
| 136 |
testArgs.compileSeparately = findTestParameter(file, "COMPILE_SEPARATELY", compileSeparatelyStr); |
|---|
| 137 |
|
|---|
| 138 |
if (findTestParameter(file, "POST_SCRIPT", testArgs.postScript)) |
|---|
| 139 |
testArgs.postScript = replace(testArgs.postScript, "/", to!string(envData.sep)); |
|---|
| 140 |
} |
|---|
| 141 |
|
|---|
| 142 |
string[] combinations(string argstr) |
|---|
| 143 |
{ |
|---|
| 144 |
string[] results; |
|---|
| 145 |
string[] args = split(argstr, " "); |
|---|
| 146 |
long combinations = 1 << args.length; |
|---|
| 147 |
for (size_t i = 0; i < combinations; i++) |
|---|
| 148 |
{ |
|---|
| 149 |
string r; |
|---|
| 150 |
bool printed = false; |
|---|
| 151 |
|
|---|
| 152 |
for (size_t j = 0; j < args.length; j++) |
|---|
| 153 |
{ |
|---|
| 154 |
if (i & 1 << j) |
|---|
| 155 |
{ |
|---|
| 156 |
if (printed) |
|---|
| 157 |
r ~= " "; |
|---|
| 158 |
r ~= args[j]; |
|---|
| 159 |
printed = true; |
|---|
| 160 |
} |
|---|
| 161 |
} |
|---|
| 162 |
|
|---|
| 163 |
results ~= r; |
|---|
| 164 |
} |
|---|
| 165 |
|
|---|
| 166 |
return results; |
|---|
| 167 |
} |
|---|
| 168 |
|
|---|
| 169 |
string genTempFilename() |
|---|
| 170 |
{ |
|---|
| 171 |
auto a = appender!string(); |
|---|
| 172 |
foreach (ref e; 0 .. 8) |
|---|
| 173 |
{ |
|---|
| 174 |
formattedWrite(a, "%x", rndGen.front); |
|---|
| 175 |
rndGen.popFront; |
|---|
| 176 |
} |
|---|
| 177 |
|
|---|
| 178 |
return a.data; |
|---|
| 179 |
} |
|---|
| 180 |
|
|---|
| 181 |
int system(string command) |
|---|
| 182 |
{ |
|---|
| 183 |
if (!command) return std.c.process.system(null); |
|---|
| 184 |
const commandz = toStringz(command); |
|---|
| 185 |
auto status = std.c.process.system(commandz); |
|---|
| 186 |
if (status == -1) return status; |
|---|
| 187 |
version (Windows) status <<= 8; |
|---|
| 188 |
return status; |
|---|
| 189 |
} |
|---|
| 190 |
|
|---|
| 191 |
version(Windows) |
|---|
| 192 |
{ |
|---|
| 193 |
extern (D) bool WIFEXITED( int status ) { return ( status & 0x7F ) == 0; } |
|---|
| 194 |
extern (D) int WEXITSTATUS( int status ) { return ( status & 0xFF00 ) >> 8; } |
|---|
| 195 |
extern (D) int WTERMSIG( int status ) { return status & 0x7F; } |
|---|
| 196 |
extern (D) bool WIFSIGNALED( int status ) |
|---|
| 197 |
{ |
|---|
| 198 |
return ( cast(byte) ( ( status & 0x7F ) + 1 ) >> 1 ) > 0; |
|---|
| 199 |
} |
|---|
| 200 |
} |
|---|
| 201 |
|
|---|
| 202 |
void execute(ref File f, string command, bool expectpass) |
|---|
| 203 |
{ |
|---|
| 204 |
auto filename = genTempFilename(); |
|---|
| 205 |
scope(exit) if (std.file.exists(filename)) std.file.remove(filename); |
|---|
| 206 |
|
|---|
| 207 |
f.writeln(command); |
|---|
| 208 |
auto rc = system(command ~ " > " ~ filename ~ " 2>&1"); |
|---|
| 209 |
|
|---|
| 210 |
f.write(readText(filename)); |
|---|
| 211 |
|
|---|
| 212 |
if (WIFSIGNALED(rc)) |
|---|
| 213 |
{ |
|---|
| 214 |
auto value = WTERMSIG(rc); |
|---|
| 215 |
enforce(0 == value, "caught signal: " ~ to!string(value)); |
|---|
| 216 |
} |
|---|
| 217 |
else if (WIFEXITED(rc)) |
|---|
| 218 |
{ |
|---|
| 219 |
auto value = WEXITSTATUS(rc); |
|---|
| 220 |
if (expectpass) |
|---|
| 221 |
enforce(0 == value, "expected rc == 0, exited with rc=" ~ to!string(value)); |
|---|
| 222 |
else |
|---|
| 223 |
enforce(1 == value, "expected rc == 1, but exited with rc=" ~ to!string(value)); |
|---|
| 224 |
} |
|---|
| 225 |
} |
|---|
| 226 |
|
|---|
| 227 |
int main(string[] args) |
|---|
| 228 |
{ |
|---|
| 229 |
if (args.length != 4) |
|---|
| 230 |
{ |
|---|
| 231 |
usage(); |
|---|
| 232 |
return 1; |
|---|
| 233 |
} |
|---|
| 234 |
|
|---|
| 235 |
string input_dir = args[1]; |
|---|
| 236 |
string test_name = args[2]; |
|---|
| 237 |
string test_extension = args[3]; |
|---|
| 238 |
|
|---|
| 239 |
EnvData envData; |
|---|
| 240 |
envData.all_args = getenv("ARGS"); |
|---|
| 241 |
envData.results_dir = getenv("RESULTS_DIR"); |
|---|
| 242 |
envData.sep = getenv("SEP"); |
|---|
| 243 |
envData.dsep = getenv("DSEP"); |
|---|
| 244 |
envData.obj = getenv("OBJ"); |
|---|
| 245 |
envData.exe = getenv("EXE"); |
|---|
| 246 |
envData.os = getenv("OS"); |
|---|
| 247 |
envData.dmd = replace(getenv("DMD"), "/", envData.sep); |
|---|
| 248 |
envData.model = getenv("MODEL"); |
|---|
| 249 |
|
|---|
| 250 |
string input_file = input_dir ~ envData.sep ~ test_name ~ "." ~ test_extension; |
|---|
| 251 |
string output_dir = envData.results_dir ~ envData.sep ~ input_dir; |
|---|
| 252 |
string output_file = envData.results_dir ~ envData.sep ~ input_dir ~ envData.sep ~ test_name ~ "." ~ test_extension ~ ".out"; |
|---|
| 253 |
string test_app_dmd_base = output_dir ~ envData.sep ~ test_name ~ "_"; |
|---|
| 254 |
|
|---|
| 255 |
TestArgs testArgs; |
|---|
| 256 |
|
|---|
| 257 |
switch (input_dir) |
|---|
| 258 |
{ |
|---|
| 259 |
case "compilable": testArgs.mode = TestMode.COMPILE; break; |
|---|
| 260 |
case "fail_compilation": testArgs.mode = TestMode.FAIL_COMPILE; break; |
|---|
| 261 |
case "runnable": testArgs.mode = TestMode.RUN; break; |
|---|
| 262 |
} |
|---|
| 263 |
|
|---|
| 264 |
gatherTestParameters(testArgs, input_dir, input_file, envData); |
|---|
| 265 |
|
|---|
| 266 |
writefln(" ... %-30s %s%s(%s)", |
|---|
| 267 |
input_file, |
|---|
| 268 |
testArgs.requiredArgs, |
|---|
| 269 |
(testArgs.requiredArgs ? " " : ""), |
|---|
| 270 |
testArgs.permuteArgs); |
|---|
| 271 |
|
|---|
| 272 |
if (std.file.exists(output_file)) |
|---|
| 273 |
std.file.remove(output_file); |
|---|
| 274 |
|
|---|
| 275 |
auto f = File(output_file, "a"); |
|---|
| 276 |
|
|---|
| 277 |
foreach(i, c; combinations(testArgs.permuteArgs)) |
|---|
| 278 |
{ |
|---|
| 279 |
string[] toCleanup; |
|---|
| 280 |
|
|---|
| 281 |
string test_app_dmd = test_app_dmd_base ~ to!string(i) ~ envData.exe; |
|---|
| 282 |
|
|---|
| 283 |
try |
|---|
| 284 |
{ |
|---|
| 285 |
if (!testArgs.compileSeparately) |
|---|
| 286 |
{ |
|---|
| 287 |
string objfile = output_dir ~ envData.sep ~ test_name ~ "_" ~ to!string(i) ~ envData.obj; |
|---|
| 288 |
toCleanup ~= objfile; |
|---|
| 289 |
|
|---|
| 290 |
if (testArgs.mode == TestMode.RUN) |
|---|
| 291 |
toCleanup ~= test_app_dmd; |
|---|
| 292 |
|
|---|
| 293 |
string command = format("%s -m%s -I%s %s %s -od%s -of%s %s%s", envData.dmd, envData.model, input_dir, |
|---|
| 294 |
testArgs.requiredArgs, c, output_dir, |
|---|
| 295 |
(testArgs.mode == TestMode.RUN ? test_app_dmd : objfile), |
|---|
| 296 |
(testArgs.mode == TestMode.RUN ? "" : "-c "), |
|---|
| 297 |
join(testArgs.sources, " ")); |
|---|
| 298 |
version(Windows) command ~= " -map nul.map"; |
|---|
| 299 |
execute(f, command, testArgs.mode != TestMode.FAIL_COMPILE); |
|---|
| 300 |
} |
|---|
| 301 |
else |
|---|
| 302 |
{ |
|---|
| 303 |
foreach (filename; testArgs.sources) |
|---|
| 304 |
{ |
|---|
| 305 |
string newo= envData.results_dir ~ envData.sep ~ |
|---|
| 306 |
replace(replace(filename, ".d", envData.obj), envData.sep~"imports"~envData.sep, envData.sep); |
|---|
| 307 |
toCleanup ~= newo; |
|---|
| 308 |
|
|---|
| 309 |
string command = format("%s -m%s -I%s %s %s -od%s -c %s", envData.dmd, envData.model, input_dir, |
|---|
| 310 |
testArgs.requiredArgs, c, output_dir, filename); |
|---|
| 311 |
execute(f, command, testArgs.mode != TestMode.FAIL_COMPILE); |
|---|
| 312 |
} |
|---|
| 313 |
|
|---|
| 314 |
if (testArgs.mode == TestMode.RUN) |
|---|
| 315 |
{ |
|---|
| 316 |
// link .o's into an executable |
|---|
| 317 |
string command = format("%s -m%s -od%s -of%s %s", envData.dmd, envData.model, output_dir, test_app_dmd, join(toCleanup, " ")); |
|---|
| 318 |
version(Windows) command ~= " -map nul.map"; |
|---|
| 319 |
|
|---|
| 320 |
// add after building the command so that before now, it's purely the .o's involved |
|---|
| 321 |
toCleanup ~= test_app_dmd; |
|---|
| 322 |
|
|---|
| 323 |
execute(f, command, true); |
|---|
| 324 |
} |
|---|
| 325 |
} |
|---|
| 326 |
|
|---|
| 327 |
if (testArgs.mode == TestMode.RUN) |
|---|
| 328 |
{ |
|---|
| 329 |
string command = test_app_dmd; |
|---|
| 330 |
if (testArgs.executeArgs) command ~= " " ~ testArgs.executeArgs; |
|---|
| 331 |
|
|---|
| 332 |
execute(f, command, true); |
|---|
| 333 |
} |
|---|
| 334 |
|
|---|
| 335 |
if (testArgs.postScript) |
|---|
| 336 |
{ |
|---|
| 337 |
f.write("Executing post-test script: "); |
|---|
| 338 |
version (Windows) testArgs.postScript = "bash " ~ testArgs.postScript; |
|---|
| 339 |
execute(f, testArgs.postScript, true); |
|---|
| 340 |
} |
|---|
| 341 |
|
|---|
| 342 |
// cleanup |
|---|
| 343 |
foreach (file; toCleanup) |
|---|
| 344 |
collectException(std.file.remove(file)); |
|---|
| 345 |
|
|---|
| 346 |
f.writeln(); |
|---|
| 347 |
} |
|---|
| 348 |
catch(Exception e) |
|---|
| 349 |
{ |
|---|
| 350 |
f.writeln("Caught exception, bailing: ", e.toString()); |
|---|
| 351 |
f.close(); |
|---|
| 352 |
|
|---|
| 353 |
writeln("Test failed. The logged output:"); |
|---|
| 354 |
if (std.file.exists(output_file)) |
|---|
| 355 |
{ |
|---|
| 356 |
writeln(std.file.read(output_file)); |
|---|
| 357 |
std.file.remove(output_file); |
|---|
| 358 |
} |
|---|
| 359 |
return 1; |
|---|
| 360 |
} |
|---|
| 361 |
} |
|---|
| 362 |
|
|---|
| 363 |
return 0; |
|---|
| 364 |
} |
|---|