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.testconstructor; 8 9 10 version(unittest) 11 { 12 13 import std.datetime; 14 import std.exception; 15 import std.path; 16 import std.string; 17 import std.typecons; 18 19 import dyaml.tag; 20 import dyaml.testcommon; 21 22 23 ///Expected results of loading test inputs. 24 Node[][string] expected; 25 26 ///Initialize expected. 27 static this() 28 { 29 expected["aliases-cdumper-bug"] = constructAliasesCDumperBug(); 30 expected["construct-binary"] = constructBinary(); 31 expected["construct-bool"] = constructBool(); 32 expected["construct-custom"] = constructCustom(); 33 expected["construct-float"] = constructFloat(); 34 expected["construct-int"] = constructInt(); 35 expected["construct-map"] = constructMap(); 36 expected["construct-merge"] = constructMerge(); 37 expected["construct-null"] = constructNull(); 38 expected["construct-omap"] = constructOMap(); 39 expected["construct-pairs"] = constructPairs(); 40 expected["construct-seq"] = constructSeq(); 41 expected["construct-set"] = constructSet(); 42 expected["construct-str-ascii"] = constructStrASCII(); 43 expected["construct-str"] = constructStr(); 44 expected["construct-str-utf8"] = constructStrUTF8(); 45 expected["construct-timestamp"] = constructTimestamp(); 46 expected["construct-value"] = constructValue(); 47 expected["duplicate-merge-key"] = duplicateMergeKey(); 48 expected["float-representer-2.3-bug"] = floatRepresenterBug(); 49 expected["invalid-single-quote-bug"] = invalidSingleQuoteBug(); 50 expected["more-floats"] = moreFloats(); 51 expected["negative-float-bug"] = negativeFloatBug(); 52 expected["single-dot-is-not-float-bug"] = singleDotFloatBug(); 53 expected["timestamp-bugs"] = timestampBugs(); 54 expected["utf16be"] = utf16be(); 55 expected["utf16le"] = utf16le(); 56 expected["utf8"] = utf8(); 57 expected["utf8-implicit"] = utf8implicit(); 58 } 59 60 ///Construct a pair of nodes with specified values. 61 Node.Pair pair(A, B)(A a, B b) 62 { 63 return Node.Pair(a,b); 64 } 65 66 ///Test cases: 67 68 Node[] constructAliasesCDumperBug() 69 { 70 return [Node(["today", "today"])]; 71 } 72 73 Node[] constructBinary() 74 { 75 auto canonical = cast(ubyte[])"GIF89a\x0c\x00\x0c\x00\x84\x00\x00\xff\xff\xf7\xf5\xf5\xee\xe9\xe9\xe5fff\x00\x00\x00\xe7\xe7\xe7^^^\xf3\xf3\xed\x8e\x8e\x8e\xe0\xe0\xe0\x9f\x9f\x9f\x93\x93\x93\xa7\xa7\xa7\x9e\x9e\x9eiiiccc\xa3\xa3\xa3\x84\x84\x84\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9!\xfe\x0eMade with GIMP\x00,\x00\x00\x00\x00\x0c\x00\x0c\x00\x00\x05, \x8e\x810\x9e\xe3@\x14\xe8i\x10\xc4\xd1\x8a\x08\x1c\xcf\x80M$z\xef\xff0\x85p\xb8\xb01f\r\x1b\xce\x01\xc3\x01\x1e\x10' \x82\n\x01\x00;"; 76 auto generic = cast(ubyte[])"GIF89a\x0c\x00\x0c\x00\x84\x00\x00\xff\xff\xf7\xf5\xf5\xee\xe9\xe9\xe5fff\x00\x00\x00\xe7\xe7\xe7^^^\xf3\xf3\xed\x8e\x8e\x8e\xe0\xe0\xe0\x9f\x9f\x9f\x93\x93\x93\xa7\xa7\xa7\x9e\x9e\x9eiiiccc\xa3\xa3\xa3\x84\x84\x84\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9\xff\xfe\xf9!\xfe\x0eMade with GIMP\x00,\x00\x00\x00\x00\x0c\x00\x0c\x00\x00\x05, \x8e\x810\x9e\xe3@\x14\xe8i\x10\xc4\xd1\x8a\x08\x1c\xcf\x80M$z\xef\xff0\x85p\xb8\xb01f\r\x1b\xce\x01\xc3\x01\x1e\x10' \x82\n\x01\x00;"; 77 auto description = "The binary value above is a tiny arrow encoded as a gif image."; 78 79 return [Node([pair("canonical", canonical), 80 pair("generic", generic), 81 pair("description", description)])]; 82 } 83 84 Node[] constructBool() 85 { 86 const(bool) a = true; 87 immutable(bool) b = true; 88 const bool aa = true; 89 immutable bool bb = true; 90 return [Node([pair("canonical", true), 91 pair("answer", false), 92 pair("logical", true), 93 pair("option", true), 94 pair("constbool", a), 95 pair("imutbool", b), 96 pair("const_bool", aa), 97 pair("imut_bool", bb), 98 pair("but", [pair("y", "is a string"), pair("n", "is a string")])])]; 99 } 100 101 Node[] constructCustom() 102 { 103 return [Node([Node(new TestClass(1, 2, 3)), 104 Node(TestStruct(10))])]; 105 } 106 107 Node[] constructFloat() 108 { 109 return [Node([pair("canonical", cast(real)685230.15), 110 pair("exponential", cast(real)685230.15), 111 pair("fixed", cast(real)685230.15), 112 pair("sexagesimal", cast(real)685230.15), 113 pair("negative infinity", -real.infinity), 114 pair("not a number", real.nan)])]; 115 } 116 117 Node[] constructInt() 118 { 119 return [Node([pair("canonical", 685230L), 120 pair("decimal", 685230L), 121 pair("octal", 685230L), 122 pair("hexadecimal", 685230L), 123 pair("binary", 685230L), 124 pair("sexagesimal", 685230L)])]; 125 } 126 127 Node[] constructMap() 128 { 129 return [Node([pair("Block style", 130 [pair("Clark", "Evans"), 131 pair("Brian", "Ingerson"), 132 pair("Oren", "Ben-Kiki")]), 133 pair("Flow style", 134 [pair("Clark", "Evans"), 135 pair("Brian", "Ingerson"), 136 pair("Oren", "Ben-Kiki")])])]; 137 } 138 139 Node[] constructMerge() 140 { 141 return [Node([Node([pair("x", 1L), pair("y", 2L)]), 142 Node([pair("x", 0L), pair("y", 2L)]), 143 Node([pair("r", 10L)]), 144 Node([pair("r", 1L)]), 145 Node([pair("x", 1L), pair("y", 2L), pair("r", 10L), pair("label", "center/big")]), 146 Node([pair("r", 10L), pair("label", "center/big"), pair("x", 1L), pair("y", 2L)]), 147 Node([pair("label", "center/big"), pair("x", 1L), pair("y", 2L), pair("r", 10L)]), 148 Node([pair("x", 1L), pair("label", "center/big"), pair("r", 10L), pair("y", 2L)])])]; 149 } 150 151 Node[] constructNull() 152 { 153 return [Node(YAMLNull()), 154 Node([pair("empty", YAMLNull()), 155 pair("canonical", YAMLNull()), 156 pair("english", YAMLNull()), 157 pair(YAMLNull(), "null key")]), 158 Node([pair("sparse", 159 [Node(YAMLNull()), 160 Node("2nd entry"), 161 Node(YAMLNull()), 162 Node("4th entry"), 163 Node(YAMLNull())])])]; 164 } 165 166 Node[] constructOMap() 167 { 168 return [Node([pair("Bestiary", 169 [pair("aardvark", "African pig-like ant eater. Ugly."), 170 pair("anteater", "South-American ant eater. Two species."), 171 pair("anaconda", "South-American constrictor snake. Scaly.")]), 172 pair("Numbers",[pair("one", 1L), 173 pair("two", 2L), 174 pair("three", 3L)])])]; 175 } 176 177 Node[] constructPairs() 178 { 179 return [Node([pair("Block tasks", 180 Node([pair("meeting", "with team."), 181 pair("meeting", "with boss."), 182 pair("break", "lunch."), 183 pair("meeting", "with client.")], "tag:yaml.org,2002:pairs")), 184 pair("Flow tasks", 185 Node([pair("meeting", "with team"), 186 pair("meeting", "with boss")], "tag:yaml.org,2002:pairs"))])]; 187 } 188 189 Node[] constructSeq() 190 { 191 return [Node([pair("Block style", 192 [Node("Mercury"), Node("Venus"), Node("Earth"), Node("Mars"), 193 Node("Jupiter"), Node("Saturn"), Node("Uranus"), Node("Neptune"), 194 Node("Pluto")]), 195 pair("Flow style", 196 [Node("Mercury"), Node("Venus"), Node("Earth"), Node("Mars"), 197 Node("Jupiter"), Node("Saturn"), Node("Uranus"), Node("Neptune"), 198 Node("Pluto")])])]; 199 } 200 201 Node[] constructSet() 202 { 203 return [Node([pair("baseball players", 204 [Node("Mark McGwire"), Node("Sammy Sosa"), Node("Ken Griffey")]), 205 pair("baseball teams", 206 [Node("Boston Red Sox"), Node("Detroit Tigers"), Node("New York Yankees")])])]; 207 } 208 209 Node[] constructStrASCII() 210 { 211 return [Node("ascii string")]; 212 } 213 214 Node[] constructStr() 215 { 216 return [Node([pair("string", "abcd")])]; 217 } 218 219 Node[] constructStrUTF8() 220 { 221 return [Node("\u042d\u0442\u043e \u0443\u043d\u0438\u043a\u043e\u0434\u043d\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430")]; 222 } 223 224 Node[] constructTimestamp() 225 { 226 alias DT = DateTime; 227 alias ST = SysTime; 228 return [Node([pair("canonical", ST(DT(2001, 12, 15, 2, 59, 43), 1000000.dur!"hnsecs", UTC())), 229 pair("valid iso8601", ST(DT(2001, 12, 15, 2, 59, 43), 1000000.dur!"hnsecs", UTC())), 230 pair("space separated", ST(DT(2001, 12, 15, 2, 59, 43), 1000000.dur!"hnsecs", UTC())), 231 pair("no time zone (Z)", ST(DT(2001, 12, 15, 2, 59, 43), 1000000.dur!"hnsecs", UTC())), 232 pair("date (00:00:00Z)", ST(DT(2002, 12, 14), UTC()))])]; 233 } 234 235 Node[] constructValue() 236 { 237 return[Node([pair("link with", 238 [Node("library1.dll"), Node("library2.dll")])]), 239 Node([pair("link with", 240 [Node([pair("=", "library1.dll"), pair("version", cast(real)1.2)]), 241 Node([pair("=", "library2.dll"), pair("version", cast(real)2.3)])])])]; 242 } 243 244 Node[] duplicateMergeKey() 245 { 246 return [Node([pair("foo", "bar"), 247 pair("x", 1L), 248 pair("y", 2L), 249 pair("z", 3L), 250 pair("t", 4L)])]; 251 } 252 253 Node[] floatRepresenterBug() 254 { 255 return [Node([pair(cast(real)1.0, 1L), 256 pair(real.infinity, 10L), 257 pair(-real.infinity, -10L), 258 pair(real.nan, 100L)])]; 259 } 260 261 Node[] invalidSingleQuoteBug() 262 { 263 return [Node([Node("foo \'bar\'"), Node("foo\n\'bar\'")])]; 264 } 265 266 Node[] moreFloats() 267 { 268 return [Node([Node(cast(real)0.0), 269 Node(cast(real)1.0), 270 Node(cast(real)-1.0), 271 Node(real.infinity), 272 Node(-real.infinity), 273 Node(real.nan), 274 Node(real.nan)])]; 275 } 276 277 Node[] negativeFloatBug() 278 { 279 return [Node(cast(real)-1.0)]; 280 } 281 282 Node[] singleDotFloatBug() 283 { 284 return [Node(".")]; 285 } 286 287 Node[] timestampBugs() 288 { 289 alias DT = DateTime; 290 alias ST = SysTime; 291 alias STZ = immutable SimpleTimeZone; 292 return [Node([Node(ST(DT(2001, 12, 15, 3, 29, 43), 1000000.dur!"hnsecs", UTC())), 293 Node(ST(DT(2001, 12, 14, 16, 29, 43), 1000000.dur!"hnsecs", UTC())), 294 Node(ST(DT(2001, 12, 14, 21, 59, 43), 10100.dur!"hnsecs", UTC())), 295 Node(ST(DT(2001, 12, 14, 21, 59, 43), new STZ(60.dur!"minutes"))), 296 Node(ST(DT(2001, 12, 14, 21, 59, 43), new STZ(-90.dur!"minutes"))), 297 Node(ST(DT(2005, 7, 8, 17, 35, 4), 5176000.dur!"hnsecs", UTC()))])]; 298 } 299 300 Node[] utf16be() 301 { 302 return [Node("UTF-16-BE")]; 303 } 304 305 Node[] utf16le() 306 { 307 return [Node("UTF-16-LE")]; 308 } 309 310 Node[] utf8() 311 { 312 return [Node("UTF-8")]; 313 } 314 315 Node[] utf8implicit() 316 { 317 return [Node("implicit UTF-8")]; 318 } 319 320 ///Testing custom YAML class type. 321 class TestClass 322 { 323 int x, y, z; 324 325 this(int x, int y, int z) 326 { 327 this.x = x; 328 this.y = y; 329 this.z = z; 330 } 331 332 //Any D:YAML type must have a custom opCmp operator. 333 //This is used for ordering in mappings. 334 override int opCmp(Object o) 335 { 336 TestClass s = cast(TestClass)o; 337 if(s is null){return -1;} 338 if(x != s.x){return x - s.x;} 339 if(y != s.y){return y - s.y;} 340 if(z != s.z){return z - s.z;} 341 return 0; 342 } 343 344 override string toString() 345 { 346 return format("TestClass(", x, ", ", y, ", ", z, ")"); 347 } 348 } 349 350 ///Testing custom YAML struct type. 351 struct TestStruct 352 { 353 int value; 354 355 //Any D:YAML type must have a custom opCmp operator. 356 //This is used for ordering in mappings. 357 const int opCmp(ref const TestStruct s) 358 { 359 return value - s.value; 360 } 361 } 362 363 ///Constructor function for TestClass. 364 TestClass constructClass(ref Node node) 365 { 366 return new TestClass(node["x"].as!int, node["y"].as!int, node["z"].as!int); 367 } 368 369 Node representClass(ref Node node, Representer representer) 370 { 371 auto value = node.as!TestClass; 372 auto pairs = [Node.Pair("x", value.x), 373 Node.Pair("y", value.y), 374 Node.Pair("z", value.z)]; 375 auto result = representer.representMapping("!tag1", pairs); 376 377 return result; 378 } 379 380 ///Constructor function for TestStruct. 381 TestStruct constructStruct(ref Node node) 382 { 383 return TestStruct(to!int(node.as!string)); 384 } 385 386 ///Representer function for TestStruct. 387 Node representStruct(ref Node node, Representer representer) 388 { 389 string[] keys, values; 390 auto value = node.as!TestStruct; 391 return representer.representScalar("!tag2", to!string(value.value)); 392 } 393 394 /** 395 * Constructor unittest. 396 * 397 * Params: verbose = Print verbose output? 398 * dataFilename = File name to read from. 399 * codeDummy = Dummy .code filename, used to determine that 400 * .data file with the same name should be used in this test. 401 */ 402 void testConstructor(bool verbose, string dataFilename, string codeDummy) 403 { 404 string base = dataFilename.baseName.stripExtension; 405 enforce((base in expected) !is null, 406 new Exception("Unimplemented constructor test: " ~ base)); 407 408 auto constructor = new Constructor; 409 constructor.addConstructorMapping("!tag1", &constructClass); 410 constructor.addConstructorScalar("!tag2", &constructStruct); 411 412 auto loader = Loader(dataFilename); 413 loader.constructor = constructor; 414 loader.resolver = new Resolver; 415 416 Node[] exp = expected[base]; 417 418 //Compare with expected results document by document. 419 size_t i = 0; 420 foreach(node; loader) 421 { 422 if(!node.equals!(No.useTag)(exp[i])) 423 { 424 if(verbose) 425 { 426 writeln("Expected value:"); 427 writeln(exp[i].debugString); 428 writeln("\n"); 429 writeln("Actual value:"); 430 writeln(node.debugString); 431 } 432 assert(false); 433 } 434 ++i; 435 } 436 assert(i == exp.length); 437 } 438 439 440 unittest 441 { 442 writeln("D:YAML Constructor unittest"); 443 run("testConstructor", &testConstructor, ["data", "code"]); 444 } 445 446 } // version(unittest)