private MethodReference GetGenericMethod(GenericInstanceMethod genericInstanceMethod) { string value = Method.GetLabel(this, genericInstanceMethod); if (this.methodsDictionary.ContainsKey(value)) { return(this.methodsDictionary [value].MethodDefinition); } foreach (Method method in this.methods) { if (!method.IsGenericType) { continue; } if (method.Name != genericInstanceMethod.Name) { continue; } if (method.MethodDefinition.Parameters.Count != genericInstanceMethod.Parameters.Count) { continue; } bool ok = true; for (int i = 0; i < method.MethodDefinition.Parameters.Count; i++) { if (method.MethodDefinition.Parameters [i].ParameterType != genericInstanceMethod.Parameters [i].ParameterType) { if (!(method.MethodDefinition.Parameters [i].ParameterType is GenericParameter) || !(genericInstanceMethod.Parameters [i].ParameterType is GenericParameter)) { ok = false; break; } } } if (ok) { this.methodsDictionary [value] = method; return(method.MethodDefinition); } } throw new EngineException(string.Format("Method '{0}' not found.", genericInstanceMethod.ToString())); }
/// <summary> /// Executes. /// </summary> /// /// <param name="job"> The parameter. </param> /// /// <returns> /// A Dictionary<string,bool> /// </returns> public Boolean Execute(IJob job) { currentJob = job; host.AddResult(Severity.Debug, true, $"{GetType().Name}.Execute('{job.Parm}')"); host.AddResult(Severity.Info, true, $"[Examining and Executing Unit Test in {Path.GetFileNameWithoutExtension(job.Parm)} Project]"); Utils.TestTypes tt = Utils.TestTypes.Unknown; if (File.Exists(job.Parm)) { host.AddResult(Severity.Info, true, $"'{job.Parm}' exists."); //! Only needed for Microsoft Test. List <String> Unpatched = new List <String>(); //! Only needed for Microsoft Test. AssemblyDefinition assertAsmDef = AssemblyDefinition.ReadAssembly(GetType().Assembly.Location); AssemblyDefinition testAsmDef = AssemblyDefinition.ReadAssembly(job.Parm); //! ------------------------------------------------------------ //! Enumerate and process all classes with a TestClassAttribute. //! ------------------------------------------------------------ // foreach (TypeDefinition td in testAsmDef.MainModule.Types) { //! ------------------------------------------------------------ //! Microsoft Test. // //! Needs a fixup for references to the Assert class that resides in a pair of non-distributable assemblies. //! Here the references are changed to a Assert class defined in RQAT. // // [TestClass] // [TestInitialize] // [TestMethod] // [TestCleanup] //! ------------------------------------------------------------ // if (td.CustomAttributes.Any(q => q.AttributeType.Name.Equals("TestClassAttribute")) || td.Methods.Any(q => q.CustomAttributes.Any(p => p.AttributeType.Name.Equals("TestMethodAttribute")))) { tt = Utils.TestTypes.Microsoft; //! ------------------------------------------------------------ //! Weave Start... //! ------------------------------------------------------------ // //! https://www.simplethread.com/static-method-interception-in-net-with-c-and-monocecil/ // //! Patch all methods where the Assert class is used (so these end up in this code). // foreach (MethodDefinition foundMethod in td.Methods.OfType <MethodDefinition>()) { //! https://stackoverflow.com/questions/25077830/replace-references-to-a-type-namespace-using-mono-cecil //! https://blog.elishalom.com/2012/02/04/monitoring-execution-using-mono-cecil/ //! https://gist.github.com/7H3LaughingMan/311662c07b8bf8f8d2c6 // ILProcessor ilProcessor = foundMethod.Body.GetILProcessor(); Debug.WriteLine($"\r\n-----------------"); Debug.WriteLine($"Weaving: {foundMethod.Name}"); //! Examine and patch Call statements to the Visual Studio UnitTesting.Assert class. // for (Int32 pc = 0; pc < ilProcessor.Body.Instructions.Count; pc++) { Instruction il = ilProcessor.Body.Instructions[pc]; if (il.OpCode.Code == Code.Call) { Debug.WriteLine($"Examening: {il.OpCode.Code} {il.Operand}"); //! Methods with generics need special handling. // if (il.Operand is GenericInstanceMethod && ((GenericInstanceMethod)il.Operand).DeclaringType.FullName.Equals("Microsoft.VisualStudio.TestTools.UnitTesting.Assert")) { Debug.WriteLine(il.Operand.ToString()); GenericInstanceMethod gim = ((GenericInstanceMethod)il.Operand); Debug.WriteLine($"Method: {gim.Name}"); Debug.WriteLine($"Signature: {gim}"); //! Check if there is a matching non-generic method. // MethodDefinition md = FindReplacementMethod(assertAsmDef, gim.ToString()); if (md != null) { //! Replace generic method call by a non generic one. // Debug.WriteLine($"Replacement Definition: {md}"); //! Find MethodInfo from MethodDefinition by matching MetadataToken value. // MethodInfo mi = typeof(Assert).GetMethods().First(p => p.MetadataToken == md.MetadataToken.ToInt32()); Debug.WriteLine($"Replacement Info: {mi}"); MethodReference mri = testAsmDef.MainModule.ImportReference(mi); Debug.WriteLine($"Replacement Import: {mri}"); //! Patch. // ilProcessor.Replace(foundMethod.Body.Instructions[pc], ilProcessor.Create(OpCodes.Call, mri)); } else { //! Check if there is a matching generic method. // md = FindGenericReplacementMethod(assertAsmDef, gim.ToString()); if (md != null) { //! Replace generic method call by a generic one. // Debug.WriteLine($"Replacement Definition: {md}"); //! Find MethodInfo from MethodDefinition by matching MetadataToken value. // MethodInfo mi = typeof(Assert).GetMethods().First(p => p.MetadataToken == md.MetadataToken.ToInt32()); Debug.WriteLine($"Replacement Info: {mi}"); //! Adjust Generic method to match the one to be replaced. // GenericInstanceMethod mri = new GenericInstanceMethod(testAsmDef.MainModule.ImportReference(mi)); foreach (TypeReference tr in gim.GenericArguments) { mri.GenericArguments.Add(tr); } Debug.WriteLine($"Replacement Import: {mri}"); //! Patch. // ilProcessor.Replace(foundMethod.Body.Instructions[pc], ilProcessor.Create(OpCodes.Call, mri)); } else { if (!Unpatched.Contains(foundMethod.Name)) { Unpatched.Add(foundMethod.Name); } Debug.WriteLine($"Failed to weave: {gim}"); } } } else if (il.Operand is MethodReference && ((MethodReference)il.Operand).DeclaringType.FullName.Equals("Microsoft.VisualStudio.TestTools.UnitTesting.Assert")) { Debug.WriteLine(il.Operand.ToString()); MethodReference mr = ((MethodReference)il.Operand); Debug.WriteLine($"Method: {mr.Name}"); Debug.WriteLine($"Signature: {mr}"); //! Check if there is a matching non-generic method. // MethodDefinition md = FindReplacementMethod(assertAsmDef, mr.ToString()); if (md != null) { //! Replace non-generic method call by a non-generic one. // Debug.WriteLine($"Replacement Definition: {md}"); //! Find MethodInfo from MethodDefinition by matching MetadataToken value. // MethodInfo mi = typeof(Assert).GetMethods().First(p => p.MetadataToken == md.MetadataToken.ToInt32()); Debug.WriteLine($"Replacement Info: {mi}"); MethodReference mri = testAsmDef.MainModule.ImportReference(mi); Debug.WriteLine($"Replacement Import: {mri}"); //! Patch. // ilProcessor.Replace(foundMethod.Body.Instructions[pc], ilProcessor.Create(OpCodes.Call, mri)); } else { if (!Unpatched.Contains(foundMethod.Name)) { Unpatched.Add(foundMethod.Name); } Debug.WriteLine($"Failed to weave: {mr}"); } } } } } } else if (td.CustomAttributes.Any(q => q.AttributeType.Name.Equals("TestFixtureAttribute")) || td.Methods.Any(q => q.CustomAttributes.Any(p => p.AttributeType.Name.Equals("TestAttribute")))) { tt = Utils.TestTypes.Nunit; } } //! ------------------------------------------------------------ //! Finish up and save the resulting Weaved Assembly. //! ------------------------------------------------------------ // //https://stackoverflow.com/questions/13499384/is-it-possible-to-debug-assemblies-compiled-with-mono-xbuild-with-visual-studi switch (tt) { case Utils.TestTypes.Microsoft: { CustomAttribute debuggableAttribute = new CustomAttribute(testAsmDef.MainModule.ImportReference( typeof(DebuggableAttribute).GetConstructor(new[] { typeof(bool), typeof(bool) }))); debuggableAttribute.ConstructorArguments.Add(new CustomAttributeArgument( testAsmDef.MainModule.ImportReference(typeof(bool)), true)); debuggableAttribute.ConstructorArguments.Add(new CustomAttributeArgument( testAsmDef.MainModule.ImportReference(typeof(bool)), true)); // !Only one DebuggableAttribute is allowed, so replace an existing one. Int32 ndx = testAsmDef.CustomAttributes.ToList().FindIndex(p => p.AttributeType.Name.Equals("DebuggableAttribute")); if (ndx != -1) { testAsmDef.CustomAttributes.RemoveAt(ndx); } testAsmDef.CustomAttributes.Add(debuggableAttribute); //!Remove references to Visual Studio Assembly. //!Removing leads to failing to enumerate the test methods further along. //ndx = assemblyDef.MainModule.AssemblyReferences.ToList().FindIndex(p => p.Name.Equals("Microsoft.VisualStudio.QualityTools.UnitTestFramework")); //if (ndx != -1) //{ // assemblyDef.MainModule.AssemblyReferences.RemoveAt(ndx); //} //! Create a method call: // See https://stackoverflow.com/questions/35948733/mono-cecil-method-and-instruction-insertion // testAsmDef.Write(Path.Combine(Path.GetDirectoryName(job.Parm), "weaved.dll"), new WriterParameters() { SymbolWriterProvider = new PdbWriterProvider(), WriteSymbols = true }); //! ------------------------------------------------------------ //! Weave End //! ------------------------------------------------------------ } break; case Utils.TestTypes.Nunit: { File.Copy(job.Parm, Path.Combine(Path.GetDirectoryName(job.Parm), "weaved.dll")); } break; default: return(false); } String dll = Path.Combine(Path.GetDirectoryName(job.Parm), "weaved.dll"); String pdb = Path.ChangeExtension(dll, ".pdb"); //AppDomain currentDomain = AppDomain.CurrentDomain; //currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler); //tests.TestAssembly = Assembly.Load(File.ReadAllBytes(dll), File.ReadAllBytes(pdb)); testAsm.TestAssembly = Assembly.LoadFrom(dll); host.AddResult(Severity.Info, true, $"{tt} test class found in {Path.GetFileNameWithoutExtension(job.Parm)} assembly.", 1); //! ------------------------------------------------------------ //! Enumerate all patched tests and build a list. //! ------------------------------------------------------------ // foreach (Type type in testAsm.TestAssembly.GetTypes()) { switch (tt) { case Utils.TestTypes.Microsoft: { //! Microsoft Test needs a TestClass Attribute. // foreach (Attribute att1 in type.GetCustomAttributes(false)) { if (att1.GetType().Name.Equals("TestClassAttribute")) { testAsm.Add(new TestClass()); testAsm.Last().TestType = type; host.AddResult(Severity.Info, true, $"{att1.GetType().Name} found on {testAsm.Last().TestType.Name} class.", 1); foreach (MethodInfo method in type.GetMethods()) { foreach (Attribute att2 in method.GetCustomAttributes(false)) { String attname = att2.GetType().Name; //! See https://stackoverflow.com/questions/933613/how-do-i-use-assert-to-verify-that-an-exception-has-been-thrown // [ExpectedException(typeof(ArgumentException), "A userId of null was inappropriately allowed.")] if (attname.Equals("TestInitializeAttribute")) { testAsm.Last().Initialize = method; host.AddResult(Severity.Info, true, $"Initialize found in {testAsm.Last().TestType.Name}: {method.Name}() is {att2.GetType().Name}", 2); } else if (attname.Equals("TestCleanupAttribute")) { testAsm.Last().Cleanup = method; host.AddResult(Severity.Info, true, $"Cleanup found in {testAsm.Last().TestType.Name}: {method.Name}() is {att2.GetType().Name}", 2); } else if (attname.Equals("TestMethodAttribute")) { //! Skip unpatched methods // if (!Unpatched.Contains(method.Name)) { testAsm.Last().Methods.Add(method); } } } } host.AddResult(Severity.Info, true, $"Patched Assert Class {testAsm.Last().TestType.Name} method calls in TestMethods: {testAsm.Last().Methods.Count}", 2); host.AddResult(Severity.Info, true, $"Unpatched TestMethods in {testAsm.Last().TestType.Name}: {Unpatched.Count}", 2); } } } break; case Utils.TestTypes.Nunit: { //! Nunit Test might lack a TestFixture Attribute. // if (type.GetCustomAttributes(false).Any(q => q.GetType().Name.Equals("TestFixtureAttribute")) || type.GetMethods().Any(q => q.GetCustomAttributes(false).Any(p => p.GetType().Name.Equals("TestAttribute")))) { testAsm.Add(new TestClass()); testAsm.Last().TestType = type; foreach (MethodInfo method in type.GetMethods()) { foreach (Attribute att2 in method.GetCustomAttributes(false)) { String attname = att2.GetType().Name; //! See https://stackoverflow.com/questions/933613/how-do-i-use-assert-to-verify-that-an-exception-has-been-thrown // [ExpectedException(typeof(ArgumentException), "A userId of null was inappropriately allowed.")] if (attname.Equals("SetupAttribute")) { testAsm.Last().Initialize = method; host.AddResult(Severity.Info, true, $"Setup found in {testAsm.Last().TestType.Name}: {method.Name}() is {att2.GetType().Name}", 2); } else if (attname.Equals("TearDownAttribute")) { testAsm.Last().Cleanup = method; host.AddResult(Severity.Info, true, $"TearDown found in {testAsm.Last().TestType.Name}: {method.Name}() is {att2.GetType().Name}", 2); } else if (attname.Equals("TestAttribute")) { //! Skip unpatched methods // if (!Unpatched.Contains(method.Name)) { testAsm.Last().Methods.Add(method); } } } } } } break; } } //! ------------------------------------------------------------ //! Run Patched Method from (last added) Weaved Test Class. //! ------------------------------------------------------------ // foreach (TestClass testclass in testAsm) { foreach (MethodInfo test in testclass.Methods) { host.AddResult(Severity.Info, true, $"Invoking Test: {testclass.TestType.Name}.{test.Name}", 1); Object cls = Activator.CreateInstance(testclass.TestType); //! Initialize Test // testclass.Initialize?.Invoke(cls, new Object[] { }); //! Execute Test // try { test?.Invoke(cls, new Object[] { }); } catch (Exception e) { host.AddResult(Severity.Error, false, $"{test.Name} - {e.GetType().Name} - {e.Message}."); } //! Cleanup Test // testclass.Cleanup?.Invoke(cls, new Object[] { }); } } testAsm.TestAssembly = null; } else { host.AddResult(Severity.Warning, false, $"Failed to Locate UnitTest Assembly."); } return(true); }