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