Skip to content

Commit fa076fe

Browse files
committed
added ConvetionTest/Data for non-type based tests
added IProjectLocator for locating based on debugging info in a project
1 parent bd05325 commit fa076fe

10 files changed

+264
-9
lines changed

ConventionTests/ConventionTests.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<Reference Include="Castle.Windsor">
4444
<HintPath>..\packages\Castle.Windsor.3.0.0.4001\lib\net40\Castle.Windsor.dll</HintPath>
4545
</Reference>
46+
<Reference Include="Microsoft.Build.Engine" />
4647
<Reference Include="Mono.Cecil">
4748
<HintPath>..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.dll</HintPath>
4849
</Reference>
@@ -67,12 +68,18 @@
6768
<Reference Include="System.Xml" />
6869
</ItemGroup>
6970
<ItemGroup>
71+
<Compile Include="Conventions\Internal\AssemblyProjectLocator.cs" />
7072
<Compile Include="Conventions\Internal\ConventionData.cs" />
73+
<Compile Include="Conventions\Internal\ConventionData.Generic.cs" />
7174
<Compile Include="Conventions\Internal\ConventionTest.cs" />
75+
<Compile Include="Conventions\Internal\ConventionTest.Generic.cs" />
7276
<Compile Include="Conventions\Internal\ConventionTestBase.cs" />
7377
<Compile Include="Conventions\Internal\ConventionTestNamer.cs" />
7478
<Compile Include="Conventions\Internal\IAssert.cs" />
7579
<Compile Include="Conventions\Internal\IConventionTest.cs" />
80+
<Compile Include="Conventions\Internal\IProjectLocator.cs" />
81+
<Compile Include="Conventions\Internal\ProjectConventionData.cs" />
82+
<Compile Include="Conventions\Internal\ProjectConvetionTest.cs" />
7683
<Compile Include="Conventions\Internal\ReflectionExtensions.cs" />
7784
<Compile Include="Conventions\Internal\WindsorConventionData.cs" />
7885
<Compile Include="Conventions\Internal\WindsorConventionTest.cs" />
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
namespace ConventionTests
2+
{
3+
using System;
4+
using System.Reflection;
5+
using Mono.Cecil;
6+
using Mono.Cecil.Cil;
7+
8+
public class AssemblyProjectLocator : IProjectLocator
9+
{
10+
readonly Assembly assembly;
11+
12+
public AssemblyProjectLocator(Assembly assembly)
13+
{
14+
this.assembly = assembly;
15+
}
16+
17+
public string ProjectFilePath
18+
{
19+
get { return ResolveProjectFilePath(); }
20+
}
21+
22+
string ResolveProjectFilePath()
23+
{
24+
var readerParameters = new ReaderParameters {ReadSymbols = true, ReadingMode = ReadingMode.Deferred};
25+
var definition = AssemblyDefinition.ReadAssembly(assembly.Location, readerParameters);
26+
var methodDefinition = GetMethodWithBody(definition);
27+
var document = GetMethodDocument(methodDefinition);
28+
return document.Url;
29+
}
30+
31+
static Document GetMethodDocument(MethodDefinition methodDefinition)
32+
{
33+
var instruction = methodDefinition.Body.Instructions[0];
34+
var document = instruction.SequencePoint.Document;
35+
return document;
36+
}
37+
38+
static MethodDefinition GetMethodWithBody(AssemblyDefinition definition)
39+
{
40+
foreach (var type in definition.MainModule.Types)
41+
{
42+
if (type.HasMethods == false) continue;
43+
44+
foreach (var method in type.Methods)
45+
{
46+
if (method.HasBody)
47+
{
48+
return method;
49+
}
50+
}
51+
}
52+
throw new InvalidOperationException("Coudln't find any type with methods with body");
53+
}
54+
}
55+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
namespace ConventionTests
2+
{
3+
using System;
4+
using System.Text;
5+
6+
/// <summary>
7+
/// This is where we set what our convention is all about
8+
/// </summary>
9+
public class ConventionData<TItem>
10+
{
11+
public static readonly Predicate<TItem> All = _ => true;
12+
public static readonly Predicate<TItem> None = _ => false;
13+
Action<TItem, StringBuilder> DefaultItemDescription = DefaultItemDescriptionMethod;
14+
15+
static void DefaultItemDescriptionMethod(TItem item, StringBuilder message)
16+
{
17+
if (ReferenceEquals(item, null))
18+
{
19+
message.AppendLine("<<null>>");
20+
}
21+
else
22+
{
23+
message.AppendLine(item.ToString());
24+
}
25+
}
26+
27+
28+
public ConventionData(params TItem[] sourceItems)
29+
{
30+
SourceItems = sourceItems;
31+
Items = All;
32+
Must = None;
33+
OrderBy = HashCode;
34+
ItemDescription = DefaultItemDescription;
35+
}
36+
37+
public TItem[] SourceItems { get; set; }
38+
39+
public Func<TItem, object> OrderBy { get; set; }
40+
41+
/// <summary>
42+
/// Descriptive text used for failure message in test. Should explan what is wrong, and how to fix it (how to make types that do not conform to the convention do so).
43+
/// </summary>
44+
public string Description { get; set; }
45+
46+
/// <summary>
47+
/// Specifies that there are valid exceptions to the rule specified by the convention.
48+
/// </summary>
49+
/// <remarks>
50+
/// When set to <c>true</c> will run the test as Approval test so that the exceptional cases can be reviewed and approved.
51+
/// </remarks>
52+
public bool HasApprovedExceptions { get; set; }
53+
54+
/// <summary>
55+
/// This is the convention. The predicate should return <c>true</c> for types that do conform to the convention, and <c>false</c> otherwise
56+
/// </summary>
57+
public Predicate<TItem> Must { get; set; }
58+
59+
/// <summary>
60+
/// Predicate that finds types that we want to apply out convention to.
61+
/// </summary>
62+
public Predicate<TItem> Items { get; set; }
63+
64+
public Action<TItem, StringBuilder> ItemDescription { get; set; }
65+
66+
object HashCode(TItem arg)
67+
{
68+
if (ReferenceEquals(arg, null))
69+
{
70+
return 0;
71+
}
72+
return arg.GetHashCode();
73+
}
74+
75+
/// <summary>
76+
/// helper method to set <see cref="HasApprovedExceptions" /> in a more convenient manner.
77+
/// </summary>
78+
/// <returns> </returns>
79+
public ConventionData<TItem> WithApprovedExceptions(string explanation = null)
80+
{
81+
HasApprovedExceptions = true;
82+
return this;
83+
}
84+
}
85+
}

ConventionTests/Conventions/Internal/ConventionData.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{
33
using System;
44
using System.Reflection;
5+
using System.Text;
56

67
/// <summary>
78
/// This is where we set what our convention is all about
@@ -31,7 +32,6 @@ public ConventionData(params Type[] sourceTypes)
3132

3233
public Type[] SourceTypes { get; set; }
3334

34-
3535
/// <summary>
3636
/// list of assemblies to scan for types that our convention is related to. Can be null, in which case all assemblies starting with The same prefix as your test assembly will be scanned
3737
/// </summary>
@@ -60,7 +60,7 @@ public ConventionData(params Type[] sourceTypes)
6060
/// </summary>
6161
public Predicate<Type> Types { get; set; }
6262

63-
public Func<Type, string> ItemDescription { get; set; }
63+
public Action<Type, StringBuilder> ItemDescription { get; set; }
6464

6565
/// <summary>
6666
/// helper method to set <see cref="Assemblies" /> in a more convenient manner.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
namespace ConventionTests
2+
{
3+
using System;
4+
using System.Linq;
5+
using System.Text;
6+
7+
/// <summary>
8+
/// Base class for convention tests. Inherited types should be put in "/Conventions" folder in test assembly and follow Sentence_naming_convention_with_underscores_indead_of_spaces These tests will be ran by
9+
/// <see
10+
/// cref="ConventionTestsRunner" />
11+
/// .
12+
/// </summary>
13+
public abstract class ConventionTest<TItem> : ConventionTestBase
14+
{
15+
public override void Execute(IAssert assert)
16+
{
17+
var data = SetUp();
18+
var itemsToTest = GetItemsToTest(data);
19+
if (itemsToTest.Length == 0)
20+
{
21+
assert.Inconclusive(
22+
"No items found to apply the convention to. Make sure the Items predicate is correct and that the right sourceItems are specified.");
23+
}
24+
var invalidItems = Array.FindAll(itemsToTest, t => data.Must(t) == false);
25+
var message = new StringBuilder();
26+
message.AppendLine(data.Description ?? "Invalid items found");
27+
foreach (var invalidType in invalidItems)
28+
{
29+
message.Append('\t');
30+
data.ItemDescription(invalidType, message);
31+
}
32+
if (data.HasApprovedExceptions)
33+
{
34+
Approve(message.ToString());
35+
}
36+
else
37+
{
38+
assert.AreEqual(0, invalidItems.Count(), message.ToString());
39+
}
40+
}
41+
42+
/// <summary>
43+
/// This is the only method you need to override. Return a <see cref="ConventionData" /> that describes your convention.
44+
/// </summary>
45+
/// <returns> </returns>
46+
protected abstract ConventionData<TItem> SetUp();
47+
48+
protected virtual TItem[] GetItemsToTest(ConventionData<TItem> data)
49+
{
50+
return data.SourceItems
51+
.Where(data.Items.Invoke)
52+
.OrderBy(data.OrderBy)
53+
.ToArray();
54+
}
55+
}
56+
}

ConventionTests/Conventions/Internal/ConventionTest.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System;
44
using System.Linq;
55
using System.Reflection;
6+
using System.Text;
67

78
/// <summary>
89
/// Base class for convention tests. Inherited types should be put in "/Conventions" folder in test assembly and follow Sentence_naming_convention_with_underscores_indead_of_spaces These tests will be ran by
@@ -21,17 +22,22 @@ public override void Execute(IAssert assert)
2122
assert.Inconclusive(
2223
"No types found to apply the convention to. Make sure the Types predicate is correct and that the right assemblies to scan are specified.");
2324
}
24-
var itemDescription = (data.ItemDescription ?? (t => t.ToString()));
25-
var invalidTypes = Array.FindAll(typesToTest, t => data.Must(t) == false);
26-
var message = (data.Description ?? "Invalid types found") + Environment.NewLine + "\t" +
27-
string.Join(Environment.NewLine + "\t", invalidTypes.Select(itemDescription));
25+
var invalidItems = Array.FindAll(typesToTest, t => data.Must(t) == false);
26+
27+
var message = new StringBuilder();
28+
message.AppendLine(data.Description ?? "Invalid types found");
29+
foreach (var invalidType in invalidItems)
30+
{
31+
message.Append('\t');
32+
data.ItemDescription(invalidType, message);
33+
}
2834
if (data.HasApprovedExceptions)
2935
{
30-
Approve(message);
36+
Approve(message.ToString());
3137
}
3238
else
3339
{
34-
assert.AreEqual(0, invalidTypes.Count(), message);
40+
assert.AreEqual(0, invalidItems.Count(), message.ToString());
3541
}
3642
}
3743

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace ConventionTests
2+
{
3+
public interface IProjectLocator
4+
{
5+
string ProjectFilePath { get; }
6+
}
7+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace ConventionTests
2+
{
3+
using System;
4+
using System.Reflection;
5+
6+
public class ProjectConventionData
7+
{
8+
readonly IProjectLocator projectLocator;
9+
10+
public ProjectConventionData(IProjectLocator projectLocator)
11+
{
12+
this.projectLocator = projectLocator;
13+
}
14+
15+
public ProjectConventionData(Assembly assembly) : this(new AssemblyProjectLocator(assembly))
16+
{
17+
18+
}
19+
20+
public ProjectConventionData(Type type) : this(type.Assembly)
21+
{
22+
}
23+
}
24+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace ConventionTests
2+
{
3+
public abstract class ProjectConvetionTest : ConventionTestBase
4+
{
5+
public override void Execute(IAssert assert)
6+
{
7+
var data = SetUp();
8+
}
9+
10+
/// <summary>
11+
/// This is the only method you need to override. Return a <see cref="ConventionData" /> that describes your convention.
12+
/// </summary>
13+
/// <returns> </returns>
14+
protected abstract ProjectConventionData SetUp();
15+
}
16+
}

ConventionTests/Conventions/Internal/WindsorConventionTest.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ TDiagnosticData[] GetDataToTest(WindsorConventionData<TDiagnosticData> data)
7171
protected abstract WindsorConventionData<TDiagnosticData> SetUp();
7272
}
7373

74-
7574
public abstract class WindsorConventionTest : ConventionTestBase
7675
{
7776
public override void Execute(IAssert assert)

0 commit comments

Comments
 (0)