fluentasserts.core.operations.registry 53/69(76%) 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
280
290
300
310
320
330
340
350
36756
37756
38126
390
400
410
42126
430
440
450
460
47126
48126
490
500
510
520
5314049
540
5514101
5614086
570
5814077
590
600
610
620
6313956
640
650
660
670
68556
69556
700
71556
72556
730
742623
75689
76556
77556
780
790
800
81556
820
83556
840
850
860
870
881668
890
900
910
92556
930
940
950
960
97556
980
990
1000
1010
10290
1030
1040
1050
1060
1070
1080
1090
1100
1110
1120
1130
1140
1150
1160
1170
1180
1190
1200
1210
1220
1230
1240
1250
1260
1270
1280
1290
1300
1310
1320
1330
1340
1350
1360
1370
1380
1390
1400
1410
1420
1430
1440
1450
1460
1470
1480
1490
1500
1510
1520
1530
1540
1550
1560
157556
1580
1593336
1603336
161556
1620
1630
1640
165556
1660
1670
1680
1691112
1701112
1711041
1720
1730
17471
1750
17671
1770
17871
17971
18071
18171
1820
183639
184142
185142
18671
1870
1880
189142
19071
1910
1920
193213
19471
1950
1960
197213
1980
1990
2000
2010
202142
20371
2040
2050
2060
20771
2080
2090
2100
2110
21271
2130
21471
2150
2160
2170
2180
2190
2200
2210
2220
2230
2240
2250
2260
2270
2280
2290
2300
2310
2320
2330
2340
2350
2360
2370
2380
2390
2400
2410
2420
2430
2440
2450
2460
2470
2480
2490
module fluentasserts.core.operations.registry; import fluentasserts.core.results; import fluentasserts.core.evaluation; import std.functional; import std.string; import std.array; import std.algorithm; /// Delegate type that can handle asserts alias Operation = IResult[] delegate(ref Evaluation) @safe nothrow; /// ditto alias OperationFunc = IResult[] delegate(ref Evaluation) @safe nothrow; struct OperationPair { string valueType; string expectedValueType; } /// class Registry { /// Global instance for the assert operations static Registry instance; private { Operation[string] operations; OperationPair[][string] pairs; string[string] descriptions; } /// Register a new assert operation Registry register(T, U)(string name, Operation operation) { foreach(valueType; extractTypes!T) { foreach(expectedValueType; extractTypes!U) { register(valueType, expectedValueType, name, operation); } } return this; } /// ditto Registry register(T, U)(string name, IResult[] function(ref Evaluation) @safe nothrow operation) { const operationDelegate = operation.toDelegate; return this.register!(T, U)(name, operationDelegate); } /// ditto Registry register(string valueType, string expectedValueType, string name, Operation operation) { string key = valueType ~ "." ~ expectedValueType ~ "." ~ name; operations[key] = operation; pairs[name] ~= OperationPair(valueType, expectedValueType); return this; } /// ditto Registry register(string valueType, string expectedValueType, string name, IResult[] function(ref Evaluation) @safe nothrow operation) { return this.register(valueType, expectedValueType, name, operation.toDelegate); } /// Get an operation function Operation get(string valueType, string expectedValueType, string name) @safe nothrow { assert(valueType != "", "The value type is not set!"); assert(name != "", "The operation name is not set!"); auto genericKeys = [valueType ~ "." ~ expectedValueType ~ "." ~ name] ~ generalizeKey(valueType, expectedValueType, name); string matchedKey; foreach(key; genericKeys) { if(key in operations) { matchedKey = key; break; } } assert(matchedKey != "", "There are no matching assert operations. Register any of `" ~ genericKeys.join("`, `") ~ "` to perform this assert."); return operations[matchedKey]; } /// IResult[] handle(ref Evaluation evaluation) @safe nothrow { if(evaluation.operationName == "" || evaluation.operationName == "to" || evaluation.operationName == "should") { return []; } auto operation = this.get( evaluation.currentValue.typeName, evaluation.expectedValue.typeName, evaluation.operationName); return operation(evaluation); } /// void describe(string name, string text) { descriptions[name] = text; } /// string describe(string name) { if(name !in descriptions) { return ""; } return descriptions[name]; } /// OperationPair[] bindingsForName(string name) { return pairs[name]; } /// string[] registeredOperations() { return operations.keys .map!(a => a.split(".")) .map!(a => a[a.length - 1]) .array .sort .uniq .array; } /// string docs() { string result = ""; string[] operationNames = registeredOperations .map!(a => "- [" ~ a ~ "](api/" ~ a ~ ".md)") .array; return operationNames.join("\n"); } } /// It generates a list of md links for docs unittest { import std.datetime; import fluentasserts.core.operations.equal; import fluentasserts.core.operations.lessThan; auto instance = new Registry(); instance.register("*", "*", "equal", &equal); instance.register!(Duration, Duration)("lessThan", &lessThanDuration); instance.docs.should.equal("- [equal](api/equal.md)\n" ~ "- [lessThan](api/lessThan.md)"); } string[] generalizeKey(string valueType, string expectedValueType, string name) @safe nothrow { string[] results; foreach (string generalizedValueType; generalizeType(valueType)) { foreach (string generalizedExpectedValueType; generalizeType(expectedValueType)) { results ~= generalizedValueType ~ "." ~ generalizedExpectedValueType ~ "." ~ name; } } return results; } string[] generalizeType(string typeName) @safe nothrow { auto pos = typeName.indexOf("["); if(pos == -1) { return ["*"]; } string[] results = []; const pieces = typeName.split("["); string arrayType; bool isHashMap; int index = 0; int diff = 0; foreach (ch; typeName[pos..$]) { diff++; if(ch == '[') { index++; } if(ch == ']') { index--; } if(index == 0 && diff == 2) { arrayType ~= "[]"; } if(index == 0 && diff != 2) { arrayType ~= "[*]"; isHashMap = true; } if(index == 0) { diff = 0; } } if(isHashMap) { results ~= "*" ~ typeName[pos..$]; results ~= pieces[0] ~ arrayType; } results ~= "*" ~ arrayType; return results; } version(unittest) { import fluentasserts.core.base; } /// It can generalize an int unittest { generalizeType("int").should.equal(["*"]); } /// It can generalize a list unittest { generalizeType("int[]").should.equal(["*[]"]); } /// It can generalize a list of lists unittest { generalizeType("int[][]").should.equal(["*[][]"]); } /// It can generalize an assoc array unittest { generalizeType("int[int]").should.equal(["*[int]", "int[*]", "*[*]"]); } /// It can generalize a combination of assoc arrays and lists unittest { generalizeType("int[int][][string][]").should.equal(["*[int][][string][]", "int[*][][*][]", "*[*][][*][]"]); } /// It can generalize an assoc array with a key list unittest { generalizeType("int[int[]]").should.equal(["*[int[]]", "int[*]", "*[*]"]); }