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)