fluentasserts.core.serializers 232/233(99%) line coverage

      
10
20
30
40
50
60
70
80
90
100
110
120
130
140
150
160
170
180
190
200
210
220
230
240
250
260
270
286
296
300
310
322
330
340
3514
3614
370
380
394
400
410
420
430
440
450
460
470
480
490
500
510
520
530
540
550
560
570
5824
590
609496
610
620
630
640
650
669
670
6824
690
700
710
720
73311
74311
750
760
77289
786
790
800
810
820
8322
8414
850
860
870
880
892
900
910
920
930
94291
950
960
97105
9812
990
10093
1010
1020
10390
1040
10575
1060
10721
1080
1090
110291
1114
1120
1134
1144
1150
1160
117291
1181
1191
1201
1210
1220
123291
1240
1250
1260
1270
1280
12911245
1300
131165
1320
1339159
1340
1350
1360
1370
1380
13942
1400
14154
1420
1436299
1440
1450
1460
1470
1480
1490
1500
1510
1520
1533
1540
1551
1561
1570
1582
1592
1602
1610
1620
1630
1640
1650
1660
1670
1683
1690
1701
1711
1720
1731
1740
1752
1762
1772
1780
1790
1800
1810
1820
1830
1840
1854
1860
1871
1881
1890
1901
1911
1921
1930
1942
1952
1962
1972
1982
1992
2000
2010
2020
2030
2040
2050
2060
2070
2083
2090
2101
2111
2120
2132
2142
2152
2160
2170
2180
2190
2200
2210
2220
2233
2240
2251
2261
2270
2281
2290
2302
2312
2322
2330
2340
2350
2360
2370
2380
2390
2404
2410
2421
2431
2440
2451
2461
2471
2480
2492
2502
2512
2522
2532
2542
2550
2560
2570
2580
2591
2601
2611
2620
2632
2642
2652
2660
2670
2680
2690
2701
2711
2721
2730
2742
2752
2762
2770
2780
2790
2800
2811
2821
2831
2840
2852
2862
2872
2880
2890
2900
2910
2921
2931
2941
2950
2962
2972
2982
2990
3000
3010
3020
3031
3041
3051
3060
3072
3082
3092
3100
3110
3120
3130
3141
3151
3161
3170
3182
3192
3202
3210
3220
3230
3240
3251
3261
3271
3280
3292
3302
3312
3320
3330
3340
3350
3361
3371
3381
3390
3402
3412
3422
3430
3440
3450
3460
3470
3481
3491
3501
3510
3522
3532
3542
3550
3560
3570
3580
3590
3600
3610
3620
3630
3640
3650
3660
367212
3680
3696352
3700
3710
3720
3730
3740
3750
3760
3770
3780
3790
3800
3810
3820
3830
3840
3850
3860
3870
3880
3890
3900
3910
3920
3930
3940
3950
3960
3970
3980
3990
400839
4011
4020
4030
404838
40510
4060
4070
4081477
409179
4100
4110
412649
413649
4140
415649
416649
417649
418649
4190
42025764
4217939
42222261
4230
42416289
4251255
4261255
4271255
4280
4290
43013356
4315900
43215
43315
4340
4350
4365900
43715
4380
43915
44010
4410
4420
4430
4440
4456684
44613236
447384
4480
4490
45012476
45112
4520
4530
4540
4556684
4560
4570
458649
459641
4600
4610
462649
4630
4640
4650
4660
4671
4680
4692
4700
4710
4720
4730
4741
4750
4762
4770
4780
4790
4800
4810
4821
4830
4842
4850
4860
4870
4880
4891
4900
4912
4920
4930
4940
4950
4961
4970
4982
4990
5000
5010
5020
5031
5040
5052
5060
5070
5080
5090
5101
5110
5122
5130
5140
5150
5160
5171
5180
5192
5200
5210
5220
5230
5241
5250
5262
5270
5280
5290
5300
5311
5320
5332
5340
5350
5360
5370
5381
5390
5402
5410
5420
5430
5440
5451
5460
5472
5480
5490
5500
5510
5521
5530
5542
5550
5560
5570
5580
5591
5602
5610
5620
5630
5640
5651
5662
5670
5680
5690
5700
5711
5722
5730
5740
5750
5760
5771
5782
5790
5800
5810
5820
5831
5842
5850
5860
5870
5880
5892986
590295
5910
5920
5932691
5942691
5950
5963446
597688
5980
5990
6000
6012003
6020
6030
6040
6050
6062
6070
6080
6090
6100
6112
6120
6130
6140
6150
6162
6170
6180
6190
6200
6212
6220
6230
6240
6250
6262881
6270
6280
6290
6300
6311
6320
6332
6340
6350
6360
6370
6382
6390
module fluentasserts.core.serializers; import std.array; import std.string; import std.algorithm; import std.traits; import std.conv; import std.datetime; import std.functional; version(unittest) import fluent.asserts; /// Singleton used to serialize to string the tested values class SerializerRegistry { static SerializerRegistry instance; private { string delegate(void*)[string] serializers; string delegate(const void*)[string] constSerializers; string delegate(immutable void*)[string] immutableSerializers; } /// void register(T)(string delegate(T) serializer) if(isAggregateType!T) { enum key = T.stringof; static if(is(Unqual!T == T)) { string wrap(void* val) { auto value = (cast(T*) val); return serializer(*value); } serializers[key] = &wrap; } else static if(is(ConstOf!T == T)) { string wrap(const void* val) { auto value = (cast(T*) val); return serializer(*value); } constSerializers[key] = &wrap; } else static if(is(ImmutableOf!T == T)) { string wrap(immutable void* val) { auto value = (cast(T*) val); return serializer(*value); } immutableSerializers[key] = &wrap; } } void register(T)(string function(T) serializer) { auto serializerDelegate = serializer.toDelegate; this.register(serializerDelegate); } /// string serialize(T)(T[] value) if(!isSomeString!(T[])) { static if(is(Unqual!T == void)) { return "[]"; } else { return "[" ~ value.map!(a => serialize(a)).joiner(", ").array.to!string ~ "]"; } } /// string serialize(T: V[K], V, K)(T value) { auto keys = value.byKey.array.sort; return "[" ~ keys.map!(a => serialize(a) ~ ":" ~ serialize(value[a])).joiner(", ").array.to!string ~ "]"; } /// string serialize(T)(T value) if(isAggregateType!T) { auto key = T.stringof; auto tmp = &value; static if(is(Unqual!T == T)) { if(key in serializers) { return serializers[key](tmp); } } static if(is(ConstOf!T == T)) { if(key in constSerializers) { return constSerializers[key](tmp); } } static if(is(ImmutableOf!T == T)) { if(key in immutableSerializers) { return immutableSerializers[key](tmp); } } string result; static if(is(T == class)) { if(value is null) { result = "null"; } else { result = T.stringof ~ "(" ~ (cast() value).toHash.to!string ~ ")"; } } else static if(is(Unqual!T == Duration)) { result = value.total!"nsecs".to!string; } else static if(is(Unqual!T == SysTime)) { result = value.toISOExtString; } else { result = value.to!string; } if(result.indexOf("const(") == 0) { result = result[6..$]; auto pos = result.indexOf(")"); result = result[0..pos] ~ result[pos + 1..$]; } if(result.indexOf("immutable(") == 0) { result = result[10..$]; auto pos = result.indexOf(")"); result = result[0..pos] ~ result[pos + 1..$]; } return result; } /// string serialize(T)(T value) if(isSomeString!T || (!isArray!T && !isAssociativeArray!T && !isAggregateType!T)) { static if(isSomeString!T) { return `"` ~ value.to!string ~ `"`; } else static if(isSomeChar!T) { return `'` ~ value.to!string ~ `'`; } else { return value.to!string; } } string niceValue(T)(T value) { static if(is(Unqual!T == SysTime)) { return value.toISOExtString; } else static if(is(Unqual!T == Duration)) { return value.to!string; } else { return serialize(value); } } } /// It should be able to override the default struct serializer unittest { struct A {} string serializer(A) { return "custom value"; } auto registry = new SerializerRegistry(); registry.register(&serializer); registry.serialize(A()).should.equal("custom value"); registry.serialize([A()]).should.equal("[custom value]"); registry.serialize(["key": A()]).should.equal(`["key":custom value]`); } /// It should be able to override the default const struct serializer unittest { struct A {} string serializer(const A) { return "custom value"; } auto registry = new SerializerRegistry(); registry.register(&serializer); const A value; registry.serialize(value).should.equal("custom value"); registry.serialize([value]).should.equal("[custom value]"); registry.serialize(["key": value]).should.equal(`["key":custom value]`); } /// It should be able to override the default immutable struct serializer unittest { struct A {} string serializer(immutable A) { return "value"; } auto registry = new SerializerRegistry(); registry.register(&serializer); immutable A ivalue; const A cvalue; A value; registry.serialize(value).should.equal("A()"); registry.serialize(cvalue).should.equal("A()"); registry.serialize(ivalue).should.equal("value"); registry.serialize(ivalue).should.equal("value"); registry.serialize([ivalue]).should.equal("[value]"); registry.serialize(["key": ivalue]).should.equal(`["key":value]`); } /// It should be able to override the default class serializer unittest { class A {} string serializer(A) { return "custom value"; } auto registry = new SerializerRegistry(); registry.register(&serializer); registry.serialize(new A()).should.equal("custom value"); registry.serialize([new A()]).should.equal("[custom value]"); registry.serialize(["key": new A()]).should.equal(`["key":custom value]`); } /// It should be able to override the default const class serializer unittest { class A {} string serializer(const A) { return "custom value"; } auto registry = new SerializerRegistry(); registry.register(&serializer); const A value = new A; registry.serialize(value).should.equal("custom value"); registry.serialize([value]).should.equal("[custom value]"); registry.serialize(["key": value]).should.equal(`["key":custom value]`); } /// It should be able to override the default immutable class serializer unittest { class A {} string serializer(immutable A) { return "value"; } auto registry = new SerializerRegistry(); registry.register(&serializer); immutable A ivalue; const A cvalue; A value; registry.serialize(value).should.equal("null"); registry.serialize(cvalue).should.equal("null"); registry.serialize(ivalue).should.equal("value"); registry.serialize(ivalue).should.equal("value"); registry.serialize([ivalue]).should.equal("[value]"); registry.serialize(["key": ivalue]).should.equal(`["key":value]`); } /// It should serialize a char unittest { char ch = 'a'; const char cch = 'a'; immutable char ich = 'a'; SerializerRegistry.instance.serialize(ch).should.equal("'a'"); SerializerRegistry.instance.serialize(cch).should.equal("'a'"); SerializerRegistry.instance.serialize(ich).should.equal("'a'"); } /// It should serialize a SysTime unittest { SysTime val = SysTime.fromISOExtString("2010-07-04T07:06:12"); const SysTime cval = SysTime.fromISOExtString("2010-07-04T07:06:12"); immutable SysTime ival = SysTime.fromISOExtString("2010-07-04T07:06:12"); SerializerRegistry.instance.serialize(val).should.equal("2010-07-04T07:06:12"); SerializerRegistry.instance.serialize(cval).should.equal("2010-07-04T07:06:12"); SerializerRegistry.instance.serialize(ival).should.equal("2010-07-04T07:06:12"); } /// It should serialize a string unittest { string str = "aaa"; const string cstr = "aaa"; immutable string istr = "aaa"; SerializerRegistry.instance.serialize(str).should.equal(`"aaa"`); SerializerRegistry.instance.serialize(cstr).should.equal(`"aaa"`); SerializerRegistry.instance.serialize(istr).should.equal(`"aaa"`); } /// It should serialize an int unittest { int value = 23; const int cvalue = 23; immutable int ivalue = 23; SerializerRegistry.instance.serialize(value).should.equal(`23`); SerializerRegistry.instance.serialize(cvalue).should.equal(`23`); SerializerRegistry.instance.serialize(ivalue).should.equal(`23`); } /// It should serialize an int list unittest { int[] value = [2,3]; const int[] cvalue = [2,3]; immutable int[] ivalue = [2,3]; SerializerRegistry.instance.serialize(value).should.equal(`[2, 3]`); SerializerRegistry.instance.serialize(cvalue).should.equal(`[2, 3]`); SerializerRegistry.instance.serialize(ivalue).should.equal(`[2, 3]`); } /// It should serialize a void list unittest { void[] value = []; const void[] cvalue = []; immutable void[] ivalue = []; SerializerRegistry.instance.serialize(value).should.equal(`[]`); SerializerRegistry.instance.serialize(cvalue).should.equal(`[]`); SerializerRegistry.instance.serialize(ivalue).should.equal(`[]`); } /// It should serialize a nested int list unittest { int[][] value = [[0,1],[2,3]]; const int[][] cvalue = [[0,1],[2,3]]; immutable int[][] ivalue = [[0,1],[2,3]]; SerializerRegistry.instance.serialize(value).should.equal(`[[0, 1], [2, 3]]`); SerializerRegistry.instance.serialize(cvalue).should.equal(`[[0, 1], [2, 3]]`); SerializerRegistry.instance.serialize(ivalue).should.equal(`[[0, 1], [2, 3]]`); } /// It should serialize an assoc array unittest { int[string] value = ["a": 2,"b": 3, "c": 4]; const int[string] cvalue = ["a": 2,"b": 3, "c": 4]; immutable int[string] ivalue = ["a": 2,"b": 3, "c": 4]; SerializerRegistry.instance.serialize(value).should.equal(`["a":2, "b":3, "c":4]`); SerializerRegistry.instance.serialize(cvalue).should.equal(`["a":2, "b":3, "c":4]`); SerializerRegistry.instance.serialize(ivalue).should.equal(`["a":2, "b":3, "c":4]`); } version(unittest) { struct TestStruct { int a; string b; }; } /// It should serialize a struct unittest { TestStruct value = TestStruct(1, "2"); const TestStruct cvalue = TestStruct(1, "2"); immutable TestStruct ivalue = TestStruct(1, "2"); SerializerRegistry.instance.serialize(value).should.equal(`TestStruct(1, "2")`); SerializerRegistry.instance.serialize(cvalue).should.equal(`TestStruct(1, "2")`); SerializerRegistry.instance.serialize(ivalue).should.equal(`TestStruct(1, "2")`); } string unqualString(T: U[], U)() if(isArray!T && !isSomeString!T) { return unqualString!U ~ "[]"; } string unqualString(T: V[K], V, K)() if(isAssociativeArray!T) { return unqualString!V ~ "[" ~ unqualString!K ~ "]"; } string unqualString(T)() if(isSomeString!T || (!isArray!T && !isAssociativeArray!T)) { static if(is(T == class) || is(T == struct) || is(T == interface)) { return fullyQualifiedName!(Unqual!(T)); } else { return Unqual!T.stringof; } } string joinClassTypes(T)() { string result; static if(is(T == class)) { static foreach(Type; BaseClassesTuple!T) { result ~= Type.stringof; } } static if(is(T == interface) || is(T == class)) { static foreach(Type; InterfacesTuple!T) { if(result.length > 0) result ~= ":"; result ~= Type.stringof; } } static if(!is(T == interface) && !is(T == class)) { result = Unqual!T.stringof; } return result; } /// string[] parseList(string value) @safe nothrow { if(value.length == 0) { return []; } if(value.length == 1) { return [ value ]; } if(value[0] != '[' || value[value.length - 1] != ']') { return [ value ]; } string[] result; string currentValue; bool isInsideString; bool isInsideChar; bool isInsideArray; long arrayIndex = 0; foreach(index; 1..value.length - 1) { auto ch = value[index]; auto canSplit = !isInsideString && !isInsideChar && !isInsideArray; if(canSplit && ch == ',' && currentValue.length > 0) { result ~= currentValue.strip.dup; currentValue = ""; continue; } if(!isInsideChar && !isInsideString) { if(ch == '[') { arrayIndex++; isInsideArray = true; } if(ch == ']') { arrayIndex--; if(arrayIndex == 0) { isInsideArray = false; } } } if(!isInsideArray) { if(!isInsideChar && ch == '"') { isInsideString = !isInsideString; } if(!isInsideString && ch == '\'') { isInsideChar = !isInsideChar; } } currentValue ~= ch; } if(currentValue.length > 0) { result ~= currentValue.strip; } return result; } /// it should parse an empty string unittest { auto pieces = "".parseList; pieces.should.equal([]); } /// it should not parse a string that does not contain [] unittest { auto pieces = "test".parseList; pieces.should.equal([ "test" ]); } /// it should not parse a char that does not contain [] unittest { auto pieces = "t".parseList; pieces.should.equal([ "t" ]); } /// it should parse an empty array unittest { auto pieces = "[]".parseList; pieces.should.equal([]); } /// it should parse a list of one number unittest { auto pieces = "[1]".parseList; pieces.should.equal(["1"]); } /// it should parse a list of two numbers unittest { auto pieces = "[1,2]".parseList; pieces.should.equal(["1","2"]); } /// it should remove the whitespaces from the parsed values unittest { auto pieces = "[ 1, 2 ]".parseList; pieces.should.equal(["1","2"]); } /// it should parse two string values that contain a `,` unittest { auto pieces = `[ "a,b", "c,d" ]`.parseList; pieces.should.equal([`"a,b"`,`"c,d"`]); } /// it should parse two string values that contain a `'` unittest { auto pieces = `[ "a'b", "c'd" ]`.parseList; pieces.should.equal([`"a'b"`,`"c'd"`]); } /// it should parse two char values that contain a `,` unittest { auto pieces = `[ ',' , ',' ]`.parseList; pieces.should.equal([`','`,`','`]); } /// it should parse two char values that contain `[` and `]` unittest { auto pieces = `[ '[' , ']' ]`.parseList; pieces.should.equal([`'['`,`']'`]); } /// it should parse two string values that contain `[` and `]` unittest { auto pieces = `[ "[" , "]" ]`.parseList; pieces.should.equal([`"["`,`"]"`]); } /// it should parse two char values that contain a `"` unittest { auto pieces = `[ '"' , '"' ]`.parseList; pieces.should.equal([`'"'`,`'"'`]); } /// it should parse two empty lists unittest { auto pieces = `[ [] , [] ]`.parseList; pieces.should.equal([`[]`,`[]`]); } /// it should parse two nested lists unittest { auto pieces = `[ [[],[]] , [[[]],[]] ]`.parseList; pieces.should.equal([`[[],[]]`,`[[[]],[]]`]); } /// it should parse two lists with items unittest { auto pieces = `[ [1,2] , [3,4] ]`.parseList; pieces.should.equal([`[1,2]`,`[3,4]`]); } /// it should parse two lists with string and char items unittest { auto pieces = `[ ["1", "2"] , ['3', '4'] ]`.parseList; pieces.should.equal([`["1", "2"]`,`['3', '4']`]); } /// it should parse two lists with string and char items unittest { auto pieces = `[ ["1", "2"] , ['3', '4'] ]`.parseList; pieces.should.equal([`["1", "2"]`,`['3', '4']`]); } /// string cleanString(string value) @safe nothrow { if(value.length <= 1) { return value; } char first = value[0]; char last = value[value.length - 1]; if(first == last && (first == '"' || first == '\'')) { return value[1..$-1]; } return value; } /// it should return an empty string when the input is an empty string unittest { "".cleanString.should.equal(""); } /// it should return the input value when it has one char unittest { "'".cleanString.should.equal("'"); } /// it should remove the " from start and end of the string unittest { `""`.cleanString.should.equal(``); } /// it should remove the ' from start and end of the string unittest { `''`.cleanString.should.equal(``); } /// string[] cleanString(string[] pieces) @safe nothrow { return pieces.map!(a => a.cleanString).array; } /// It should return an empty array when the input list is empty unittest { string[] empty; empty.cleanString.should.equal(empty); } /// It should remove the `"` from the begin and end of the string unittest { [`"1"`, `"2"`].cleanString.should.equal([`1`, `2`]); }