// // // // // $Revision$ // using System; using System.Collections.Generic; using ICSharpCode.Core; using ICSharpCode.SharpDevelop.Dom; namespace ICSharpCode.UnitTesting { /// /// Represents a class that can be tested. In order for a /// class to be considered to be testable it needs to have the /// [TestFixture] attribute. /// public class TestClass { IClass c; TestMethodCollection testMethods; TestResultType testResultType; /// /// Raised when the test class result is changed. /// public event EventHandler ResultChanged; public TestClass(IClass c) { this.c = c; } /// /// Gets the underlying IClass for this test class. /// public IClass Class { get { return c; } } /// /// Determines whether the class is a test fixture. A class /// is considered to be a test class if it contains certain /// test attributes. /// public static bool IsTestClass(IClass c) { StringComparer nameComparer = GetNameComparer(c); if (nameComparer != null) { TestAttributeName testAttributeName = new TestAttributeName("TestFixture", nameComparer); foreach (IAttribute attribute in c.Attributes) { if (testAttributeName.IsEqual(attribute.Name)) { return true; } } } return false; } /// /// Returns the name comparer for the specified class. /// public static StringComparer GetNameComparer(IClass c) { if (c != null) { IProjectContent projectContent = c.ProjectContent; if (projectContent != null) { LanguageProperties language = projectContent.Language; if (language != null) { return language.NameComparer; } } } return null; } /// /// Gets the test classes that exist in the specified namespace. /// public static TestClass[] GetTestClasses(ICollection classes, string ns) { List matchedClasses = new List(); foreach (TestClass c in classes) { if (c.Namespace == ns) { matchedClasses.Add(c); } } return matchedClasses.ToArray(); } /// /// Gets the test classes that namespaces starts with the specified /// string. /// public static TestClass[] GetAllTestClasses(ICollection classes, string namespaceStartsWith) { List matchedClasses = new List(); foreach (TestClass c in classes) { if (c.Namespace.StartsWith(namespaceStartsWith)) { matchedClasses.Add(c); } } return matchedClasses.ToArray(); } /// /// Gets all child namespaces that starts with the specified string. /// /// /// If the starts with string is 'ICSharpCode' and there is a code coverage /// method with a namespace of 'ICSharpCode.XmlEditor.Tests', then this /// method will return 'XmlEditor' as one of its strings. /// public static string[] GetChildNamespaces(ICollection classes, string parentNamespace) { List items = new List(); foreach (TestClass c in classes) { string ns = c.GetChildNamespace(parentNamespace); if (ns.Length > 0) { if (!items.Contains(ns)) { items.Add(ns); } } } return items.ToArray(); } /// /// Gets the name of the class. /// public string Name { get { return c.Name; } } /// /// Gets the fully qualified name of the class. /// public string QualifiedName { get { return c.FullyQualifiedName; } } /// /// Gets the namespace of this class. /// public string Namespace { get { return c.Namespace; } } /// /// Gets the root namespace for this class. /// public string RootNamespace { get { return GetRootNamespace(c.Namespace); } } /// /// Gets the test result for this class. /// public TestResultType Result { get { return testResultType; } set { TestResultType previousTestResultType = testResultType; testResultType = value; if (previousTestResultType != testResultType) { OnResultChanged(); } } } /// /// Gets the child namespace from the specified namespace /// based on the parent namespace. /// /// Can contain multiple namespaces /// (e.g. ICSharpCode.XmlEditor). public static string GetChildNamespace(string ns, string parentNamespace) { if (parentNamespace.Length > 0) { if (ns.StartsWith(String.Concat(parentNamespace, "."))) { string end = ns.Substring(parentNamespace.Length + 1); return GetRootNamespace(end); } return String.Empty; } return ns; } /// /// Gets the child namespace based on the parent namespace /// from this class. /// /// Can contain multiple namespaces /// (e.g. ICSharpCode.XmlEditor). public string GetChildNamespace(string parentNamespace) { return GetChildNamespace(Namespace, parentNamespace); } /// /// Gets the test methods in this class. /// public TestMethodCollection TestMethods { get { if (testMethods == null) { GetTestMethods(); } return testMethods; } } /// /// Gets the test method with the specified name. /// /// Null if the method cannot be found. public TestMethod GetTestMethod(string name) { if (TestMethods.Contains(name)) { return TestMethods[name]; } return null; } /// /// Updates the test method with the specified test result. /// public void UpdateTestResult(TestResult testResult) { TestMethod method = null; string methodName = TestMethod.GetMethodName(testResult.Name); if (methodName != null) { method = GetTestMethod(methodName); if (method == null) { method = GetPrefixedTestMethod(testResult.Name); } } if (method != null) { method.Result = testResult.ResultType; } } /// /// Resets all the test results back to none. /// public void ResetTestResults() { Result = TestResultType.None; TestMethods.ResetTestResults(); } /// /// Updates the methods and class based on the new class /// information that has been parsed. /// public void UpdateClass(IClass c) { this.c = c.GetCompoundClass(); // Remove missing methods. TestMethodCollection newTestMethods = GetTestMethods(this.c); TestMethodCollection existingTestMethods = TestMethods; for (int i = existingTestMethods.Count - 1; i >= 0; --i) { TestMethod method = existingTestMethods[i]; if (newTestMethods.Contains(method.Name)) { method.Update(newTestMethods[method.Name].Method); } else { existingTestMethods.RemoveAt(i); } } // Add new methods. foreach (TestMethod method in newTestMethods) { if (existingTestMethods.Contains(method.Name)) { } else { existingTestMethods.Add(method); } } } /// /// Gets the first dotted part of the namespace. /// static string GetRootNamespace(string ns) { int index = ns.IndexOf('.'); if (index > 0) { return ns.Substring(0, index); } return ns; } /// /// Gets the test methods for the class. /// void GetTestMethods() { testMethods = GetTestMethods(c); testMethods.ResultChanged += TestMethodsResultChanged; } /// /// Gets the test methods for the specified class. /// static TestMethodCollection GetTestMethods(IClass c) { TestMethodCollection testMethods = new TestMethodCollection(); foreach (IMethod method in c.Methods) { if (TestMethod.IsTestMethod(method)) { if (!testMethods.Contains(method.Name)) { testMethods.Add(new TestMethod(method)); } } } // Add base class test methods. if (c.BaseClass != null) { foreach (IMethod method in c.BaseClass.Methods) { if (TestMethod.IsTestMethod(method)) { TestMethod testMethod = new TestMethod(c.BaseClass.Name, method); if (!testMethods.Contains(testMethod.Name)) { testMethods.Add(testMethod); } } } } return testMethods; } /// /// Updates the test class's test result after the test method's /// test result has changed. /// void TestMethodsResultChanged(object source, EventArgs e) { Result = testMethods.Result; } /// /// Raises the ResultChanged event. /// void OnResultChanged() { if (ResultChanged != null) { ResultChanged(this, new EventArgs()); } } /// /// First tries the last dotted part of the test result name as the /// method name. If there is no matching method the preceding dotted /// part is prefixed to the method name until a match is found. /// /// Given a test result of: /// /// RootNamespace.ClassName.BaseClass1.BaseClass2.TestMethod /// /// The method names tried are: /// /// TestMethod /// BaseClass2.TestMethod /// BaseClass2.BaseClass1.TestMethod /// etc. /// TestMethod GetPrefixedTestMethod(string testResultName) { int index = 0; string methodName = TestMethod.GetMethodName(testResultName); string className = TestMethod.GetQualifiedClassName(testResultName); do { index = className.LastIndexOf('.'); if (index > 0) { methodName = String.Concat(className.Substring(index + 1), ".", methodName); TestMethod method = GetTestMethod(methodName); if (method != null) { return method; } className = className.Substring(0, index); } } while (index > 0); return null; } } }