1 /*******************************************************************************
2 
3     Test module for ocean.text.convert.Formatter
4 
5     Copyright:
6         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
7         All rights reserved.
8 
9     License:
10         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
11         Alternatively, this file may be distributed under the terms of the Tango
12         3-Clause BSD License (see LICENSE_BSD.txt for details).
13 
14 *******************************************************************************/
15 
16 module ocean.text.convert.Formatter_test;
17 
18 import ocean.core.Test;
19 import ocean.core.Buffer;
20 import ocean.text.convert.Formatter;
21 import ocean.meta.types.Qualifiers;
22 
23 unittest
24 {
25     static struct Foo
26     {
27         int i = 0x2A;
28         void toString (scope void delegate (cstring) sink)
29         {
30             sink("Hello void");
31         }
32     }
33 
34     Foo f;
35     test!("==")(format("{}", f), "Hello void");
36 }
37 
38 /// Test for Buffer overload
39 unittest
40 {
41     Buffer!(char) buff;
42     sformat(buff, "{}", 42);
43     test!("==")(buff[], "42");
44 }
45 
46 /*******************************************************************************
47 
48     Original tango Layout unittest, minus changes of behaviour
49 
50     Copyright:
51         These unit tests come from `tango.text.convert.Layout`.
52         Copyright Kris & Larsivi
53 
54 *******************************************************************************/
55 
56 unittest
57 {
58     // basic layout tests
59     test(format("abc") == "abc");
60     test(format("{0}", 1) == "1");
61 
62     test(format("X{}Y", mstring.init) == "XY");
63 
64     test(format("{0}", -1) == "-1");
65 
66     test(format("{}", 1) == "1");
67     test(format("{} {}", 1, 2) == "1 2");
68     test(format("{} {0} {}", 1, 3) == "1 1 3");
69     test(format("{} {0} {} {}", 1, 3) == "1 1 3 {invalid index}");
70     test(format("{} {0} {} {:x}", 1, 3) == "1 1 3 {invalid index}");
71 
72     test(format("{0}", true) == "true");
73     test(format("{0}", false) == "false");
74 
75     test(format("{0}", cast(byte)-128) == "-128");
76     test(format("{0}", cast(byte)127) == "127");
77     test(format("{0}", cast(ubyte)255) == "255");
78 
79     test(format("{0}", cast(short)-32768 ) == "-32768");
80     test(format("{0}", cast(short)32767) == "32767");
81     test(format("{0}", cast(ushort)65535) == "65535");
82     test(format("{0:x4}", cast(ushort)0xafe) == "0afe");
83     test(format("{0:X4}", cast(ushort)0xafe) == "0AFE");
84 
85     test(format("{0}", -2147483648) == "-2147483648");
86     test(format("{0}", 2147483647) == "2147483647");
87     test(format("{0}", 4294967295) == "4294967295");
88 
89     // large integers
90     test(format("{0}", -9223372036854775807L) == "-9223372036854775807");
91     test(format("{0}", 0x8000_0000_0000_0000L) == "9223372036854775808");
92     test(format("{0}", 9223372036854775807L) == "9223372036854775807");
93     test(format("{0:X}", 0xFFFF_FFFF_FFFF_FFFF) == "FFFFFFFFFFFFFFFF");
94     test(format("{0:x}", 0xFFFF_FFFF_FFFF_FFFF) == "ffffffffffffffff");
95     test(format("{0:x}", 0xFFFF_1234_FFFF_FFFF) == "ffff1234ffffffff");
96     test(format("{0:x19}", 0x1234_FFFF_FFFF) == "00000001234ffffffff");
97     test(format("{0}", 18446744073709551615UL) == "18446744073709551615");
98     test(format("{0}", 18446744073709551615UL) == "18446744073709551615");
99 
100     // fragments before and after
101     test(format("d{0}d", "s") == "dsd");
102     test(format("d{0}d", "1234567890") == "d1234567890d");
103 
104     // brace escaping
105     test(format("d{0}d", "<string>") == "d<string>d");
106     test(format("d{{0}d", "<string>") == "d{0}d");
107     test(format("d{{{0}d", "<string>") == "d{<string>d");
108     test(format("d{0}}d", "<string>") == "d<string>}d");
109 
110     // hex conversions, where width indicates leading zeroes
111     test(format("{0:x}", 0xafe0000) == "afe0000");
112     test(format("{0:x7}", 0xafe0000) == "afe0000");
113     test(format("{0:x8}", 0xafe0000) == "0afe0000");
114     test(format("{0:X8}", 0xafe0000) == "0AFE0000");
115     test(format("{0:X9}", 0xafe0000) == "00AFE0000");
116     test(format("{0:X13}", 0xafe0000) == "000000AFE0000");
117     test(format("{0:x13}", 0xafe0000) == "000000afe0000");
118 
119     // decimal width
120     test(format("{0:d6}", 123) == "000123");
121     test(format("{0,7:d6}", 123) == " 000123");
122     test(format("{0,-7:d6}", 123) == "000123 ");
123 
124     // width & sign combinations
125     test(format("{0:d7}", -123) == "-0000123");
126     test(format("{0,7:d6}", 123) == " 000123");
127     test(format("{0,7:d7}", -123) == "-0000123");
128     test(format("{0,8:d7}", -123) == "-0000123");
129     test(format("{0,5:d7}", -123) == "-0000123");
130 
131     // Negative numbers in various bases
132     test(format("{:b}", cast(byte) -1) == "11111111");
133     test(format("{:b}", cast(short) -1) == "1111111111111111");
134     test(format("{:b}", cast(int) -1)
135            , "11111111111111111111111111111111");
136     test(format("{:b}", cast(long) -1)
137            , "1111111111111111111111111111111111111111111111111111111111111111");
138 
139     test(format("{:o}", cast(byte) -1) == "377");
140     test(format("{:o}", cast(short) -1) == "177777");
141     test(format("{:o}", cast(int) -1) == "37777777777");
142     test(format("{:o}", cast(long) -1) == "1777777777777777777777");
143 
144     test(format("{:d}", cast(byte) -1) == "-1");
145     test(format("{:d}", cast(short) -1) == "-1");
146     test(format("{:d}", cast(int) -1) == "-1");
147     test(format("{:d}", cast(long) -1) == "-1");
148 
149     test(format("{:x}", cast(byte) -1) == "ff");
150     test(format("{:x}", cast(short) -1) == "ffff");
151     test(format("{:x}", cast(int) -1) == "ffffffff");
152     test(format("{:x}", cast(long) -1) == "ffffffffffffffff");
153 
154     // argument index
155     test(format("a{0}b{1}c{2}", "x", "y", "z") == "axbycz");
156     test(format("a{2}b{1}c{0}", "x", "y", "z") == "azbycx");
157     test(format("a{1}b{1}c{1}", "x", "y", "z") == "aybycy");
158 
159     // alignment does not restrict the length
160     test(format("{0,5}", "hellohello") == "hellohello");
161 
162     // alignment fills with spaces
163     test(format("->{0,-10}<-", "hello") == "->hello     <-");
164     test(format("->{0,10}<-", "hello") == "->     hello<-");
165     test(format("->{0,-10}<-", 12345) == "->12345     <-");
166     test(format("->{0,10}<-", 12345) == "->     12345<-");
167 
168     // chop at maximum specified length; insert ellipses when chopped
169     test(format("->{.5}<-", "hello") == "->hello<-");
170     test(format("->{.4}<-", "hello") == "->hell...<-");
171     test(format("->{.-3}<-", "hello") == "->...llo<-");
172 
173     // width specifier indicates number of decimal places
174     test(format("{0:f}", 1.23f) == "1.23");
175     test(format("{0:f4}", 1.23456789L) == "1.2346");
176     test(format("{0:e4}", 0.0001) == "1.0000e-04");
177 
178     // 'f.' & 'e.' format truncates zeroes from floating decimals
179     test(format("{:f4.}", 1.230) == "1.23");
180     test(format("{:f6.}", 1.230) == "1.23");
181     test(format("{:f1.}", 1.230) == "1.2");
182     test(format("{:f.}", 1.233) == "1.23");
183     test(format("{:f.}", 1.237) == "1.24");
184     test(format("{:f.}", 1.000) == "1");
185     test(format("{:f2.}", 200.001) == "200");
186 
187     // array output
188     int[] a = [ 51, 52, 53, 54, 55 ];
189     test(format("{}", a) == "[51, 52, 53, 54, 55]");
190     test(format("{:x}", a) == "[33, 34, 35, 36, 37]");
191     test(format("{,-4}", a) == "[51  , 52  , 53  , 54  , 55  ]");
192     test(format("{,4}", a) == "[  51,   52,   53,   54,   55]");
193     int[][] b = [ [ 51, 52 ], [ 53, 54, 55 ] ];
194     test(format("{}", b) == "[[51, 52], [53, 54, 55]]");
195 
196     char[1024] static_buffer;
197     static_buffer[0..10] = "1234567890";
198 
199     test (format("{}", static_buffer[0..10]) == "1234567890");
200 
201     // sformat()
202     mstring buffer;
203     test(sformat(buffer, "{}", 1) == "1");
204     test(buffer == "1");
205 
206     buffer.length = 0;
207     assumeSafeAppend(buffer);
208     test(sformat(buffer, "{}", 1234567890123) == "1234567890123");
209     test(buffer == "1234567890123");
210 
211     auto old_buffer_ptr = buffer.ptr;
212     buffer.length = 0;
213     assumeSafeAppend(buffer);
214     test(sformat(buffer, "{}", 1.24) == "1.24");
215     test(buffer == "1.24");
216     test(buffer.ptr == old_buffer_ptr);
217 
218     interface I
219     {
220     }
221 
222     class C : I
223     {
224         override istring toString()
225         {
226             return "something";
227         }
228     }
229 
230     C c = new C;
231     I i = c;
232 
233     test (format("{}", i) == "something");
234     test (format("{}", c) == "something");
235 
236     static struct S
237     {
238         istring toString()
239         {
240             return "something";
241         }
242     }
243 
244     test(format("{}", S.init) == "something");
245 
246     // Time struct
247     // Should result in something similar to "01/01/70 00:00:00" but it's
248     // dependent on the system locale so we just make sure that it's handled
249     version(none)
250     {
251         test(format("{}", Time.epoch1970).length);
252     }
253 
254 
255     // snformat is supposed to overwrite the provided buffer without changing
256     // its length and ignore any remaining formatted data that does not fit
257     mstring target;
258     snformat(target, "{}", 42);
259     test(target.ptr is null);
260     target.length = 5; target[] = 'a';
261     snformat(target, "{}", 42);
262     test(target, "42aaa");
263 }
264 
265 
266 /*******************************************************************************
267 
268     Tests for the new behaviour that diverge from the original Layout unit tests
269 
270 *******************************************************************************/
271 
272 unittest
273 {
274     // This is handled as a pointer, not as an integer literal
275     test(format("{}", null) == "null");
276 
277     // Imaginary and complex numbers aren't supported in D2
278     // test(format("{0:f}", 1.23f*1i) == "1.23*1i");
279     // See the original Tango's code for more examples
280 
281     static struct S2 { }
282     test(format("{}", S2.init) == "{ empty struct }");
283     // This used to produce '{unhandled argument type}'
284 
285     // Basic wchar / dchar support
286     test(format("{}", "42"w) == "42");
287     test(format("{}", "42"d) == "42");
288     wchar wc = '4';
289     dchar dc = '2';
290     test(format("{}", wc) == "4");
291     test(format("{}", dc) == "2");
292 
293     test(format("{,3}", '8') == "  8");
294 
295     /*
296      * Associative array formatting used to be in the form `{key => value, ...}`
297      * However this looks too much like struct, and does not match AA literals
298      * syntax (hence it's useless for any code formatting).
299      * So it was changed to `[ key: value, ... ]`
300      */
301 
302     // integer AA
303     ushort[long] d;
304     d[42] = 21;
305     d[512] = 256;
306     cstring formatted = format("{}", d);
307     test(formatted == "[ 42: 21, 512: 256 ]"
308            || formatted == "[ 512: 256, 42: 21 ]");
309 
310     // bool/string AA
311     bool[istring] e;
312     e["key"] = false;
313     e["value"] = true;
314     formatted = format("{}", e);
315     test(formatted == `[ "key": false, "value": true ]`
316            || formatted == `[ "value": true, "key": false ]`);
317 
318     // string/double AA
319     mstring[double] f;
320     f[ 2.0 ] = "two".dup;
321     f[ 3.14 ] = "PI".dup;
322     formatted = format("{}", f);
323     test(formatted == `[ 2.00: "two", 3.14: "PI" ]`
324            || formatted == `[ 3.14: "PI", 2.00: "two" ]`);
325 
326     // This used to yield `[aa, bb]` but is now quoted
327     test(format("{}", [ "aa", "bb" ]) == `["aa", "bb"]`);
328 }
329 
330 
331 /*******************************************************************************
332 
333     Additional unit tests
334 
335 *******************************************************************************/
336 
337 version (unittest)
338 {
339     import ocean.meta.types.Typedef;
340 }
341 
342 
343 unittest
344 {
345     // This was not tested by tango, but the behaviour was the same
346     test(format("{0", 42) == "{missing closing '}'}");
347 
348     // Wasn't tested either, but also the same behaviour
349     test(format("foo {1} bar", 42) == "foo {invalid index} bar");
350 
351     // Typedefs are correctly formatted
352     mixin(Typedef!(ulong, "RandomTypedef"));
353     RandomTypedef r;
354     test(format("{}", r) == "0");
355 
356     // Support for new sink-based toString
357     static struct S1
358     {
359         void toString (scope FormatterSink sink)
360         {
361             sink("42424242424242");
362         }
363     }
364     S1 s1;
365     test(format("The answer is {0.2}", s1) == "The answer is 42...");
366 
367     // For classes too
368     static class C1
369     {
370         void toString (scope FormatterSink sink)
371         {
372             sink("42424242424242");
373         }
374     }
375     C1 c1 = new C1;
376     test(format("The answer is {.2}", c1) == "The answer is 42...");
377 
378     // Compile time support is awesome, isn't it ?
379     static struct S2
380     {
381         void toString (scope FormatterSink sink, cstring default_ = "42")
382         {
383             sink(default_);
384         }
385     }
386     S2 s2;
387     test(format("The answer is {0.2}", s2) == "The answer is 42");
388 
389     // Support for formatting struct (!)
390     static struct S3
391     {
392         C1 c;
393         int a = 42;
394         int* ptr;
395         char[] foo;
396         cstring bar = "Hello World";
397     }
398     S3 s3;
399     test(format("Woot {} it works", s3)
400            == `Woot { c: null, a: 42, ptr: null, foo: "", bar: "Hello World" } it works`);
401 
402     // Pointers are nice too
403     int* x = cast(int*)0x2A2A_0000_2A2A;
404     test(format("Here you go: {1}", 42, x) == "Here you go: 0X00002A2A00002A2A");
405 
406     // Null AA / array
407     int[] empty_arr;
408     int[int] empty_aa;
409     test(format("{}", empty_arr) == "[]");
410     test(format("{}", empty_aa) == "[:]");
411     int[1] static_arr;
412     test(format("{}", static_arr[$ .. $]) == "[]");
413 
414     empty_aa[42] = 42;
415     empty_aa.remove(42);
416     test(format("{}", empty_aa) == "[:]");
417 
418     // Basic integer-based enums (use `switch` jump table)
419     enum Foo : ulong
420     {
421         A = 0,
422         B = 1,
423         FooBar = 42
424     }
425     char[256] buffer;
426     Foo[] foo_inputs = [ Foo.FooBar, cast(Foo)1, cast(Foo)36 ];
427     string[] foo_outputs = [ "Foo.FooBar", "Foo.B", "cast(Foo) 36" ];
428     foreach (i, ref item; foo_inputs)
429         testNoAlloc(assert(snformat(buffer, "{}", item) == foo_outputs[i]));
430 
431     // Simple test for `const` and `immutable` values
432     const Foo fa = Foo.A;
433     immutable Foo fb = Foo.B;
434     const Foo fc = cast(Foo) 420;
435     test(format("{}", fa) == "const(Foo).A");
436     test(format("{}", fb) == "immutable(Foo).B");
437     test(format("{}", fc) == "cast(const(Foo)) 420");
438 
439     // Enums with `string` as base type (use `switch` binary search)
440     enum FooA : string
441     {
442         a = "alpha",
443         b = "beta"
444     }
445     FooA[] fooa_inputs = [ FooA.a, cast(FooA)"beta", cast(FooA)"gamma" ];
446     string[] fooa_outputs = [ "FooA.a", "FooA.b", "cast(FooA) gamma" ];
447     foreach (i, ref item; fooa_inputs)
448         testNoAlloc(assert(snformat(buffer, "{}", item) == fooa_outputs[i]));
449 
450     // Enums with `real` as base type (use `if` forest)
451     enum FooB : real
452     {
453         a = 1,
454         b = 1.41421,
455         c = 1.73205
456     }
457     FooB[] foob_inputs = [ FooB.a, cast(FooB)1.41421, cast(FooB)42 ];
458     string[] foob_outputs = [ "FooB.a", "FooB.b", "cast(FooB) 42.00" ];
459     foreach (i, ref item; foob_inputs)
460         testNoAlloc(assert(snformat(buffer, "{}", item) == foob_outputs[i]));
461 
462     // Enums with struct as base type (use `if` forest)
463     static struct S
464     {
465         int value;
466     }
467     enum FooC : S { a = S(1), b = S(2), c = S(3) }
468     FooC[] fooc_inputs = [ FooC.a, cast(FooC)S(2), cast(FooC)S(42) ];
469     string[] fooc_outputs = [ "FooC.a", "FooC.b", "cast(FooC) { value: 42 }" ];
470     foreach (i, ref item; fooc_inputs)
471         testNoAlloc(assert(snformat(buffer, "{}", item) == fooc_outputs[i]));
472 
473     // Chars
474     static struct CharC { char c = 'H'; }
475     char c = '4';
476     CharC cc;
477     test("4" == format("{}", c));
478     test("{ c: 'H' }" == format("{}", cc));
479 
480     // void[] array are 'special'
481     ubyte[5] arr = [42, 43, 44, 45, 92];
482     void[] varr = arr;
483     test(format("{}", varr) == "[42, 43, 44, 45, 92]");
484 
485     static immutable ubyte[5] carr = [42, 43, 44, 45, 92];
486     auto cvarr = carr; // Immutable, cannot be marked `const` in D1
487     test(format("{}", cvarr) == "[42, 43, 44, 45, 92]");
488 
489     // Function ptr / delegates
490     auto func = cast(int function(char[], char, int)) 0x4444_1111_2222_3333;
491     int delegate(void[], char, int) dg;
492     dg.funcptr = cast(typeof(dg.funcptr)) 0x1111_2222_3333_4444;
493     dg.ptr     = cast(typeof(dg.ptr))     0x5555_6666_7777_8888;
494     test(format("{}", func)
495            == "int function(char[], char, int): 0X4444111122223333");
496     test(format("{}", dg)
497            == "int delegate(void[], char, int): { funcptr: 0X1111222233334444, ptr: 0X5555666677778888 }");
498 }
499 
500 // Const tests
501 unittest
502 {
503     static immutable int ai = 42;
504     static immutable double ad = 42.00;
505     static struct Answer_struct { int value; }
506     static class Answer_class
507     {
508         public override istring toString () const
509         {
510             return "42";
511         }
512     }
513 
514     const(Answer_struct) as = Answer_struct(42);
515     auto ac = new const(Answer_class);
516 
517     test(format("{}", ai) == "42");
518     test(format("{:f2}", ad) == "42.00", format("{:f2}", ad));
519     test(format("{}", as) == "{ value: 42 }");
520     test(format("{}", ac) == "42");
521 }
522 
523 // Check that `IsTypeofNull` does its job,
524 // and that pointers to objects are not dereferenced
525 unittest
526 {
527     // Since `Object* o; istring s = o.toString();`
528     // compiles, the test for `toString` used to pass
529     // on pointers to object, which is wrong.
530     // Fixed in sociomantic/ocean#1605
531     Object* o = cast(Object*) 0xDEADBEEF_DEADBEEF;
532     void* ptr = cast(void*) 0xDEADBEEF_DEADBEEF;
533 
534     static immutable istring expected = "0XDEADBEEFDEADBEEF";
535     istring object_str = format("{}", o);
536     istring ptr_str = format("{}", ptr);
537     istring null_str = format("{}", null);
538 
539     test(ptr_str != null_str);
540     test(object_str != null_str);
541     test(ptr_str == expected);
542     test(object_str == expected);
543 }
544 
545 // Check for pointers to Typedef
546 unittest
547 {
548     mixin(Typedef!(ulong, "Typedefed"));
549     mixin(Typedef!(Typedefed*, "TypedefedPtr"));
550 
551     Typedefed* t1 =   cast(Typedefed*)   0xDEADBEEF_00000000;
552     TypedefedPtr t2 = cast(Typedefed*)   0xDEADBEEF_DEADBEEF;
553 
554     test(format("{}", t1) == "0XDEADBEEF00000000");
555     test(format("{}", t2) == "0XDEADBEEFDEADBEEF");
556 }
557 
558 unittest
559 {
560     static immutable bool YES = true;
561     static immutable bool NO  = false;
562     test(format("{} -- {}", YES, NO) == "true -- false");
563 }
564 
565 unittest
566 {
567     // Used to work only with "{:X}", however this limitation was lifted
568     assert(format("{X}", 42) == "2A");
569 }