From 7774e4ef2242b6aef889aaa4edc4a31a6cfe6c60 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozmic Date: Sat, 10 Aug 2013 19:42:39 +1000 Subject: [PATCH 1/6] brought back ConventionResult --- .../Internal/ConventionResult.cs | 162 ++++++++++++++++++ .../TestStack.ConventionTests.csproj | 1 + 2 files changed, 163 insertions(+) create mode 100644 TestStack.ConventionTests/Internal/ConventionResult.cs diff --git a/TestStack.ConventionTests/Internal/ConventionResult.cs b/TestStack.ConventionTests/Internal/ConventionResult.cs new file mode 100644 index 0000000..fcfefd1 --- /dev/null +++ b/TestStack.ConventionTests/Internal/ConventionResult.cs @@ -0,0 +1,162 @@ +namespace TestStack.ConventionTests.Internal +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Text; + + public class ConventionResult + { + ConventionResult() + { + } + + public string Message { get; private set; } + + public bool Failed + { + get { return !string.IsNullOrEmpty(Message); } + } + + public static ConventionResult For( + IEnumerable items, + string header) + { + var array = items.ToArray(); + var result = new ConventionResult(); + if (array.None()) + { + return result; + } + // ROUGHSKETCH: We split the job between formatter and reporter. Formatter provides the data, reporter provides the structure + var formatter = new DefaultFormatter(typeof (TResult)); + var reporter = new CsvReporter(); + return new ConventionResult + { + Message = reporter.Build(array, header, formatter) + }; + } + + public static ConventionResult For( + IEnumerable items, + string header, + Action itemDescriptor) + { + var array = items.ToArray(); + var result = new ConventionResult(); + if (array.None()) + { + return result; + } + + // NOTE: we might possibly want to abstract the StringBuilder to have more high level construct that would allow us to plug rich reports here... + var message = new StringBuilder(header); + message.AppendLine(); + message.AppendLine(string.Empty.PadRight(header.Length, '-')); + message.AppendLine(); + Array.ForEach(array, r => itemDescriptor(r, message)); + result.Message = message.ToString(); + return result; + } + + public static ConventionResult For( + IEnumerable items, + string header, + Func itemDescriptor) + { + return For(items, header, (item, message) => message.AppendLine(itemDescriptor(item))); + } + + public static ConventionResult ForSymmetric( + string firstHeader, TResult[] firstResults, + string secondHeader, TResult[] secondResults, + Action itemDescriptor) + { + var firstArray = firstResults.ToArray(); + var secondArray = secondResults.ToArray(); + var result = new ConventionResult(); + if (firstArray.None() && secondArray.None()) + { + return result; + } + + var message = new StringBuilder(); + if (firstArray.Any()) + { + message.AppendLine(firstHeader); + message.AppendLine(string.Empty.PadRight(firstHeader.Length, '-')); + message.AppendLine(); + Array.ForEach(firstArray, r => itemDescriptor(r, message)); + } + if (secondArray.Any()) + { + if (firstArray.Any()) + { + message.AppendLine(); + message.AppendLine(); + } + message.AppendLine(secondHeader); + message.AppendLine(string.Empty.PadRight(secondHeader.Length, '-')); + message.AppendLine(); + Array.ForEach(secondArray, r => itemDescriptor(r, message)); + } + result.Message = message.ToString(); + return result; + } + + public static ConventionResult ForSymmetric( + string firstHeader, TResult[] firstResults, + string secondHeader, TResult[] secondResults, + Func itemDescriptor) + { + return ForSymmetric( + firstHeader, firstResults, secondHeader, + secondResults, + (item, message) => message.AppendLine(itemDescriptor(item))); + } + } + + public class CsvReporter + { + public string Build(IEnumerable results, string header, DefaultFormatter formatter) + { + var message = new StringBuilder(); + message.AppendLine(string.Join(",", formatter.DesribeType())); + foreach (var result in results) + { + message.AppendLine(string.Join(",", formatter.DesribeItem(result))); + } + return message.ToString(); + } + } + + public class DefaultFormatter + { + readonly Type type; + readonly PropertyInfo[] properties; + + public DefaultFormatter(Type type) + { + this.type = type; + properties = type.GetProperties(); + } + + // TODO: this is a very crappy name for a method + public string[] DesribeType() + { + return properties.Select(Describe).ToArray(); + } + + string Describe(PropertyInfo property) + { + return property.Name.Replace('_', ' '); + } + + public string[] DesribeItem(object result) + { + return properties.Select(p => p.GetValue(result, null).ToString()).ToArray(); + } + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests/TestStack.ConventionTests.csproj b/TestStack.ConventionTests/TestStack.ConventionTests.csproj index 8235b14..81d3399 100644 --- a/TestStack.ConventionTests/TestStack.ConventionTests.csproj +++ b/TestStack.ConventionTests/TestStack.ConventionTests.csproj @@ -62,6 +62,7 @@ + From d5f4e0ddbcc08efcda9102ec9bd5311d4c789d65 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozmic Date: Sun, 11 Aug 2013 14:04:13 +1000 Subject: [PATCH 2/6] using ConventionResult again --- .../ConventionAssertionClassTests.cs | 4 +- TestStack.ConventionTests/Convention.cs | 64 +--- .../AllClassesHaveDefaultConstructor.cs | 10 +- .../Conventions/AllMethodsAreVirtual.cs | 6 +- .../ClassTypeHasSpecificNamespace.cs | 8 +- .../Conventions/FilesAreEmbeddedResources.cs | 17 +- .../Conventions/ISymmetricConvention.cs | 12 - ...NotReferenceDllsFromBinOrObjDirectories.cs | 15 +- TestStack.ConventionTests/IConvention.cs | 4 +- .../Internal/ConventionResult.cs | 313 +++++++++--------- .../Internal/Executor.cs | 21 +- .../TestStack.ConventionTests.csproj | 1 - 12 files changed, 205 insertions(+), 270 deletions(-) delete mode 100644 TestStack.ConventionTests/Conventions/ISymmetricConvention.cs diff --git a/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs b/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs index 6fd3730..1bdbf4c 100644 --- a/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs +++ b/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs @@ -35,9 +35,9 @@ public ConventionReportFailure Format(string failingData) public class FailingConvention : IConvention { public string ConventionTitle { get { return "Header"; } } - public IEnumerable GetFailingData(FakeData data) + public ConventionResult Execute(FakeData data) { - return new[] { "Different" }; + return ConventionResult.For(new[] {"Different"}); } } } diff --git a/TestStack.ConventionTests/Convention.cs b/TestStack.ConventionTests/Convention.cs index 606d671..0060787 100644 --- a/TestStack.ConventionTests/Convention.cs +++ b/TestStack.ConventionTests/Convention.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.IO; - using System.Linq; using System.Reflection; using ApprovalTests; using ApprovalTests.Core.Exceptions; @@ -42,7 +41,12 @@ public static void Is(IConvention convention, TDataSou { try { - var conventionResult = Executor.GetConventionReport(convention.ConventionTitle, convention.GetFailingData(data).ToArray(), data); + // we want to run that first so that we don't even bother running the convention if theere's no data + // conveniton author can assume that data is available. That will simplify the conventions + if (!data.HasData) + throw new ConventionSourceInvalidException(string.Format("{0} has no data", data.Description)); + + var conventionResult = Executor.GetConventionReport(convention.ConventionTitle, convention.Execute(data), data); Reports.Add(conventionResult); new ConventionReportTraceRenderer().Render(conventionResult); @@ -57,7 +61,7 @@ public static void Is(IConvention convention, TDataSou public static void IsWithApprovedExeptions(IConvention convention, TDataSource data) where TDataSource : IConventionData { - var conventionResult = Executor.GetConventionReportWithApprovedExeptions(convention.ConventionTitle, convention.GetFailingData(data).ToArray(), data); + var conventionResult = Executor.GetConventionReportWithApprovedExeptions(convention.ConventionTitle, convention.Execute(data), data); Reports.Add(conventionResult); try @@ -78,60 +82,6 @@ public static void IsWithApprovedExeptions(IConvention } } - public static void Is(ISymmetricConvention convention, TDataSource data) - where TDataSource : IConventionData - { - Is(convention, data, new ConventionResultExceptionReporter()); - } - - public static void Is(ISymmetricConvention convention, TDataSource data, IConventionReportRenderer reporter) - where TDataSource : IConventionData - { - try - { - var conventionResult = Executor.GetConventionReport(convention.ConventionTitle, convention.GetFailingData(data).ToArray(), data); - var inverseConventionResult = Executor.GetConventionReport(convention.InverseTitle, convention.GetFailingInverseData(data).ToArray(), data); - - Reports.Add(conventionResult); - Reports.Add(inverseConventionResult); - - new ConventionReportTraceRenderer().Render(conventionResult, inverseConventionResult); - reporter.Render(conventionResult, inverseConventionResult); - } - finally - { - HtmlRenderer.Render(Reports.ToArray()); - } - } - - public static void IsWithApprovedExeptions(ISymmetricConvention convention, TDataSource data) - where TDataSource : IConventionData - { - var conventionResult = Executor.GetConventionReportWithApprovedExeptions(convention.ConventionTitle, convention.GetFailingData(data).ToArray(), data); - var inverseConventionResult = Executor.GetConventionReportWithApprovedExeptions(convention.InverseTitle, convention.GetFailingInverseData(data).ToArray(), data); - Reports.Add(conventionResult); - Reports.Add(inverseConventionResult); - - try - { - //Render both, with approved exceptions included - var conventionReportTextRenderer = new ConventionReportTextRenderer(); - conventionReportTextRenderer.Render(conventionResult, inverseConventionResult); - Approvals.Verify(conventionReportTextRenderer.Output); - - // Trace on success - new ConventionReportTraceRenderer().Render(conventionResult, inverseConventionResult); - } - catch (ApprovalException ex) - { - throw new ConventionFailedException("Approved exceptions for convention differs\r\n\r\n" + ex.Message, ex); - } - finally - { - HtmlRenderer.Render(Reports.ToArray()); - } - } - // http://stackoverflow.com/questions/52797/c-how-do-i-get-the-path-of-the-assembly-the-code-is-in#answer-283917 static string AssemblyDirectory { diff --git a/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs b/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs index 63ee4f8..2493a71 100644 --- a/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs +++ b/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs @@ -1,17 +1,19 @@ namespace TestStack.ConventionTests.Conventions { - using System.Collections.Generic; using System.Linq; using TestStack.ConventionTests.ConventionData; using TestStack.ConventionTests.Internal; public class AllClassesHaveDefaultConstructor : IConvention { - public string ConventionTitle { get { return "Types must have a default constructor"; } } + public string ConventionTitle + { + get { return "Types must have a default constructor"; } + } - public IEnumerable GetFailingData(Types data) + public ConventionResult Execute(Types data) { - return data.TypesToVerify.Where(t => t.HasDefaultConstructor() == false); + return ConventionResult.For(data.TypesToVerify.Where(t => t.HasDefaultConstructor() == false)); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs b/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs index dcf659e..6f9604e 100644 --- a/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs +++ b/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs @@ -1,8 +1,6 @@ namespace TestStack.ConventionTests.Conventions { - using System.Collections.Generic; using System.Linq; - using System.Reflection; using TestStack.ConventionTests.ConventionData; using TestStack.ConventionTests.Internal; @@ -10,9 +8,9 @@ public class AllMethodsAreVirtual : IConvention { public string ConventionTitle { get { return "Methods must be virtual"; } } - public IEnumerable GetFailingData(Types data) + public ConventionResult Execute(Types data) { - return data.TypesToVerify.SelectMany(t => t.NonVirtualMethods()); + return ConventionResult.For(data.TypesToVerify.SelectMany(t => t.NonVirtualMethods())); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs b/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs index d109d37..72b51ca 100644 --- a/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs +++ b/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using TestStack.ConventionTests.ConventionData; + using TestStack.ConventionTests.Internal; /// /// This convention allows you to enforce a particular type of class is under a namespace, for instance. @@ -14,7 +15,7 @@ /// /// This is a Symmetric convention, and will verify all of a Class Type lives in the namespace, but also that only that class type is in that namespace /// - public class ClassTypeHasSpecificNamespace : ISymmetricConvention + public class ClassTypeHasSpecificNamespace:IConvention { readonly Func classIsApplicable; readonly string namespaceToCheck; @@ -41,6 +42,11 @@ public string ConventionTitle } } + public ConventionResult Execute(Types data) + { + throw new NotImplementedException(); + } + public string InverseTitle { get diff --git a/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs b/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs index 88fc915..6b1ec16 100644 --- a/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs +++ b/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs @@ -1,8 +1,8 @@ namespace TestStack.ConventionTests.Conventions { - using System.Collections.Generic; using System.Linq; using TestStack.ConventionTests.ConventionData; + using TestStack.ConventionTests.Internal; public class FilesAreEmbeddedResources : IConvention { @@ -11,19 +11,18 @@ public FilesAreEmbeddedResources(string fileExtension) FileExtension = fileExtension; } + public string FileExtension { get; set; } + public string ConventionTitle { - get - { - return string.Format("{0} Files must be embedded resources", FileExtension); - } + get { return string.Format("{0} Files must be embedded resources", FileExtension); } } - public string FileExtension { get; set; } - - public IEnumerable GetFailingData(ProjectFiles data) + public ConventionResult Execute(ProjectFiles data) { - return data.Files.Where(s => s.FilePath.EndsWith(FileExtension) && s.ReferenceType != "EmbeddedResource"); + return + ConventionResult.For( + data.Files.Where(s => s.FilePath.EndsWith(FileExtension) && s.ReferenceType != "EmbeddedResource")); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/ISymmetricConvention.cs b/TestStack.ConventionTests/Conventions/ISymmetricConvention.cs deleted file mode 100644 index e7454bf..0000000 --- a/TestStack.ConventionTests/Conventions/ISymmetricConvention.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace TestStack.ConventionTests.Conventions -{ - using System.Collections.Generic; - - public interface ISymmetricConvention where T : IConventionData - { - string ConventionTitle { get; } - string InverseTitle { get; } - IEnumerable GetFailingData(T data); - IEnumerable GetFailingInverseData(T data); - } -} \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs b/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs index 04d9fdb..d0bdff4 100644 --- a/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs +++ b/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs @@ -1,24 +1,27 @@ namespace TestStack.ConventionTests.Conventions { - using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using TestStack.ConventionTests.ConventionData; + using TestStack.ConventionTests.Internal; public class ProjectDoesNotReferenceDllsFromBinOrObjDirectories : IConvention { const string AssemblyReferencingObjRegex = @"^(?.*?(obj|bin).*?)$"; - static bool IsBinOrObjReference(ProjectReference reference) + public string ConventionTitle { - return Regex.IsMatch(reference.ReferencedPath, AssemblyReferencingObjRegex, RegexOptions.IgnoreCase); + get { return "Project must not reference dlls from bin or obj directories"; } } - public string ConventionTitle { get { return "Project must not reference dlls from bin or obj directories"; } } + public ConventionResult Execute(ProjectReferences data) + { + return ConventionResult.For(data.References.Where(IsBinOrObjReference)); + } - public IEnumerable GetFailingData(ProjectReferences data) + static bool IsBinOrObjReference(ProjectReference reference) { - return data.References.Where(IsBinOrObjReference); + return Regex.IsMatch(reference.ReferencedPath, AssemblyReferencingObjRegex, RegexOptions.IgnoreCase); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/IConvention.cs b/TestStack.ConventionTests/IConvention.cs index 468fdf6..40b8399 100644 --- a/TestStack.ConventionTests/IConvention.cs +++ b/TestStack.ConventionTests/IConvention.cs @@ -1,10 +1,10 @@ namespace TestStack.ConventionTests { - using System.Collections.Generic; + using TestStack.ConventionTests.Internal; public interface IConvention where T : IConventionData { string ConventionTitle { get; } - IEnumerable GetFailingData(T data); + ConventionResult Execute(T data); } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Internal/ConventionResult.cs b/TestStack.ConventionTests/Internal/ConventionResult.cs index fcfefd1..e9a2fc1 100644 --- a/TestStack.ConventionTests/Internal/ConventionResult.cs +++ b/TestStack.ConventionTests/Internal/ConventionResult.cs @@ -1,162 +1,153 @@ -namespace TestStack.ConventionTests.Internal -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using System.Text; - - public class ConventionResult - { - ConventionResult() - { - } - - public string Message { get; private set; } - - public bool Failed - { - get { return !string.IsNullOrEmpty(Message); } - } - - public static ConventionResult For( - IEnumerable items, - string header) - { - var array = items.ToArray(); - var result = new ConventionResult(); - if (array.None()) - { - return result; - } - // ROUGHSKETCH: We split the job between formatter and reporter. Formatter provides the data, reporter provides the structure - var formatter = new DefaultFormatter(typeof (TResult)); - var reporter = new CsvReporter(); - return new ConventionResult - { - Message = reporter.Build(array, header, formatter) - }; - } - - public static ConventionResult For( - IEnumerable items, - string header, - Action itemDescriptor) - { - var array = items.ToArray(); - var result = new ConventionResult(); - if (array.None()) - { - return result; - } - - // NOTE: we might possibly want to abstract the StringBuilder to have more high level construct that would allow us to plug rich reports here... - var message = new StringBuilder(header); - message.AppendLine(); - message.AppendLine(string.Empty.PadRight(header.Length, '-')); - message.AppendLine(); - Array.ForEach(array, r => itemDescriptor(r, message)); - result.Message = message.ToString(); - return result; - } - - public static ConventionResult For( - IEnumerable items, - string header, - Func itemDescriptor) - { - return For(items, header, (item, message) => message.AppendLine(itemDescriptor(item))); - } - - public static ConventionResult ForSymmetric( - string firstHeader, TResult[] firstResults, - string secondHeader, TResult[] secondResults, - Action itemDescriptor) - { - var firstArray = firstResults.ToArray(); - var secondArray = secondResults.ToArray(); - var result = new ConventionResult(); - if (firstArray.None() && secondArray.None()) - { - return result; - } - - var message = new StringBuilder(); - if (firstArray.Any()) - { - message.AppendLine(firstHeader); - message.AppendLine(string.Empty.PadRight(firstHeader.Length, '-')); - message.AppendLine(); - Array.ForEach(firstArray, r => itemDescriptor(r, message)); - } - if (secondArray.Any()) - { - if (firstArray.Any()) - { - message.AppendLine(); - message.AppendLine(); - } - message.AppendLine(secondHeader); - message.AppendLine(string.Empty.PadRight(secondHeader.Length, '-')); - message.AppendLine(); - Array.ForEach(secondArray, r => itemDescriptor(r, message)); - } - result.Message = message.ToString(); - return result; - } - - public static ConventionResult ForSymmetric( - string firstHeader, TResult[] firstResults, - string secondHeader, TResult[] secondResults, - Func itemDescriptor) - { - return ForSymmetric( - firstHeader, firstResults, secondHeader, - secondResults, - (item, message) => message.AppendLine(itemDescriptor(item))); - } - } - - public class CsvReporter - { - public string Build(IEnumerable results, string header, DefaultFormatter formatter) - { - var message = new StringBuilder(); - message.AppendLine(string.Join(",", formatter.DesribeType())); - foreach (var result in results) - { - message.AppendLine(string.Join(",", formatter.DesribeItem(result))); - } - return message.ToString(); - } - } - - public class DefaultFormatter - { - readonly Type type; - readonly PropertyInfo[] properties; - - public DefaultFormatter(Type type) - { - this.type = type; - properties = type.GetProperties(); - } - - // TODO: this is a very crappy name for a method - public string[] DesribeType() - { - return properties.Select(Describe).ToArray(); - } - - string Describe(PropertyInfo property) - { - return property.Name.Replace('_', ' '); - } - - public string[] DesribeItem(object result) - { - return properties.Select(p => p.GetValue(result, null).ToString()).ToArray(); - } - } +namespace TestStack.ConventionTests.Internal +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Text; + + public class ConventionResult + { + ConventionResult() + { + } + + public string Message { get; private set; } + + public bool Failed + { + get { return !string.IsNullOrEmpty(Message); } + } + + public static ConventionResult For(IEnumerable items) + { + return new ConventionResult + { + Items = items.Select(item => (object) item).ToList(), + }; + } + + public IEnumerable Items { get; set; } + + public static ConventionResult For( + IEnumerable items, + string header, + Action itemDescriptor) + { + var array = items.ToArray(); + var result = new ConventionResult(); + if (array.None()) + { + return result; + } + + // NOTE: we might possibly want to abstract the StringBuilder to have more high level construct that would allow us to plug rich reports here... + var message = new StringBuilder(header); + message.AppendLine(); + message.AppendLine(string.Empty.PadRight(header.Length, '-')); + message.AppendLine(); + Array.ForEach(array, r => itemDescriptor(r, message)); + result.Message = message.ToString(); + return result; + } + + public static ConventionResult For( + IEnumerable items, + string header, + Func itemDescriptor) + { + return For(items, header, (item, message) => message.AppendLine(itemDescriptor(item))); + } + + public static ConventionResult ForSymmetric( + string firstHeader, TResult[] firstResults, + string secondHeader, TResult[] secondResults, + Action itemDescriptor) + { + var firstArray = firstResults.ToArray(); + var secondArray = secondResults.ToArray(); + var result = new ConventionResult(); + if (firstArray.None() && secondArray.None()) + { + return result; + } + + var message = new StringBuilder(); + if (firstArray.Any()) + { + message.AppendLine(firstHeader); + message.AppendLine(string.Empty.PadRight(firstHeader.Length, '-')); + message.AppendLine(); + Array.ForEach(firstArray, r => itemDescriptor(r, message)); + } + if (secondArray.Any()) + { + if (firstArray.Any()) + { + message.AppendLine(); + message.AppendLine(); + } + message.AppendLine(secondHeader); + message.AppendLine(string.Empty.PadRight(secondHeader.Length, '-')); + message.AppendLine(); + Array.ForEach(secondArray, r => itemDescriptor(r, message)); + } + result.Message = message.ToString(); + return result; + } + + public static ConventionResult ForSymmetric( + string firstHeader, TResult[] firstResults, + string secondHeader, TResult[] secondResults, + Func itemDescriptor) + { + return ForSymmetric( + firstHeader, firstResults, secondHeader, + secondResults, + (item, message) => message.AppendLine(itemDescriptor(item))); + } + } + + public class CsvReporter + { + public string Build(IEnumerable results, string header, DefaultFormatter formatter) + { + var message = new StringBuilder(); + message.AppendLine(string.Join(",", formatter.DesribeType())); + foreach (var result in results) + { + message.AppendLine(string.Join(",", formatter.DesribeItem(result))); + } + return message.ToString(); + } + } + + public class DefaultFormatter + { + readonly Type type; + readonly PropertyInfo[] properties; + + public DefaultFormatter(Type type) + { + this.type = type; + properties = type.GetProperties(); + } + + // TODO: this is a very crappy name for a method + public string[] DesribeType() + { + return properties.Select(Describe).ToArray(); + } + + string Describe(PropertyInfo property) + { + return property.Name.Replace('_', ' '); + } + + public string[] DesribeItem(object result) + { + return properties.Select(p => p.GetValue(result, null).ToString()).ToArray(); + } + } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Internal/Executor.cs b/TestStack.ConventionTests/Internal/Executor.cs index c054e29..5aee628 100644 --- a/TestStack.ConventionTests/Internal/Executor.cs +++ b/TestStack.ConventionTests/Internal/Executor.cs @@ -1,29 +1,27 @@ namespace TestStack.ConventionTests.Internal { using System.Linq; - using TestStack.ConventionTests.Conventions; using TestStack.ConventionTests.Reporting; public static class Executor { - public static ResultInfo GetConventionReport(string conventionTitle, object[] failingData, IConventionData data) + public static ResultInfo GetConventionReport(string conventionTitle, ConventionResult failingData, + IConventionData data) { - if (!data.HasData) - throw new ConventionSourceInvalidException(string.Format("{0} has no data", data.Description)); - - var passed = failingData.None(); + var result = failingData.Failed; var conventionResult = new ResultInfo( - passed ? TestResult.Passed : TestResult.Failed, + result ? TestResult.Passed : TestResult.Failed, conventionTitle, data.Description, - failingData.Select(FormatData).ToArray()); + failingData.Items.Select(FormatData).ToArray()); return conventionResult; } - public static ResultInfo GetConventionReportWithApprovedExeptions(string conventionTitle, object[] failingData, IConventionData data) + public static ResultInfo GetConventionReportWithApprovedExeptions(string conventionTitle, + ConventionResult failingData, IConventionData data) { - var conventionResult = Executor.GetConventionReport(conventionTitle, failingData, data); + var conventionResult = GetConventionReport(conventionTitle, failingData, data); var conventionReportTextRenderer = new ConventionReportTextRenderer(); // Add approved exceptions to report conventionReportTextRenderer.RenderItems(conventionResult); @@ -37,7 +35,8 @@ static ConventionReportFailure FormatData(T failingData) var formatter = Convention.Formatters.FirstOrDefault(f => f.CanFormat(failingData)); if (formatter == null) - throw new NoDataFormatterFoundException(typeof(T).Name + " has no formatter, add one with `Convention.Formatters.Add(new MyDataFormatter());`"); + throw new NoDataFormatterFoundException(typeof (T).Name + + " has no formatter, add one with `Convention.Formatters.Add(new MyDataFormatter());`"); return formatter.Format(failingData); } diff --git a/TestStack.ConventionTests/TestStack.ConventionTests.csproj b/TestStack.ConventionTests/TestStack.ConventionTests.csproj index 81d3399..8f6e1f4 100644 --- a/TestStack.ConventionTests/TestStack.ConventionTests.csproj +++ b/TestStack.ConventionTests/TestStack.ConventionTests.csproj @@ -76,7 +76,6 @@ - From c62f0b44d9a11e085e52b8a46ef39bd077d7be1d Mon Sep 17 00:00:00 2001 From: Krzysztof Kozmic Date: Sun, 11 Aug 2013 14:25:30 +1000 Subject: [PATCH 3/6] introduced IConventionResult Not terribly convinced yet I prefer this approach, but it's an experiment. This allows us to remove static factory method call and we can, if needed, treat this object as a context object if needed. Also changed the Execute method on the convention to be void... it technically makes things simpler, although on the other hand, doesn't in any way indicate you're supposed to call result.Something and that you're supposed to do it only once... We'll just need to play with it and see where it takes us --- .../ConventionAssertionClassTests.cs | 4 +- TestStack.ConventionTests/Convention.cs | 17 ++++++- .../AllClassesHaveDefaultConstructor.cs | 4 +- .../Conventions/AllMethodsAreVirtual.cs | 9 ++-- .../ClassTypeHasSpecificNamespace.cs | 4 +- .../Conventions/FilesAreEmbeddedResources.cs | 7 +-- ...NotReferenceDllsFromBinOrObjDirectories.cs | 5 +- TestStack.ConventionTests/IConvention.cs | 4 +- .../IConventionResult.cs | 29 +++++++++++ .../Internal/ConventionResult.cs | 48 +++++++------------ .../TestStack.ConventionTests.csproj | 1 + 11 files changed, 80 insertions(+), 52 deletions(-) create mode 100644 TestStack.ConventionTests/IConventionResult.cs diff --git a/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs b/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs index 1bdbf4c..a68836c 100644 --- a/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs +++ b/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs @@ -35,9 +35,9 @@ public ConventionReportFailure Format(string failingData) public class FailingConvention : IConvention { public string ConventionTitle { get { return "Header"; } } - public ConventionResult Execute(FakeData data) + public void Execute(FakeData data, IConventionResult result) { - return ConventionResult.For(new[] {"Different"}); + result.Is(new[] {"Different"}); } } } diff --git a/TestStack.ConventionTests/Convention.cs b/TestStack.ConventionTests/Convention.cs index 0060787..d663e37 100644 --- a/TestStack.ConventionTests/Convention.cs +++ b/TestStack.ConventionTests/Convention.cs @@ -46,7 +46,11 @@ public static void Is(IConvention convention, TDataSou if (!data.HasData) throw new ConventionSourceInvalidException(string.Format("{0} has no data", data.Description)); - var conventionResult = Executor.GetConventionReport(convention.ConventionTitle, convention.Execute(data), data); + var result = new ConventionResult(); + + convention.Execute(data, result); + + var conventionResult = Executor.GetConventionReport(convention.ConventionTitle, result, data); Reports.Add(conventionResult); new ConventionReportTraceRenderer().Render(conventionResult); @@ -61,7 +65,16 @@ public static void Is(IConvention convention, TDataSou public static void IsWithApprovedExeptions(IConvention convention, TDataSource data) where TDataSource : IConventionData { - var conventionResult = Executor.GetConventionReportWithApprovedExeptions(convention.ConventionTitle, convention.Execute(data), data); + + // we want to run that first so that we don't even bother running the convention if theere's no data + // conveniton author can assume that data is available. That will simplify the conventions + if (!data.HasData) + throw new ConventionSourceInvalidException(string.Format("{0} has no data", data.Description)); + + var result = new ConventionResult(); + convention.Execute(data,result); + + var conventionResult = Executor.GetConventionReportWithApprovedExeptions(convention.ConventionTitle, result, data); Reports.Add(conventionResult); try diff --git a/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs b/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs index 2493a71..850994e 100644 --- a/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs +++ b/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs @@ -11,9 +11,9 @@ public string ConventionTitle get { return "Types must have a default constructor"; } } - public ConventionResult Execute(Types data) + public void Execute(Types data, IConventionResult result) { - return ConventionResult.For(data.TypesToVerify.Where(t => t.HasDefaultConstructor() == false)); + result.Is(data.TypesToVerify.Where(t => t.HasDefaultConstructor() == false)); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs b/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs index 6f9604e..2bdbe3c 100644 --- a/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs +++ b/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs @@ -6,11 +6,14 @@ public class AllMethodsAreVirtual : IConvention { - public string ConventionTitle { get { return "Methods must be virtual"; } } + public string ConventionTitle + { + get { return "Methods must be virtual"; } + } - public ConventionResult Execute(Types data) + public void Execute(Types data, IConventionResult result) { - return ConventionResult.For(data.TypesToVerify.SelectMany(t => t.NonVirtualMethods())); + result.Is(data.TypesToVerify.SelectMany(t => t.NonVirtualMethods())); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs b/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs index 72b51ca..49f8cc2 100644 --- a/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs +++ b/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs @@ -42,9 +42,9 @@ public string ConventionTitle } } - public ConventionResult Execute(Types data) + public void Execute(Types data, IConventionResult result) { - throw new NotImplementedException(); + throw new NotImplementedException("NOT DONE YET"); } public string InverseTitle diff --git a/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs b/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs index 6b1ec16..94f524e 100644 --- a/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs +++ b/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs @@ -2,7 +2,6 @@ { using System.Linq; using TestStack.ConventionTests.ConventionData; - using TestStack.ConventionTests.Internal; public class FilesAreEmbeddedResources : IConvention { @@ -18,11 +17,9 @@ public string ConventionTitle get { return string.Format("{0} Files must be embedded resources", FileExtension); } } - public ConventionResult Execute(ProjectFiles data) + public void Execute(ProjectFiles data, IConventionResult result) { - return - ConventionResult.For( - data.Files.Where(s => s.FilePath.EndsWith(FileExtension) && s.ReferenceType != "EmbeddedResource")); + result.Is(data.Files.Where(s => s.FilePath.EndsWith(FileExtension) && s.ReferenceType != "EmbeddedResource")); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs b/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs index d0bdff4..509045b 100644 --- a/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs +++ b/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Text.RegularExpressions; using TestStack.ConventionTests.ConventionData; - using TestStack.ConventionTests.Internal; public class ProjectDoesNotReferenceDllsFromBinOrObjDirectories : IConvention { @@ -14,9 +13,9 @@ public string ConventionTitle get { return "Project must not reference dlls from bin or obj directories"; } } - public ConventionResult Execute(ProjectReferences data) + public void Execute(ProjectReferences data, IConventionResult result) { - return ConventionResult.For(data.References.Where(IsBinOrObjReference)); + result.Is(data.References.Where(IsBinOrObjReference)); } static bool IsBinOrObjReference(ProjectReference reference) diff --git a/TestStack.ConventionTests/IConvention.cs b/TestStack.ConventionTests/IConvention.cs index 40b8399..2facab7 100644 --- a/TestStack.ConventionTests/IConvention.cs +++ b/TestStack.ConventionTests/IConvention.cs @@ -1,10 +1,8 @@ namespace TestStack.ConventionTests { - using TestStack.ConventionTests.Internal; - public interface IConvention where T : IConventionData { string ConventionTitle { get; } - ConventionResult Execute(T data); + void Execute(T data, IConventionResult result); } } \ No newline at end of file diff --git a/TestStack.ConventionTests/IConventionResult.cs b/TestStack.ConventionTests/IConventionResult.cs new file mode 100644 index 0000000..8ba6f69 --- /dev/null +++ b/TestStack.ConventionTests/IConventionResult.cs @@ -0,0 +1,29 @@ +namespace TestStack.ConventionTests +{ + using System; + using System.Collections.Generic; + using System.Text; + + public interface IConventionResult + { + void Is(IEnumerable items); + + void Is( + IEnumerable items, + Action itemDescriptor); + + void Is( + IEnumerable items, + Func itemDescriptor); + + void IsSymmetric( + string firstHeader, TResult[] firstResults, + string secondHeader, TResult[] secondResults, + Action itemDescriptor); + + void IsSymmetric( + string firstHeader, TResult[] firstResults, + string secondHeader, TResult[] secondResults, + Func itemDescriptor); + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests/Internal/ConventionResult.cs b/TestStack.ConventionTests/Internal/ConventionResult.cs index e9a2fc1..f0bdfc0 100644 --- a/TestStack.ConventionTests/Internal/ConventionResult.cs +++ b/TestStack.ConventionTests/Internal/ConventionResult.cs @@ -7,11 +7,8 @@ using System.Reflection; using System.Text; - public class ConventionResult + public class ConventionResult : IConventionResult { - ConventionResult() - { - } public string Message { get; private set; } @@ -20,57 +17,49 @@ public bool Failed get { return !string.IsNullOrEmpty(Message); } } - public static ConventionResult For(IEnumerable items) - { - return new ConventionResult - { - Items = items.Select(item => (object) item).ToList(), - }; - } public IEnumerable Items { get; set; } - public static ConventionResult For( + void IConventionResult.Is(IEnumerable items) + { + Items = items.Select(item => (object) item).ToList(); + } + + public void Is( IEnumerable items, - string header, Action itemDescriptor) { var array = items.ToArray(); - var result = new ConventionResult(); if (array.None()) { - return result; + return; } // NOTE: we might possibly want to abstract the StringBuilder to have more high level construct that would allow us to plug rich reports here... - var message = new StringBuilder(header); + var message = new StringBuilder(); message.AppendLine(); - message.AppendLine(string.Empty.PadRight(header.Length, '-')); message.AppendLine(); Array.ForEach(array, r => itemDescriptor(r, message)); - result.Message = message.ToString(); - return result; + Message = message.ToString(); } - public static ConventionResult For( + public void Is( IEnumerable items, - string header, Func itemDescriptor) { - return For(items, header, (item, message) => message.AppendLine(itemDescriptor(item))); + Is(items, (item, message) => message.AppendLine(itemDescriptor(item))); } - public static ConventionResult ForSymmetric( + public void IsSymmetric( string firstHeader, TResult[] firstResults, string secondHeader, TResult[] secondResults, Action itemDescriptor) { var firstArray = firstResults.ToArray(); var secondArray = secondResults.ToArray(); - var result = new ConventionResult(); if (firstArray.None() && secondArray.None()) { - return result; + return; } var message = new StringBuilder(); @@ -93,16 +82,15 @@ public static ConventionResult ForSymmetric( message.AppendLine(); Array.ForEach(secondArray, r => itemDescriptor(r, message)); } - result.Message = message.ToString(); - return result; + Message = message.ToString(); } - public static ConventionResult ForSymmetric( + public void IsSymmetric( string firstHeader, TResult[] firstResults, string secondHeader, TResult[] secondResults, Func itemDescriptor) { - return ForSymmetric( + IsSymmetric( firstHeader, firstResults, secondHeader, secondResults, (item, message) => message.AppendLine(itemDescriptor(item))); @@ -125,8 +113,8 @@ public string Build(IEnumerable results, string header, DefaultFormatter formatt public class DefaultFormatter { - readonly Type type; readonly PropertyInfo[] properties; + readonly Type type; public DefaultFormatter(Type type) { diff --git a/TestStack.ConventionTests/TestStack.ConventionTests.csproj b/TestStack.ConventionTests/TestStack.ConventionTests.csproj index 8f6e1f4..3e8b083 100644 --- a/TestStack.ConventionTests/TestStack.ConventionTests.csproj +++ b/TestStack.ConventionTests/TestStack.ConventionTests.csproj @@ -61,6 +61,7 @@ + From 463362156042421cfd176c5503e5e1f9afc75bec Mon Sep 17 00:00:00 2001 From: Krzysztof Kozmic Date: Sun, 11 Aug 2013 14:40:50 +1000 Subject: [PATCH 4/6] added abstract base Convention class I quite liked the simplicity of @jakeGinnivan's solution that just returned IEnumerable This allows us to do that as an option, but also to fallback to using IConventionResult explicitly when we need more control --- .../Convention.Generic.cs | 20 +++++++++++++++++++ .../AllClassesHaveDefaultConstructor.cs | 9 +++++---- .../Conventions/AllMethodsAreVirtual.cs | 9 +++++---- .../Conventions/FilesAreEmbeddedResources.cs | 9 +++++---- ...NotReferenceDllsFromBinOrObjDirectories.cs | 9 +++++---- TestStack.ConventionTests/IConvention.cs | 4 ++-- .../TestStack.ConventionTests.csproj | 1 + 7 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 TestStack.ConventionTests/Convention.Generic.cs diff --git a/TestStack.ConventionTests/Convention.Generic.cs b/TestStack.ConventionTests/Convention.Generic.cs new file mode 100644 index 0000000..5dc597d --- /dev/null +++ b/TestStack.ConventionTests/Convention.Generic.cs @@ -0,0 +1,20 @@ +namespace TestStack.ConventionTests +{ + using System; + using System.Collections.Generic; + + public abstract class Convention : IConvention where TData : IConventionData + { + public abstract string ConventionTitle { get; } + + public virtual void Execute(TData data, IConventionResult result) + { + result.Is(Execute(data)); + } + + protected virtual IEnumerable Execute(TData data) + { + throw new NotImplementedException("You need to overwrite the Execute method"); + } + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs b/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs index 850994e..74544cf 100644 --- a/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs +++ b/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs @@ -1,19 +1,20 @@ namespace TestStack.ConventionTests.Conventions { + using System.Collections.Generic; using System.Linq; using TestStack.ConventionTests.ConventionData; using TestStack.ConventionTests.Internal; - public class AllClassesHaveDefaultConstructor : IConvention + public class AllClassesHaveDefaultConstructor : Convention { - public string ConventionTitle + public override string ConventionTitle { get { return "Types must have a default constructor"; } } - public void Execute(Types data, IConventionResult result) + protected override IEnumerable Execute(Types data) { - result.Is(data.TypesToVerify.Where(t => t.HasDefaultConstructor() == false)); + return data.TypesToVerify.Where(t => t.HasDefaultConstructor() == false); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs b/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs index 2bdbe3c..0dd9144 100644 --- a/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs +++ b/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs @@ -1,19 +1,20 @@ namespace TestStack.ConventionTests.Conventions { + using System.Collections.Generic; using System.Linq; using TestStack.ConventionTests.ConventionData; using TestStack.ConventionTests.Internal; - public class AllMethodsAreVirtual : IConvention + public class AllMethodsAreVirtual : Convention { - public string ConventionTitle + public override string ConventionTitle { get { return "Methods must be virtual"; } } - public void Execute(Types data, IConventionResult result) + protected override IEnumerable Execute(Types data) { - result.Is(data.TypesToVerify.SelectMany(t => t.NonVirtualMethods())); + return data.TypesToVerify.SelectMany(t => t.NonVirtualMethods()); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs b/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs index 94f524e..05f7757 100644 --- a/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs +++ b/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs @@ -1,9 +1,10 @@ namespace TestStack.ConventionTests.Conventions { + using System.Collections.Generic; using System.Linq; using TestStack.ConventionTests.ConventionData; - public class FilesAreEmbeddedResources : IConvention + public class FilesAreEmbeddedResources : Convention { public FilesAreEmbeddedResources(string fileExtension) { @@ -12,14 +13,14 @@ public FilesAreEmbeddedResources(string fileExtension) public string FileExtension { get; set; } - public string ConventionTitle + public override string ConventionTitle { get { return string.Format("{0} Files must be embedded resources", FileExtension); } } - public void Execute(ProjectFiles data, IConventionResult result) + protected override IEnumerable Execute(ProjectFiles data) { - result.Is(data.Files.Where(s => s.FilePath.EndsWith(FileExtension) && s.ReferenceType != "EmbeddedResource")); + return data.Files.Where(s => s.FilePath.EndsWith(FileExtension) && s.ReferenceType != "EmbeddedResource"); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs b/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs index 509045b..c2eaf13 100644 --- a/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs +++ b/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs @@ -1,21 +1,22 @@ namespace TestStack.ConventionTests.Conventions { + using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using TestStack.ConventionTests.ConventionData; - public class ProjectDoesNotReferenceDllsFromBinOrObjDirectories : IConvention + public class ProjectDoesNotReferenceDllsFromBinOrObjDirectories : Convention { const string AssemblyReferencingObjRegex = @"^(?.*?(obj|bin).*?)$"; - public string ConventionTitle + public override string ConventionTitle { get { return "Project must not reference dlls from bin or obj directories"; } } - public void Execute(ProjectReferences data, IConventionResult result) + protected override IEnumerable Execute(ProjectReferences data) { - result.Is(data.References.Where(IsBinOrObjReference)); + return data.References.Where(IsBinOrObjReference); } static bool IsBinOrObjReference(ProjectReference reference) diff --git a/TestStack.ConventionTests/IConvention.cs b/TestStack.ConventionTests/IConvention.cs index 2facab7..9f76f3f 100644 --- a/TestStack.ConventionTests/IConvention.cs +++ b/TestStack.ConventionTests/IConvention.cs @@ -1,8 +1,8 @@ namespace TestStack.ConventionTests { - public interface IConvention where T : IConventionData + public interface IConvention where TData : IConventionData { string ConventionTitle { get; } - void Execute(T data, IConventionResult result); + void Execute(TData data, IConventionResult result); } } \ No newline at end of file diff --git a/TestStack.ConventionTests/TestStack.ConventionTests.csproj b/TestStack.ConventionTests/TestStack.ConventionTests.csproj index 3e8b083..626677d 100644 --- a/TestStack.ConventionTests/TestStack.ConventionTests.csproj +++ b/TestStack.ConventionTests/TestStack.ConventionTests.csproj @@ -61,6 +61,7 @@ + From 00439066155e8154f51cac5ed16ad749e87e0202 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozmic Date: Sun, 11 Aug 2013 18:08:16 +1000 Subject: [PATCH 5/6] got symmetric conventions working again --- .../TypeBasedConventions.cs | 14 ++- TestStack.ConventionTests/Convention.cs | 91 ++++++++++++------- .../ClassTypeHasSpecificNamespace.cs | 59 ++++-------- .../IConventionResult.cs | 13 +-- ...nventionResult.cs => ConventionContext.cs} | 37 ++++++-- .../Internal/Executor.cs | 15 ++- .../TestStack.ConventionTests.csproj | 2 +- 7 files changed, 129 insertions(+), 102 deletions(-) rename TestStack.ConventionTests/Internal/{ConventionResult.cs => ConventionContext.cs} (76%) diff --git a/TestStack.ConventionTests.Tests/TypeBasedConventions.cs b/TestStack.ConventionTests.Tests/TypeBasedConventions.cs index 5f3c2d4..a2af0c3 100644 --- a/TestStack.ConventionTests.Tests/TypeBasedConventions.cs +++ b/TestStack.ConventionTests.Tests/TypeBasedConventions.cs @@ -26,7 +26,9 @@ public TypeBasedConventions() [Test] public void all_classes_have_default_constructor() { - var ex = Assert.Throws(()=>Convention.Is(new AllClassesHaveDefaultConstructor(), nhibernateEntities)); + var ex = + Assert.Throws( + () => Convention.Is(new AllClassesHaveDefaultConstructor(), nhibernateEntities)); Approvals.Verify(ex.Message); } @@ -40,7 +42,9 @@ public void all_classes_have_default_constructor_wth_approved_exceptions() [Test] public void all_methods_are_virtual() { - var ex = Assert.Throws(()=>Convention.Is(new AllMethodsAreVirtual(), nhibernateEntities)); + var ex = + Assert.Throws( + () => Convention.Is(new AllMethodsAreVirtual(), nhibernateEntities)); Approvals.Verify(ex.Message); } @@ -56,11 +60,11 @@ public void dtos_exists_in_dto_namespace() { var types = new Types("TestAssembly types") { - TypesToVerify = new[] { typeof(SomeDto), typeof(BlahDto), typeof(AnotherClass)} + TypesToVerify = new[] {typeof (SomeDto), typeof (BlahDto), typeof (AnotherClass)} }; var convention = new ClassTypeHasSpecificNamespace(t => t.Name.EndsWith("Dto"), "TestAssembly.Dtos", "Dto"); - var ex = Assert.Throws(() =>Convention.Is(convention, types)); + var ex = Assert.Throws(() => Convention.Is(convention, types)); Approvals.Verify(ex.Message); } @@ -69,7 +73,7 @@ public void dtos_exists_in_dto_namespace_wth_approved_exceptions() { var types = new Types("TestAssembly types") { - TypesToVerify = new[] { typeof(SomeDto), typeof(BlahDto), typeof(AnotherClass) } + TypesToVerify = new[] {typeof (SomeDto), typeof (BlahDto), typeof (AnotherClass)} }; var convention = new ClassTypeHasSpecificNamespace(t => t.Name.EndsWith("Dto"), "TestAssembly.Dtos", "Dto"); diff --git a/TestStack.ConventionTests/Convention.cs b/TestStack.ConventionTests/Convention.cs index d663e37..cf93539 100644 --- a/TestStack.ConventionTests/Convention.cs +++ b/TestStack.ConventionTests/Convention.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; + using System.Linq; using System.Reflection; using ApprovalTests; using ApprovalTests.Core.Exceptions; @@ -27,8 +28,24 @@ static Convention() }; } - public static IEnumerable ConventionReports { get { return Reports; } } - public static IList Formatters { get; set; } + public static IEnumerable ConventionReports + { + get { return Reports; } + } + + public static IList Formatters { get; set; } + + 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 static void Is(IConvention convention, TDataSource data) where TDataSource : IConventionData @@ -36,7 +53,8 @@ public static void Is(IConvention convention, TDataSou Is(convention, data, new ConventionResultExceptionReporter()); } - public static void Is(IConvention convention, TDataSource data, IConventionReportRenderer reporter) + public static void Is(IConvention convention, TDataSource data, + IConventionReportRenderer reporter) where TDataSource : IConventionData { try @@ -46,15 +64,11 @@ public static void Is(IConvention convention, TDataSou if (!data.HasData) throw new ConventionSourceInvalidException(string.Format("{0} has no data", data.Description)); - var result = new ConventionResult(); + var result = new ConventionContext(); convention.Execute(data, result); - - var conventionResult = Executor.GetConventionReport(convention.ConventionTitle, result, data); - Reports.Add(conventionResult); - - new ConventionReportTraceRenderer().Render(conventionResult); - reporter.Render(conventionResult); + Render(convention, data, result, Executor.GetConventionReport, reporter, + new ConventionReportTraceRenderer()); } finally { @@ -62,49 +76,60 @@ public static void Is(IConvention convention, TDataSou } } + //ResultInfo GetConventionReport(string conventionTitle, IConventionData data, IEnumerable items, bool failed) + static void Render(IConvention convention, TDataSource data, + ConventionContext result, + Func, ResultInfo> getConventionReport, + params IConventionReportRenderer[] renderers) where TDataSource : IConventionData + { + if (result.IsSymmetricResult) + { + var result1 = getConventionReport(result.FirstDescription, + data, result.FirstOnly); + var result2 = getConventionReport(result.SecondDescription, + data, result.SecondOnly); + Reports.Add(result1); + Reports.Add(result2); + + + Array.ForEach(renderers, r => r.Render(result1, result2)); + } + else + { + var conventionResult = getConventionReport(convention.ConventionTitle, data, result.Items); + Reports.Add(conventionResult); + + Array.ForEach(renderers, r => r.Render(conventionResult)); + } + } + public static void IsWithApprovedExeptions(IConvention convention, TDataSource data) where TDataSource : IConventionData { - // we want to run that first so that we don't even bother running the convention if theere's no data // conveniton author can assume that data is available. That will simplify the conventions if (!data.HasData) throw new ConventionSourceInvalidException(string.Format("{0} has no data", data.Description)); - var result = new ConventionResult(); - convention.Execute(data,result); - - var conventionResult = Executor.GetConventionReportWithApprovedExeptions(convention.ConventionTitle, result, data); - Reports.Add(conventionResult); + var result = new ConventionContext(); + convention.Execute(data, result); + var conventionReportTextRenderer = new ConventionReportTextRenderer(); + Render(convention, data, result, Executor.GetConventionReportWithApprovedExeptions, + conventionReportTextRenderer, new ConventionReportTraceRenderer()); try { - var conventionReportTextRenderer = new ConventionReportTextRenderer(); - conventionReportTextRenderer.Render(conventionResult); Approvals.Verify(conventionReportTextRenderer.Output); - - new ConventionReportTraceRenderer().Render(conventionResult); } catch (ApprovalException ex) { - throw new ConventionFailedException("Approved exceptions for convention differs\r\n\r\n"+ex.Message, ex); + throw new ConventionFailedException("Approved exceptions for convention differs\r\n\r\n" + ex.Message, + ex); } finally { HtmlRenderer.Render(Reports.ToArray()); } } - - // http://stackoverflow.com/questions/52797/c-how-do-i-get-the-path-of-the-assembly-the-code-is-in#answer-283917 - static string AssemblyDirectory - { - get - { - string codeBase = Assembly.GetExecutingAssembly().CodeBase; - var uri = new UriBuilder(codeBase); - string path = Uri.UnescapeDataString(uri.Path); - return Path.GetDirectoryName(path); - } - } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs b/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs index 49f8cc2..7643849 100644 --- a/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs +++ b/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs @@ -1,72 +1,53 @@ namespace TestStack.ConventionTests.Conventions { using System; - using System.Collections.Generic; - using System.Linq; using TestStack.ConventionTests.ConventionData; - using TestStack.ConventionTests.Internal; /// - /// This convention allows you to enforce a particular type of class is under a namespace, for instance. - /// - /// Dto must be under App.Contracts.Dtos - /// Domain Objects must be under App.Domain - /// Event Handlers must be under App.Handlers - /// - /// This is a Symmetric convention, and will verify all of a Class Type lives in the namespace, but also that only that class type is in that namespace + /// This convention allows you to enforce a particular type of class is under a namespace, for instance. + /// Dto must be under App.Contracts.Dtos + /// Domain Objects must be under App.Domain + /// Event Handlers must be under App.Handlers + /// This is a Symmetric convention, and will verify all of a Class Type lives in the namespace, but also that only that + /// class type is in that namespace /// - public class ClassTypeHasSpecificNamespace:IConvention + public class ClassTypeHasSpecificNamespace : IConvention { readonly Func classIsApplicable; - readonly string namespaceToCheck; readonly string classType; + readonly string namespaceToCheck; /// - /// Ctor + /// Ctor /// /// Predicate to verify if the class is the right type /// /// The class type. ie, Dto, Domain Object, Event Handler - public ClassTypeHasSpecificNamespace(Func classIsApplicable, string namespaceToCheck, string classType) + public ClassTypeHasSpecificNamespace(Func classIsApplicable, string namespaceToCheck, + string classType) { this.classIsApplicable = classIsApplicable; this.namespaceToCheck = namespaceToCheck; this.classType = classType; } - public string ConventionTitle - { - get - { - return string.Format("{0}s must be under the '{1}' namespace", classType, namespaceToCheck); - } - } - - public void Execute(Types data, IConventionResult result) - { - throw new NotImplementedException("NOT DONE YET"); - } - public string InverseTitle { - get - { - return string.Format("Non-{0}s must not be under the '{1}' namespace", classType, namespaceToCheck); - } + get { return string.Format("Non-{0}s must not be under the '{1}' namespace", classType, namespaceToCheck); } } - public IEnumerable GetFailingData(Types data) + public string ConventionTitle { - return data.TypesToVerify - .Where(classIsApplicable) - .Where(t => t.Namespace == null || !t.Namespace.StartsWith(namespaceToCheck)); + get { return string.Format("{0}s must be under the '{1}' namespace", classType, namespaceToCheck); } } - public IEnumerable GetFailingInverseData(Types data) + public void Execute(Types data, IConventionResult result) { - return data.TypesToVerify - .Where(t => !classIsApplicable(t)) - .Where(t => t.Namespace != null && t.Namespace.StartsWith(namespaceToCheck)); + result.IsSymmetric(data.TypesToVerify, + classIsApplicable, + t => t.Namespace != null && t.Namespace.StartsWith(namespaceToCheck), + ConventionTitle, + InverseTitle); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/IConventionResult.cs b/TestStack.ConventionTests/IConventionResult.cs index 8ba6f69..06223a8 100644 --- a/TestStack.ConventionTests/IConventionResult.cs +++ b/TestStack.ConventionTests/IConventionResult.cs @@ -17,13 +17,10 @@ void Is( Func itemDescriptor); void IsSymmetric( - string firstHeader, TResult[] firstResults, - string secondHeader, TResult[] secondResults, - Action itemDescriptor); - - void IsSymmetric( - string firstHeader, TResult[] firstResults, - string secondHeader, TResult[] secondResults, - Func itemDescriptor); + IEnumerable items, + Func firstPredicate, + Func secondPredicate, + string firstDescription = null, + string secondDescription = null); } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Internal/ConventionResult.cs b/TestStack.ConventionTests/Internal/ConventionContext.cs similarity index 76% rename from TestStack.ConventionTests/Internal/ConventionResult.cs rename to TestStack.ConventionTests/Internal/ConventionContext.cs index f0bdfc0..003eb5f 100644 --- a/TestStack.ConventionTests/Internal/ConventionResult.cs +++ b/TestStack.ConventionTests/Internal/ConventionContext.cs @@ -7,18 +7,20 @@ using System.Reflection; using System.Text; - public class ConventionResult : IConventionResult + public class ConventionContext : IConventionResult { - public string Message { get; private set; } - public bool Failed - { - get { return !string.IsNullOrEmpty(Message); } - } + public IEnumerable Items { get; set; } + public object[] SecondOnly { get; set; } - public IEnumerable Items { get; set; } + public object[] FirstOnly { get; set; } + + public string SecondDescription { get; set; } + + public string FirstDescription { get; set; } + public bool IsSymmetricResult { get; set; } void IConventionResult.Is(IEnumerable items) { @@ -50,6 +52,27 @@ public void Is( Is(items, (item, message) => message.AppendLine(itemDescriptor(item))); } + public void IsSymmetric( + IEnumerable items, + Func firstPredicate, + Func secondPredicate, + string firstDescription = null, + string secondDescription = null) + { + IsSymmetricResult = true; + FirstDescription = firstDescription; + SecondDescription = secondDescription; + var array = items.ToArray(); + if (array.None()) + { + return; + } + FirstOnly = array.Where(firstPredicate).Where(i => secondPredicate(i) == false) + .Select(i => (object) i).ToArray(); + SecondOnly = array.Where(secondPredicate).Where(i => firstPredicate(i) == false) + .Select(i => (object) i).ToArray(); + } + public void IsSymmetric( string firstHeader, TResult[] firstResults, string secondHeader, TResult[] secondResults, diff --git a/TestStack.ConventionTests/Internal/Executor.cs b/TestStack.ConventionTests/Internal/Executor.cs index 5aee628..4c23d91 100644 --- a/TestStack.ConventionTests/Internal/Executor.cs +++ b/TestStack.ConventionTests/Internal/Executor.cs @@ -1,27 +1,24 @@ namespace TestStack.ConventionTests.Internal { + using System.Collections.Generic; using System.Linq; using TestStack.ConventionTests.Reporting; public static class Executor { - public static ResultInfo GetConventionReport(string conventionTitle, ConventionResult failingData, - IConventionData data) + public static ResultInfo GetConventionReport(string conventionTitle, IConventionData data, IEnumerable items) { - var result = failingData.Failed; - var conventionResult = new ResultInfo( - result ? TestResult.Passed : TestResult.Failed, + items.None() ? TestResult.Passed : TestResult.Failed, conventionTitle, data.Description, - failingData.Items.Select(FormatData).ToArray()); + items.Select(FormatData).ToArray()); return conventionResult; } - public static ResultInfo GetConventionReportWithApprovedExeptions(string conventionTitle, - ConventionResult failingData, IConventionData data) + public static ResultInfo GetConventionReportWithApprovedExeptions(string conventionTitle, IConventionData data, IEnumerable items) { - var conventionResult = GetConventionReport(conventionTitle, failingData, data); + var conventionResult = GetConventionReport(conventionTitle, data, items); var conventionReportTextRenderer = new ConventionReportTextRenderer(); // Add approved exceptions to report conventionReportTextRenderer.RenderItems(conventionResult); diff --git a/TestStack.ConventionTests/TestStack.ConventionTests.csproj b/TestStack.ConventionTests/TestStack.ConventionTests.csproj index 626677d..c68cbb8 100644 --- a/TestStack.ConventionTests/TestStack.ConventionTests.csproj +++ b/TestStack.ConventionTests/TestStack.ConventionTests.csproj @@ -64,7 +64,7 @@ - + From 587c68b79995044cdb253d91a7244f13400d046e Mon Sep 17 00:00:00 2001 From: Krzysztof Kozmic Date: Sun, 11 Aug 2013 18:31:21 +1000 Subject: [PATCH 6/6] moving away from using static state introduced IConventionContext (to be used internally). Still a lot of cleaning up left to do --- TestStack.ConventionTests/Convention.cs | 38 +++++++++---------- .../Internal/ConventionContext.cs | 26 ++++++++++++- .../Internal/Executor.cs | 16 ++++---- .../Internal/IConventionContext.cs | 12 ++++++ .../TestStack.ConventionTests.csproj | 1 + 5 files changed, 66 insertions(+), 27 deletions(-) create mode 100644 TestStack.ConventionTests/Internal/IConventionContext.cs diff --git a/TestStack.ConventionTests/Convention.cs b/TestStack.ConventionTests/Convention.cs index cf93539..1c1a91c 100644 --- a/TestStack.ConventionTests/Convention.cs +++ b/TestStack.ConventionTests/Convention.cs @@ -64,11 +64,10 @@ public static void Is(IConvention convention, TDataSou if (!data.HasData) throw new ConventionSourceInvalidException(string.Format("{0} has no data", data.Description)); - var result = new ConventionContext(); + var result = new ConventionContext(data, Formatters, reporter, new ConventionReportTraceRenderer()); convention.Execute(data, result); - Render(convention, data, result, Executor.GetConventionReport, reporter, - new ConventionReportTraceRenderer()); + Render(convention, data, result, Executor.GetConventionReport); } finally { @@ -77,29 +76,32 @@ public static void Is(IConvention convention, TDataSou } //ResultInfo GetConventionReport(string conventionTitle, IConventionData data, IEnumerable items, bool failed) - static void Render(IConvention convention, TDataSource data, - ConventionContext result, - Func, ResultInfo> getConventionReport, - params IConventionReportRenderer[] renderers) where TDataSource : IConventionData + static void Render(IConvention convention, TDataSource data, ConventionContext result, + Func, ResultInfo> getConventionReport) where TDataSource : IConventionData { if (result.IsSymmetricResult) { var result1 = getConventionReport(result.FirstDescription, - data, result.FirstOnly); + result, result.FirstOnly); var result2 = getConventionReport(result.SecondDescription, - data, result.SecondOnly); + result, result.SecondOnly); Reports.Add(result1); Reports.Add(result2); - - Array.ForEach(renderers, r => r.Render(result1, result2)); + foreach (var renderer in result.Renderers) + { + renderer.Render(result1, result2); + } } else { - var conventionResult = getConventionReport(convention.ConventionTitle, data, result.Items); + var conventionResult = getConventionReport(convention.ConventionTitle, result, result.Items); Reports.Add(conventionResult); - Array.ForEach(renderers, r => r.Render(conventionResult)); + foreach (var renderer in result.Renderers) + { + renderer.Render(conventionResult); + } } } @@ -111,20 +113,18 @@ public static void IsWithApprovedExeptions(IConvention if (!data.HasData) throw new ConventionSourceInvalidException(string.Format("{0} has no data", data.Description)); - var result = new ConventionContext(); + var conventionReportTextRenderer = new ConventionReportTextRenderer(); + var result = new ConventionContext(data, Formatters, conventionReportTextRenderer, new ConventionReportTraceRenderer()); convention.Execute(data, result); - var conventionReportTextRenderer = new ConventionReportTextRenderer(); - Render(convention, data, result, Executor.GetConventionReportWithApprovedExeptions, - conventionReportTextRenderer, new ConventionReportTraceRenderer()); + Render(convention, data, result, Executor.GetConventionReportWithApprovedExeptions); try { Approvals.Verify(conventionReportTextRenderer.Output); } catch (ApprovalException ex) { - throw new ConventionFailedException("Approved exceptions for convention differs\r\n\r\n" + ex.Message, - ex); + throw new ConventionFailedException("Approved exceptions for convention differs\r\n\r\n" + ex.Message, ex); } finally { diff --git a/TestStack.ConventionTests/Internal/ConventionContext.cs b/TestStack.ConventionTests/Internal/ConventionContext.cs index 003eb5f..7065945 100644 --- a/TestStack.ConventionTests/Internal/ConventionContext.cs +++ b/TestStack.ConventionTests/Internal/ConventionContext.cs @@ -6,9 +6,21 @@ using System.Linq; using System.Reflection; using System.Text; + using TestStack.ConventionTests.Reporting; - public class ConventionContext : IConventionResult + public class ConventionContext : IConventionResult, IConventionContext { + readonly ICollection formatters; + readonly ICollection renderers; + + public ConventionContext(IConventionData data, ICollection formatters, + params IConventionReportRenderer[] renderers) + { + Data = data; + this.formatters = formatters; + this.renderers = renderers; + } + public string Message { get; private set; } public IEnumerable Items { get; set; } @@ -22,6 +34,18 @@ public class ConventionContext : IConventionResult public string FirstDescription { get; set; } public bool IsSymmetricResult { get; set; } + public IEnumerable Renderers + { + get { return renderers; } + } + + public IConventionData Data { get; private set; } + + public IEnumerable Formatters + { + get { return formatters; } + } + void IConventionResult.Is(IEnumerable items) { Items = items.Select(item => (object) item).ToList(); diff --git a/TestStack.ConventionTests/Internal/Executor.cs b/TestStack.ConventionTests/Internal/Executor.cs index 4c23d91..e883de6 100644 --- a/TestStack.ConventionTests/Internal/Executor.cs +++ b/TestStack.ConventionTests/Internal/Executor.cs @@ -6,19 +6,21 @@ public static class Executor { - public static ResultInfo GetConventionReport(string conventionTitle, IConventionData data, IEnumerable items) + public static ResultInfo GetConventionReport(string conventionTitle, IConventionContext context, + IEnumerable items) { var conventionResult = new ResultInfo( items.None() ? TestResult.Passed : TestResult.Failed, conventionTitle, - data.Description, - items.Select(FormatData).ToArray()); + context.Data.Description, + items.Select(o => FormatData(o, context)).ToArray()); return conventionResult; } - public static ResultInfo GetConventionReportWithApprovedExeptions(string conventionTitle, IConventionData data, IEnumerable items) + public static ResultInfo GetConventionReportWithApprovedExeptions(string conventionTitle, + IConventionContext context, IEnumerable items) { - var conventionResult = GetConventionReport(conventionTitle, data, items); + var conventionResult = GetConventionReport(conventionTitle, context, items); var conventionReportTextRenderer = new ConventionReportTextRenderer(); // Add approved exceptions to report conventionReportTextRenderer.RenderItems(conventionResult); @@ -27,9 +29,9 @@ public static ResultInfo GetConventionReportWithApprovedExeptions(string convent return conventionResult; } - static ConventionReportFailure FormatData(T failingData) + static ConventionReportFailure FormatData(T failingData, IConventionContext context) { - var formatter = Convention.Formatters.FirstOrDefault(f => f.CanFormat(failingData)); + var formatter = context.Formatters.FirstOrDefault(f => f.CanFormat(failingData)); if (formatter == null) throw new NoDataFormatterFoundException(typeof (T).Name + diff --git a/TestStack.ConventionTests/Internal/IConventionContext.cs b/TestStack.ConventionTests/Internal/IConventionContext.cs new file mode 100644 index 0000000..7353de2 --- /dev/null +++ b/TestStack.ConventionTests/Internal/IConventionContext.cs @@ -0,0 +1,12 @@ +namespace TestStack.ConventionTests.Internal +{ + using System.Collections.Generic; + using TestStack.ConventionTests.Reporting; + + public interface IConventionContext + { + IEnumerable Formatters { get; } + IEnumerable Renderers { get; } + IConventionData Data { get; } + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests/TestStack.ConventionTests.csproj b/TestStack.ConventionTests/TestStack.ConventionTests.csproj index c68cbb8..2773b3e 100644 --- a/TestStack.ConventionTests/TestStack.ConventionTests.csproj +++ b/TestStack.ConventionTests/TestStack.ConventionTests.csproj @@ -66,6 +66,7 @@ +