1020304050607080901001101201301401501601701801902002102202302402502602702802903003103203303403503603703803911124084104204311044404504604704804905005105205305405505605705805906006106206306406506606706806907007107207307407507607707807908008108208308408508608708808909009109209309409509609709809901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700 module fluentasserts.core.evaluation; import std.datetime; import std.typecons; import std.traits; import std.conv; import std.range; import std.array; import std.algorithm : map, sort; import fluentasserts.core.serializers; import fluentasserts.core.results; import fluentasserts.core.base : TestException; /// struct ValueEvaluation { /// The exception thrown during evaluation Throwable throwable; /// Time needed to evaluate the value Duration duration; /// Serialized value as string string strValue; /// Proxy object holding the evaluated value to help doing better comparisions EquableValue proxyValue; /// Human readable value string niceValue; /// The name of the type before it was converted to string string[] typeNames; /// Other info about the value string[string] meta; string typeName() @safe nothrow { if(typeNames.length == 0) { return "unknown"; } return typeNames[0]; } } /// class Evaluation { /// The id of the current evaluation size_t id; /// The value that will be validated ValueEvaluation currentValue; /// The expected value that we will use to perform the comparison ValueEvaluation expectedValue; /// The operation name string operationName; /// True if the operation result needs to be negated to have a successful result bool isNegated; /// The nice message printed to the user MessageResult message; /// The source code where the assert is located SourceResult source; /// Results generated during evaluation IResult[] results; /// The throwable generated by the evaluation Throwable throwable; /// True when the evaluation is done bool isEvaluated; } /// auto evaluate(T)(lazy T testData) @trusted if(isInputRange!T && !isArray!T && !isAssociativeArray!T) { return evaluate(testData.array); } /// auto evaluate(T)(lazy T testData) @trusted if(!isInputRange!T || isArray!T || isAssociativeArray!T) { auto begin = Clock.currTime; alias Result = Tuple!(T, "value", ValueEvaluation, "evaluation"); try { auto value = testData; alias TT = typeof(value); static if(isCallable!T) { if(value !is null) { begin = Clock.currTime; value(); } } auto duration = Clock.currTime - begin; auto serializedValue = SerializerRegistry.instance.serialize(value); auto niceValue = SerializerRegistry.instance.niceValue(value); return Result(value, ValueEvaluation(null, duration, serializedValue, equableValue(value, niceValue), niceValue, extractTypes!TT )); } catch(Throwable t) { T result; static if(isCallable!T) { result = testData; } return Result(result, ValueEvaluation(t, Clock.currTime - begin, result.to!string, equableValue(result, result.to!string), result.to!string, extractTypes!T )); } } /// evaluate a lazy value should capture an exception unittest { int value() { throw new Exception("message"); } auto result = evaluate(value); assert(result.evaluation.throwable !is null); assert(result.evaluation.throwable.msg == "message"); } /// evaluate should capture an exception thrown by a callable unittest { void value() { throw new Exception("message"); } auto result = evaluate(&value); assert(result.evaluation.throwable !is null); assert(result.evaluation.throwable.msg == "message"); } string[] extractTypes(T)() if((!isArray!T && !isAssociativeArray!T) || isSomeString!T) { string[] types; types ~= unqualString!T; static if(is(T == class)) { static foreach(Type; BaseClassesTuple!T) { types ~= unqualString!Type; } } static if(is(T == interface) || is(T == class)) { static foreach(Type; InterfacesTuple!T) { types ~= unqualString!Type; } } return types; } string[] extractTypes(T: U[], U)() if(isArray!T && !isSomeString!T) { return extractTypes!(U).map!(a => a ~ "[]").array; } string[] extractTypes(T: U[K], U, K)() { string k = unqualString!(K); return extractTypes!(U).map!(a => a ~ "[" ~ k ~ "]").array; } /// It can get the type of a string unittest { auto result = extractTypes!string; assert(result == ["string"]); } /// It can get the type of a string list unittest { auto result = extractTypes!(string[]); assert(result == ["string[]"]); } /// It can get the type of a string assoc array unittest { auto result = extractTypes!(string[string]); assert(result == ["string[string]"]); } /// It can get all types of a class unittest { interface I {} class T : I {} auto result = extractTypes!(T[]); 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] ); } /// A proxy type that allows to compare the native values interface EquableValue { @safe nothrow: bool isEqualTo(EquableValue value); 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 && !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 new AssocArrayEquable!T(value, serialized); } else { return new ObjectEquable!T(value, serialized); } } /// class ObjectEquable(T) : EquableValue { private { T value; string serialized; } @trusted nothrow: this(T value, string serialized) { this.value = value; this.serialized = serialized; } bool isEqualTo(EquableValue otherEquable) { try { auto other = cast(ObjectEquable) otherEquable; if(other !is null) { return value == other.value; } auto generalized = otherEquable.generalize; static if(is(T == class)) { auto otherGeneralized = cast(ObjectEquable!Object) generalized; if(otherGeneralized !is null) { return value == otherGeneralized.value; } } return serialized == otherEquable.getSerialized; } catch(Exception) { return false; } } string getSerialized() { return serialized; } EquableValue generalize() { static if(is(T == class)) { auto obj = cast(Object) value; if(obj !is null) { return new ObjectEquable!Object(obj, serialized); } } return new ObjectEquable!string(serialized, serialized); } EquableValue[] toArray() { static if(__traits(hasMember, T, "byValue")) { try { return value.byValue.map!(a => a.equableValue(SerializerRegistry.instance.serialize(a))).array; } catch(Exception) {} } return [ this ]; } override string toString() { return "Equable." ~ serialized; } override int opCmp (Object o) { return -1; } } /// an object with byValue method should return an array with all elements unittest { class TestObject { auto byValue() { auto items = [1, 2]; return items.inputRangeObject; } } auto value = equableValue(new TestObject(), "[1, 2]").toArray; assert(value.length == 2, "invalid length"); assert(value[0].toString == "Equable.1", value[0].toString ~ " != Equable.1"); assert(value[1].toString == "Equable.2", value[1].toString ~ " != Equable.2"); } /// class ArrayEquable(U: T[], T) : EquableValue { private { T[] values; string serialized; } @safe nothrow: this(T[] values, string serialized) { this.values = values; this.serialized = serialized; } bool isEqualTo(EquableValue otherEquable) { auto other = cast(ArrayEquable!U) otherEquable; if(other is null) { return false; } return serialized == other.serialized; } 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); } }
module fluentasserts.core.evaluation; import std.datetime; import std.typecons; import std.traits; import std.conv; import std.range; import std.array; import std.algorithm : map, sort; import fluentasserts.core.serializers; import fluentasserts.core.results; import fluentasserts.core.base : TestException; /// struct ValueEvaluation { /// The exception thrown during evaluation Throwable throwable; /// Time needed to evaluate the value Duration duration; /// Serialized value as string string strValue; /// Proxy object holding the evaluated value to help doing better comparisions EquableValue proxyValue; /// Human readable value string niceValue; /// The name of the type before it was converted to string string[] typeNames; /// Other info about the value string[string] meta; string typeName() @safe nothrow { if(typeNames.length == 0) { return "unknown"; } return typeNames[0]; } } /// class Evaluation { /// The id of the current evaluation size_t id; /// The value that will be validated ValueEvaluation currentValue; /// The expected value that we will use to perform the comparison ValueEvaluation expectedValue; /// The operation name string operationName; /// True if the operation result needs to be negated to have a successful result bool isNegated; /// The nice message printed to the user MessageResult message; /// The source code where the assert is located SourceResult source; /// Results generated during evaluation IResult[] results; /// The throwable generated by the evaluation Throwable throwable; /// True when the evaluation is done bool isEvaluated; } /// auto evaluate(T)(lazy T testData) @trusted if(isInputRange!T && !isArray!T && !isAssociativeArray!T) { return evaluate(testData.array); } /// auto evaluate(T)(lazy T testData) @trusted if(!isInputRange!T || isArray!T || isAssociativeArray!T) { auto begin = Clock.currTime; alias Result = Tuple!(T, "value", ValueEvaluation, "evaluation"); try { auto value = testData; alias TT = typeof(value); static if(isCallable!T) { if(value !is null) { begin = Clock.currTime; value(); } } auto duration = Clock.currTime - begin; auto serializedValue = SerializerRegistry.instance.serialize(value); auto niceValue = SerializerRegistry.instance.niceValue(value); return Result(value, ValueEvaluation(null, duration, serializedValue, equableValue(value, niceValue), niceValue, extractTypes!TT )); } catch(Throwable t) { T result; static if(isCallable!T) { result = testData; } return Result(result, ValueEvaluation(t, Clock.currTime - begin, result.to!string, equableValue(result, result.to!string), result.to!string, extractTypes!T )); } } /// evaluate a lazy value should capture an exception unittest { int value() { throw new Exception("message"); } auto result = evaluate(value); assert(result.evaluation.throwable !is null); assert(result.evaluation.throwable.msg == "message"); } /// evaluate should capture an exception thrown by a callable unittest { void value() { throw new Exception("message"); } auto result = evaluate(&value); assert(result.evaluation.throwable !is null); assert(result.evaluation.throwable.msg == "message"); } string[] extractTypes(T)() if((!isArray!T && !isAssociativeArray!T) || isSomeString!T) { string[] types; types ~= unqualString!T; static if(is(T == class)) { static foreach(Type; BaseClassesTuple!T) { types ~= unqualString!Type; } } static if(is(T == interface) || is(T == class)) { static foreach(Type; InterfacesTuple!T) { types ~= unqualString!Type; } } return types; } string[] extractTypes(T: U[], U)() if(isArray!T && !isSomeString!T) { return extractTypes!(U).map!(a => a ~ "[]").array; } string[] extractTypes(T: U[K], U, K)() { string k = unqualString!(K); return extractTypes!(U).map!(a => a ~ "[" ~ k ~ "]").array; } /// It can get the type of a string unittest { auto result = extractTypes!string; assert(result == ["string"]); } /// It can get the type of a string list unittest { auto result = extractTypes!(string[]); assert(result == ["string[]"]); } /// It can get the type of a string assoc array unittest { auto result = extractTypes!(string[string]); assert(result == ["string[string]"]); } /// It can get all types of a class unittest { interface I {} class T : I {} auto result = extractTypes!(T[]); 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] ); } /// A proxy type that allows to compare the native values interface EquableValue { @safe nothrow: bool isEqualTo(EquableValue value); 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 && !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 new AssocArrayEquable!T(value, serialized); } else { return new ObjectEquable!T(value, serialized); } } /// class ObjectEquable(T) : EquableValue { private { T value; string serialized; } @trusted nothrow: this(T value, string serialized) { this.value = value; this.serialized = serialized; } bool isEqualTo(EquableValue otherEquable) { try { auto other = cast(ObjectEquable) otherEquable; if(other !is null) { return value == other.value; } auto generalized = otherEquable.generalize; static if(is(T == class)) { auto otherGeneralized = cast(ObjectEquable!Object) generalized; if(otherGeneralized !is null) { return value == otherGeneralized.value; } } return serialized == otherEquable.getSerialized; } catch(Exception) { return false; } } string getSerialized() { return serialized; } EquableValue generalize() { static if(is(T == class)) { auto obj = cast(Object) value; if(obj !is null) { return new ObjectEquable!Object(obj, serialized); } } return new ObjectEquable!string(serialized, serialized); } EquableValue[] toArray() { static if(__traits(hasMember, T, "byValue")) { try { return value.byValue.map!(a => a.equableValue(SerializerRegistry.instance.serialize(a))).array; } catch(Exception) {} } return [ this ]; } override string toString() { return "Equable." ~ serialized; } override int opCmp (Object o) { return -1; } } /// an object with byValue method should return an array with all elements unittest { class TestObject { auto byValue() { auto items = [1, 2]; return items.inputRangeObject; } } auto value = equableValue(new TestObject(), "[1, 2]").toArray; assert(value.length == 2, "invalid length"); assert(value[0].toString == "Equable.1", value[0].toString ~ " != Equable.1"); assert(value[1].toString == "Equable.2", value[1].toString ~ " != Equable.2"); } /// class ArrayEquable(U: T[], T) : EquableValue { private { T[] values; string serialized; } @safe nothrow: this(T[] values, string serialized) { this.values = values; this.serialized = serialized; } bool isEqualTo(EquableValue otherEquable) { auto other = cast(ArrayEquable!U) otherEquable; if(other is null) { return false; } return serialized == other.serialized; } 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); } }