1 2 // Copyright Ferdinand Majerech 2011. 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE_1_0.txt or copy at 5 // http://www.boost.org/LICENSE_1_0.txt) 6 7 module dyaml.test.common; 8 9 version(unittest) 10 { 11 12 public import std.conv; 13 public import std.stdio; 14 public import dyaml; 15 16 import core.exception; 17 import std.algorithm; 18 import std.array; 19 import std.conv; 20 import std.file; 21 import std.path; 22 import std.traits; 23 import std.typecons; 24 25 package: 26 27 debug(verbose) 28 { 29 enum verbose = true; 30 enum quiet = false; 31 } 32 else 33 { 34 enum verbose = false; 35 debug(noisy) enum quiet = false; 36 else enum quiet = true; 37 } 38 39 /** 40 * Run an unittest. 41 * 42 * Params: testName = Name of the unittest. 43 * testFunction = Unittest function. 44 * unittestExt = Extensions of data files needed for the unittest. 45 * skipExt = Extensions that must not be used for the unittest. 46 */ 47 void run(D)(string testName, D testFunction, 48 string[] unittestExt, string[] skipExt = []) 49 { 50 immutable string dataDir = __FILE_FULL_PATH__.dirName ~ "/../../../test/data"; 51 auto testFilenames = findTestFilenames(dataDir); 52 53 Result[] results; 54 if(unittestExt.length > 0) 55 { 56 outer: foreach(base, extensions; testFilenames) 57 { 58 string[] filenames; 59 foreach(ext; unittestExt) 60 { 61 if(!extensions.canFind(ext)){continue outer;} 62 filenames ~= base ~ '.' ~ ext; 63 } 64 foreach(ext; skipExt) 65 { 66 if(extensions.canFind(ext)){continue outer;} 67 } 68 69 results ~= execute(testName, testFunction, filenames); 70 } 71 } 72 else 73 { 74 results ~= execute(testName, testFunction, string[].init); 75 } 76 display(results); 77 } 78 79 /** 80 * Prints an exception if verbosity is turned on. 81 * Params: e = Exception to print. 82 */ 83 void printException(YAMLException e) @trusted 84 { 85 static if(verbose) { writeln(typeid(e).toString(), "\n", e); } 86 } 87 88 void printProgress(T...)(T params) @safe 89 { 90 static if(!quiet) 91 { 92 writeln(params); 93 } 94 } 95 96 private: 97 98 ///Unittest status. 99 enum TestStatus 100 { 101 success, //Unittest passed. 102 failure, //Unittest failed. 103 error //There's an error in the unittest. 104 } 105 106 ///Unittest result. 107 alias Result = Tuple!(string, "name", string[], "filenames", TestStatus, "kind", string, "info"); 108 109 /** 110 * Find unittest input filenames. 111 * 112 * Params: dir = Directory to look in. 113 * 114 * Returns: Test input base filenames and their extensions. 115 */ 116 string[][string] findTestFilenames(const string dir) @trusted 117 { 118 //Groups of extensions indexed by base names. 119 string[][string] names; 120 foreach(string name; dirEntries(dir, SpanMode.shallow)) 121 { 122 if(isFile(name)) 123 { 124 string base = name.stripExtension(); 125 string ext = name.extension(); 126 if(ext is null){ext = "";} 127 if(ext[0] == '.'){ext = ext[1 .. $];} 128 129 //If the base name doesn't exist yet, add it; otherwise add new extension. 130 names[base] = ((base in names) is null) ? [ext] : names[base] ~ ext; 131 } 132 } 133 return names; 134 } 135 136 /** 137 * Recursively copy an array of strings to a tuple to use for unittest function input. 138 * 139 * Params: index = Current index in the array/tuple. 140 * tuple = Tuple to copy to. 141 * strings = Strings to copy. 142 */ 143 void stringsToTuple(uint index, F ...)(ref F tuple, const string[] strings) 144 in{assert(F.length == strings.length);} 145 do 146 { 147 tuple[index] = strings[index]; 148 static if(index > 0){stringsToTuple!(index - 1, F)(tuple, strings);} 149 } 150 151 /** 152 * Execute an unittest on specified files. 153 * 154 * Params: testName = Name of the unittest. 155 * testFunction = Unittest function. 156 * filenames = Names of input files to test with. 157 * 158 * Returns: Information about the results of the unittest. 159 */ 160 Result execute(D)(const string testName, D testFunction, 161 string[] filenames) @trusted 162 { 163 static if(verbose) 164 { 165 writeln("==========================================================================="); 166 writeln(testName ~ "(" ~ filenames.join(", ") ~ ")..."); 167 } 168 169 auto kind = TestStatus.success; 170 string info = ""; 171 try 172 { 173 //Convert filenames to parameters tuple and call the test function. 174 alias F = Parameters!D[0..$]; 175 F parameters; 176 stringsToTuple!(F.length - 1, F)(parameters, filenames); 177 testFunction(parameters); 178 static if (!quiet){write(".");} 179 } 180 catch(Throwable e) 181 { 182 info = to!string(typeid(e)) ~ "\n" ~ to!string(e); 183 kind = (typeid(e) is typeid(AssertError)) ? TestStatus.failure : TestStatus.error; 184 write((verbose ? to!string(e) : to!string(kind)) ~ " "); 185 } 186 187 stdout.flush(); 188 189 return Result(testName, filenames, kind, info); 190 } 191 192 /** 193 * Display unittest results. 194 * 195 * Params: results = Unittest results. 196 */ 197 void display(Result[] results) @safe 198 { 199 if(results.length > 0 && !verbose && !quiet){write("\n");} 200 201 size_t failures, errors; 202 203 static if(verbose) 204 { 205 writeln("==========================================================================="); 206 } 207 //Results of each test. 208 foreach(result; results) 209 { 210 static if(verbose) 211 { 212 writeln(result.name, "(" ~ result.filenames.join(", ") ~ "): ", 213 to!string(result.kind)); 214 } 215 216 if(result.kind == TestStatus.success){continue;} 217 218 if(result.kind == TestStatus.failure){++failures;} 219 else if(result.kind == TestStatus.error){++errors;} 220 writeln(result.info); 221 writeln("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); 222 } 223 224 //Totals. 225 printProgress("==========================================================================="); 226 printProgress("TESTS: ", results.length); 227 if(failures > 0){writeln("FAILURES: ", failures);} 228 if(errors > 0) {writeln("ERRORS: ", errors);} 229 } 230 231 } // version(unittest)