Skip to content

Commit

Permalink
Enable arrayEqual asserts for objects
Browse files Browse the repository at this point in the history
  • Loading branch information
Szabo Bogdan committed Oct 6, 2021
1 parent c10dc78 commit e5ae31b
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 33 deletions.
69 changes: 55 additions & 14 deletions source/fluentasserts/core/evaluation.d
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import std.traits;
import std.conv;
import std.range;
import std.array;
import std.algorithm : map;
import std.algorithm : map, sort;

import fluentasserts.core.serializers;
import fluentasserts.core.results;
Expand Down Expand Up @@ -191,7 +191,7 @@ unittest {

auto result = extractTypes!(T[]);

assert(result[0] == "fluentasserts.core.evaluation.__unittest_L188_C1.T[]", `Expected: ` ~ result[0] );
assert(result[0] == "fluentasserts.core.evaluation.__unittest_L188_C1.T[]", `Expected: ` ~ result[0]);
assert(result[1] == "object.Object[]", `Expected: ` ~ result[1] );
assert(result[2] == "fluentasserts.core.evaluation.__unittest_L188_C1.I[]", `Expected: ` ~ result[2] );
}
Expand All @@ -203,17 +203,18 @@ interface EquableValue {
EquableValue[] toArray();
string toString();
EquableValue generalize();
string getSerialized();
}

/// Wraps a value into equable value
EquableValue equableValue(T)(T value, string serialized) {

static if(isArray!T) {
return null;
} else static if(isInputRange!T) {
return null;
static if(isArray!T && !isSomeString!T) {
return new ArrayEquable!T(value, serialized);
} else static if(isInputRange!T && !isSomeString!T) {
return new ArrayEquable!T(value.array, serialized);
} else static if(isAssociativeArray!T) {
return null;
return new AssocArrayEquable!T(value, serialized);
} else {
return new ObjectEquable!T(value, serialized);
}
Expand Down Expand Up @@ -250,12 +251,16 @@ class ObjectEquable(T) : EquableValue {
}
}

return false;
return serialized == otherEquable.getSerialized;
} catch(Exception) {
return false;
}
}

string getSerialized() {
return serialized;
}

