1 /*
2 *             Copyright Lodovico Giaretta 2016 - .
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 
8 module test;
9 
10 import std.experimental.xml;
11 
12 import std.encoding: transcode, Latin1String;
13 import std.file: read, readText;
14 import std.functional: toDelegate;
15 import std.path;
16 import std.stdio: write, writeln;
17 import std.utf: UTFException;
18 
19 auto indexes = 
20 [
21     "tests/sun/sun-valid.xml",
22     "tests/sun/sun-error.xml",
23     "tests/sun/sun-invalid.xml",
24     "tests/sun/sun-not-wf.xml",
25     "tests/xmltest/xmltest.xml",
26     "tests/oasis/oasis.xml",
27     "tests/ibm/ibm_oasis_invalid.xml",
28     "tests/ibm/ibm_oasis_not-wf.xml",
29     "tests/ibm/ibm_oasis_valid.xml",
30     "tests/ibm/xml-1.1/ibm_invalid.xml",
31     "tests/ibm/xml-1.1/ibm_not-wf.xml",
32     "tests/ibm/xml-1.1/ibm_valid.xml",
33     "tests/eduni/errata-2e/errata2e.xml",
34     "tests/eduni/xml-1.1/xml11.xml",
35     "tests/eduni/namespaces/1.0/rmt-ns10.xml",
36     "tests/eduni/namespaces/1.1/rmt-ns11.xml",
37     "tests/eduni/errata-3e/errata3e.xml",
38     "tests/eduni/namespaces/errata-1e/errata1e.xml",
39     "tests/eduni/errata-4e/errata4e.xml",
40     "tests/eduni/misc/ht-bh.xml"
41 ];
42 
43 struct Results
44 {
45     int[string] totals;
46     int[string] wrong;
47     
48     static Results opCall()
49     {
50         Results result;
51         result.totals = ["valid": 0, "invalid": 0, "not-wf": 0, "error": 0, "skipped": 0];
52         result.wrong = ["valid": 0, "invalid": 0, "not-wf": 0, "error": 0];
53         return result;
54     }
55     
56     void opOpAssign(string op)(Results other)
57     {
58         static if (op == "+")
59         {
60             foreach (key, val; other.totals)
61                 totals[key] += val;
62             foreach (key, val; other.wrong)
63                 wrong[key] += val;
64         }
65         else
66         {
67             static assert(0);
68         }
69     }
70 }
71 
72 void writeIndent(int depth)
73 {
74     for (int i = 0; i < depth; i++)
75         write("\t");
76 }
77 
78 void printResults(Results results, int depth)
79 {
80     writeIndent(depth);
81     writeln("== RESULTS ==");
82     writeIndent(depth);
83     writeln(results.wrong["valid"], " valid inputs rejected out of ", results.totals["valid"], " total.");
84     writeIndent(depth);
85     writeln(results.wrong["invalid"], " invalid inputs accepted out of ", results.totals["invalid"], " total.");
86     writeIndent(depth);
87     writeln(results.wrong["not-wf"], " ill-formed inputs accepted out of ", results.totals["not-wf"], " total.");
88     writeIndent(depth);
89     writeln(results.wrong["error"], " erroneous inputs accepted out of ", results.totals["error"], " total.");
90     writeIndent(depth);
91     writeln(results.totals["skipped"], " inputs skipped because of unsupported features.");
92 }
93 
94 Results handleTestcases(T)(string directory, ref T cursor, int depth)
95 {
96     auto results = Results();
97     do
98     {
99         if (cursor.getName() == "TESTCASES")
100         {
101             writeIndent(depth);
102             write("TESTCASES");
103             foreach (att; cursor.getAttributes())
104                 if (att.name == "PROFILE")
105                     write(" -- ", att.value);
106             writeln();
107             
108             if (cursor.enter())
109             {
110                 results += handleTestcases(directory, cursor, depth + 1);
111                 cursor.exit();
112             }
113         }
114         else if (cursor.getName() == "TEST")
115         {
116             results += handleTest(directory, cursor, depth);
117         }
118     }
119     while (cursor.next());
120     printResults(results, depth);
121     writeln();
122     return results;
123 }
124 
125 Results handleTest(T)(string directory, ref T cursor, int depth)
126 {
127     auto result = Results();
128     
129     string file, kind;
130     foreach (att; cursor.getAttributes())
131         if (att.name == "ENTITIES" && att.value != "none")
132         {
133             result.totals["skipped"]++;
134             return result;
135         }
136         else if (att.name == "TYPE" && att.value in result.totals)
137             kind = att.value;
138         else if (att.name == "URI")
139             file = att.value;
140     
141     result.totals[kind]++;
142     
143     bool passed = true;
144     Throwable error;
145     try
146     {
147         parseFile(directory ~ dirSeparator ~ file);
148     }
149     catch (Throwable err)
150     {
151         passed = false;
152         error = err;
153     }
154     if (passed && kind != "valid")
155     {
156         writeIndent(depth);
157         write("FAILED: accepted ");
158         result.wrong[kind]++;
159         switch (kind)
160         {
161             case "invalid":
162                 write("invalid ");
163                 break;
164             case "not-wf":
165                 write("ill-formed ");
166                 break;
167             case "error":
168                 write("erroneous ");
169                 break;
170             default:
171                 assert(0);
172         }
173         writeln("file ", file);
174     }
175     else if (!passed && kind == "valid")
176     {
177         writeIndent(depth);
178         result.wrong["valid"]++;
179         writeln("FAILED: rejected valid file ", file);
180     }
181     else
182     {
183         writeIndent(depth);
184         writeln("OK: ", file);
185     }
186     
187     return result;
188 }
189 
190 // callback used to ignore missing xml declaration, while throwing on invalid attributes
191 void uselessCallback(CursorError err)
192 {
193     if (err != CursorError.MISSING_XML_DECLARATION)
194         assert(0);
195 }
196 
197 /++
198 + Most tests are currently not working for the following reasons:
199 + - We don't have any validation, so we accept all files that seem well formed;
200 +/
201 void main()
202 {
203     auto cursor = 
204          chooseLexer!string
205         .parse
206         .cursor(&uselessCallback); // If an index is not well-formed, just tell us but continue parsing
207     
208     auto results = Results();
209     foreach (i, index; indexes)
210     {
211         writeln(i, " -- ", index);
212         
213         cursor.setSource(readText(index));
214         cursor.enter();
215         
216         results += handleTestcases(dirName(index), cursor, 1);
217     }
218     
219     printResults(results, 0);
220     writeln();
221 }
222 
223 void parseFile(string filename)
224 {
225     void inspectOneLevel(T)(ref T cursor)
226     {
227         do
228         {
229             if (cursor.enter)
230             {
231                 inspectOneLevel(cursor);
232                 cursor.exit();
233             }
234         }
235         while (cursor.next());
236     }
237 
238     string text;
239     try
240     {
241         text = readText(filename);
242     }
243     catch (UTFException)
244     {
245         auto raw = read(filename);
246         transcode(cast(Latin1String)raw, text);
247     }
248     
249     auto cursor = 
250          chooseParser!text(() { throw new Exception("AAAAHHHHH"); })
251         .cursor(&uselessCallback); // lots of tests do not have an xml declaration
252     
253     cursor.setSource(text);
254     inspectOneLevel(cursor);
255 }