1 /****************************************************************************** 2 3 Enhancement to VersionDecorator that allows converting through multiple 4 struct versions at once. It is kept separate from core implementation 5 because additional overhead may be not suitable for real-time apps 6 7 Copyright: 8 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 9 All rights reserved. 10 11 License: 12 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 13 Alternatively, this file may be distributed under the terms of the Tango 14 3-Clause BSD License (see LICENSE_BSD.txt for details). 15 16 *******************************************************************************/ 17 18 module ocean.util.serialize.contiguous.MultiVersionDecorator; 19 20 import ocean.transition; 21 22 import ocean.core.Buffer; 23 import ocean.core.Verify; 24 import ocean.core.Exception; 25 import ocean.core.Enforce; 26 import ocean.core.StructConverter : structConvert; 27 28 import ocean.stdc.string : memmove; 29 import ocean.math.Math : abs; 30 import ocean.util.container.ConcatBuffer; 31 32 import ocean.util.serialize.Version; 33 import ocean.util.serialize.contiguous.Serializer; 34 import ocean.util.serialize.contiguous.Deserializer; 35 import ocean.util.serialize.contiguous.Contiguous; 36 37 version (UnitTest) 38 { 39 import ocean.core.array.Search : rfind; 40 import ocean.core.Test; 41 import ocean.text.convert.Formatter; 42 } 43 44 /******************************************************************************* 45 46 Alternative contiguous version decorator implementation for usage in less 47 performance critical applications. Is capable of converting through 48 multiple struct versions in one go for added convenience. 49 50 Amount of allowed conversions for single call is set via constructor 51 argument, 10 by default 52 53 *******************************************************************************/ 54 55 class VersionDecorator 56 { 57 /// Convenience shortcut 58 public alias VersionDecorator This; 59 60 /// Reused exception instance 61 protected VersionHandlingException e; 62 63 /// Allowed difference between struct versions to be converted in one go 64 private size_t conversion_limit; 65 66 /// Persistent buffer reused for temporary allocations needed for struct 67 /// conversions between different versions 68 private ConcatBuffer!(void) convert_buffer; 69 70 /// Struct buffer for copy of deserialized data needed for in-place 71 /// deserialization with conversion 72 private Buffer!(void) struct_buffer; 73 74 /*************************************************************************** 75 76 Constructor 77 78 Params: 79 limit = maximum allowed difference between struct versions 80 buffer_size = starting this of convert_buffer, does not really 81 matter much in practice because it will quickly grow to the 82 maximum required size and stay there 83 84 ***************************************************************************/ 85 86 public this ( size_t limit = 10, size_t buffer_size = 512 ) 87 { 88 this.e = new VersionHandlingException; 89 this.conversion_limit =limit; 90 this.convert_buffer = new ConcatBuffer!(void)(buffer_size); 91 } 92 93 /*************************************************************************** 94 95 Serializes `input` with This.Serializer and prepends version number 96 before struct data in the buffer. 97 98 Params: 99 input = struct instance to serialize 100 buffer = destination buffer for serialized data 101 102 Returns: 103 full slice of `buffer` 104 105 ***************************************************************************/ 106 107 public static void[] store ( S ) ( S input, ref Buffer!(void) buffer ) 108 { 109 alias Version.Info!(S) VInfo; 110 111 static assert ( 112 VInfo.exists, 113 "Trying to use " ~ This.stringof ~ " with unversioned struct " 114 ~ S.stringof 115 ); 116 117 buffer.length = .Serializer.countRequiredSize(input) 118 + Version.Type.sizeof; 119 auto unversioned = Version.inject(buffer, VInfo.number); 120 .Serializer.serialize(input, unversioned); 121 122 verify(unversioned.ptr is (buffer[].ptr + Version.Type.sizeof)); 123 124 return buffer[]; 125 } 126 127 /// ditto 128 public static void[] store ( S, D ) ( S input, ref D[] buffer ) 129 { 130 static assert (D.sizeof == 1, 131 "buffer can't be interpreted as void[]"); 132 return store!(S)(input, *cast(Buffer!(void)*) &buffer); 133 } 134 135 /*************************************************************************** 136 137 Loads versioned struct from `buffer` in-place 138 139 If deserialized struct is of different version than requested one, 140 converts it iteratively, one version increment/decrement at time. 141 142 Params: 143 buffer = data previously generated by `store` method, contains both 144 version data and serialized struct. Will be extended if needed 145 and modified in-place, version bytes removed 146 147 Returns: 148 part of `buffer` after deserialization and version stripping, may be 149 wrapped in deserializer-specific struct 150 151 ***************************************************************************/ 152 153 public Contiguous!(S) load ( S ) ( ref Buffer!(void) buffer ) 154 { 155 static assert ( 156 Version.Info!(S).exists, 157 "Trying to use " ~ This.stringof ~ " with unversioned struct " 158 ~ S.stringof 159 ); 160 161 this.e.enforceInputLength!(S)(buffer.length); 162 163 Version.Type input_version; 164 auto unversioned = Version.extract(buffer[], input_version); 165 // can't just do `buffer = unversioned` because it will create new 166 // gc root and slowly leak memory with each load 167 memmove(buffer[].ptr, unversioned.ptr, unversioned.length); 168 169 return this.handleVersion!(S)(buffer, input_version); 170 } 171 172 /// ditto 173 public Contiguous!(S) load(S)(ref void[] buffer) 174 { 175 return this.load!(S)(* cast(Buffer!(void)*) &buffer); 176 } 177 178 /*************************************************************************** 179 180 Loads versioned struct from `buffer` and stores resulting data 181 in `copy_buffer`, leaving `buffer` untouched. 182 183 If deserialized struct is of different version than requested one, 184 converts it iteratively, one version increment/decrement at time. 185 186 Params: 187 buffer = data previously generated by `store` method, contains both 188 version data and serialized struct. Effectively const 189 copy_buffer = buffer where deserialized struct data will be stored. 190 Will be extended if needed and won't contain version bytes 191 192 Returns: 193 slice of `buffer` after deserialization and version stripping 194 195 ***************************************************************************/ 196 197 public Contiguous!(S) loadCopy ( S ) ( in void[] buffer, 198 ref Contiguous!(S) copy_buffer ) 199 { 200 static assert ( 201 Version.Info!(S).exists, 202 "Trying to use " ~ This.stringof ~ " with unversioned struct " 203 ~ S.stringof 204 ); 205 206 this.e.enforceInputLength!(S)(buffer.length); 207 208 Version.Type input_version; 209 auto unversioned = Version.extract(buffer, input_version); 210 copy_buffer.data.length = unversioned.length; 211 enableStomping(copy_buffer.data); 212 copy_buffer.data[0 .. unversioned.length] = unversioned[]; 213 214 return this.handleVersion!(S)(copy_buffer.data, input_version); 215 } 216 217 /*************************************************************************** 218 219 Utility method to convert struct contained in input buffer to needed 220 struct version. Converted struct will be stored in the same buffer 221 replacing old data. 222 223 You can override this method to change version converting logic. 224 225 Params: 226 S = final struct version to get 227 buffer = input buffer after version bytes have been stripped off, 228 will contain resulting struct data after this method exits 229 input_version = version that was extracted from buffer 230 231 Returns: 232 deserialize() result for the last struct conversion 233 234 Throws: 235 VersionHandlingException if can't convert between provided versions 236 237 ***************************************************************************/ 238 239 protected Contiguous!(S) handleVersion ( S ) 240 ( ref Buffer!(void) buffer, Version.Type input_version ) 241 { 242 alias Version.Info!(S) VInfo; 243 244 if (abs(input_version - VInfo.number) >= this.conversion_limit) 245 { 246 this.e.throwCantConvert!(S)(input_version); 247 } 248 249 if (input_version == VInfo.number) 250 { 251 // no conversion is necessary 252 return .Deserializer.deserialize!(S)(buffer); 253 } 254 255 if (input_version > VInfo.number) 256 { 257 // input is of higher version, need to convert down 258 static if (VInfo.next.exists) 259 { 260 this.handleVersion!(VInfo.next.type)(buffer, input_version); 261 return this.convert!(S, VInfo.next.type)(buffer); 262 } 263 else 264 { 265 this.e.throwCantConvert!(S)(input_version); 266 } 267 } 268 269 if (input_version < VInfo.number) 270 { 271 // input is of lower version, need to convert up 272 static if (VInfo.prev.exists) 273 { 274 this.handleVersion!(VInfo.prev.type)(buffer, input_version); 275 return this.convert!(S, VInfo.prev.type)(buffer); 276 } 277 else 278 { 279 this.e.throwCantConvert!(S)(input_version); 280 } 281 } 282 283 assert(0); 284 } 285 286 /// ditto 287 public Contiguous!(S) handleVersion ( S ) 288 ( ref void[] buffer, Version.Type input_version ) 289 { 290 return handleVersion!(S)(*cast(Buffer!(void)*) &buffer, 291 input_version); 292 } 293 294 /*************************************************************************** 295 296 Helper method that takes care of actual conversion routine between 297 two struct types (those are assumed to be of compatible versions) 298 299 Uses this.convert_buffer for temporary allocations 300 301 Template_Params: 302 S = needed struct type 303 Source = struct type seralized into buffer 304 305 Params: 306 buffer = contains serialized Source instance, will be modified to 307 store deserialized S instance instead. 308 309 ***************************************************************************/ 310 311 public Contiguous!(S) convert ( S, Source ) ( ref Buffer!(void) buffer ) 312 { 313 scope(exit) 314 { 315 this.convert_buffer.clear(); 316 } 317 318 if (this.struct_buffer.length < buffer.length) 319 this.struct_buffer.length = buffer.length; 320 this.struct_buffer[0 .. buffer.length] = buffer[]; 321 322 auto tmp_struct = .Deserializer.deserialize!(Source)(this.struct_buffer); 323 S result_struct; 324 structConvert!(Source, S)( 325 *tmp_struct.ptr, 326 result_struct, 327 &this.convert_buffer.add 328 ); 329 .Serializer.serialize(result_struct, buffer); 330 return .Deserializer.deserialize!(S)(buffer); 331 } 332 333 /// ditto 334 public Contiguous!(S) convert ( S, Source ) ( ref void[] buffer ) 335 { 336 return convert!(S, Source)(*cast(Buffer!(void)*) &buffer); 337 } 338 } 339 340 /*************************************************************************** 341 342 Exception thrown when the loaded encounters any issues with version 343 support 344 345 ***************************************************************************/ 346 347 public class VersionHandlingException : Exception 348 { 349 mixin ReusableExceptionImplementation; 350 351 /*************************************************************************** 352 353 Used to enforce that input is large enough to store version 354 bytes and some offset. 355 356 Params: 357 input_length = size of input buffer 358 file = inferred 359 line = inferred 360 361 Template_Params: 362 S = struct type that was attempted to be loaded 363 364 ***************************************************************************/ 365 366 void enforceInputLength ( S ) ( size_t input_length, 367 istring file = __FILE__, int line = __LINE__ ) 368 { 369 if (input_length <= Version.Type.sizeof) 370 { 371 this.set("Loading ") 372 .append(S.stringof) 373 .append(" has failed, input buffer too short (length ") 374 .append(input_length) 375 .append(", need ") 376 .append(Version.Type.sizeof) 377 .append(")"); 378 this.line = line; 379 this.file = file; 380 381 throw this; 382 } 383 } 384 385 /*************************************************************************** 386 387 Used in case of version mismatch between requested struct and incoming 388 buffer 389 390 Params: 391 input_version = version found in input buffer 392 file = inferred 393 line = inferred 394 395 Template_Params: 396 S = struct type that was attempted to be loaded 397 398 ***************************************************************************/ 399 400 void throwCantConvert ( S ) ( Version.Type input_version, 401 istring file = __FILE__, int line = __LINE__ ) 402 { 403 this.set("Got version ") 404 .append(input_version) 405 .append(" for struct ") 406 .append(S.stringof) 407 .append(", expected ") 408 .append(Version.Info!(S).number) 409 .append(". Can't convert between these"); 410 this.line = line; 411 this.file = file; 412 413 throw this; 414 } 415 } 416 417 version(UnitTest) 418 { 419 struct Multi_Test1 420 { 421 static struct Version0 422 { 423 enum StructVersion = 0; 424 alias Version1 StructNext; 425 426 int a, b; 427 428 mstring[] strarr; 429 } 430 431 static struct Version1 432 { 433 enum StructVersion = 1; 434 alias Version0 StructPrevious; 435 alias Version2 StructNext; 436 437 int b, a; 438 439 mstring[] strarr; 440 } 441 442 static struct Version2 443 { 444 enum StructVersion = 2; 445 alias Version1 StructPrevious; 446 447 int a, b, c; 448 449 mstring[] strarr; 450 451 static void convert_c(ref Version1 s, ref Version2 dst) 452 { 453 dst.c = s.a + s.b; 454 } 455 } 456 } 457 } 458 459 unittest 460 { 461 // loadCopy 462 463 auto loader = new VersionDecorator(); 464 auto ver0 = Multi_Test1.Version0(42, 43, ["version0".dup]); 465 void[] serialized; 466 Contiguous!(Multi_Test1.Version2) buffer; 467 468 loader.store(ver0, serialized); 469 auto ver2 = loader.loadCopy(serialized, buffer); 470 471 testNoAlloc({ 472 auto ver2 = loader.loadCopy(serialized, buffer); 473 } ()); 474 475 test!("==")(ver2.ptr.a, ver0.a); 476 test!("==")(ver2.ptr.b, ver0.b); 477 test!("==")(ver2.ptr.c, ver0.a + ver0.b); 478 test!("==")(ver2.ptr.strarr, ver0.strarr); 479 } 480 481 unittest 482 { 483 // in-place load 484 485 auto loader = new VersionDecorator(); 486 487 auto ver0 = Multi_Test1.Version0(42, 43, ["version0".dup]); 488 void[] buffer; 489 490 loader.store(ver0, buffer); 491 auto ver2 = loader.load!(Multi_Test1.Version2)(buffer); 492 493 test!("==")(ver2.ptr.a, ver0.a); 494 test!("==")(ver2.ptr.b, ver0.b); 495 test!("==")(ver2.ptr.c, ver0.a + ver0.b); 496 test!("==")(ver2.ptr.strarr, ver0.strarr); 497 498 void[] buffer2; 499 loader.store(*ver2.ptr, buffer2); 500 auto ver0_again = loader.load!(Multi_Test1.Version0)(buffer2); 501 502 } 503 504 // error handling 505 506 version (UnitTest) 507 { 508 struct Multi_Test2 509 { 510 static struct Version3 511 { 512 int a, b; 513 enum StructVersion = 3; 514 } 515 516 static struct VersionHuge 517 { 518 enum StructVersion = 100; 519 } 520 } 521 } 522 523 unittest 524 { 525 auto loader = new VersionDecorator(); 526 527 auto ver0 = Multi_Test1.Version0(42, 43, ["version0".dup]); 528 void[] buffer; 529 530 // version number difference too big 531 loader.store(ver0, buffer); 532 testThrown!(VersionHandlingException)( 533 loader.load!(Multi_Test2.VersionHuge)(buffer) 534 ); 535 536 // "next" alias is not defined 537 loader.store(ver0, buffer); 538 testThrown!(VersionHandlingException)( 539 loader.load!(Multi_Test2.Version3)(buffer) 540 ); 541 542 // "prev" alias is not defined 543 loader.store(Multi_Test2.Version3.init, buffer); 544 testThrown!(VersionHandlingException)( 545 loader.load!(Multi_Test1.Version2)(buffer) 546 ); 547 } 548 549 550 version (UnitTest): 551 552 /******************************************************************************* 553 554 No conversion. More extensively covered by (de)serializer base tests in 555 package_test.d 556 557 *******************************************************************************/ 558 559 unittest 560 { 561 struct S 562 { 563 enum StructVersion = 1; 564 565 int a = 42; 566 double b = 2.0; 567 } 568 569 auto loader = new VersionDecorator; 570 571 void[] buffer; 572 S t; 573 loader.store(t, buffer); 574 575 Contiguous!(S) dst; 576 loader.loadCopy!(S)(buffer, dst); 577 578 test!("==")(dst.ptr.a, t.a); 579 test!("==")(dst.ptr.b, t.b); 580 581 dst = loader.load!(S)(buffer); 582 583 test!("==")(dst.ptr.a, t.a); 584 test!("==")(dst.ptr.b, t.b); 585 } 586 587 /******************************************************************************* 588 589 No conversion. Check non void[] API. 590 591 *******************************************************************************/ 592 593 unittest 594 { 595 struct S 596 { 597 enum StructVersion = 1; 598 int a = 42; 599 } 600 601 auto loader = new VersionDecorator; 602 603 ubyte[] buffer; 604 S t; 605 loader.store(t, buffer); 606 607 Contiguous!(S) dst; 608 loader.loadCopy!(S)(buffer, dst); 609 610 test!("==")(dst.ptr.a, t.a); 611 } 612 613 /******************************************************************************* 614 615 Error handling 616 617 *******************************************************************************/ 618 619 unittest 620 { 621 auto loader = new VersionDecorator; 622 void[] buffer = null; 623 624 // must not accept non-versioned 625 626 struct NoVersion { } 627 static assert (!is(typeof(loader.load!(NoVersion)(buffer)))); 628 629 // must detect if input size is too small 630 631 struct Dummy { enum StructVersion = 1; } 632 633 testThrown!(VersionHandlingException)(loader.load!(Dummy)(buffer)); 634 635 Contiguous!(Dummy) dst; 636 testThrown!(VersionHandlingException)( 637 loader.loadCopy!(Dummy)(buffer, dst)); 638 639 // must detect if conversion is not defined 640 641 struct Dummy2 { enum StructVersion = 2; } 642 643 loader.store(Dummy2.init, buffer); 644 testThrown!(VersionHandlingException)(loader.load!(Dummy)(buffer)); 645 646 loader.store(Dummy.init, buffer); 647 testThrown!(VersionHandlingException)(loader.load!(Dummy2)(buffer)); 648 } 649 650 /******************************************************************************* 651 652 Conversion from higher version, trivial struct 653 654 *******************************************************************************/ 655 656 struct Test1 657 { 658 struct Version1 659 { 660 enum StructVersion = 1; 661 662 alias Version2 StructNext; 663 664 static void convert_a(ref Version2 src, ref Version1 dst) 665 { 666 dst.a = src.a + 1; 667 } 668 669 int a; 670 } 671 672 struct Version2 673 { 674 enum StructVersion = 2; 675 676 int a = 42; 677 } 678 } 679 680 unittest 681 { 682 auto loader = new VersionDecorator; 683 684 with (Test1) 685 { 686 void[] buffer; 687 Version2 t; 688 loader.store(t, buffer); 689 690 Contiguous!(Version1) dst; 691 loader.loadCopy(buffer, dst); 692 test!("==")(dst.ptr.a, t.a + 1); 693 694 auto result = loader.load!(Version1)(buffer); 695 test!("==")(result.ptr.a, t.a + 1); 696 } 697 } 698 699 /******************************************************************************* 700 701 Conversion from lower version, trivial struct 702 703 *******************************************************************************/ 704 705 struct Test2 706 { 707 struct Version1 708 { 709 enum StructVersion = 1; 710 711 int a; 712 } 713 714 struct Version2 715 { 716 enum StructVersion = 2; 717 718 alias Version1 StructPrevious; 719 720 static void convert_a(ref Version1 src, ref Version2 dst) 721 { 722 dst.a = src.a + 1; 723 } 724 725 int a = 42; 726 } 727 } 728 729 unittest 730 { 731 auto loader = new VersionDecorator; 732 733 with (Test2) 734 { 735 Buffer!(void) buffer; 736 Version1 t; 737 loader.store(t, buffer); 738 739 Contiguous!(Version2) dst; 740 loader.loadCopy(buffer[], dst); 741 test!("==")(dst.ptr.a, t.a + 1); 742 743 auto result = loader.load!(Version2)(buffer); 744 test!("==")(result.ptr.a, t.a + 1); 745 } 746 } 747 748 /******************************************************************************* 749 750 Chained bi-directional conversions 751 752 *******************************************************************************/ 753 754 struct Test3 755 { 756 struct Version0 757 { 758 enum ubyte StructVersion = 0; 759 alias Version1 StructNext; 760 761 struct Nested0 762 { 763 int a; 764 } 765 766 int a; 767 int b; 768 769 Nested0[] nested_arr; 770 char[][] string_arr; 771 772 static Version0 create () 773 { 774 Version0 t; 775 776 t.a = 100; 777 t.b = -100; 778 t.nested_arr = [ Nested0(42), Nested0(43), Nested0(44) ]; 779 t.string_arr = [ "This".dup, "Is".dup, 780 "A".dup, "Freaking".dup, "String!".dup ]; 781 782 return t; 783 } 784 785 void compare ( NamedTest t, Version0 other ) 786 { 787 foreach (index, ref element; (&this).tupleof) 788 { 789 t.test!("==")(element, other.tupleof[index]); 790 } 791 } 792 793 void compare ( NamedTest t, Version1 other ) 794 { 795 foreach (index, ref element; (&this).tupleof) 796 { 797 enum name = this.tupleof[index].stringof[ 798 rfind(this.tupleof[index].stringof, "."[]) + 1 .. $ 799 ]; 800 801 static if (name == "nested_arr") 802 { 803 foreach (i, elem; (&this).nested_arr) 804 { 805 t.test!("==")(elem.a, other.nested_arr[i].a); 806 } 807 } 808 else 809 mixin(`test!("==")(element, other.` ~ name ~ `);`); 810 } 811 } 812 } 813 814 struct Version1 815 { 816 enum ubyte StructVersion = 1; 817 alias Version0 StructPrevious; 818 alias Version2 StructNext; 819 820 struct Nested1 821 { 822 int a; 823 int b; 824 825 static void convert_b ( ref Version0.Nested0 s, ref Nested1 dst ) 826 { 827 dst.b = s.a + 1; 828 } 829 static void convert_b ( ref Version2.Nested2 s, ref Nested1 dst ) 830 { 831 dst.b = s.a / 2; 832 } 833 } 834 835 int a; 836 int b; 837 int c; 838 839 Nested1[] nested_arr; 840 char[][] string_arr; 841 842 static void convert_c ( ref Version0 s, ref Version1 dst ) 843 { 844 dst.c = s.b - s.a; 845 } 846 847 static void convert_c ( ref Version2 s, ref Version1 dst ) 848 { 849 dst.c = s.d; 850 } 851 852 void compare ( NamedTest t, Version0 other ) 853 { 854 foreach (index, ref member; (&this).tupleof) 855 { 856 enum name = this.tupleof[index].stringof[ 857 rfind(this.tupleof[index].stringof, "."[]) + 1 .. $ 858 ]; 859 860 static if (name == "nested_arr") 861 { 862 foreach (i, ref nested; (&this).nested_arr) 863 { 864 test!("==")(nested.a, other.nested_arr[i].a); 865 test!("==")(nested.b, other.nested_arr[i].a + 1); 866 } 867 } 868 else static if (name == "c") 869 { 870 test!("==")((&this).c, other.b - other.a); 871 } 872 else 873 { 874 mixin(`test!("==")(member, other.` ~ name ~ ");"); 875 } 876 } 877 } 878 879 void compare ( NamedTest t, Version1 other ) 880 { 881 foreach (index, ref element; (&this).tupleof) 882 { 883 t.test!("==")(element, other.tupleof[index]); 884 } 885 } 886 887 void compare ( NamedTest t, Version2 other ) 888 { 889 foreach (index, ref member; (&this).tupleof) 890 { 891 enum name = this.tupleof[index].stringof[ 892 rfind(this.tupleof[index].stringof, "."[]) + 1 .. $ 893 ]; 894 895 static if (name == "nested_arr") 896 { 897 foreach (i, ref nested; (&this).nested_arr) 898 { 899 test!("==")(nested.a, other.nested_arr[i].a); 900 test!("==")(nested.b, other.nested_arr[i].a / 2); 901 } 902 } 903 else static if (name == "c") 904 { 905 test!("==")((&this).c, other.d); 906 } 907 else 908 { 909 mixin(`test!("==")(member, other.` ~ name ~ ");"); 910 } 911 } 912 } 913 } 914 915 struct Version2 916 { 917 enum ubyte StructVersion = 2; 918 919 alias Version1 StructPrevious; 920 921 struct Nested2 922 { 923 int a; 924 925 static void convert_a ( ref Version1.Nested1 s, ref Nested2 dst ) 926 { 927 dst.a = s.b * 2; 928 } 929 } 930 931 Nested2[] nested_arr; 932 933 int b; 934 int a; 935 int d; 936 937 char[][] string_arr; 938 939 static void convert_d ( ref Version1 s, ref Version2 dst ) 940 { 941 dst.d = s.c; 942 } 943 944 void compare ( NamedTest t, ref Version0 other ) 945 { 946 assert (false); 947 } 948 949 void compare ( NamedTest t, ref Version1 other ) 950 { 951 foreach (index, ref member; (&this).tupleof) 952 { 953 enum name = this.tupleof[index].stringof[ 954 rfind(this.tupleof[index].stringof, "."[]) + 1 .. $ 955 ]; 956 957 static if (name == "nested_arr") 958 { 959 foreach (i, ref nested; (&this).nested_arr) 960 { 961 test!("==")(nested.a, other.nested_arr[i].b * 2); 962 } 963 } 964 else static if (name == "d") 965 { 966 test!("==")((&this).d, other.c); 967 } 968 else 969 { 970 mixin(`test!("==")(member, other.` ~ name ~ ");"); 971 } 972 } 973 } 974 975 void compare ( NamedTest t, ref Version2 other ) 976 { 977 foreach (index, member; (&this).tupleof) 978 { 979 t.test!("==")(member, other.tupleof[index]); 980 } 981 } 982 } 983 } 984 985 Dst testConv(Src, Dst)(Src src, size_t limit = 10) 986 { 987 auto test = new NamedTest(Src.stringof ~ " -> " ~ Dst.stringof); 988 989 try 990 { 991 auto loader = new VersionDecorator(limit); 992 void[] buffer; 993 994 loader.store(src, buffer); 995 auto dst = loader.load!(Dst)(buffer); 996 dst.ptr.compare(test, src); 997 998 return *dst.ptr; 999 } 1000 catch (Exception e) 1001 { 1002 if (e.classinfo == TestException.classinfo) 1003 throw e; 1004 1005 test.msg = format( 1006 "Unhandled exception of type {} from {}:{} - '{}'", 1007 e.classinfo.name, 1008 e.file, 1009 e.line, 1010 e.message() 1011 ); 1012 test.file = __FILE__; 1013 test.line = __LINE__; 1014 throw test; 1015 } 1016 } 1017 1018 unittest 1019 { 1020 with (Test3) 1021 { 1022 // internal sanity : exceptions must propagate as NamedTest exceptions 1023 testThrown!(NamedTest)( 1024 testConv!(Version0, Version2)(Version0.create(), 1) 1025 ); 1026 1027 auto ver0 = testConv!(Version0, Version0)(Version0.create()); 1028 auto ver1 = testConv!(Version0, Version1)(ver0); 1029 auto ver2 = testConv!(Version1, Version2)(ver1); 1030 auto ver1_r = testConv!(Version2, Version1)(ver2); 1031 auto ver0_r = testConv!(Version1, Version0)(ver1_r); 1032 1033 testConv!(Version1, Version1)(ver1); 1034 testConv!(Version2, Version2)(ver2); 1035 } 1036 } 1037 1038 Dst testConvMemory(Src, Dst)(Src src, size_t limit = 10) 1039 { 1040 auto test = new NamedTest(Src.stringof ~ " -> " ~ Dst.stringof); 1041 1042 Contiguous!(Dst) result; 1043 auto loader = new VersionDecorator(limit); 1044 void[] buffer; 1045 1046 static immutable iterations = 10_000; 1047 1048 static void storeThenLoad (ref NamedTest test, ref VersionDecorator loader, 1049 ref Src src, ref void[] buffer, 1050 ref Contiguous!(Dst) result) 1051 { 1052 try 1053 { 1054 loader.store(src, buffer); 1055 result = loader.load!(Dst)(buffer); 1056 // result.ptr.compare(test, src); 1057 } 1058 catch (Exception e) 1059 { 1060 if (e.classinfo == TestException.classinfo) 1061 throw e; 1062 1063 test.msg = format( 1064 "Unhandled exception of type {} from {}:{} - '{}'", 1065 e.classinfo.name, 1066 e.file, 1067 e.line, 1068 e.message() 1069 ); 1070 test.file = __FILE__; 1071 test.line = __LINE__; 1072 throw test; 1073 } 1074 } 1075 1076 // After 1% of the iterations, memory usage shouldn't grow anymore 1077 for ( size_t i = 0; i < (iterations / 100); ++i ) 1078 { 1079 storeThenLoad(test, loader, src, buffer, result); 1080 } 1081 1082 // Do the other 99% 1083 testNoAlloc( 1084 { 1085 for ( size_t i = 0; i < iterations - (iterations / 100); ++i ) 1086 { 1087 storeThenLoad(test, loader, src, buffer, result); 1088 } 1089 }()); 1090 1091 return *result.ptr; 1092 } 1093 1094 unittest 1095 { 1096 with (Test3) 1097 { 1098 // internal sanity : exceptions must propagate as NamedTest exceptions 1099 testThrown!(NamedTest)( 1100 testConvMemory!(Version0, Version2)(Version0.create(), 1) 1101 ); 1102 1103 auto ver0 = testConvMemory!(Version0, Version0)(Version0.create()); 1104 auto ver1 = testConvMemory!(Version0, Version1)(ver0); 1105 auto ver2 = testConvMemory!(Version1, Version2)(ver1); 1106 auto ver1_r = testConvMemory!(Version2, Version1)(ver2); 1107 auto ver0_r = testConvMemory!(Version1, Version0)(ver1_r); 1108 1109 testConvMemory!(Version1, Version1)(ver1); 1110 testConvMemory!(Version2, Version2)(ver2); 1111 } 1112 } 1113 1114 /****************************************************************************** 1115 1116 Conversion which replaces struct fields completely 1117 1118 ******************************************************************************/ 1119 1120 struct Test4 1121 { 1122 struct Ver0 1123 { 1124 enum ubyte StructVersion = 0; 1125 int a; 1126 } 1127 1128 struct Ver1 1129 { 1130 enum ubyte StructVersion = 1; 1131 alias Test4.Ver0 StructPrevious; 1132 1133 long b; 1134 static void convert_b(ref Ver0 rhs, ref Ver1 dst) { dst.b = 42; } 1135 } 1136 } 1137 1138 unittest 1139 { 1140 auto loader = new VersionDecorator; 1141 void[] buffer; 1142 1143 auto src = Test4.Ver0(20); 1144 loader.store(src, buffer); 1145 auto dst = loader.load!(Test4.Ver1)(buffer); 1146 test!("==")(dst.ptr.b, 42); 1147 }