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)