EquableValue generalize() {
static if(is(T == class)) {
auto obj = cast(Object) value;
Expand All @@ -273,7 +278,11 @@ class ObjectEquable(T) : EquableValue {
}

override string toString() {
return serialized;
return "Equable." ~ serialized;
}

override int opCmp (Object o) {
return -1;
}
}

Expand All @@ -285,14 +294,14 @@ class ArrayEquable(U: T[], T) : EquableValue {
string serialized;
}

this(T[] value, string serialized) {
this.value = values;
this(T[] values, string serialized) {
this.values = values;
this.serialized = serialized;
}

@safe nothrow:
bool isEqualTo(EquableValue otherEquable) {
auto other = cast(ArrayEquable!T) otherEquable;
auto other = cast(ArrayEquable!U) otherEquable;

if(other is null) {
return false;
Expand All @@ -301,11 +310,43 @@ class ArrayEquable(U: T[], T) : EquableValue {
return serialized == other.serialized;
}

EquableValue[] toArray() {
return values.map!(a => new ArrayEquable!U(a)).array;
string getSerialized() {
return serialized;
}

@trusted EquableValue[] toArray() {
static if(is(T == void)) {
return [];
} else {
try {
auto newList = values.map!(a => equableValue(a, SerializerRegistry.instance.niceValue(a))).array;

return cast(EquableValue[]) newList;
} catch(Exception) {
return [];
}
}
}

EquableValue generalize() {
return this;
}

override string toString() {
return serialized;
}
}


///
class AssocArrayEquable(U: T[V], T, V) : ArrayEquable!(string[], string) {
this(T[V] values, string serialized) {
auto sortedKeys = values.keys.sort;

auto sortedValues = sortedKeys
.map!(a => SerializerRegistry.instance.niceValue(a) ~ `: ` ~ SerializerRegistry.instance.niceValue(values[a]))
.array;

super(sortedValues, serialized);
}
}
1 change: 1 addition & 0 deletions source/fluentasserts/core/lifecycle.d
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ static this() {
}

Registry.instance.register("*[]", "*[]", "equal", &arrayEqual);
Registry.instance.register("*[*]", "*[*]", "equal", &arrayEqual);
Registry.instance.register("*[][]", "*[][]", "equal", &arrayEqual);
Registry.instance.register("*", "*", "equal", &equal);

Expand Down
17 changes: 15 additions & 2 deletions source/fluentasserts/core/operations/arrayEqual.d
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,21 @@ version(unittest) {
///
IResult[] arrayEqual(ref Evaluation evaluation) @safe nothrow {
evaluation.message.addText(".");

auto result = evaluation.currentValue.strValue == evaluation.expectedValue.strValue;
bool result = true;

EquableValue[] expectedPieces = evaluation.expectedValue.proxyValue.toArray;
EquableValue[] testData = evaluation.currentValue.proxyValue.toArray;

if(testData.length == expectedPieces.length) {
foreach(index, testedValue; testData) {
if(testedValue !is null && !testedValue.isEqualTo(expectedPieces[index])) {
result = false;
break;
}
}
} else {
result = false;
}

if(evaluation.isNegated) {
result = !result;
Expand Down
112 changes: 98 additions & 14 deletions source/fluentasserts/core/operations/registry.d
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,19 @@ class Registry {
assert(valueType != "", "The value type is not set!");
assert(name != "", "The operation name is not set!");

string key = valueType ~ "." ~ expectedValueType ~ "." ~ name;

if(key !in operations) {
auto genericKey = generalizeKey(valueType, expectedValueType, name);

assert(key in operations || genericKey in operations, "There is no `" ~ key ~ "` or `" ~ genericKey ~ "` registered to the assert operations.");
auto genericKeys = [valueType ~ "." ~ expectedValueType ~ "." ~ name] ~ generalizeKey(valueType, expectedValueType, name);
string matchedKey;

key = genericKey;
foreach(key; genericKeys) {
if(key in operations) {
matchedKey = key;
break;
}
}

return operations[key];
assert(matchedKey != "", "There are no matching assert operations. Register any of `" ~ genericKeys.join("`, `") ~ "` to perform this assert.");

return operations[matchedKey];
}

///
Expand All @@ -81,15 +83,97 @@ class Registry {
}
}

string generalizeKey(string valueType, string expectedValueType, string name) @safe nothrow {
return generalizeType(valueType) ~ "." ~ generalizeType(expectedValueType) ~ "." ~ name;
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 {
string[] generalizeType(string typeName) @safe nothrow {
auto pos = typeName.indexOf("[");
if(pos == -1) {
return "*";
return ["*"];
}

return "*" ~ typeName[pos..$];
}
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[*]", "*[*]"]);
}
3 changes: 2 additions & 1 deletion source/fluentasserts/core/serializers.d
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ class SerializerRegistry {
if(value is null) {
result = "null";
} else {
result = T.stringof ~ "(" ~ (cast() value).toHash.to!string ~ ")";
auto v = (cast() value);
result = T.stringof ~ "(" ~ v.toHash.to!string ~ ")";
}
} else static if(is(Unqual!T == Duration)) {
result = value.total!"nsecs".to!string;
Expand Down
4 changes: 2 additions & 2 deletions test/operations/approximately.d
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ alias s = Spec!({
[testValue].should.not.be.approximately([3], 0.24);
});

it("should not compare a string with a ", {
it("should not compare a string with a number", {
auto msg = ({
"".should.be.approximately(3, 0.34);
}).should.throwSomething.msg;

msg.split("\n")[0].should.equal("There is no `string.int.approximately` or `*.*.approximately` registered to the assert operations.");
msg.split("\n")[0].should.equal("There are no matching assert operations. Register any of `string.int.approximately`, `*.*.approximately` to perform this assert.");
});
});

Expand Down
Loading

0 comments on commit e5ae31b

Please sign in to comment.