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, "linted": 0, "invalid": 0, "not-wf": 0, "error": 0, "skipped": 0]; 52 result.wrong = ["valid": 0, "linted":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["linted"], " wrong outputs out of ", results.totals["linted"], " total written files."); 86 writeIndent(depth); 87 writeln(results.wrong["invalid"], " invalid inputs accepted out of ", results.totals["invalid"], " total."); 88 writeIndent(depth); 89 writeln(results.wrong["not-wf"], " ill-formed inputs accepted out of ", results.totals["not-wf"], " total."); 90 writeIndent(depth); 91 writeln(results.wrong["error"], " erroneous inputs accepted out of ", results.totals["error"], " total."); 92 writeIndent(depth); 93 writeln(results.totals["skipped"], " inputs skipped because of unsupported features."); 94 } 95 96 Results handleTestcases(T)(string directory, ref T cursor, int depth) 97 { 98 auto results = Results(); 99 do 100 { 101 if (cursor.name == "TESTCASES") 102 { 103 writeIndent(depth); 104 write("TESTCASES"); 105 foreach (att; cursor.attributes) 106 if (att.name == "PROFILE") 107 write(" -- ", att.value); 108 writeln(); 109 110 if (cursor.enter()) 111 { 112 results += handleTestcases(directory, cursor, depth + 1); 113 cursor.exit(); 114 } 115 } 116 else if (cursor.name == "TEST") 117 { 118 results += handleTest(directory, cursor, depth); 119 } 120 } 121 while (cursor.next()); 122 printResults(results, depth); 123 writeln(); 124 return results; 125 } 126 127 Results handleTest(T)(string directory, ref T cursor, int depth) 128 { 129 auto result = Results(); 130 131 string file, kind; 132 foreach (att; cursor.attributes) 133 if (att.name == "ENTITIES" && att.value != "none") 134 { 135 result.totals["skipped"]++; 136 return result; 137 } 138 else if (att.name == "TYPE" && att.value in result.totals) 139 kind = att.value; 140 else if (att.name == "URI") 141 file = att.value; 142 143 result.totals[kind]++; 144 145 bool passed = true, linted = false, linted_ok = false; 146 try 147 { 148 linted_ok = parseFile(directory ~ dirSeparator ~ file, linted); 149 } 150 catch (MyException err) 151 { 152 passed = false; 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 if (passed && linted) 186 { 187 result.totals["linted"]++; 188 if (!linted_ok) 189 { 190 result.wrong["linted"]++; 191 writeIndent(depth + 1); 192 writeln("[WRONG DIFF]"); 193 } 194 } 195 } 196 197 return result; 198 } 199 200 class MyException: Exception 201 { 202 this(string msg) 203 { 204 super(msg); 205 } 206 } 207 208 // callback used to ignore missing xml declaration, while throwing on invalid attributes 209 void uselessCallback(CursorError err) 210 { 211 if (err != CursorError.missingXMLDeclaration) 212 throw new MyException("AAAAHHHHH"); 213 } 214 215 /++ 216 + Most tests are currently not working for the following reasons: 217 + - We don't have any validation, so we accept all files that seem well formed; 218 +/ 219 void main() 220 { 221 auto cursor = 222 chooseLexer!string 223 .parser 224 .cursor(&uselessCallback); // If an index is not well-formed, just tell us but continue parsing 225 226 auto results = Results(); 227 foreach (i, index; indexes) 228 { 229 writeln(i, " -- ", index); 230 231 cursor.setSource(readText(index)); 232 cursor.enter(); 233 234 results += handleTestcases(dirName(index), cursor, 1); 235 } 236 237 printResults(results, 0); 238 writeln(); 239 } 240 241 bool parseFile(string filename, ref bool lint) 242 { 243 void inspectOneLevel(T)(ref T cursor) 244 { 245 do 246 { 247 if (cursor.enter) 248 { 249 inspectOneLevel(cursor); 250 cursor.exit(); 251 } 252 } 253 while (cursor.next()); 254 } 255 256 string text; 257 try 258 { 259 text = readText(filename); 260 } 261 catch (UTFException) 262 { 263 try 264 { 265 auto raw = read(filename); 266 auto bytes = cast(ubyte[])raw; 267 268 if(bytes.length > 1 && bytes[0] == 0xFF && bytes[1] == 0xFE) 269 { 270 auto shorts = cast(ushort[])raw; 271 transcode(cast(wstring)(shorts[1..$]), text); 272 } 273 else 274 transcode(cast(Latin1String)raw, text); 275 } 276 catch(Throwable) 277 { 278 throw new MyException("AAAAHHHHH"); 279 } 280 } 281 282 auto cursor = 283 text 284 .parser(() { throw new MyException("AAAAHHHHH"); }) 285 .cursor(&uselessCallback); // lots of tests do not have an xml declaration 286 287 cursor.setSource(text); 288 289 lint = false; 290 foreach (attr; cursor.attributes) 291 if (attr.name == "version" && attr.value == "1.0") 292 lint = true; 293 294 inspectOneLevel(cursor); 295 296 if (lint) 297 { 298 import std.process, std.stdio, std.array; 299 import std.experimental.xml.writer; 300 301 lint = false; 302 bool result = false; 303 auto xmllint = executeShell("xmllint --pretty 2 --c14n11 " ~ filename ~ " > linted_input.xml"); 304 if (xmllint.status == 0) 305 { 306 { 307 cursor.setSource(text); 308 auto file = File("output.xml", "w"); 309 auto ltw = file.lockingTextWriter; 310 auto writer = Writer!(string, typeof(ltw))(); 311 312 writer.setSink(ltw); 313 writer.writeCursor(cursor); 314 } 315 316 xmllint = executeShell("xmllint --pretty 2 --c14n11 output.xml > linted_output.xml"); 317 if (xmllint.status == 0) 318 { 319 lint = true; 320 auto diff = executeShell("diff linted_input.xml linted_output.xml"); 321 result = diff.status == 0; 322 } 323 } 324 executeShell("rm -f linted_output.xml linted_input.xml output.xml"); 325 return result; 326 } 327 return false; 328 }