1 module unittester; 2 3 import tango.core.ThreadPool; 4 import tango.io.Stdout; 5 import Path = tango.io.Path; 6 import tango.io.FilePath; 7 import tango.io.device.File; 8 import tango.text.Arguments; 9 import tango.sys.Process; 10 import tango.core.sync.Condition; 11 import tango.core.sync.Mutex; 12 13 enum immutable(char)[] DummyFileContent = "void main() {}"; 14 enum immutable(char)[] Help = 15 ` 16 unittester - TangoD2's unittest utility 17 Copyright (C) 2012-2014 Pavel Sountsov. 18 19 Usage: unittester [OPTION]... [FILES]... 20 Example: unittester -c dmd -d .unittest -a "-m32" tango/text/Util.d 21 22 FILES is a list of files you want to run unittests in. This program essentially 23 compiles each of the passed files individually along with a dummy main file. 24 Then it runs the resultant executable to see if it's unittests run. 25 Additionally, if the unittest runs longer than 5 seconds, it is aborted. 26 27 Options: 28 -a, --additional=OPTIONS what additional options to pass the compiler. 29 These are appended after the options specified by 30 the --options option. 31 -c, --compiler=COMPILER what compiler to use when compiling the unittests 32 Default: dmd 33 -d, --directory=DIRECTORY what directory to use to write the test 34 executables to. Note that this directory is not 35 removed after the program finishes. 36 Default: .unittest 37 -h, --help print this help text 38 -o, --options=OPTIONS what options to pass to the compiler. 39 Default if compiler is dmd: 40 -unittest -L-ltango -debug=UnitTest 41 Default if compiler is ldc2: 42 -unittest -L-ltango -d-debug=UnitTest 43 `; 44 45 void main(const(char[])[] args) 46 { 47 const(char)[] compiler = "dmd"; 48 const(char)[] compiler_options; 49 const(char)[] directory = ".unittest"; 50 const(char)[] additional_options; 51 52 const(char)[] null_str = null; /* DMD bug*/ 53 54 auto arguments = new Arguments; 55 arguments("compiler").aliased('c').params(1).bind( 56 (const(char)[] arg) 57 { 58 compiler = arg; 59 return null_str; 60 }); 61 arguments("options").aliased('o').params(1).bind( 62 (const(char)[] arg) 63 { 64 compiler_options = arg; 65 return null_str; 66 }); 67 arguments("additional").aliased('a').params(1).bind( 68 (const(char)[] arg) 69 { 70 additional_options = arg; 71 return null_str; 72 }); 73 arguments("directory").aliased('d').params(1).bind( 74 (const(char)[] arg) 75 { 76 directory = arg; 77 return null_str; 78 }); 79 arguments("help").aliased('h').halt(); 80 arguments(null).params(0, 1024); 81 82 if(!arguments.parse(args[1..$]) || arguments("help").set) 83 { 84 Stdout(arguments.errors(&Stdout.layout.sprint)); 85 return; 86 } 87 88 if(compiler_options == "") 89 { 90 version(Windows) 91 { 92 compiler_options = "-unittest libtango-dmd.lib -debug=UnitTest"; 93 } 94 else 95 { 96 auto compiler_path = Path.parse(compiler); 97 switch(compiler_path.name) 98 { 99 case "dmd": 100 compiler_options = "-unittest -L-ltango-dmd -debug=UnitTest"; 101 break; 102 case "ldc2": 103 compiler_options = "-unittest -L-ltango-ldc -d-debug=UnitTest"; 104 break; 105 default: 106 assert(0, "Unsupported compiler."); 107 } 108 } 109 } 110 111 /* 112 * Set up files and directories 113 */ 114 if(!Path.exists(directory)) 115 Path.createFolder(directory); 116 117 auto dummy_file_fp = new FilePath(directory.dup); 118 if(dummy_file_fp.isFile) 119 { 120 throw new Exception("'" ~ directory.idup ~ "' already exists and is a file!"); 121 } 122 dummy_file_fp.append("dummy.d"); 123 dummy_file_fp.native; 124 125 { 126 auto dummy_file = new File(dummy_file_fp.cString()[0..$-1], File.WriteCreate); 127 scope(exit) dummy_file.close(); 128 dummy_file.write(DummyFileContent); 129 } 130 131 /* 132 * Set up compiler business 133 */ 134 auto output_fp = new FilePath(directory.dup); 135 output_fp.append("out"); 136 output_fp.native; 137 auto proc_raw_arguments = compiler ~ " " ~ compiler_options ~ " " ~ 138 additional_options ~ " -of" ~ output_fp.cString()[0..$-1] ~ " " ~ 139 dummy_file_fp.cString()[0..$-1]; 140 auto compiler_proc = new Process(true, proc_raw_arguments); 141 compiler_proc.setRedirect(Redirect.All | Redirect.ErrorToOutput); 142 auto proc_arguments = compiler_proc.args().dup; 143 144 auto test_proc = new Process(true, output_fp.cString()[0..$-1]); 145 test_proc.setRedirect(Redirect.All | Redirect.ErrorToOutput); 146 147 auto test_mutex = new Mutex; 148 auto test_condition = new Condition(test_mutex); 149 150 auto thread_pool = new ThreadPool!()(1); 151 152 size_t skipped = 0; 153 size_t total = 0; 154 size_t pass = 0; 155 size_t fail = 0; 156 size_t timeout = 0; 157 size_t compile_error = 0; 158 159 foreach(file; arguments(null).assigned()) 160 { 161 if(Path.parse(file).ext != "d") 162 { 163 Stdout.formatln("Skipping '{}'... Not a D file.", file); 164 skipped++; 165 continue; 166 } 167 if(!Path.exists(file)) 168 { 169 Stdout.formatln("Skipping '{}'... Doesn't exist.", file); 170 skipped++; 171 continue; 172 } 173 174 total++; 175 176 Stdout.formatln("Compiling '{}'", file); 177 178 auto file_fp = new FilePath(file.dup); 179 file_fp.native; 180 auto new_args = proc_arguments ~ file_fp.cString()[0..$-1]; 181 compiler_proc.setArgs(compiler, new_args).execute(); 182 Stdout.copy(compiler_proc.stdout).flush(); 183 auto result = compiler_proc.wait(); 184 185 if(result.status != 0) 186 { 187 Stdout("COMPILEERROR").nl; 188 Stdout(result.toString()).nl; 189 compile_error++; 190 continue; 191 } 192 193 Stdout("Testing...").nl; 194 195 Process.Result test_result; 196 bool killed; 197 198 void test_thread() 199 { 200 synchronized(test_mutex) 201 { 202 if(!test_condition.wait(10)) 203 { 204 killed = true; 205 test_proc.kill(); 206 } 207 } 208 } 209 210 thread_pool.assign(&test_thread); 211 212 test_proc.execute(); 213 Stdout.copy(test_proc.stdout).flush(); 214 test_result = test_proc.wait(); 215 216 test_condition.notify(); 217 218 thread_pool.wait(); 219 220 if(test_result.reason == Process.Result.Exit && test_result.status == 0) 221 { 222 Stdout("PASS").nl; 223 pass++; 224 } 225 else if(test_result.reason == Process.Result.Signal && killed) 226 { 227 Stdout("TIMEDOUT").nl; 228 Stdout(result.toString()).nl; 229 timeout++; 230 } 231 else 232 { 233 Stdout("FAIL").nl; 234 Stdout(result.toString()).nl; 235 fail++; 236 } 237 } 238 239 Stdout.nl; 240 Stdout.formatln("{} tested. {} skipped.", total, skipped); 241 Stdout.formatln("PASS: {}", pass); 242 Stdout.formatln("FAIL: {}", fail); 243 Stdout.formatln("TIMEDOUT: {}", timeout); 244 Stdout.formatln("COMPILEERROR: {}", compile_error); 245 }