diff --git a/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs b/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs index 8b187f1..0a733d4 100644 --- a/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs +++ b/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs @@ -2,10 +2,9 @@ { using ApprovalTests.Reporters; using NUnit.Framework; - using TestStack.ConventionTests.Internal; [TestFixture] - [UseReporter(typeof(DiffReporter))] //NOTE: Can we take care of this in IsWithApprovedExceptions? + [UseReporter(typeof(QuietReporter))] //NOTE: Can we take care of this in IsWithApprovedExceptions? public class ConventionAssertionClassTests { [Test] @@ -19,19 +18,14 @@ public void approval_mismatch() StringAssert.Contains("does not match approved file", ex.Message); } - public class FakeData : IConventionData + class FakeData : IConventionData { public string Description { get { return "Fake data"; } } public bool HasData { get { return true; } } - - public ConventionReportFailure Format(string failingData) - { - return new ConventionReportFailure(failingData); - } } - public class FailingConvention : IConvention + class FailingConvention : IConvention { public void Execute(FakeData data, IConventionResultContext result) { diff --git a/TestStack.ConventionTests.Tests/MvcConventions.api_controller_conventions.approved.txt b/TestStack.ConventionTests.Tests/MvcConventions.api_controller_conventions.approved.txt index 0983058..a58a53b 100644 --- a/TestStack.ConventionTests.Tests/MvcConventions.api_controller_conventions.approved.txt +++ b/TestStack.ConventionTests.Tests/MvcConventions.api_controller_conventions.approved.txt @@ -1,10 +1,10 @@ -'Api Controllers must be suffixed with Controller' for 'TestAssembly' ---------------------------------------------------------------------- +'Api Controllers must be suffixed with Controller' for 'Types in TestAssembly' +------------------------------------------------------------------------------ TestAssembly.Controllers.BarApiControler -'Types named *Controller must inherit from ApiController or Controller' for 'TestAssembly' ------------------------------------------------------------------------------------------- +'Types named *Controller must inherit from ApiController or Controller' for 'Types in TestAssembly' +--------------------------------------------------------------------------------------------------- TestAssembly.Controllers.TestApiController TestAssembly.Controllers.TestController diff --git a/TestStack.ConventionTests.Tests/MvcConventions.controller_conventions.approved.txt b/TestStack.ConventionTests.Tests/MvcConventions.controller_conventions.approved.txt index d564def..a445745 100644 --- a/TestStack.ConventionTests.Tests/MvcConventions.controller_conventions.approved.txt +++ b/TestStack.ConventionTests.Tests/MvcConventions.controller_conventions.approved.txt @@ -1,10 +1,10 @@ -'Mvc Controllers must be suffixed with Controller' for 'TestAssembly' ---------------------------------------------------------------------- +'Mvc Controllers must be suffixed with Controller' for 'Types in TestAssembly' +------------------------------------------------------------------------------ TestAssembly.Controllers.FooControler -'Types named *Controller must inherit from ApiController or Controller' for 'TestAssembly' ------------------------------------------------------------------------------------------- +'Types named *Controller must inherit from ApiController or Controller' for 'Types in TestAssembly' +--------------------------------------------------------------------------------------------------- TestAssembly.Controllers.TestApiController TestAssembly.Controllers.TestController diff --git a/TestStack.ConventionTests.Tests/ProjectBasedConventions.assemblies_referencing_bin_obj.approved.txt b/TestStack.ConventionTests.Tests/ProjectBasedConventions.assemblies_referencing_bin_obj.approved.txt index 5e98f99..1e8871b 100644 --- a/TestStack.ConventionTests.Tests/ProjectBasedConventions.assemblies_referencing_bin_obj.approved.txt +++ b/TestStack.ConventionTests.Tests/ProjectBasedConventions.assemblies_referencing_bin_obj.approved.txt @@ -1,4 +1,4 @@ -'Project must not reference dlls from bin or obj directories' for 'TestStack.ConventionTests.Tests' ---------------------------------------------------------------------------------------------------- +'Project must not reference dlls from bin or obj directories' for 'Project references in TestStack.ConventionTests.Tests' +------------------------------------------------------------------------------------------------------------------------- bin\Debug\ApprovalTests.dll diff --git a/TestStack.ConventionTests.Tests/ProjectBasedConventions.assemblies_referencing_bin_obj_with_approved_exceptions.approved.txt b/TestStack.ConventionTests.Tests/ProjectBasedConventions.assemblies_referencing_bin_obj_with_approved_exceptions.approved.txt index 5e98f99..1e8871b 100644 --- a/TestStack.ConventionTests.Tests/ProjectBasedConventions.assemblies_referencing_bin_obj_with_approved_exceptions.approved.txt +++ b/TestStack.ConventionTests.Tests/ProjectBasedConventions.assemblies_referencing_bin_obj_with_approved_exceptions.approved.txt @@ -1,4 +1,4 @@ -'Project must not reference dlls from bin or obj directories' for 'TestStack.ConventionTests.Tests' ---------------------------------------------------------------------------------------------------- +'Project must not reference dlls from bin or obj directories' for 'Project references in TestStack.ConventionTests.Tests' +------------------------------------------------------------------------------------------------------------------------- bin\Debug\ApprovalTests.dll diff --git a/TestStack.ConventionTests.Tests/ProjectBasedConventions.cs b/TestStack.ConventionTests.Tests/ProjectBasedConventions.cs index 45ee86e..d3066b2 100644 --- a/TestStack.ConventionTests.Tests/ProjectBasedConventions.cs +++ b/TestStack.ConventionTests.Tests/ProjectBasedConventions.cs @@ -57,7 +57,7 @@ public void scripts_not_embedded_resources() .Returns(XDocument.Parse(Resources.ProjectFileWithInvalidSqlScriptFile)); var projectLocator = Substitute.For(); - var project = new ProjectFiles(typeof (ProjectBasedConventions).Assembly, projectProvider, projectLocator); + var project = new ProjectFileItems(typeof (ProjectBasedConventions).Assembly, projectProvider, projectLocator); var ex = Assert.Throws(() => Convention.Is(new FilesAreEmbeddedResources(".sql"), project)); Approvals.Verify(ex.Message); @@ -67,7 +67,7 @@ public void scripts_not_embedded_resources() public void scripts_not_embedded_resources_with_approved_exceptions() { var projectLocator = Substitute.For(); - var project = new ProjectFiles(typeof (ProjectBasedConventions).Assembly, projectProvider, projectLocator); + var project = new ProjectFileItems(typeof (ProjectBasedConventions).Assembly, projectProvider, projectLocator); projectProvider .LoadProjectDocument(Arg.Any()) .Returns(XDocument.Parse(Resources.ProjectFileWithInvalidSqlScriptFile)); diff --git a/TestStack.ConventionTests.Tests/ProjectBasedConventions.scripts_not_embedded_resources.approved.txt b/TestStack.ConventionTests.Tests/ProjectBasedConventions.scripts_not_embedded_resources.approved.txt index ef39646..3202fa8 100644 --- a/TestStack.ConventionTests.Tests/ProjectBasedConventions.scripts_not_embedded_resources.approved.txt +++ b/TestStack.ConventionTests.Tests/ProjectBasedConventions.scripts_not_embedded_resources.approved.txt @@ -1,4 +1,4 @@ -'.sql Files must be embedded resources' for 'TestStack.ConventionTests.Tests' ------------------------------------------------------------------------------ +'.sql Files must be embedded resources' for 'Project file items in TestStack.ConventionTests.Tests' +--------------------------------------------------------------------------------------------------- Scripts\Script2.sql diff --git a/TestStack.ConventionTests.Tests/ProjectBasedConventions.scripts_not_embedded_resources_with_approved_exceptions.approved.txt b/TestStack.ConventionTests.Tests/ProjectBasedConventions.scripts_not_embedded_resources_with_approved_exceptions.approved.txt index ef39646..3202fa8 100644 --- a/TestStack.ConventionTests.Tests/ProjectBasedConventions.scripts_not_embedded_resources_with_approved_exceptions.approved.txt +++ b/TestStack.ConventionTests.Tests/ProjectBasedConventions.scripts_not_embedded_resources_with_approved_exceptions.approved.txt @@ -1,4 +1,4 @@ -'.sql Files must be embedded resources' for 'TestStack.ConventionTests.Tests' ------------------------------------------------------------------------------ +'.sql Files must be embedded resources' for 'Project file items in TestStack.ConventionTests.Tests' +--------------------------------------------------------------------------------------------------- Scripts\Script2.sql diff --git a/TestStack.ConventionTests.Tests/Properties/AssemblyInfo.cs b/TestStack.ConventionTests.Tests/Properties/AssemblyInfo.cs index 76836cd..7e55925 100644 --- a/TestStack.ConventionTests.Tests/Properties/AssemblyInfo.cs +++ b/TestStack.ConventionTests.Tests/Properties/AssemblyInfo.cs @@ -1,9 +1,11 @@ using System.Reflection; using System.Runtime.InteropServices; - // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. +using TestStack.ConventionTests; +using TestStack.ConventionTests.Reporting; + [assembly: AssemblyTitle("TestStack.ConventionTests.Tests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] @@ -33,3 +35,6 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: ConventionReporter(typeof(HtmlConventionResultsReporter))] +[assembly: ConventionReporter(typeof(MarkdownConventionResultsReporter))] \ No newline at end of file diff --git a/TestStack.ConventionTests.Tests/TypeBasedConventions.all_classes_have_default_constructor.approved.txt b/TestStack.ConventionTests.Tests/TypeBasedConventions.all_classes_have_default_constructor.approved.txt index a0fc6e2..e3fd256 100644 --- a/TestStack.ConventionTests.Tests/TypeBasedConventions.all_classes_have_default_constructor.approved.txt +++ b/TestStack.ConventionTests.Tests/TypeBasedConventions.all_classes_have_default_constructor.approved.txt @@ -1,5 +1,5 @@ -'Types must have a default constructor' for 'nHibernate Entitites' ------------------------------------------------------------------- +'Types must have a default constructor' for 'Types in nHibernate Entitites' +--------------------------------------------------------------------------- TestAssembly.ClassWithNoDefaultCtor TestAssembly.ClassWithPrivateDefaultCtor diff --git a/TestStack.ConventionTests.Tests/TypeBasedConventions.all_classes_have_default_constructor_wth_approved_exceptions.approved.txt b/TestStack.ConventionTests.Tests/TypeBasedConventions.all_classes_have_default_constructor_wth_approved_exceptions.approved.txt index a0fc6e2..e3fd256 100644 --- a/TestStack.ConventionTests.Tests/TypeBasedConventions.all_classes_have_default_constructor_wth_approved_exceptions.approved.txt +++ b/TestStack.ConventionTests.Tests/TypeBasedConventions.all_classes_have_default_constructor_wth_approved_exceptions.approved.txt @@ -1,5 +1,5 @@ -'Types must have a default constructor' for 'nHibernate Entitites' ------------------------------------------------------------------- +'Types must have a default constructor' for 'Types in nHibernate Entitites' +--------------------------------------------------------------------------- TestAssembly.ClassWithNoDefaultCtor TestAssembly.ClassWithPrivateDefaultCtor diff --git a/TestStack.ConventionTests.Tests/TypeBasedConventions.all_methods_are_virtual.approved.txt b/TestStack.ConventionTests.Tests/TypeBasedConventions.all_methods_are_virtual.approved.txt index ec621b2..9081ea0 100644 --- a/TestStack.ConventionTests.Tests/TypeBasedConventions.all_methods_are_virtual.approved.txt +++ b/TestStack.ConventionTests.Tests/TypeBasedConventions.all_methods_are_virtual.approved.txt @@ -1,4 +1,4 @@ -'Methods must be virtual' for 'nHibernate Entitites' ----------------------------------------------------- +'Methods must be virtual' for 'Types in nHibernate Entitites' +------------------------------------------------------------- TestAssembly.SampleDomainClass.TestNonVirtual diff --git a/TestStack.ConventionTests.Tests/TypeBasedConventions.all_methods_are_virtual_wth_approved_exceptions.approved.txt b/TestStack.ConventionTests.Tests/TypeBasedConventions.all_methods_are_virtual_wth_approved_exceptions.approved.txt index ec621b2..9081ea0 100644 --- a/TestStack.ConventionTests.Tests/TypeBasedConventions.all_methods_are_virtual_wth_approved_exceptions.approved.txt +++ b/TestStack.ConventionTests.Tests/TypeBasedConventions.all_methods_are_virtual_wth_approved_exceptions.approved.txt @@ -1,4 +1,4 @@ -'Methods must be virtual' for 'nHibernate Entitites' ----------------------------------------------------- +'Methods must be virtual' for 'Types in nHibernate Entitites' +------------------------------------------------------------- TestAssembly.SampleDomainClass.TestNonVirtual diff --git a/TestStack.ConventionTests.Tests/TypeBasedConventions.dtos_exists_in_dto_namespace.approved.txt b/TestStack.ConventionTests.Tests/TypeBasedConventions.dtos_exists_in_dto_namespace.approved.txt index d0deea5..aab6589 100644 --- a/TestStack.ConventionTests.Tests/TypeBasedConventions.dtos_exists_in_dto_namespace.approved.txt +++ b/TestStack.ConventionTests.Tests/TypeBasedConventions.dtos_exists_in_dto_namespace.approved.txt @@ -1,9 +1,9 @@ -'Dtos must be under the 'TestAssembly.Dtos' namespace' for 'TestAssembly' -------------------------------------------------------------------------- +'Dtos must be under the 'TestAssembly.Dtos' namespace' for 'Types in TestAssembly' +---------------------------------------------------------------------------------- TestAssembly.SomeDto -'Non-Dtos must not be under the 'TestAssembly.Dtos' namespace' for 'TestAssembly' ---------------------------------------------------------------------------------- +'Non-Dtos must not be under the 'TestAssembly.Dtos' namespace' for 'Types in TestAssembly' +------------------------------------------------------------------------------------------ TestAssembly.Dtos.AnotherClass diff --git a/TestStack.ConventionTests.Tests/TypeBasedConventions.dtos_exists_in_dto_namespace_wth_approved_exceptions.approved.txt b/TestStack.ConventionTests.Tests/TypeBasedConventions.dtos_exists_in_dto_namespace_wth_approved_exceptions.approved.txt index 547d592..0aa693d 100644 --- a/TestStack.ConventionTests.Tests/TypeBasedConventions.dtos_exists_in_dto_namespace_wth_approved_exceptions.approved.txt +++ b/TestStack.ConventionTests.Tests/TypeBasedConventions.dtos_exists_in_dto_namespace_wth_approved_exceptions.approved.txt @@ -1,4 +1,4 @@ -'Dtos must be under the 'TestAssembly.Dtos' namespace' for 'TestAssembly' -------------------------------------------------------------------------- +'Dtos must be under the 'TestAssembly.Dtos' namespace' for 'Types in TestAssembly' +---------------------------------------------------------------------------------- TestAssembly.SomeDto diff --git a/TestStack.ConventionTests.Tests/TypeBasedConventions.dtos_exists_in_dto_namespace_wth_approved_exceptions1.approved.txt b/TestStack.ConventionTests.Tests/TypeBasedConventions.dtos_exists_in_dto_namespace_wth_approved_exceptions1.approved.txt index faaec50..3995d97 100644 --- a/TestStack.ConventionTests.Tests/TypeBasedConventions.dtos_exists_in_dto_namespace_wth_approved_exceptions1.approved.txt +++ b/TestStack.ConventionTests.Tests/TypeBasedConventions.dtos_exists_in_dto_namespace_wth_approved_exceptions1.approved.txt @@ -1,4 +1,4 @@ -'Non-Dtos must not be under the 'TestAssembly.Dtos' namespace' for 'TestAssembly' ---------------------------------------------------------------------------------- +'Non-Dtos must not be under the 'TestAssembly.Dtos' namespace' for 'Types in TestAssembly' +------------------------------------------------------------------------------------------ TestAssembly.Dtos.AnotherClass diff --git a/TestStack.ConventionTests/Convention.cs b/TestStack.ConventionTests/Convention.cs index 447d520..ba5d9aa 100644 --- a/TestStack.ConventionTests/Convention.cs +++ b/TestStack.ConventionTests/Convention.cs @@ -2,14 +2,16 @@ { using System; using System.Collections.Generic; - using System.IO; using System.Reflection; + using System.Text.RegularExpressions; + using TestStack.ConventionTests.ConventionData; using TestStack.ConventionTests.Internal; using TestStack.ConventionTests.Reporting; public static class Convention { - static readonly HtmlReportRenderer HtmlRenderer = new HtmlReportRenderer(AssemblyDirectory); + static IResultsProcessor[] defaultProcessors; + static IResultsProcessor[] defaultApprovalProcessors; static Convention() { @@ -27,52 +29,53 @@ static Convention() public static IList Formatters { get; set; } - static string AssemblyDirectory + public static void Is(IConvention convention, TDataSource data, + ITestResultProcessor resultProcessor = null) + where TDataSource : IConventionData { - get - { - // http://stackoverflow.com/questions/52797/c-how-do-i-get-the-path-of-the-assembly-the-code-is-in#answer-283917 - var codeBase = Assembly.GetExecutingAssembly().CodeBase; - var uri = new UriBuilder(codeBase); - var path = Uri.UnescapeDataString(uri.Path); - return Path.GetDirectoryName(path); - } + if (defaultProcessors == null || defaultApprovalProcessors == null) + Init(Assembly.GetCallingAssembly()); + Execute(convention, data, defaultProcessors, resultProcessor ?? new ConventionReportTextRenderer()); } - public static void Is(IConvention convention, TDataSource data, - params IResultsProcessor[] extraResultProcessors) + public static void IsWithApprovedExeptions(IConvention convention, TDataSource data, + ITestResultProcessor resultProcessor = null) where TDataSource : IConventionData { - var processors = new List(extraResultProcessors) - { - new ConventionReportTextRenderer(), - HtmlRenderer, - new ConventionReportTraceRenderer(), - new ThrowOnFailureResultsProcessor() - }; - Execute(convention, data, processors.ToArray()); + if (defaultProcessors == null || defaultApprovalProcessors == null) + Init(Assembly.GetCallingAssembly()); + Execute(convention, data, defaultApprovalProcessors, resultProcessor ?? new ConventionReportTextRenderer()); } static void Execute(IConvention convention, TDataSource data, - IResultsProcessor[] processors) + IResultsProcessor[] processors, ITestResultProcessor resultProcessor) where TDataSource : IConventionData { - var context = new ConventionContext(data.Description, Formatters, processors); + var dataDescription = string.Format("{0} in {1}", data.GetType().GetSentenceCaseName(), data.Description); + var context = new ConventionContext(dataDescription, Formatters, processors, resultProcessor); context.Execute(convention, data); } - public static void IsWithApprovedExeptions(IConvention convention, TDataSource data, - params IResultsProcessor[] extraResultProcessors) - where TDataSource : IConventionData + static void Init(Assembly assembly) { - var processors = new List(extraResultProcessors) + var customReporters = assembly.GetCustomAttributes(typeof(ConventionReporterAttribute), false); + + defaultProcessors = new IResultsProcessor[customReporters.Length + 2]; + defaultApprovalProcessors = new IResultsProcessor[customReporters.Length + 2]; + + for (var index = 0; index < customReporters.Length; index++) { - new ConventionReportTextRenderer(), - HtmlRenderer, - new ConventionReportTraceRenderer(), - new ApproveResultsProcessor() - }; - Execute(convention, data, processors.ToArray()); + var customReporter = (ConventionReporterAttribute)customReporters[index]; + var resultsProcessor = (IResultsProcessor)Activator.CreateInstance(customReporter.ReporterType); + defaultProcessors[index] = resultsProcessor; + defaultApprovalProcessors[index] = resultsProcessor; + } + + var conventionReportTraceRenderer = new ConventionReportTraceRenderer(); + defaultProcessors[defaultProcessors.Length - 2] = conventionReportTraceRenderer; + defaultApprovalProcessors[defaultProcessors.Length - 2] = conventionReportTraceRenderer; + defaultProcessors[defaultProcessors.Length - 1] = new ThrowOnFailureResultsProcessor(); + defaultApprovalProcessors[defaultProcessors.Length - 1] = new ApproveResultsProcessor(); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/ConventionData/AbstractProjectData.cs b/TestStack.ConventionTests/ConventionData/AbstractProjectData.cs index 48d8483..9a20974 100644 --- a/TestStack.ConventionTests/ConventionData/AbstractProjectData.cs +++ b/TestStack.ConventionTests/ConventionData/AbstractProjectData.cs @@ -2,7 +2,6 @@ { using System.Reflection; using System.Xml.Linq; - using TestStack.ConventionTests.Conventions; using TestStack.ConventionTests.Internal; public abstract class AbstractProjectData : IConventionData diff --git a/TestStack.ConventionTests/ConventionData/ProjectFile.cs b/TestStack.ConventionTests/ConventionData/ProjectFileItem.cs similarity index 83% rename from TestStack.ConventionTests/ConventionData/ProjectFile.cs rename to TestStack.ConventionTests/ConventionData/ProjectFileItem.cs index 6537606..19f367f 100644 --- a/TestStack.ConventionTests/ConventionData/ProjectFile.cs +++ b/TestStack.ConventionTests/ConventionData/ProjectFileItem.cs @@ -1,6 +1,6 @@ namespace TestStack.ConventionTests.ConventionData { - public class ProjectFile + public class ProjectFileItem { public string FilePath { get; set; } public string ReferenceType { get; set; } diff --git a/TestStack.ConventionTests/ConventionData/ProjectFiles.cs b/TestStack.ConventionTests/ConventionData/ProjectFileItems.cs similarity index 70% rename from TestStack.ConventionTests/ConventionData/ProjectFiles.cs rename to TestStack.ConventionTests/ConventionData/ProjectFileItems.cs index bbaf6a2..4785818 100644 --- a/TestStack.ConventionTests/ConventionData/ProjectFiles.cs +++ b/TestStack.ConventionTests/ConventionData/ProjectFileItems.cs @@ -5,14 +5,17 @@ using System.Xml.Linq; using TestStack.ConventionTests.Internal; - public class ProjectFiles : AbstractProjectData + /// + /// Items/Files in a .*proj project file + /// + public class ProjectFileItems : AbstractProjectData { - public ProjectFiles(Assembly assembly, IProjectProvider projectProvider = null, IProjectLocator projectLocator = null) + public ProjectFileItems(Assembly assembly, IProjectProvider projectProvider = null, IProjectLocator projectLocator = null) : base(assembly, projectProvider, projectLocator) { } - public ProjectFile[] Files + public ProjectFileItem[] Items { get { @@ -23,7 +26,7 @@ public ProjectFile[] Files .Elements(XName.Get("ItemGroup", msbuild)) .Elements() .Select(refElem => - new ProjectFile + new ProjectFileItem { ReferenceType = refElem.Name.LocalName, FilePath = refElem.Attribute("Include").Value diff --git a/TestStack.ConventionTests/ConventionData/TypeExtensions.cs b/TestStack.ConventionTests/ConventionData/TypeExtensions.cs index 93a8ea8..9d22184 100644 --- a/TestStack.ConventionTests/ConventionData/TypeExtensions.cs +++ b/TestStack.ConventionTests/ConventionData/TypeExtensions.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; + using System.Text.RegularExpressions; public static class TypeExtensions { @@ -66,5 +67,10 @@ public static bool ClosesInterface(this Type t, Type openGeneric) { return t.GetClosedInterfacesOf(openGeneric).Any(); } + + internal static string GetSentenceCaseName(this Type type) + { + return Regex.Replace(type.Name, "[a-z][A-Z]", m => m.Value[0] + " " + char.ToLower(m.Value[1])); + } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/ConventionReporterAttribute.cs b/TestStack.ConventionTests/ConventionReporterAttribute.cs new file mode 100644 index 0000000..4466e51 --- /dev/null +++ b/TestStack.ConventionTests/ConventionReporterAttribute.cs @@ -0,0 +1,18 @@ +namespace TestStack.ConventionTests +{ + using System; + using TestStack.ConventionTests.Reporting; + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public class ConventionReporterAttribute : Attribute + { + public ConventionReporterAttribute(Type reporterType) + { + ReporterType = reporterType; + if (!typeof(IResultsProcessor).IsAssignableFrom(reporterType)) + throw new ArgumentException("Reporters must inherit from IResultsProcessor", "reporterType"); + } + + public Type ReporterType { get; private set; } + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs b/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs index 3d6becf..a6ecc11 100644 --- a/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs +++ b/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs @@ -3,7 +3,7 @@ using System.Linq; using TestStack.ConventionTests.ConventionData; - public class FilesAreEmbeddedResources : IConvention + public class FilesAreEmbeddedResources : IConvention { public FilesAreEmbeddedResources(string fileExtension) { @@ -12,11 +12,11 @@ public FilesAreEmbeddedResources(string fileExtension) public string FileExtension { get; private set; } - public void Execute(ProjectFiles data, IConventionResultContext result) + public void Execute(ProjectFileItems data, IConventionResultContext result) { result.Is( string.Format("{0} Files must be embedded resources", FileExtension), - data.Files.Where(s => s.FilePath.EndsWith(FileExtension) && s.ReferenceType != "EmbeddedResource")); + data.Items.Where(s => s.FilePath.EndsWith(FileExtension) && s.ReferenceType != "EmbeddedResource")); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Internal/ConventionContext.cs b/TestStack.ConventionTests/Internal/ConventionContext.cs index 2fdba19..4a13938 100644 --- a/TestStack.ConventionTests/Internal/ConventionContext.cs +++ b/TestStack.ConventionTests/Internal/ConventionContext.cs @@ -11,13 +11,15 @@ public class ConventionContext : IConventionResultContext, IConventionFormatCont readonly string dataDescription; readonly IList formatters; readonly IList processors; + readonly ITestResultProcessor testResultProcessor; readonly IList results = new List(); public ConventionContext(string dataDescription, IList formatters, - IList processors) + IList processors, ITestResultProcessor testResultProcessor) { this.formatters = formatters; this.processors = processors; + this.testResultProcessor = testResultProcessor; this.dataDescription = dataDescription; } @@ -26,24 +28,40 @@ public ConventionResult[] ConventionResults get { return results.ToArray(); } } - ConventionReportFailure IConventionFormatContext.FormatData(object failingData) + string IConventionFormatContext.FormatDataAsHtml(object data) { - var formatter = formatters.FirstOrDefault(f => f.CanFormat(failingData)); + var formatter = GetReportDataFormatterFor(data); + return formatter.FormatHtml(data); + } + + ITestResultProcessor IConventionFormatContext.TestResultProcessor + { + get { return testResultProcessor; } + } + + string IConventionFormatContext.FormatDataAsString(object data) + { + var formatter = GetReportDataFormatterFor(data); + + return formatter.FormatString(data); + } + + IReportDataFormatter GetReportDataFormatterFor(object data) + { + IReportDataFormatter formatter = formatters.FirstOrDefault(f => f.CanFormat(data)); if (formatter == null) { throw new NoDataFormatterFoundException( - failingData.GetType().Name + - " has no formatter, add one with `Convention.Formatters.Add(new MyDataFormatter());`"); + data.GetType().Name + " has no formatter, add one with `Convention.Formatters.Add(new MyDataFormatter());`"); } - - return formatter.Format(failingData); + return formatter; } void IConventionResultContext.Is(string resultTitle, IEnumerable failingData) { // ReSharper disable PossibleMultipleEnumeration results.Add(new ConventionResult( - typeof(TResult), + typeof (TResult), resultTitle, dataDescription, failingData.ToObjectArray())); @@ -54,11 +72,11 @@ void IConventionResultContext.IsSymmetric( string secondSetFailureTitle, IEnumerable secondSetFailureData) { results.Add(new ConventionResult( - typeof(TResult), firstSetFailureTitle, + typeof (TResult), firstSetFailureTitle, dataDescription, firstSetFailureData.ToObjectArray())); results.Add(new ConventionResult( - typeof(TResult), secondSetFailureTitle, + typeof (TResult), secondSetFailureTitle, dataDescription, secondSetFailureData.ToObjectArray())); } @@ -70,8 +88,8 @@ void IConventionResultContext.IsSymmetric( Func isPartOfSecondSet, IEnumerable allData) { - var firstSetFailingData = allData.Where(isPartOfFirstSet).Unless(isPartOfSecondSet); - var secondSetFailingData = allData.Where(isPartOfSecondSet).Unless(isPartOfFirstSet); + IEnumerable firstSetFailingData = allData.Where(isPartOfFirstSet).Unless(isPartOfSecondSet); + IEnumerable secondSetFailingData = allData.Where(isPartOfSecondSet).Unless(isPartOfFirstSet); (this as IConventionResultContext).IsSymmetric( firstSetFailureTitle, firstSetFailingData, @@ -85,7 +103,7 @@ public void Execute(IConvention convention, TDataSourc throw new ConventionSourceInvalidException(String.Format("{0} has no data", data.Description)); convention.Execute(data, this); - foreach (var resultsProcessor in processors) + foreach (IResultsProcessor resultsProcessor in processors) { resultsProcessor.Process(this, ConventionResults); } diff --git a/TestStack.ConventionTests/Internal/ConventionReportFailure.cs b/TestStack.ConventionTests/Internal/ConventionReportFailure.cs deleted file mode 100644 index 199b94c..0000000 --- a/TestStack.ConventionTests/Internal/ConventionReportFailure.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace TestStack.ConventionTests.Internal -{ - public class ConventionReportFailure - { - public string Failure { get; set; } - - public ConventionReportFailure(string failure) - { - Failure = failure; - } - - public override string ToString() - { - return Failure; - } - } -} \ No newline at end of file diff --git a/TestStack.ConventionTests/Internal/ConventionResult.cs b/TestStack.ConventionTests/Internal/ConventionResult.cs index 91c635e..0a833d4 100644 --- a/TestStack.ConventionTests/Internal/ConventionResult.cs +++ b/TestStack.ConventionTests/Internal/ConventionResult.cs @@ -13,7 +13,6 @@ public ConventionResult(Type dataType, string conventionTitle, string dataDescri Data = data; } - public string RecommendedFileExtension { get; private set; } public Type DataType { get; private set; } public string ConventionTitle { get; private set; } public string DataDescription { get; private set; } @@ -24,12 +23,43 @@ public bool HasData get { return Data.Any(); } } - public string FormattedResult { get; private set; } + protected bool Equals(ConventionResult other) + { + return DataType == other.DataType && string.Equals(ConventionTitle, other.ConventionTitle) && string.Equals(DataDescription, other.DataDescription); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((ConventionResult) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (DataType != null ? DataType.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ConventionTitle != null ? ConventionTitle.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (DataDescription != null ? DataDescription.GetHashCode() : 0); + return hashCode; + } + } + + public static bool operator ==(ConventionResult left, ConventionResult right) + { + return Equals(left, right); + } + + public static bool operator !=(ConventionResult left, ConventionResult right) + { + return !Equals(left, right); + } - public void WithFormattedResult(string formattedResult, string recommendedFileExtension = "txt") + public override string ToString() { - FormattedResult = formattedResult; - RecommendedFileExtension = recommendedFileExtension; + return string.Format("DataType: {0}, ConventionTitle: {1}, DataDescription: {2}, HasData: {3}", DataType, ConventionTitle, DataDescription, HasData); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Internal/IConventionFormatContext.cs b/TestStack.ConventionTests/Internal/IConventionFormatContext.cs index e3d024d..7318057 100644 --- a/TestStack.ConventionTests/Internal/IConventionFormatContext.cs +++ b/TestStack.ConventionTests/Internal/IConventionFormatContext.cs @@ -1,7 +1,11 @@ namespace TestStack.ConventionTests.Internal -{ +{ + using TestStack.ConventionTests.Reporting; + public interface IConventionFormatContext - { - ConventionReportFailure FormatData(object failingData); + { + string FormatDataAsString(object data); + string FormatDataAsHtml(object data); + ITestResultProcessor TestResultProcessor { get; } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/AggregatedConventionResultsReporter.cs b/TestStack.ConventionTests/Reporting/AggregatedConventionResultsReporter.cs new file mode 100644 index 0000000..d43cc7e --- /dev/null +++ b/TestStack.ConventionTests/Reporting/AggregatedConventionResultsReporter.cs @@ -0,0 +1,46 @@ +namespace TestStack.ConventionTests.Reporting +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using TestStack.ConventionTests.Internal; + + /// + /// Aggregates all previous results + /// + public abstract class AggregatedConventionResultsReporter : IResultsProcessor + { + static readonly List Reports = new List(); + readonly string output; + + protected AggregatedConventionResultsReporter(string outputFilename) + { + output = Path.Combine(AssemblyDirectory, outputFilename); + } + + static string AssemblyDirectory + { + get + { + // http://stackoverflow.com/questions/52797/c-how-do-i-get-the-path-of-the-assembly-the-code-is-in#answer-283917 + var codeBase = Assembly.GetExecutingAssembly().CodeBase; + var uri = new UriBuilder(codeBase); + var path = Uri.UnescapeDataString(uri.Path); + return Path.GetDirectoryName(path); + } + } + + public IEnumerable AggregatedReports { get { return Reports; } } + + public void Process(IConventionFormatContext context, params ConventionResult[] results) + { + Reports.AddRange(results.Except(Reports)); + var outputContent = Process(context); + File.WriteAllText(output, outputContent); + } + + protected abstract string Process(IConventionFormatContext context); + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/ApproveResultsProcessor.cs b/TestStack.ConventionTests/Reporting/ApproveResultsProcessor.cs index 43d53e8..06c809f 100644 --- a/TestStack.ConventionTests/Reporting/ApproveResultsProcessor.cs +++ b/TestStack.ConventionTests/Reporting/ApproveResultsProcessor.cs @@ -17,7 +17,10 @@ public void Process(IConventionFormatContext context, params ConventionResult[] var result = results[count]; try { - Approvals.Verify(new ConventionTestsApprovalTextWriter(result.FormattedResult, count,result.RecommendedFileExtension)); + //TODO Law of Demeter + var formattedResult = context.TestResultProcessor.Process(context, result); + var recommendedFileExtension = context.TestResultProcessor.RecommendedFileExtension; + Approvals.Verify(new ConventionTestsApprovalTextWriter(formattedResult, count, recommendedFileExtension)); } catch (ApprovalException ex) { diff --git a/TestStack.ConventionTests/Reporting/ConventionReportTextRenderer.cs b/TestStack.ConventionTests/Reporting/ConventionReportTextRenderer.cs index 89197aa..647e67b 100644 --- a/TestStack.ConventionTests/Reporting/ConventionReportTextRenderer.cs +++ b/TestStack.ConventionTests/Reporting/ConventionReportTextRenderer.cs @@ -3,26 +3,21 @@ using System.Text; using TestStack.ConventionTests.Internal; - public class ConventionReportTextRenderer : IResultsProcessor + public class ConventionReportTextRenderer : ITestResultProcessor { - public void Process(IConventionFormatContext context, params ConventionResult[] results) + public string RecommendedFileExtension { get { return "txt"; } } + + public string Process(IConventionFormatContext context, ConventionResult result) { - foreach (var result in results) - { - if (result.FormattedResult != null) - { - continue; - } - var description = new StringBuilder(); - var title = string.Format("'{0}' for '{1}'", result.ConventionTitle, - result.DataDescription); - description.AppendLine(title); - description.AppendLine(string.Empty.PadRight(title.Length, '-')); - description.AppendLine(); + var description = new StringBuilder(); + var title = string.Format("'{0}' for '{1}'", result.ConventionTitle, result.DataDescription); + description.AppendLine(title); + description.AppendLine(string.Empty.PadRight(title.Length, '-')); + description.AppendLine(); - RenderItems(result, description, context); - result.WithFormattedResult(description.ToString()); - } + RenderItems(result, description, context); + + return description.ToString(); } static void RenderItems(ConventionResult resultInfo, StringBuilder stringBuilder, IConventionFormatContext context) @@ -30,7 +25,7 @@ static void RenderItems(ConventionResult resultInfo, StringBuilder stringBuilder foreach (var conventionFailure in resultInfo.Data) { stringBuilder.Append("\t"); - stringBuilder.AppendLine(context.FormatData(conventionFailure).ToString()); + stringBuilder.AppendLine(context.FormatDataAsString(conventionFailure)); } } } diff --git a/TestStack.ConventionTests/Reporting/ConventionReportTraceRenderer.cs b/TestStack.ConventionTests/Reporting/ConventionReportTraceRenderer.cs index 4d5b2fa..4bd776f 100644 --- a/TestStack.ConventionTests/Reporting/ConventionReportTraceRenderer.cs +++ b/TestStack.ConventionTests/Reporting/ConventionReportTraceRenderer.cs @@ -9,7 +9,7 @@ public void Process(IConventionFormatContext context, params ConventionResult[] { foreach (var conventionResult in results) { - Trace.WriteLine(conventionResult.FormattedResult); + Trace.WriteLine(context.TestResultProcessor.Process(context, conventionResult)); } } } diff --git a/TestStack.ConventionTests/Reporting/ConvertibleFormatter.cs b/TestStack.ConventionTests/Reporting/ConvertibleFormatter.cs index 94a71a2..cada564 100644 --- a/TestStack.ConventionTests/Reporting/ConvertibleFormatter.cs +++ b/TestStack.ConventionTests/Reporting/ConvertibleFormatter.cs @@ -3,20 +3,24 @@ namespace TestStack.ConventionTests.Reporting using System; using System.Diagnostics; using System.Globalization; - using TestStack.ConventionTests.Internal; public class ConvertibleFormatter : IReportDataFormatter { - public bool CanFormat(object failingData) + public bool CanFormat(object data) { - return failingData is IConvertible; + return data is IConvertible; } - public ConventionReportFailure Format(object failingData) + public string FormatString(object data) { - var convertible = failingData as IConvertible; + var convertible = data as IConvertible; Debug.Assert(convertible != null, "convertible != null"); - return new ConventionReportFailure(convertible.ToString(CultureInfo.InvariantCulture)); + return convertible.ToString(CultureInfo.InvariantCulture); + } + + public string FormatHtml(object data) + { + return FormatString(data); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/CsvReporter.cs b/TestStack.ConventionTests/Reporting/CsvReporter.cs index 7dc529e..d311c4b 100644 --- a/TestStack.ConventionTests/Reporting/CsvReporter.cs +++ b/TestStack.ConventionTests/Reporting/CsvReporter.cs @@ -3,17 +3,9 @@ using System.Text; using TestStack.ConventionTests.Internal; - public class CsvReporter : IResultsProcessor + public class CsvReporter : ITestResultProcessor { - public void Process(IConventionFormatContext context, params ConventionResult[] results) - { - foreach (var result in results) - { - result.WithFormattedResult(Process(context, result), "csv"); - } - } - - string Process(IConventionFormatContext context, ConventionResult result) + public string Process(IConventionFormatContext context, ConventionResult result) { var formatter = new DefaultFormatter(result.DataType); var message = new StringBuilder(); @@ -24,5 +16,7 @@ string Process(IConventionFormatContext context, ConventionResult result) } return message.ToString(); } + + public string RecommendedFileExtension { get { return "csv"; } } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/DefaultFormatter.cs b/TestStack.ConventionTests/Reporting/DefaultFormatter.cs index f5ea878..fcbb7ab 100644 --- a/TestStack.ConventionTests/Reporting/DefaultFormatter.cs +++ b/TestStack.ConventionTests/Reporting/DefaultFormatter.cs @@ -27,7 +27,7 @@ string Describe(PropertyInfo property) public string[] DesribeItem(object result, IConventionFormatContext context) { - return properties.Select(p => context.FormatData(p.GetValue(result, null)).ToString()).ToArray(); + return properties.Select(p => context.FormatDataAsString(p.GetValue(result, null)).ToString()).ToArray(); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/FallbackFormatter.cs b/TestStack.ConventionTests/Reporting/FallbackFormatter.cs index a7b89e5..beafffa 100644 --- a/TestStack.ConventionTests/Reporting/FallbackFormatter.cs +++ b/TestStack.ConventionTests/Reporting/FallbackFormatter.cs @@ -1,18 +1,22 @@ namespace TestStack.ConventionTests.Reporting { - using TestStack.ConventionTests.Internal; - public class FallbackFormatter : IReportDataFormatter { - public bool CanFormat(object failingData) + public bool CanFormat(object data) { return true; } - public ConventionReportFailure Format(object failingData) + public string FormatString(object data) + { + if (data == null) + return ""; + return data.ToString(); + } + + public string FormatHtml(object data) { - // TODO: for now - return new ConventionReportFailure(failingData.ToString()); + return FormatString(data); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/GroupedByDataTypeConventionResultsReporterBase.cs b/TestStack.ConventionTests/Reporting/GroupedByDataTypeConventionResultsReporterBase.cs new file mode 100644 index 0000000..0b630a8 --- /dev/null +++ b/TestStack.ConventionTests/Reporting/GroupedByDataTypeConventionResultsReporterBase.cs @@ -0,0 +1,20 @@ +namespace TestStack.ConventionTests.Reporting +{ + using System.Collections.Generic; + using System.Linq; + using TestStack.ConventionTests.Internal; + + public abstract class GroupedByDataTypeConventionResultsReporterBase : AggregatedConventionResultsReporter + { + protected GroupedByDataTypeConventionResultsReporterBase(string outputFilename) : base(outputFilename) + { + } + + protected override string Process(IConventionFormatContext context) + { + return Process(context, AggregatedReports.OrderBy(c => c.ConventionTitle).GroupBy(r => r.DataDescription)); + } + + protected abstract string Process(IConventionFormatContext context, IEnumerable> resultsGroupedByDataType); + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/HtmlConventionResultsReporter.cs b/TestStack.ConventionTests/Reporting/HtmlConventionResultsReporter.cs new file mode 100644 index 0000000..2ad93dc --- /dev/null +++ b/TestStack.ConventionTests/Reporting/HtmlConventionResultsReporter.cs @@ -0,0 +1,111 @@ +namespace TestStack.ConventionTests.Reporting +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Web.UI; + using TestStack.ConventionTests.ConventionData; + using TestStack.ConventionTests.Internal; + + public class HtmlConventionResultsReporter : GroupedByDataTypeConventionResultsReporterBase + { + public HtmlConventionResultsReporter() : base("Conventions.htm") + { + } + + protected override string Process(IConventionFormatContext context, IEnumerable> resultsGroupedByDataType) + { + var sb = new StringBuilder(); + var html = new HtmlTextWriter(new StringWriter(sb)); + html.WriteLine(""); + html.RenderBeginTag(HtmlTextWriterTag.Html); // + html.RenderBeginTag(HtmlTextWriterTag.Head); // + + + html.AddAttribute("href", "http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css"); + html.AddAttribute("rel", "stylesheet"); + html.RenderBeginTag(HtmlTextWriterTag.Link); + html.RenderEndTag(); + html.AddAttribute("href", "http://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css"); + html.AddAttribute("rel", "stylesheet"); + + html.Write(@" + "); + + html.RenderBeginTag(HtmlTextWriterTag.Link); + html.RenderEndTag(); + + html.RenderEndTag(); // + html.RenderBeginTag(HtmlTextWriterTag.Body); // + + html.RenderBeginTag(HtmlTextWriterTag.H1); + html.Write("Project Conventions"); + html.RenderEndTag(); + + foreach (var conventionReport in resultsGroupedByDataType) + { + html.RenderBeginTag(HtmlTextWriterTag.Div); + html.AddAttribute("style", "margin-left:20px;border-bottom: 1px solid"); + html.RenderBeginTag(HtmlTextWriterTag.H2); + html.Write("Conventions for '{0}'", conventionReport.Key); + html.RenderEndTag(); + foreach (var conventionResult in conventionReport) + { + var targetId = + conventionResult.ConventionTitle.Replace("'", string.Empty).Replace(" ", string.Empty).Replace(".", string.Empty) + + conventionResult.DataDescription.Replace("'", string.Empty).Replace(" ", string.Empty).Replace(".", string.Empty); + html.AddAttribute("style", "margin-left:20px;"); + html.RenderBeginTag(HtmlTextWriterTag.H4); + html.Write(conventionResult.ConventionTitle); + html.RenderEndTag(); + if (conventionResult.Data.Any()) + { + html.AddAttribute("style", "margin-left:20px;"); + html.RenderBeginTag(HtmlTextWriterTag.Div); + html.AddAttribute("class", "menu-toggle"); + html.AddAttribute("data-toggle", "collapse"); + html.AddAttribute("data-target", "." + targetId); + html.RenderBeginTag(HtmlTextWriterTag.A); + html.AddAttribute("class", "icon-angle-down"); + html.RenderBeginTag(HtmlTextWriterTag.I); + html.RenderEndTag(); + html.Write("With the exception of the following {0}: ", conventionResult.DataType.GetSentenceCaseName()); + html.RenderEndTag(); + html.AddAttribute("class", targetId + " collapse"); + html.AddAttribute("style", "margin-left:20px;"); + html.RenderBeginTag(HtmlTextWriterTag.Div); + html.RenderBeginTag(HtmlTextWriterTag.Ul); + foreach (var o in conventionResult.Data) + { + html.RenderBeginTag(HtmlTextWriterTag.Li); + html.Write(context.FormatDataAsHtml(o)); + html.RenderEndTag(); + } + html.RenderEndTag(); + html.RenderEndTag(); + } + + html.RenderEndTag(); + } + html.RenderEndTag(); + } + + html.AddAttribute("src", "http://code.jquery.com/jquery.js"); + html.RenderBeginTag(HtmlTextWriterTag.Script); + html.RenderEndTag(); + html.AddAttribute("src", "http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js"); + html.RenderBeginTag(HtmlTextWriterTag.Script); + html.RenderEndTag(); + html.RenderEndTag(); // + html.RenderEndTag(); // + html.Flush(); + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/HtmlReportRenderer.cs b/TestStack.ConventionTests/Reporting/HtmlReportRenderer.cs deleted file mode 100644 index 23dbaca..0000000 --- a/TestStack.ConventionTests/Reporting/HtmlReportRenderer.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace TestStack.ConventionTests.Reporting -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Web.UI; - using TestStack.ConventionTests.Internal; - - public class HtmlReportRenderer : IResultsProcessor - { - readonly string file; - static readonly List Reports = new List(); - public static IEnumerable ConventionReports { get { return Reports; } } - - public HtmlReportRenderer(string assemblyDirectory) - { - file = Path.Combine(assemblyDirectory, "Conventions.htm"); - } - - public void Process(IConventionFormatContext context, params ConventionResult[] results) - { - Reports.AddRange(results); - var sb = new StringBuilder(); - var html = new HtmlTextWriter(new StringWriter(sb)); - html.WriteLine(""); - html.RenderBeginTag(HtmlTextWriterTag.Html); // - html.RenderBeginTag(HtmlTextWriterTag.Head); // - html.RenderEndTag(); // - html.WriteLine(); - html.RenderBeginTag(HtmlTextWriterTag.Body); // - - html.RenderBeginTag(HtmlTextWriterTag.H1); - html.Write("Project Conventions"); - html.RenderEndTag(); - - foreach (var conventionReport in Reports) - { - html.RenderBeginTag(HtmlTextWriterTag.P); - html.RenderBeginTag(HtmlTextWriterTag.Div); - html.RenderBeginTag(HtmlTextWriterTag.Strong); - html.RenderEndTag(); - var title = String.Format("{0} for {1}", conventionReport.ConventionTitle, conventionReport.DataDescription); - html.Write(title); - - html.RenderBeginTag(HtmlTextWriterTag.Ul); - - - foreach (var conventionFailure in conventionReport.Data) - { - html.RenderBeginTag(HtmlTextWriterTag.Li); - html.Write(context.FormatData(conventionFailure).ToString()); - html.RenderEndTag(); - } - - html.RenderEndTag(); - html.RenderEndTag(); - html.RenderEndTag(); - } - - html.RenderEndTag(); // - html.RenderEndTag(); // - html.Flush(); - - File.WriteAllText(file, sb.ToString()); - } - } -} \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/IReportDataFormatter.cs b/TestStack.ConventionTests/Reporting/IReportDataFormatter.cs index 1cea26c..a9be8a0 100644 --- a/TestStack.ConventionTests/Reporting/IReportDataFormatter.cs +++ b/TestStack.ConventionTests/Reporting/IReportDataFormatter.cs @@ -1,10 +1,9 @@ namespace TestStack.ConventionTests.Reporting { - using TestStack.ConventionTests.Internal; - public interface IReportDataFormatter { - bool CanFormat(object failingData); - ConventionReportFailure Format(object failingData); + bool CanFormat(object data); + string FormatString(object data); + string FormatHtml(object data); } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/ITestResultProcessor.cs b/TestStack.ConventionTests/Reporting/ITestResultProcessor.cs new file mode 100644 index 0000000..9ba5206 --- /dev/null +++ b/TestStack.ConventionTests/Reporting/ITestResultProcessor.cs @@ -0,0 +1,10 @@ +namespace TestStack.ConventionTests.Reporting +{ + using TestStack.ConventionTests.Internal; + + public interface ITestResultProcessor + { + string Process(IConventionFormatContext context, ConventionResult result); + string RecommendedFileExtension { get; } + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/MarkdownConventionResultsReporter.cs b/TestStack.ConventionTests/Reporting/MarkdownConventionResultsReporter.cs new file mode 100644 index 0000000..15697c6 --- /dev/null +++ b/TestStack.ConventionTests/Reporting/MarkdownConventionResultsReporter.cs @@ -0,0 +1,33 @@ +namespace TestStack.ConventionTests.Reporting +{ + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TestStack.ConventionTests.Internal; + + public class MarkdownConventionResultsReporter : GroupedByDataTypeConventionResultsReporterBase + { + public MarkdownConventionResultsReporter() : base("Conventions.md") + { + } + + protected override string Process(IConventionFormatContext context, IEnumerable> resultsGroupedByDataType) + { + var sb = new StringBuilder(); + sb.AppendLine("# Project Conventions"); + foreach (var conventionReport in resultsGroupedByDataType) + { + sb.Append("## "); + sb.AppendLine(conventionReport.Key); + + foreach (var conventionResult in conventionReport) + { + sb.Append(" - "); + sb.AppendLine(conventionResult.ConventionTitle); + } + } + + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/MethodInfoDataFormatter.cs b/TestStack.ConventionTests/Reporting/MethodInfoDataFormatter.cs index 12de3e3..1a7f13a 100644 --- a/TestStack.ConventionTests/Reporting/MethodInfoDataFormatter.cs +++ b/TestStack.ConventionTests/Reporting/MethodInfoDataFormatter.cs @@ -1,22 +1,123 @@ -namespace TestStack.ConventionTests.Reporting +namespace TestStack.ConventionTests.Reporting { - using System.ComponentModel; + using System; using System.Reflection; - using ApprovalTests.Namers; - using TestStack.ConventionTests.Internal; - - public class MethodInfoDataFormatter : IReportDataFormatter - { - public bool CanFormat(object failingData) - { - return failingData is MethodInfo; - } - - public ConventionReportFailure Format(object failingData) - { - var methodInfo = (MethodInfo)failingData; - - return new ConventionReportFailure(methodInfo.DeclaringType + "." + methodInfo.Name); - } - } + using System.Text; + + public class MethodInfoDataFormatter : IReportDataFormatter + { + public bool CanFormat(object data) + { + return data is MethodInfo; + } + + public string FormatString(object data) + { + var methodInfo = (MethodInfo)data; + + return methodInfo.DeclaringType + "." + methodInfo.Name; + } + + public string FormatHtml(object data) + { + const string keywordFormat = "{0}"; + const string typeFormat = "{0}"; + + var methodInfo = (MethodInfo)data; + var sb = new StringBuilder(); + var declaringType = methodInfo.DeclaringType; + sb.AppendFormat("{0} {1}.{2} {{ ", + string.Format(keywordFormat, "class"), + declaringType.Namespace, + string.Format(typeFormat, declaringType.Name)); + + AppendAccess(methodInfo, sb, keywordFormat); + + if (methodInfo.IsVirtual) + { + sb.AppendFormat(keywordFormat, "virtual"); + sb.Append(" "); + } + + AppendMethodName(methodInfo, sb); + sb.Append(" (...)"); + sb.Append("}}"); + + return sb.ToString(); + } + + void AppendMethodName(MethodInfo methodInfo, StringBuilder sb) + { + sb.Append(methodInfo.Name); + bool firstParam = true; + if (methodInfo.IsGenericMethod) + { + sb.Append("<"); + foreach (var g in methodInfo.GetGenericArguments()) + { + if (firstParam) + firstParam = false; + else + sb.Append(", "); + sb.Append(TypeName(g)); + } + sb.Append(">"); + } + } + + void AppendAccess(MethodInfo method, StringBuilder sb, string format = "{0}") + { + if (method.IsPublic) + sb.AppendFormat(format, "public "); + else if (method.IsPrivate) + sb.AppendFormat("private "); + else if (method.IsAssembly) + sb.AppendFormat("internal "); + if (method.IsFamily) + sb.AppendFormat("protected "); + if (method.IsStatic) + sb.AppendFormat("static "); + } + + static string TypeName(Type type) + { + var nullableType = Nullable.GetUnderlyingType(type); + if (nullableType != null) + return nullableType.Name + "?"; + + if (!type.IsGenericType) + switch (type.Name) + { + case "String": + return "string"; + case "Int32": + return "int"; + case "Decimal": + return "decimal"; + case "Object": + return "object"; + case "Void": + return "void"; + default: + { + return string.IsNullOrWhiteSpace(type.FullName) ? type.Name : type.FullName; + } + } + + var sb = new StringBuilder(type.Name.Substring(0, + type.Name.IndexOf('`')) + ); + sb.Append('<'); + var first = true; + foreach (var t in type.GetGenericArguments()) + { + if (!first) + sb.Append(','); + sb.Append(TypeName(t)); + first = false; + } + sb.Append('>'); + return sb.ToString(); + } + } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/ProjectFileFormatter.cs b/TestStack.ConventionTests/Reporting/ProjectFileFormatter.cs index 61667b2..cf9d877 100644 --- a/TestStack.ConventionTests/Reporting/ProjectFileFormatter.cs +++ b/TestStack.ConventionTests/Reporting/ProjectFileFormatter.cs @@ -1,18 +1,22 @@ namespace TestStack.ConventionTests.Reporting { - using TestStack.ConventionTests.ConventionData; - using TestStack.ConventionTests.Internal; - + using TestStack.ConventionTests.ConventionData; + public class ProjectFileFormatter : IReportDataFormatter { - public bool CanFormat(object failingData) + public bool CanFormat(object data) { - return failingData is ProjectFile; + return data is ProjectFileItem; } - public ConventionReportFailure Format(object failingData) + public string FormatString(object data) { - return new ConventionReportFailure(((ProjectFile)failingData).FilePath); - } + return ((ProjectFileItem)data).FilePath; + } + + public string FormatHtml(object data) + { + return ((ProjectFileItem) data).FilePath; + } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/ProjectReferenceFormatter.cs b/TestStack.ConventionTests/Reporting/ProjectReferenceFormatter.cs index 1481849..b41def6 100644 --- a/TestStack.ConventionTests/Reporting/ProjectReferenceFormatter.cs +++ b/TestStack.ConventionTests/Reporting/ProjectReferenceFormatter.cs @@ -1,18 +1,22 @@ namespace TestStack.ConventionTests.Reporting { - using TestStack.ConventionTests.ConventionData; - using TestStack.ConventionTests.Internal; - + using TestStack.ConventionTests.ConventionData; + public class ProjectReferenceFormatter : IReportDataFormatter { - public bool CanFormat(object failingData) + public bool CanFormat(object data) { - return failingData is ProjectReference; + return data is ProjectReference; } - public ConventionReportFailure Format(object failingData) + public string FormatString(object data) { - return new ConventionReportFailure(((ProjectReference)failingData).ReferencedPath); - } + return ((ProjectReference)data).ReferencedPath; + } + + public string FormatHtml(object data) + { + return ((ProjectReference)data).ReferencedPath; + } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/StringDataFormatter.cs b/TestStack.ConventionTests/Reporting/StringDataFormatter.cs index ab70c55..f477cfb 100644 --- a/TestStack.ConventionTests/Reporting/StringDataFormatter.cs +++ b/TestStack.ConventionTests/Reporting/StringDataFormatter.cs @@ -1,17 +1,20 @@ namespace TestStack.ConventionTests.Reporting -{ - using TestStack.ConventionTests.Internal; - +{ public class StringDataFormatter : IReportDataFormatter { - public bool CanFormat(object failingData) + public bool CanFormat(object data) { - return failingData is string; + return data is string; } - public ConventionReportFailure Format(object failingData) + public string FormatString(object data) { - return new ConventionReportFailure((string)failingData); - } + return (string)data; + } + + public string FormatHtml(object data) + { + return (string)data; + } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Reporting/ThrowOnFailureResultsProcessor.cs b/TestStack.ConventionTests/Reporting/ThrowOnFailureResultsProcessor.cs index f8d565a..2bae6cd 100644 --- a/TestStack.ConventionTests/Reporting/ThrowOnFailureResultsProcessor.cs +++ b/TestStack.ConventionTests/Reporting/ThrowOnFailureResultsProcessor.cs @@ -8,7 +8,7 @@ public class ThrowOnFailureResultsProcessor : IResultsProcessor { public void Process(IConventionFormatContext context, params ConventionResult[] results) { - var invalidResults = results.Where(r => r.HasData).Select(r => r.FormattedResult).ToArray(); + var invalidResults = results.Where(r => r.HasData).Select(r => context.TestResultProcessor.Process(context, r)).ToArray(); if (invalidResults.None()) { return; diff --git a/TestStack.ConventionTests/Reporting/TypeDataFormatter.cs b/TestStack.ConventionTests/Reporting/TypeDataFormatter.cs index d1b2cd2..49a3c76 100644 --- a/TestStack.ConventionTests/Reporting/TypeDataFormatter.cs +++ b/TestStack.ConventionTests/Reporting/TypeDataFormatter.cs @@ -1,18 +1,26 @@ namespace TestStack.ConventionTests.Reporting { - using System; - using TestStack.ConventionTests.Internal; - + using System; + public class TypeDataFormatter : IReportDataFormatter { - public bool CanFormat(object failingData) + public bool CanFormat(object data) { - return failingData is Type; + return data is Type; } - public ConventionReportFailure Format(object failingData) + public string FormatString(object data) { - return new ConventionReportFailure(((Type)failingData).FullName); - } + return ((Type)data).FullName; + } + + public string FormatHtml(object data) + { + var type = ((Type)data); + var ns = type.Namespace; + if (ns == null) + return type.Name; + return string.Format("{0}.{1}", ns, type.Name); + } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/TestStack.ConventionTests.csproj b/TestStack.ConventionTests/TestStack.ConventionTests.csproj index 0f12072..2d6a1b3 100644 --- a/TestStack.ConventionTests/TestStack.ConventionTests.csproj +++ b/TestStack.ConventionTests/TestStack.ConventionTests.csproj @@ -61,28 +61,32 @@ + - + - + + + + - - + +