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.testcommon; 8 9 version(unittest) 10 { 11 12 public import std.conv; 13 public import std.stdio; 14 public import dyaml.all; 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.typecons; 23 24 package: 25 26 /** 27 * Run an unittest. 28 * 29 * Params: testName = Name of the unittest. 30 * testFunction = Unittest function. 31 * unittestExt = Extensions of data files needed for the unittest. 32 * skipExt = Extensions that must not be used for the unittest. 33 */ 34 void run(F ...)(string testName, void function(bool, F) testFunction, 35 string[] unittestExt, string[] skipExt = []) 36 { 37 immutable string dataDir = "test/data"; 38 auto testFilenames = findTestFilenames(dataDir); 39 bool verbose = false; 40 41 Result[] results; 42 if(unittestExt.length > 0) 43 { 44 outer: foreach(base, extensions; testFilenames) 45 { 46 string[] filenames; 47 foreach(ext; unittestExt) 48 { 49 if(!extensions.canFind(ext)){continue outer;} 50 filenames ~= base ~ '.' ~ ext; 51 } 52 foreach(ext; skipExt) 53 { 54 if(extensions.canFind(ext)){continue outer;} 55 } 56 57 results ~= execute!F(testName, testFunction, filenames, verbose); 58 } 59 } 60 else 61 { 62 results ~= execute!F(testName, testFunction, cast(string[])[], verbose); 63 } 64 display(results, verbose); 65 } 66 67 68 private: 69 70 ///Unittest status. 71 enum TestStatus 72 { 73 Success, //Unittest passed. 74 Failure, //Unittest failed. 75 Error //There's an error in the unittest. 76 } 77 78 ///Unittest result. 79 alias Tuple!(string, "name", string[], "filenames", TestStatus, "kind", string, "info") Result; 80 81 /** 82 * Find unittest input filenames. 83 * 84 * Params: dir = Directory to look in. 85 * 86 * Returns: Test input base filenames and their extensions. 87 */ 88 string[][string] findTestFilenames(const string dir) 89 { 90 //Groups of extensions indexed by base names. 91 string[][string] names; 92 foreach(string name; dirEntries(dir, SpanMode.shallow)) 93 { 94 if(isFile(name)) 95 { 96 string base = name.stripExtension(); 97 string ext = name.extension(); 98 if(ext is null){ext = "";} 99 if(ext[0] == '.'){ext = ext[1 .. $];} 100 101 //If the base name doesn't exist yet, add it; otherwise add new extension. 102 names[base] = ((base in names) is null) ? [ext] : names[base] ~ ext; 103 } 104 } 105 return names; 106 } 107 108 /** 109 * Recursively copy an array of strings to a tuple to use for unittest function input. 110 * 111 * Params: index = Current index in the array/tuple. 112 * tuple = Tuple to copy to. 113 * strings = Strings to copy. 114 */ 115 void stringsToTuple(uint index, F ...)(ref F tuple, const string[] strings) 116 in{assert(F.length == strings.length);} 117 body 118 { 119 tuple[index] = strings[index]; 120 static if(index > 0){stringsToTuple!(index - 1, F)(tuple, strings);} 121 } 122 123 /** 124 * Execute an unittest on specified files. 125 * 126 * Params: testName = Name of the unittest. 127 * testFunction = Unittest function. 128 * filenames = Names of input files to test with. 129 * verbose = Print verbose output? 130 * 131 * Returns: Information about the results of the unittest. 132 */ 133 Result execute(F ...)(const string testName, void function(bool, F) testFunction, 134 string[] filenames, const bool verbose) 135 { 136 if(verbose) 137 { 138 writeln("==========================================================================="); 139 writeln(testName ~ "(" ~ filenames.join(", ") ~ ")..."); 140 } 141 142 auto kind = TestStatus.Success; 143 string info = ""; 144 try 145 { 146 //Convert filenames to parameters tuple and call the test function. 147 F parameters; 148 stringsToTuple!(F.length - 1, F)(parameters, filenames); 149 testFunction(verbose, parameters); 150 if(!verbose){write(".");} 151 } 152 catch(Throwable e) 153 { 154 info = to!string(typeid(e)) ~ "\n" ~ to!string(e); 155 kind = (typeid(e) is typeid(AssertError)) ? TestStatus.Failure : TestStatus.Error; 156 write((verbose ? to!string(e) : to!string(kind)) ~ " "); 157 } 158 159 stdout.flush(); 160 161 return Result(testName, filenames, kind, info); 162 } 163 164 /** 165 * Display unittest results. 166 * 167 * Params: results = Unittest results. 168 * verbose = Print verbose output? 169 */ 170 void display(Result[] results, const bool verbose) 171 { 172 if(results.length > 0 && !verbose){write("\n");} 173 174 size_t failures = 0; 175 size_t errors = 0; 176 177 if(verbose) 178 { 179 writeln("==========================================================================="); 180 } 181 //Results of each test. 182 foreach(result; results) 183 { 184 if(verbose) 185 { 186 writeln(result.name, "(" ~ result.filenames.join(", ") ~ "): ", 187 to!string(result.kind)); 188 } 189 190 if(result.kind == TestStatus.Success){continue;} 191 192 if(result.kind == TestStatus.Failure){++failures;} 193 else if(result.kind == TestStatus.Error){++errors;} 194 writeln(result.info); 195 writeln("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); 196 } 197 198 //Totals. 199 writeln("==========================================================================="); 200 writeln("TESTS: ", results.length); 201 if(failures > 0){writeln("FAILURES: ", failures);} 202 if(errors > 0) {writeln("ERRORS: ", errors);} 203 } 204 205 } // version(unittest)