diff --git a/TestAssembly/Collections/Branch.cs b/TestAssembly/Collections/Branch.cs new file mode 100644 index 0000000..0530072 --- /dev/null +++ b/TestAssembly/Collections/Branch.cs @@ -0,0 +1,20 @@ +namespace TestAssembly.Collections +{ + using System.Collections; + using System.Collections.Generic; + + public class Branch : IEnumerable + { + readonly List items = new List(); + + public IEnumerator GetEnumerator() + { + return items.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/TestAssembly/Collections/Forest.cs b/TestAssembly/Collections/Forest.cs new file mode 100644 index 0000000..cff3c49 --- /dev/null +++ b/TestAssembly/Collections/Forest.cs @@ -0,0 +1,30 @@ +namespace TestAssembly.Collections +{ + using System.Collections; + using System.Collections.Generic; + + public class Forest : ICanAdd, ICanRemove + { + readonly List items = new List(); + + public void Add(Tree item) + { + items.Add(item); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable) items).GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return items.GetEnumerator(); + } + + public bool Remove(Tree item) + { + return items.Remove(item); + } + } +} \ No newline at end of file diff --git a/TestAssembly/Collections/ICanAdd.cs b/TestAssembly/Collections/ICanAdd.cs new file mode 100644 index 0000000..1115878 --- /dev/null +++ b/TestAssembly/Collections/ICanAdd.cs @@ -0,0 +1,9 @@ +namespace TestAssembly.Collections +{ + using System.Collections.Generic; + + public interface ICanAdd : IEnumerable + { + void Add(T item); + } +} \ No newline at end of file diff --git a/TestAssembly/Collections/ICanRemove.cs b/TestAssembly/Collections/ICanRemove.cs new file mode 100644 index 0000000..23cd643 --- /dev/null +++ b/TestAssembly/Collections/ICanRemove.cs @@ -0,0 +1,9 @@ +namespace TestAssembly.Collections +{ + using System.Collections.Generic; + + public interface ICanRemove : IEnumerable + { + bool Remove(T item); + } +} \ No newline at end of file diff --git a/TestAssembly/Collections/Leaf.cs b/TestAssembly/Collections/Leaf.cs new file mode 100644 index 0000000..42dfd08 --- /dev/null +++ b/TestAssembly/Collections/Leaf.cs @@ -0,0 +1,6 @@ +namespace TestAssembly.Collections +{ + public class Leaf + { + } +} \ No newline at end of file diff --git a/TestAssembly/Collections/Tree.cs b/TestAssembly/Collections/Tree.cs new file mode 100644 index 0000000..e8da998 --- /dev/null +++ b/TestAssembly/Collections/Tree.cs @@ -0,0 +1,31 @@ +namespace TestAssembly.Collections +{ + using System.Collections; + using System.Collections.Generic; + using System.Linq; + + public class Tree : ICanAdd, IEnumerable + { + readonly List items = new List(); + + public void Add(Branch item) + { + items.Add(item); + } + + public IEnumerator GetEnumerator() + { + return items.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return items.SelectMany(i => i).GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/TestAssembly/TestAssembly.csproj b/TestAssembly/TestAssembly.csproj index 253435e..7be1ca1 100644 --- a/TestAssembly/TestAssembly.csproj +++ b/TestAssembly/TestAssembly.csproj @@ -44,6 +44,12 @@ + + + + + + diff --git a/TestStack.ConventionTests.Tests/CsvReportTests.Can_run_convention_with_simple_reporter.approved.txt b/TestStack.ConventionTests.Tests/CsvReportTests.Can_run_convention_with_simple_reporter.approved.txt new file mode 100644 index 0000000..07a98d3 --- /dev/null +++ b/TestStack.ConventionTests.Tests/CsvReportTests.Can_run_convention_with_simple_reporter.approved.txt @@ -0,0 +1,5 @@ +collection,item,can add,can remove +TestAssembly.Collections.Branch,TestAssembly.Collections.Leaf,False,False +TestAssembly.Collections.Forest,TestAssembly.Collections.Tree,True,True +TestAssembly.Collections.Tree,TestAssembly.Collections.Branch,True,False +TestAssembly.Collections.Tree,TestAssembly.Collections.Leaf,False,False diff --git a/TestStack.ConventionTests.Tests/CsvReportTests.cs b/TestStack.ConventionTests.Tests/CsvReportTests.cs new file mode 100644 index 0000000..1fbf327 --- /dev/null +++ b/TestStack.ConventionTests.Tests/CsvReportTests.cs @@ -0,0 +1,24 @@ +namespace TestStack.ConventionTests.Tests +{ + using System.Linq; + using ApprovalTests.Reporters; + using NUnit.Framework; + using TestAssembly.Collections; + using TestStack.ConventionTests.ConventionData; + using TestStack.ConventionTests.Tests.TestConventions; + + [UseReporter(typeof(DiffReporter))] + public class CsvReportTests + { + [Test] + public void Can_run_convention_with_simple_reporter() + { + Convention.IsWithApprovedExeptions(new CollectionsRelationsConvention(), new Types + { + TypesToVerify = + typeof (Leaf).Assembly.GetExportedTypes() + .Where(t => t.Namespace == typeof (Leaf).Namespace).ToArray() + }); + } + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests.Tests/TestConventions/CollectionsRelationsConvention.cs b/TestStack.ConventionTests.Tests/TestConventions/CollectionsRelationsConvention.cs new file mode 100644 index 0000000..1e342a4 --- /dev/null +++ b/TestStack.ConventionTests.Tests/TestConventions/CollectionsRelationsConvention.cs @@ -0,0 +1,41 @@ +namespace TestStack.ConventionTests.Tests.TestConventions +{ + using System; + using System.Collections.Generic; + using System.Linq; + using TestAssembly.Collections; + using TestStack.ConventionTests.ConventionData; + using TestStack.ConventionTests.Internal; + + public class CollectionsRelationsConvention : IConvention + { + public ConventionResult Execute(Types data) + { + var types = data.TypesToVerify; + var collectionToItemLookup = from collection in types + where collection.IsClass + orderby collection.FullName + from item in GetItemTypes(collection) + select new + { + collection, + item, + can_add = typeof (ICanAdd<>).MakeGenericType(item).IsAssignableFrom(collection), + can_remove = typeof (ICanRemove<>).MakeGenericType(item).IsAssignableFrom(collection) + }; + + // I feel so badass for using an anonymous type as generic parameter here :) + return ConventionResult.For(collectionToItemLookup, "well, does the header apply here all across the board? How would that work for CSV?"); + } + + IEnumerable GetItemTypes(Type type) + { + return from @interface in type.GetInterfaces() + where @interface.IsGenericType + where @interface.GetGenericTypeDefinition() == typeof (IEnumerable<>) + let item = @interface.GetGenericArguments().Single() + orderby item.FullName + select item; + } + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests.Tests/TestStack.ConventionTests.Tests.csproj b/TestStack.ConventionTests.Tests/TestStack.ConventionTests.Tests.csproj index 5736818..43afb54 100644 --- a/TestStack.ConventionTests.Tests/TestStack.ConventionTests.Tests.csproj +++ b/TestStack.ConventionTests.Tests/TestStack.ConventionTests.Tests.csproj @@ -52,6 +52,7 @@ + @@ -59,6 +60,7 @@ True Resources.resx + diff --git a/TestStack.ConventionTests.Tests/TypeBasedConventions.cs b/TestStack.ConventionTests.Tests/TypeBasedConventions.cs index 51f7df6..c65833c 100644 --- a/TestStack.ConventionTests.Tests/TypeBasedConventions.cs +++ b/TestStack.ConventionTests.Tests/TypeBasedConventions.cs @@ -1,5 +1,6 @@ namespace TestStack.ConventionTests.Tests { + using System.Linq; using ApprovalTests; using ApprovalTests.Reporters; using NUnit.Framework; @@ -16,7 +17,9 @@ public class TypeBasedConventions public TypeBasedConventions() { - var itemsToVerify = typeof (SampleDomainClass).Assembly.GetTypes(); + var itemsToVerify = typeof (SampleDomainClass).Assembly.GetTypes() + .Where(t => t.IsClass && t.Namespace == typeof (SampleDomainClass).Namespace) + .ToArray(); nhibernateEntities = new Types { TypesToVerify = itemsToVerify diff --git a/TestStack.ConventionTests/Convention.cs b/TestStack.ConventionTests/Convention.cs index 0d60479..6489ec2 100644 --- a/TestStack.ConventionTests/Convention.cs +++ b/TestStack.ConventionTests/Convention.cs @@ -1,35 +1,50 @@ namespace TestStack.ConventionTests { + using System; using System.Diagnostics; using ApprovalTests; using ApprovalTests.Core.Exceptions; + using TestStack.ConventionTests.Internal; public static class Convention { public static void Is(IConvention convention, TData data) where TData : IConventionData { - data.EnsureHasNonEmptySource(); - var result = convention.Execute(data); + var result = Execute(convention, data); if (result.Failed) throw new ConventionFailedException(result.Message); } - public static void IsWithApprovedExeptions(IConvention convention, TData data) where TData : IConventionData + static ConventionResult Execute(IConvention convention, TData data) where TData : IConventionData { data.EnsureHasNonEmptySource(); var result = convention.Execute(data); + return result; + } + + public static void IsWithApprovedExeptions(IConvention convention, TData data) + where TData : IConventionData + { + var result = Execute(convention, data); // should we encapsulate Approvals behind Settings? try { Approvals.Verify(result.Message); - Trace.WriteLine(string.Format("{0} has approved exceptions:\r\n\r\n{1}", convention.GetType().Name, result.Message)); + Trace.WriteLine(string.Format("{0} has approved exceptions:{2}{2}{1}", + convention.GetType().Name, + result.Message, + Environment.NewLine)); } 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" + + Environment.NewLine + + Environment.NewLine + + ex.Message, ex); } } } diff --git a/TestStack.ConventionTests/Internal/ConventionResult.cs b/TestStack.ConventionTests/Internal/ConventionResult.cs index 59e655c..5e38f57 100644 --- a/TestStack.ConventionTests/Internal/ConventionResult.cs +++ b/TestStack.ConventionTests/Internal/ConventionResult.cs @@ -1,8 +1,10 @@ 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 @@ -18,6 +20,26 @@ 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, @@ -74,7 +96,7 @@ public static ConventionResult ForSymmetric( if (firstArray.Any()) { message.AppendLine(); - message.AppendLine(); + message.AppendLine(); } message.AppendLine(secondHeader); message.AppendLine(string.Empty.PadRight(secondHeader.Length, '-')); @@ -92,8 +114,50 @@ public static ConventionResult ForSymmetric( { return ForSymmetric( firstHeader, firstResults, secondHeader, - secondResults, - (item, message) => message.AppendLine(itemDescriptor(item))); + 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