102030405060708090100110120130140150160170180190200210220230240250260270280290300310320330340350360370380390400410420430440450460470480490500510520530540550560570580590600610620630640650660670680690700710720730740750760770780790800810820830840850860870880890900910920930940950960970980990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050005010502050305040505050605070508050905100511051205130514051505160517051805190520052105220523052405250526052705280 module trial.discovery.testclass; import std.meta; import std.traits; import std.uni; import std.conv; import std.string; import std.algorithm; import std.random; import std.array; import trial.interfaces; public import trial.attributes; import trial.discovery.code; struct SetupEvent { string name; TestSetupAttribute setup; TestCaseFunction func; } private { SetupEvent[][string] setupMethods; Object[string] testClassInstances; size_t[string] testMethodCount; size_t[string] testMethodExecuted; } private void methodDone(string ModuleName, string ClassName)() { enum key = ModuleName ~ "." ~ ClassName; testMethodExecuted[key]++; if (testMethodExecuted[key] >= testMethodCount[key]) { if (key in setupMethods) { foreach (setupMethod; setupMethods[key].filter!(a => a.setup.afterAll)) { setupMethod.func(); } } testClassInstances.remove(key); } } private auto getTestClassInstance(string ModuleName, string ClassName)() { mixin(`import ` ~ ModuleName ~ `;`); enum key = ModuleName ~ "." ~ ClassName; if (key !in testClassInstances) { mixin(`auto instance = new ` ~ ClassName ~ `();`); testClassInstances[key] = instance; testMethodExecuted[key] = 0; if (key in setupMethods) { foreach (setupMethod; setupMethods[key].filter!(a => a.setup.beforeAll)) { setupMethod.func(); } } } mixin(`return cast(` ~ key ~ `) testClassInstances[key];`); } class TestClassDiscovery : ITestDiscovery { private TestCase[] list; TestCase[] getTestCases() { return list; } TestCase[] discoverTestCases(string file) { TestCase[] testCases = []; version(Have_fluent_asserts) version(Have_libdparse) { import fluentasserts.core.results; import std.stdio; auto tokens = fileToDTokens(file); void noTest() { assert(false, "you can not run this test"); } auto iterator = TokenIterator(tokens); auto moduleName = iterator.skipUntilType("module").skipOne.readUntilType(";").strip; string lastName; string lastSuite; DLangAttribute[] attributes; int blockIndex; foreach (token; iterator) { auto type = str(token.type); if (type == "version") { iterator.skipUntilType(")"); } if (type == "unittest") { iterator.skipNextBlock; } if (type == "class") { iterator.skipOne; auto dlangClass = iterator.readClass; lastSuite = moduleName ~ "." ~ dlangClass.name; testCases ~= dlangClass.functions.filter!(a => a.hasAttribute("Test")) .map!(a => TestCase(lastSuite, a.testName, &noTest, [], SourceLocation(file, a.getAttribute("Test").line))) .array; } } } return testCases; } void addModule(string file, string moduleName)() { discover!moduleName; } private { void discover(string ModuleName)() { mixin("static import " ~ ModuleName ~ ";"); enum classList = classMembers!(ModuleName); foreach (className; classList) { mixin("alias CurrentClass = " ~ ModuleName ~ "." ~ className ~ ";"); static if(__traits(getProtection, CurrentClass) == "public") { enum members = __traits(allMembers, CurrentClass); foreach (member; members) { static if(__traits(hasMember, CurrentClass, member)) { static if (isSetupMember!(ModuleName, className, member)) { enum setup = getSetup!(ModuleName, className, member); enum key = ModuleName ~ "." ~ className; auto exists = key in setupMethods && !setupMethods[key].filter!(a => a.name == member).empty; if (!exists) { setupMethods[key] ~= SetupEvent(member, setup, ({ mixin(`auto instance = new ` ~ className ~ `();`); mixin(`instance.` ~ member ~ `;`); })); } } } } } } foreach (className; classList) { enum key = ModuleName ~ "." ~ className; mixin("alias CurrentClass = " ~ key ~ ";"); enum protection = __traits(getProtection, CurrentClass); static if(protection == "public") { enum members = __traits(allMembers, CurrentClass); testMethodCount[key] = 0; foreach (member; members) { enum memberProtection = __traits(getProtection, member); static if(memberProtection == "public") { static if (isTestMember!(ModuleName, className, member)) { testMethodCount[key]++; string testName = getTestName!(ModuleName, className, member); auto testCase = TestCase(ModuleName ~ "." ~ className, testName, ({ auto instance = getTestClassInstance!(ModuleName, className); enum key = ModuleName ~ "." ~ className; if (key in setupMethods) { foreach (setupMethod; setupMethods[key].filter!(a => a.setup.beforeEach)) { setupMethod.func(); } } mixin(`instance.` ~ member ~ `;`); if (key in setupMethods) { foreach (setupMethod; setupMethods[key].filter!(a => a.setup.afterEach)) { setupMethod.func(); } } methodDone!(ModuleName, className); }), []); testCase.location = getTestLocation!(ModuleName, className, member); list ~= testCase; } } } } } } } } string getTestName(string ModuleName, string className, string member)() { mixin("static import " ~ ModuleName ~ ";"); mixin("enum attributes = __traits(getAttributes, " ~ ModuleName ~ "." ~ className ~ "." ~ member ~ ");"); enum testAttributes = testAttributes!attributes; string name; foreach (attribute; attributes) { static if (is(typeof(attribute) == string)) { name = attribute; } } if (name.length == 0) { return member.camelToSentence; } else { return name; } } SourceLocation getTestLocation(string ModuleName, string className, string member)() { mixin("static import " ~ ModuleName ~ ";"); mixin("enum attributes = __traits(getAttributes, " ~ ModuleName ~ "." ~ className ~ "." ~ member ~ ");"); enum testAttributes = testAttributes!attributes; return SourceLocation(testAttributes[0].file, testAttributes[0].line); } auto getSetup(string ModuleName, string className, string member)() { mixin("static import " ~ ModuleName ~ ";"); mixin("enum attributes = __traits(getAttributes, " ~ ModuleName ~ "." ~ className ~ "." ~ member ~ ");"); return setupAttributes!attributes[0]; } bool isTestMember(string ModuleName, string className, string member)() { mixin("static import " ~ ModuleName ~ ";"); static if(__traits(hasMember, mixin(ModuleName ~ "." ~ className), member)) { static if(__traits(compiles, isCallable!(mixin(ModuleName ~ "." ~ className ~ "." ~ member)))) { static if(isCallable!(mixin(ModuleName ~ "." ~ className ~ "." ~ member))) { static if(__traits(compiles, mixin("__traits(getAttributes, " ~ ModuleName ~ "." ~ className ~ "." ~ member ~ ")"))) { mixin("enum attributes = __traits(getAttributes, " ~ ModuleName ~ "." ~ className ~ "." ~ member ~ ");"); return testAttributes!attributes.length > 0; } else { return false; } } else { return false; } } else { return false; } } else { return false; } } bool isSetupMember(string ModuleName, string className, string member)() { mixin("static import " ~ ModuleName ~ ";"); static if(__traits(hasMember, mixin(ModuleName ~ "." ~ className), member)) { static if(__traits(compiles, isCallable!(mixin(ModuleName ~ "." ~ className ~ "." ~ member)))) { static if(isCallable!(mixin(ModuleName ~ "." ~ className ~ "." ~ member))) { static if(__traits(compiles, mixin("__traits(getAttributes, " ~ ModuleName ~ "." ~ className ~ "." ~ member ~ ")"))) { mixin("enum attributes = __traits(getAttributes, " ~ ModuleName ~ "." ~ className ~ "." ~ member ~ ");"); return setupAttributes!attributes.length > 0; } else { return false; } } else { return false; } } else { return false; } } else { return false; } } template isClass(string moduleName) { template isModuleClass(string name) { mixin(" static import " ~ moduleName ~ "; static if (is(" ~ moduleName ~ "." ~ name ~ " == class)) enum bool isModuleClass = true; else enum bool isModuleClass = false;"); } alias isClass = isModuleClass; } template isTestAttribute(alias Attribute) { import trial.attributes; static if (!is(CommonType!(Attribute, TestAttribute) == void)) { enum bool isTestAttribute = true; } else { enum bool isTestAttribute = false; } } template isRightParameter(string parameterName) { template isRightParameter(alias Attribute) { enum isRightParameter = Attribute.parameterName == parameterName; } } template isSetupAttribute(alias Attribute) { static if (!is(CommonType!(Attribute, TestSetupAttribute) == void)) { enum bool isSetupAttribute = true; } else { enum bool isSetupAttribute = false; } } template isValueProvider(alias Attribute) { static if (__traits(hasMember, Attribute, "provide") && __traits(hasMember, Attribute, "parameterName")) { enum bool isValueProvider = true; } else { enum bool isValueProvider = false; } } template extractClasses(string moduleName, members...) { mixin("static import " ~ moduleName ~ ";"); alias Filter!(isClass!moduleName, members) extractClasses; } template extractValueProviders(Elements...) { alias Filter!(isValueProvider, Elements) extractValueProviders; } template testAttributes(attributes...) { alias Filter!(isTestAttribute, attributes) testAttributes; } template setupAttributes(attributes...) { alias Filter!(isSetupAttribute, attributes) setupAttributes; } template classMembers(string moduleName) { mixin("static import " ~ moduleName ~ ";"); mixin("alias extractClasses!(moduleName, __traits(allMembers, " ~ moduleName ~ ")) classMembers;"); } private string generateRandomParameters(alias T, int index)() pure nothrow { alias paramTypes = std.traits.Parameters!T; enum params = ParameterIdentifierTuple!T; alias providers = Filter!(isRightParameter!(params[index].stringof[1 .. $ - 1]), extractValueProviders!(__traits(getAttributes, T))); enum provider = "Filter!(isRightParameter!(" ~ params[index].stringof ~ "), extractValueProviders!(__traits(getAttributes, T)))"; static if (providers.length > 0) { immutable string definition = "auto param_" ~ params[index] ~ " = " ~ provider ~ "[0]().provide; "; } else { immutable string definition = "auto param_" ~ params[index] ~ " = uniform!" ~ paramTypes[index].stringof ~ "(); "; } static if (index == 0) { return definition; } else { return definition ~ generateRandomParameters!(T, index - 1); } } private string generateMethodParameters(alias T, int size)() { enum params = ParameterIdentifierTuple!T; static if (size == 0) { return ""; } else static if (size == 1) { return "param_" ~ params[0]; } else { return generateMethodParameters!(T, size - 1) ~ ", param_" ~ params[size - 1]; } } void methodCaller(alias T, U)(U func) { enum parameterCount = arity!T; mixin(generateRandomParameters!(T, parameterCount - 1)); mixin("func(" ~ generateMethodParameters!(T, parameterCount) ~ ");"); } struct ValueProvider(string name, alias T) { immutable static string parameterName = name; auto provide() { return T(); } } auto For(string name, alias T)() { return ValueProvider!(name, T)(); }
module trial.discovery.testclass; import std.meta; import std.traits; import std.uni; import std.conv; import std.string; import std.algorithm; import std.random; import std.array; import trial.interfaces; public import trial.attributes; import trial.discovery.code; struct SetupEvent { string name; TestSetupAttribute setup; TestCaseFunction func; } private { SetupEvent[][string] setupMethods; Object[string] testClassInstances; size_t[string] testMethodCount; size_t[string] testMethodExecuted; } private void methodDone(string ModuleName, string ClassName)() { enum key = ModuleName ~ "." ~ ClassName; testMethodExecuted[key]++; if (testMethodExecuted[key] >= testMethodCount[key]) { if (key in setupMethods) { foreach (setupMethod; setupMethods[key].filter!(a => a.setup.afterAll)) { setupMethod.func(); } } testClassInstances.remove(key); } } private auto getTestClassInstance(string ModuleName, string ClassName)() { mixin(`import ` ~ ModuleName ~ `;`); enum key = ModuleName ~ "." ~ ClassName; if (key !in testClassInstances) { mixin(`auto instance = new ` ~ ClassName ~ `();`); testClassInstances[key] = instance; testMethodExecuted[key] = 0; if (key in setupMethods) { foreach (setupMethod; setupMethods[key].filter!(a => a.setup.beforeAll)) { setupMethod.func(); } } } mixin(`return cast(` ~ key ~ `) testClassInstances[key];`); } class TestClassDiscovery : ITestDiscovery { private TestCase[] list; TestCase[] getTestCases() { return list; } TestCase[] discoverTestCases(string file) { TestCase[] testCases = []; version(Have_fluent_asserts) version(Have_libdparse) { import fluentasserts.core.results; import std.stdio; auto tokens = fileToDTokens(file); void noTest() { assert(false, "you can not run this test"); } auto iterator = TokenIterator(tokens); auto moduleName = iterator.skipUntilType("module").skipOne.readUntilType(";").strip; string lastName; string lastSuite; DLangAttribute[] attributes; int blockIndex; foreach (token; iterator) { auto type = str(token.type); if (type == "version") { iterator.skipUntilType(")"); } if (type == "unittest") { iterator.skipNextBlock; } if (type == "class") { iterator.skipOne; auto dlangClass = iterator.readClass; lastSuite = moduleName ~ "." ~ dlangClass.name; testCases ~= dlangClass.functions.filter!(a => a.hasAttribute("Test")) .map!(a => TestCase(lastSuite, a.testName, &noTest, [], SourceLocation(file, a.getAttribute("Test").line))) .array; } } } return testCases; } void addModule(string file, string moduleName)() { discover!moduleName; } private { void discover(string ModuleName)() { mixin("static import " ~ ModuleName ~ ";"); enum classList = classMembers!(ModuleName); foreach (className; classList) { mixin("alias CurrentClass = " ~ ModuleName ~ "." ~ className ~ ";"); static if(__traits(getProtection, CurrentClass) == "public") { enum members = __traits(allMembers, CurrentClass); foreach (member; members) { static if(__traits(hasMember, CurrentClass, member)) { static if (isSetupMember!(ModuleName, className, member)) { enum setup = getSetup!(ModuleName, className, member); enum key = ModuleName ~ "." ~ className; auto exists = key in setupMethods && !setupMethods[key].filter!(a => a.name == member).empty; if (!exists) { setupMethods[key] ~= SetupEvent(member, setup, ({ mixin(`auto instance = new ` ~ className ~ `();`); mixin(`instance.` ~ member ~ `;`); })); } } } } } } foreach (className; classList) { enum key = ModuleName ~ "." ~ className; mixin("alias CurrentClass = " ~ key ~ ";"); enum protection = __traits(getProtection, CurrentClass); static if(protection == "public") { enum members = __traits(allMembers, CurrentClass); testMethodCount[key] = 0; foreach (member; members) { enum memberProtection = __traits(getProtection, member); static if(memberProtection == "public") { static if (isTestMember!(ModuleName, className, member)) { testMethodCount[key]++; string testName = getTestName!(ModuleName, className, member); auto testCase = TestCase(ModuleName ~ "." ~ className, testName, ({ auto instance = getTestClassInstance!(ModuleName, className); enum key = ModuleName ~ "." ~ className; if (key in setupMethods) { foreach (setupMethod; setupMethods[key].filter!(a => a.setup.beforeEach)) { setupMethod.func(); } } mixin(`instance.` ~ member ~ `;`); if (key in setupMethods) { foreach (setupMethod; setupMethods[key].filter!(a => a.setup.afterEach)) { setupMethod.func(); } } methodDone!(ModuleName, className); }), []); testCase.location = getTestLocation!(ModuleName, className, member); list ~= testCase; } } } } } } } } string getTestName(string ModuleName, string className, string member)() { mixin("static import " ~ ModuleName ~ ";"); mixin("enum attributes = __traits(getAttributes, " ~ ModuleName ~ "." ~ className ~ "." ~ member ~ ");"); enum testAttributes = testAttributes!attributes; string name; foreach (attribute; attributes) { static if (is(typeof(attribute) == string)) { name = attribute; } } if (name.length == 0) { return member.camelToSentence; } else { return name; } } SourceLocation getTestLocation(string ModuleName, string className, string member)() { mixin("static import " ~ ModuleName ~ ";"); mixin("enum attributes = __traits(getAttributes, " ~ ModuleName ~ "." ~ className ~ "." ~ member ~ ");"); enum testAttributes = testAttributes!attributes; return SourceLocation(testAttributes[0].file, testAttributes[0].line); } auto getSetup(string ModuleName, string className, string member)() { mixin("static import " ~ ModuleName ~ ";"); mixin("enum attributes = __traits(getAttributes, " ~ ModuleName ~ "." ~ className ~ "." ~ member ~ ");"); return setupAttributes!attributes[0]; } bool isTestMember(string ModuleName, string className, string member)() { mixin("static import " ~ ModuleName ~ ";"); static if(__traits(hasMember, mixin(ModuleName ~ "." ~ className), member)) { static if(__traits(compiles, isCallable!(mixin(ModuleName ~ "." ~ className ~ "." ~ member)))) { static if(isCallable!(mixin(ModuleName ~ "." ~ className ~ "." ~ member))) { static if(__traits(compiles, mixin("__traits(getAttributes, " ~ ModuleName ~ "." ~ className ~ "." ~ member ~ ")"))) { mixin("enum attributes = __traits(getAttributes, " ~ ModuleName ~ "." ~ className ~ "." ~ member ~ ");"); return testAttributes!attributes.length > 0; } else { return false; } } else { return false; } } else { return false; } } else { return false; } } bool isSetupMember(string ModuleName, string className, string member)() { mixin("static import " ~ ModuleName ~ ";"); static if(__traits(hasMember, mixin(ModuleName ~ "." ~ className), member)) { static if(__traits(compiles, isCallable!(mixin(ModuleName ~ "." ~ className ~ "." ~ member)))) { static if(isCallable!(mixin(ModuleName ~ "." ~ className ~ "." ~ member))) { static if(__traits(compiles, mixin("__traits(getAttributes, " ~ ModuleName ~ "." ~ className ~ "." ~ member ~ ")"))) { mixin("enum attributes = __traits(getAttributes, " ~ ModuleName ~ "." ~ className ~ "." ~ member ~ ");"); return setupAttributes!attributes.length > 0; } else { return false; } } else { return false; } } else { return false; } } else { return false; } } template isClass(string moduleName) { template isModuleClass(string name) { mixin(" static import " ~ moduleName ~ "; static if (is(" ~ moduleName ~ "." ~ name ~ " == class)) enum bool isModuleClass = true; else enum bool isModuleClass = false;"); } alias isClass = isModuleClass; } template isTestAttribute(alias Attribute) { import trial.attributes; static if (!is(CommonType!(Attribute, TestAttribute) == void)) { enum bool isTestAttribute = true; } else { enum bool isTestAttribute = false; } } template isRightParameter(string parameterName) { template isRightParameter(alias Attribute) { enum isRightParameter = Attribute.parameterName == parameterName; } } template isSetupAttribute(alias Attribute) { static if (!is(CommonType!(Attribute, TestSetupAttribute) == void)) { enum bool isSetupAttribute = true; } else { enum bool isSetupAttribute = false; } } template isValueProvider(alias Attribute) { static if (__traits(hasMember, Attribute, "provide") && __traits(hasMember, Attribute, "parameterName")) { enum bool isValueProvider = true; } else { enum bool isValueProvider = false; } } template extractClasses(string moduleName, members...) { mixin("static import " ~ moduleName ~ ";"); alias Filter!(isClass!moduleName, members) extractClasses; } template extractValueProviders(Elements...) { alias Filter!(isValueProvider, Elements) extractValueProviders; } template testAttributes(attributes...) { alias Filter!(isTestAttribute, attributes) testAttributes; } template setupAttributes(attributes...) { alias Filter!(isSetupAttribute, attributes) setupAttributes; } template classMembers(string moduleName) { mixin("static import " ~ moduleName ~ ";"); mixin("alias extractClasses!(moduleName, __traits(allMembers, " ~ moduleName ~ ")) classMembers;"); } private string generateRandomParameters(alias T, int index)() pure nothrow { alias paramTypes = std.traits.Parameters!T; enum params = ParameterIdentifierTuple!T; alias providers = Filter!(isRightParameter!(params[index].stringof[1 .. $ - 1]), extractValueProviders!(__traits(getAttributes, T))); enum provider = "Filter!(isRightParameter!(" ~ params[index].stringof ~ "), extractValueProviders!(__traits(getAttributes, T)))"; static if (providers.length > 0) { immutable string definition = "auto param_" ~ params[index] ~ " = " ~ provider ~ "[0]().provide; "; } else { immutable string definition = "auto param_" ~ params[index] ~ " = uniform!" ~ paramTypes[index].stringof ~ "(); "; } static if (index == 0) { return definition; } else { return definition ~ generateRandomParameters!(T, index - 1); } } private string generateMethodParameters(alias T, int size)() { enum params = ParameterIdentifierTuple!T; static if (size == 0) { return ""; } else static if (size == 1) { return "param_" ~ params[0]; } else { return generateMethodParameters!(T, size - 1) ~ ", param_" ~ params[size - 1]; } } void methodCaller(alias T, U)(U func) { enum parameterCount = arity!T; mixin(generateRandomParameters!(T, parameterCount - 1)); mixin("func(" ~ generateMethodParameters!(T, parameterCount) ~ ");"); } struct ValueProvider(string name, alias T) { immutable static string parameterName = name; auto provide() { return T(); } } auto For(string name, alias T)() { return ValueProvider!(name, T)(); }