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 }