/// <summary> /// Determines if a method is a valid test method. /// </summary> /// <param name="testMethodInfo"> The reflected method. </param> /// <param name="logger"> Logs message for test engine. </param> /// <returns> Return true if a method is a valid test method. </returns> internal virtual bool IsValidTestMethod(MethodInfo testMethodInfo, IMUnitLogger logger) { if (!_reflectionWorker.HasAttributeIsOrDerivedFrom(testMethodInfo, typeof(TestMethodAttribute), false)) { return(false); } // Generic method Definitions are not valid. if (testMethodInfo.IsGenericMethodDefinition) { var message = string.Format(CultureInfo.CurrentCulture, Errors.UTA_ErrorGenericTestMethod, testMethodInfo.DeclaringType.FullName, testMethodInfo.Name); logger?.RecordMessage(MessageLevel.Warning, message); return(false); } // Todo: Decide wheter parameter count matters. // The isGenericMethod check below id to verify that there are no closed generic methods slipping through. // Closed generic methods being GenericMethod<int> and open being GenericMethod<T>. var isValidTestMethod = testMethodInfo.IsPublic && !testMethodInfo.IsAbstract && !testMethodInfo.IsStatic && !testMethodInfo.IsGenericMethod && testMethodInfo.ReturnType == typeof(void); if (!isValidTestMethod) { var message = string.Format(CultureInfo.CurrentCulture, Errors.UTF_ErrorIncorrectTestMethodSignature, testMethodInfo.DeclaringType.FullName, testMethodInfo.Name); logger?.RecordMessage(MessageLevel.Error, message); return(false); } return(true); }
/// <summary> /// Determines if a type is a valid test class for this adapter. /// </summary> /// <param name="type">The reflected type.</param> /// <param name="logger">Used to record information.</param> /// <returns>Return true if it is a valid test class.</returns> internal virtual bool IsValidTestClass(Type type, IMUnitLogger logger) { if (type.IsClass && _reflectionWorker.GetAttributesHaveBase(type, typeof(TestClassAttribute), false) != null) { var isPublic = type.IsPublic || (type.IsNested && type.IsNestedPublic); // non-public class if (!isPublic) { var warning = string.Format(CultureInfo.CurrentCulture, Errors.UTA_ErrorNonPublicTestClass, type.FullName); logger?.RecordMessage(MessageLevel.Warning, warning); return(false); } // Generic class if (type.IsGenericTypeDefinition && !type.IsAbstract) { // In IDE generic classes that are not abstract are treated as not runnable. Keep consistence. var warning = string.Format(CultureInfo.CurrentCulture, Errors.UTA_ErrorNonPublicTestClass, type.FullName); logger?.RecordMessage(MessageLevel.Warning, warning); return(false); } // Class is not valid if the testContext property is incorrect if (!this.HasCorrectTestContextSignature(type)) { var warning = string.Format(CultureInfo.CurrentCulture, Errors.UTA_ErrorInValidTestContextSignature, type.FullName); logger?.RecordMessage(MessageLevel.Warning, warning); return(false); } // Abstract test classes can be base classes for derived test classes. // There is no way to see if there are derived test classes. // Thus if a test class is abstract, just ignore all test methods from it // (they will be visible in derived classes). No warnings (such as test method, deployment item, // etc attribute is defined on the class) will be generated for this class: // What we do is: // - report the class as "not valid" test class. This will cause to skip enumerating tests from it. // - Do not generate warnings/do not create NOT RUNNABLE tests. if (type.IsAbstract) { return(false); } return(true); } return(false); }
/// <summary> /// Check if the method is a valid preparation method. /// </summary> /// <param name="prepMethodInfo"> Method to be checked. </param> /// <param name="logger"> Logs information. </param> /// <returns> Return true if the method is a valid preparation method. </returns> internal bool IsValidPrepMethod(MethodInfo prepMethodInfo, IMUnitLogger logger) { if (!_reflectionWorker.HasAttributeDerivedFrom(prepMethodInfo, typeof(SupportingAttribute), false)) { return(false); } bool isTestPrep = _reflectionWorker.HasAttributeIsOrDerivedFrom(prepMethodInfo, typeof(TestCleanupAttribute), false) || _reflectionWorker.HasAttributeIsOrDerivedFrom(prepMethodInfo, typeof(TestInitializeAttribute), false); var isValidTestMethod = prepMethodInfo.IsPublic && !prepMethodInfo.IsAbstract && (prepMethodInfo.IsStatic || isTestPrep) && prepMethodInfo.GetParameters().Length == 0 && prepMethodInfo.ReturnType == typeof(void); if (!isValidTestMethod) { string message = string.Format( CultureInfo.CurrentCulture, Errors.UTF_IncorrectPrepMethodSignature, prepMethodInfo.DeclaringType.FullName, prepMethodInfo.Name, _reflectionWorker.GetAttributeAssignableTo(prepMethodInfo, typeof(SupportingAttribute), false).GetType().Name, isTestPrep ? string.Empty : "static, "); logger?.RecordMessage(MessageLevel.Error, message); return(false); } return(isValidTestMethod); }
/// <summary> /// Discover preparation methods in type. /// </summary> /// <param name="source"> Full path to the assembly that contains <paramref name="type"/>.</param> /// <param name="reference"> The type used for reference when retrieve test cycles. </param> /// <param name="type"> In which preparation methods are discovered. </param> /// <param name="testCycles"> Test cycles for query. </param> /// <param name="logger"> Log information. </param> protected virtual void DiscoverPreparationMethod(string source, Type reference, Type type, TestCycleCollection testCycles, IMUnitLogger logger) { if (type == null) { return; } ThrowUtilities.NullArgument(testCycles, nameof(testCycles)); DiscoverPreparationMethod(source, reference, type.BaseType, testCycles, logger); foreach (MethodInfo method in _reflectionWorker.GetDeclaredMethods(type)) { if (_reflectionHelper.IsValidPrepMethod(method, logger)) { IEnumerable <SupportingAttribute> preparations = _reflectionWorker.GetDerivedAttributes(method, typeof(SupportingAttribute), false) .OfType <SupportingAttribute>(); foreach (SupportingAttribute prep in preparations) { Guid testCycleID = HashUtilities.GuidForTestCycleID(source, _reflectionHelper.ResolveTestCycleFullName(reference, prep.Scope)); if (testCycles.TryGetValue(testCycleID, out ITestCycle cycle)) { prep.Register(cycle, method); logger?.RecordMessage(MessageLevel.Trace, string.Format( CultureInfo.InvariantCulture, "{0} prep method is registered to test cycle {1}", prep.PreparationType, cycle.FullName)); } else { logger?.RecordMessage(MessageLevel.Error, string.Format( CultureInfo.CurrentCulture, Errors.UTE_TestCycleNotFoundForPrep, method.Name)); } } } } }
/// <summary> /// Discover tests from a type. /// </summary> /// <param name="source"> Full path to the assembly that contains <paramref name="type"/>.</param> /// <param name="type"> Discover tests in this type. </param> /// <param name="testCycles"> Test cycles for query. </param> /// <param name="logger"> Log information. </param> protected virtual void DiscoverTests(string source, Type type, TestCycleCollection testCycles, IMUnitLogger logger) { ThrowUtilities.NullArgument(type, nameof(type)); ThrowUtilities.NullArgument(testCycles, nameof(testCycles)); foreach (MethodInfo method in _reflectionWorker.GetDeclaredMethods(type)) { if (_reflectionHelper.IsValidTestMethod(method, logger)) { TestMethodAttribute methodAttribute = _reflectionWorker.GetAttributesHaveBase(method, typeof(TestMethodAttribute), false).First() as TestMethodAttribute; Guid testCycleID = HashUtilities.GuidForTestCycleID(source, _reflectionHelper.ResolveTestCycleFullName(type, methodAttribute.Scope)); if (!testCycles.TryGetValue(testCycleID, out ITestCycle testCycle)) { testCycle = new TestCycle(source, type, TestCycleScope.Method) { DeclaringClass = type, }; testCycles.Add(testCycle); } TestMethodContext context = new TestMethodContext(source, testCycle, method, type, logger); if (_reflectionWorker.TryGetAttributeAssignableTo(method, typeof(IDataSource), false, out Attribute dataAttribute)) { if (dataAttribute is IDataProvidingMethod dataProvidingMethod) { if (dataProvidingMethod.DeclaringType == null) { dataProvidingMethod.DeclaringType = type; } } // TODO Report data method that has wrong signature. context.DataSource = dataAttribute as IDataSource; } IExecutor executor = _reflectionWorker.GetAttributeAssignableTo(method, typeof(IExecutor), false) as IExecutor; context.Executor = executor; testCycle.TestMethodContexts.Add(context); testCycles.TestContextLookup.Add(context.TestID, context); logger?.RecordMessage(MessageLevel.Trace, string.Format( CultureInfo.CurrentCulture, Resources.Strings.FoundTestMethod, type.FullName, method.Name)); } } }
/// <inheritdoc/> public IList <SourcePackage> GetTypes(IEnumerable <string> sources, IMUnitLogger logger) { ThrowUtilities.NullArgument(sources, nameof(sources)); ThrowUtilities.NullArgument(logger, nameof(logger)); List <SourcePackage> packages = new List <SourcePackage>(); foreach (string source in sources) { logger.RecordMessage(MessageLevel.Trace, "Loading tests from: " + Path.GetFullPath(source)); if (File.Exists(source)) { string fileName = Path.GetFileName(source); if (!ValidateExtension(fileName)) { logger.RecordMessage(MessageLevel.Error, Errors.InvalidExtension); continue; } else if (!TryLoaded(source, out Assembly assembly, logger)) { logger.RecordMessage(MessageLevel.Error, Errors.FileNotLoaded); continue; } else { if (packages.Find(package => package.FullName != assembly.FullName) == null) { packages.Add(new SourcePackage(Path.GetFullPath(source), assembly.FullName, assembly.GetTypes())); } else { logger.RecordMessage(MessageLevel.Warning, Errors.UTE_DuplicateAssembly); } } }
/// <inheritdoc/> public virtual TestCycleCollection BuildTestCycles(IList <SourcePackage> packages, IMUnitLogger logger) { ThrowUtilities.NullArgument(packages, nameof(packages)); TestCycle root = new TestCycle(null, GetType(), TestCycleScope.AppDomain); TestCycleCollection testCycles = new TestCycleCollection(root, logger); logger?.RecordMessage(MessageLevel.Trace, string.Format( CultureInfo.InvariantCulture, "Create root test cycle with full name: {0} and parent ID: {1}", root.FullName, root.ParentID)); foreach (SourcePackage package in packages) { BuildCycleFromAssembly(package.Source, root, package.Types, testCycles, logger); } return(testCycles); }
public void Initialize(IMUnitLogger logger, string path) { ThrowUtilities.NullArgument(logger, nameof(logger)); try { if (path == null) { path = Path.Combine(Path.GetDirectoryName(MUnitConfiguration.ConfigPath), "TestLog.txt"); } logger.WriteToFile(path); _fileStream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.None); logger.MessageEvent += Logger_MessageEvent; } catch (Exception e) { logger.RecordMessage(MessageLevel.Error, e.ToString()); } }