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)