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 return [Node([pair("canonical", true), 87 pair("answer", false), 88 pair("logical", true), 89 pair("option", true), 90 pair("but", [pair("y", "is a string"), pair("n", "is a string")])])]; 91 } 92 93 Node[] constructCustom() 94 { 95 return [Node([Node(new TestClass(1, 2, 3)), 96 Node(TestStruct(10))])]; 97 } 98 99 Node[] constructFloat() 100 { 101 return [Node([pair("canonical", cast(real)685230.15), 102 pair("exponential", cast(real)685230.15), 103 pair("fixed", cast(real)685230.15), 104 pair("sexagesimal", cast(real)685230.15), 105 pair("negative infinity", -real.infinity), 106 pair("not a number", real.nan)])]; 107 } 108 109 Node[] constructInt() 110 { 111 return [Node([pair("canonical", 685230L), 112 pair("decimal", 685230L), 113 pair("octal", 685230L), 114 pair("hexadecimal", 685230L), 115 pair("binary", 685230L), 116 pair("sexagesimal", 685230L)])]; 117 } 118 119 Node[] constructMap() 120 { 121 return [Node([pair("Block style", 122 [pair("Clark", "Evans"), 123 pair("Brian", "Ingerson"), 124 pair("Oren", "Ben-Kiki")]), 125 pair("Flow style", 126 [pair("Clark", "Evans"), 127 pair("Brian", "Ingerson"), 128 pair("Oren", "Ben-Kiki")])])]; 129 } 130 131 Node[] constructMerge() 132 { 133 return [Node([Node([pair("x", 1L), pair("y", 2L)]), 134 Node([pair("x", 0L), pair("y", 2L)]), 135 Node([pair("r", 10L)]), 136 Node([pair("r", 1L)]), 137 Node([pair("x", 1L), pair("y", 2L), pair("r", 10L), pair("label", "center/big")]), 138 Node([pair("r", 10L), pair("label", "center/big"), pair("x", 1L), pair("y", 2L)]), 139 Node([pair("label", "center/big"), pair("x", 1L), pair("y", 2L), pair("r", 10L)]), 140 Node([pair("x", 1L), pair("label", "center/big"), pair("r", 10L), pair("y", 2L)])])]; 141 } 142 143 Node[] constructNull() 144 { 145 return [Node(YAMLNull()), 146 Node([pair("empty", YAMLNull()), 147 pair("canonical", YAMLNull()), 148 pair("english", YAMLNull()), 149 pair(YAMLNull(), "null key")]), 150 Node([pair("sparse", 151 [Node(YAMLNull()), 152 Node("2nd entry"), 153 Node(YAMLNull()), 154 Node("4th entry"), 155 Node(YAMLNull())])])]; 156 } 157 158 Node[] constructOMap() 159 { 160 return [Node([pair("Bestiary", 161 [pair("aardvark", "African pig-like ant eater. Ugly."), 162 pair("anteater", "South-American ant eater. Two species."), 163 pair("anaconda", "South-American constrictor snake. Scaly.")]), 164 pair("Numbers",[pair("one", 1L), 165 pair("two", 2L), 166 pair("three", 3L)])])]; 167 } 168 169 Node[] constructPairs() 170 { 171 return [Node([pair("Block tasks", 172 Node([pair("meeting", "with team."), 173 pair("meeting", "with boss."), 174 pair("break", "lunch."), 175 pair("meeting", "with client.")], "tag:yaml.org,2002:pairs")), 176 pair("Flow tasks", 177 Node([pair("meeting", "with team"), 178 pair("meeting", "with boss")], "tag:yaml.org,2002:pairs"))])]; 179 } 180 181 Node[] constructSeq() 182 { 183 return [Node([pair("Block style", 184 [Node("Mercury"), Node("Venus"), Node("Earth"), Node("Mars"), 185 Node("Jupiter"), Node("Saturn"), Node("Uranus"), Node("Neptune"), 186 Node("Pluto")]), 187 pair("Flow style", 188 [Node("Mercury"), Node("Venus"), Node("Earth"), Node("Mars"), 189 Node("Jupiter"), Node("Saturn"), Node("Uranus"), Node("Neptune"), 190 Node("Pluto")])])]; 191 } 192 193 Node[] constructSet() 194 { 195 return [Node([pair("baseball players", 196 [Node("Mark McGwire"), Node("Sammy Sosa"), Node("Ken Griffey")]), 197 pair("baseball teams", 198 [Node("Boston Red Sox"), Node("Detroit Tigers"), Node("New York Yankees")])])]; 199 } 200 201 Node[] constructStrASCII() 202 { 203 return [Node("ascii string")]; 204 } 205 206 Node[] constructStr() 207 { 208 return [Node([pair("string", "abcd")])]; 209 } 210 211 Node[] constructStrUTF8() 212 { 213 return [Node("\u042d\u0442\u043e \u0443\u043d\u0438\u043a\u043e\u0434\u043d\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430")]; 214 } 215 216 Node[] constructTimestamp() 217 { 218 alias DT = DateTime; 219 alias ST = SysTime; 220 return [Node([pair("canonical", ST(DT(2001, 12, 15, 2, 59, 43), 1000000.dur!"hnsecs", UTC())), 221 pair("valid iso8601", ST(DT(2001, 12, 15, 2, 59, 43), 1000000.dur!"hnsecs", UTC())), 222 pair("space separated", ST(DT(2001, 12, 15, 2, 59, 43), 1000000.dur!"hnsecs", UTC())), 223 pair("no time zone (Z)", ST(DT(2001, 12, 15, 2, 59, 43), 1000000.dur!"hnsecs", UTC())), 224 pair("date (00:00:00Z)", ST(DT(2002, 12, 14), UTC()))])]; 225 } 226 227 Node[] constructValue() 228 { 229 return[Node([pair("link with", 230 [Node("library1.dll"), Node("library2.dll")])]), 231 Node([pair("link with", 232 [Node([pair("=", "library1.dll"), pair("version", cast(real)1.2)]), 233 Node([pair("=", "library2.dll"), pair("version", cast(real)2.3)])])])]; 234 } 235 236 Node[] duplicateMergeKey() 237 { 238 return [Node([pair("foo", "bar"), 239 pair("x", 1L), 240 pair("y", 2L), 241 pair("z", 3L), 242 pair("t", 4L)])]; 243 } 244 245 Node[] floatRepresenterBug() 246 { 247 return [Node([pair(cast(real)1.0, 1L), 248 pair(real.infinity, 10L), 249 pair(-real.infinity, -10L), 250 pair(real.nan, 100L)])]; 251 } 252 253 Node[] invalidSingleQuoteBug() 254 { 255 return [Node([Node("foo \'bar\'"), Node("foo\n\'bar\'")])]; 256 } 257 258 Node[] moreFloats() 259 { 260 return [Node([Node(cast(real)0.0), 261 Node(cast(real)1.0), 262 Node(cast(real)-1.0), 263 Node(real.infinity), 264 Node(-real.infinity), 265 Node(real.nan), 266 Node(real.nan)])]; 267 } 268 269 Node[] negativeFloatBug() 270 { 271 return [Node(cast(real)-1.0)]; 272 } 273 274 Node[] singleDotFloatBug() 275 { 276 return [Node(".")]; 277 } 278 279 Node[] timestampBugs() 280 { 281 alias DT = DateTime; 282 alias ST = SysTime; 283 alias STZ = immutable SimpleTimeZone; 284 return [Node([Node(ST(DT(2001, 12, 15, 3, 29, 43), 1000000.dur!"hnsecs", UTC())), 285 Node(ST(DT(2001, 12, 14, 16, 29, 43), 1000000.dur!"hnsecs", UTC())), 286 Node(ST(DT(2001, 12, 14, 21, 59, 43), 10100.dur!"hnsecs", UTC())), 287 Node(ST(DT(2001, 12, 14, 21, 59, 43), new STZ(60.dur!"minutes"))), 288 Node(ST(DT(2001, 12, 14, 21, 59, 43), new STZ(-90.dur!"minutes"))), 289 Node(ST(DT(2005, 7, 8, 17, 35, 4), 5176000.dur!"hnsecs", UTC()))])]; 290 } 291 292 Node[] utf16be() 293 { 294 return [Node("UTF-16-BE")]; 295 } 296 297 Node[] utf16le() 298 { 299 return [Node("UTF-16-LE")]; 300 } 301 302 Node[] utf8() 303 { 304 return [Node("UTF-8")]; 305 } 306 307 Node[] utf8implicit() 308 { 309 return [Node("implicit UTF-8")]; 310 } 311 312 ///Testing custom YAML class type. 313 class TestClass 314 { 315 int x, y, z; 316 317 this(int x, int y, int z) 318 { 319 this.x = x; 320 this.y = y; 321 this.z = z; 322 } 323 324 //Any D:YAML type must have a custom opCmp operator. 325 //This is used for ordering in mappings. 326 override int opCmp(Object o) 327 { 328 TestClass s = cast(TestClass)o; 329 if(s is null){return -1;} 330 if(x != s.x){return x - s.x;} 331 if(y != s.y){return y - s.y;} 332 if(z != s.z){return z - s.z;} 333 return 0; 334 } 335 336 override string toString() 337 { 338 return format("TestClass(", x, ", ", y, ", ", z, ")"); 339 } 340 } 341 342 ///Testing custom YAML struct type. 343 struct TestStruct 344 { 345 int value; 346 347 //Any D:YAML type must have a custom opCmp operator. 348 //This is used for ordering in mappings. 349 const int opCmp(ref const TestStruct s) 350 { 351 return value - s.value; 352 } 353 } 354 355 ///Constructor function for TestClass. 356 TestClass constructClass(ref Node node) 357 { 358 return new TestClass(node["x"].as!int, node["y"].as!int, node["z"].as!int); 359 } 360 361 Node representClass(ref Node node, Representer representer) 362 { 363 auto value = node.as!TestClass; 364 auto pairs = [Node.Pair("x", value.x), 365 Node.Pair("y", value.y), 366 Node.Pair("z", value.z)]; 367 auto result = representer.representMapping("!tag1", pairs); 368 369 return result; 370 } 371 372 ///Constructor function for TestStruct. 373 TestStruct constructStruct(ref Node node) 374 { 375 return TestStruct(to!int(node.as!string)); 376 } 377 378 ///Representer function for TestStruct. 379 Node representStruct(ref Node node, Representer representer) 380 { 381 string[] keys, values; 382 auto value = node.as!TestStruct; 383 return representer.representScalar("!tag2", to!string(value.value)); 384 } 385 386 /** 387 * Constructor unittest. 388 * 389 * Params: verbose = Print verbose output? 390 * dataFilename = File name to read from. 391 * codeDummy = Dummy .code filename, used to determine that 392 * .data file with the same name should be used in this test. 393 */ 394 void testConstructor(bool verbose, string dataFilename, string codeDummy) 395 { 396 string base = dataFilename.baseName.stripExtension; 397 enforce((base in expected) !is null, 398 new Exception("Unimplemented constructor test: " ~ base)); 399 400 auto constructor = new Constructor; 401 constructor.addConstructorMapping("!tag1", &constructClass); 402 constructor.addConstructorScalar("!tag2", &constructStruct); 403 404 auto loader = Loader(dataFilename); 405 loader.constructor = constructor; 406 loader.resolver = new Resolver; 407 408 Node[] exp = expected[base]; 409 410 //Compare with expected results document by document. 411 size_t i = 0; 412 foreach(node; loader) 413 { 414 if(!node.equals!(No.useTag)(exp[i])) 415 { 416 if(verbose) 417 { 418 writeln("Expected value:"); 419 writeln(exp[i].debugString); 420 writeln("\n"); 421 writeln("Actual value:"); 422 writeln(node.debugString); 423 } 424 assert(false); 425 } 426 ++i; 427 } 428 assert(i == exp.length); 429 } 430 431 432 unittest 433 { 434 writeln("D:YAML Constructor unittest"); 435 run("testConstructor", &testConstructor, ["data", "code"]); 436 } 437 438 } // version(unittest)