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)