Skip to content

Commit 8d1d5d6

Browse files
vbreussvbtig
andauthored
feat: Add MatchName extensions for assemblies (#48)
Co-authored-by: Valentin Breuß <[email protected]>
1 parent a36fe8f commit 8d1d5d6

11 files changed

+409
-98
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System.Reflection;
2+
3+
namespace Testably.Architecture.Rules;
4+
5+
public static partial class FilterOnAssemblyExtensions
6+
{
7+
/// <summary>
8+
/// Filter for assemblies where the <see cref="AssemblyName.Name" /> matches the given <paramref name="pattern" />.
9+
/// </summary>
10+
public static IAssemblyFilterResult WhichMatchName(this IAssemblyFilter @this,
11+
Match pattern,
12+
bool ignoreCase = false)
13+
{
14+
return @this.Which(assembly => pattern.Matches(assembly.GetName().Name, ignoreCase));
15+
}
16+
17+
/// <summary>
18+
/// Filter for assemblies where the <see cref="AssemblyName.Name" /> does not match the given <paramref name="pattern" />.
19+
/// </summary>
20+
public static IAssemblyFilterResult WhichDoNotMatchName(this IAssemblyFilter @this,
21+
Match pattern,
22+
bool ignoreCase = false)
23+
{
24+
return @this.Which(assembly => !pattern.Matches(assembly.GetName().Name, ignoreCase));
25+
}
26+
}

Source/Testably.Architecture.Rules/Filters/FilterOnAssemblyExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace Testably.Architecture.Rules;
77
/// <summary>
88
/// Extension methods for <see cref="IAssemblyFilter" />.
99
/// </summary>
10-
public static class FilterOnAssemblyExtensions
10+
public static partial class FilterOnAssemblyExtensions
1111
{
1212
/// <summary>
1313
/// Filters the applicable <see cref="Assembly" /> on which the expectations should be applied.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.Linq;
2+
using System.Reflection;
3+
4+
namespace Testably.Architecture.Rules;
5+
6+
public static partial class RequirementOnAssemblyExtensions
7+
{
8+
/// <summary>
9+
/// The assembly should not have dependencies on any assembly that matches
10+
/// the <paramref name="pattern" />.
11+
/// </summary>
12+
/// <param name="this">The <see cref="IAssemblyExpectation" />.</param>
13+
/// <param name="pattern">
14+
/// The wildcard condition.
15+
/// <para />
16+
/// Supports * to match zero or more characters and ? to match exactly one character.
17+
/// </param>
18+
/// <param name="ignoreCase">Flag indicating if the comparison should be case sensitive or not.</param>
19+
public static IRequirementResult<Assembly> ShouldNotHaveDependenciesOn(
20+
this IRequirement<Assembly> @this,
21+
Match pattern,
22+
bool ignoreCase = false)
23+
{
24+
bool FailCondition(AssemblyName referencedAssembly)
25+
{
26+
return pattern.Matches(referencedAssembly.Name, ignoreCase);
27+
}
28+
29+
return @this.ShouldSatisfy(
30+
Requirement.ForAssembly(
31+
p => !p.GetReferencedAssemblies().Any(FailCondition),
32+
p => new DependencyTestError(p,
33+
p.GetReferencedAssemblies().Where(FailCondition).ToArray())));
34+
}
35+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System;
2+
using System.Reflection;
3+
4+
namespace Testably.Architecture.Rules;
5+
6+
public static partial class RequirementOnAssemblyExtensions
7+
{
8+
/// <summary>
9+
/// Expect the <see cref="AssemblyName.Name" /> of the assemblies to match the given <paramref name="pattern" />.
10+
/// </summary>
11+
/// <param name="this">The <see cref="IRequirement{Assembly}" />.</param>
12+
/// <param name="pattern">
13+
/// The wildcard condition.
14+
/// <para />
15+
/// Supports * to match zero or more characters and ? to match exactly one character.
16+
/// </param>
17+
/// <param name="ignoreCase">Flag indicating if the comparison should be case sensitive or not.</param>
18+
public static IRequirementResult<Assembly> ShouldMatchName(
19+
this IRequirement<Assembly> @this,
20+
Match pattern,
21+
bool ignoreCase = false)
22+
=> @this.ShouldSatisfy(Requirement.ForAssembly(
23+
assembly => pattern.Matches(assembly.GetName().Name, ignoreCase),
24+
assembly => new AssemblyTestError(assembly,
25+
$"Assembly '{assembly.GetName().Name}' should match pattern '{pattern}'.")));
26+
27+
/// <summary>
28+
/// Expect the <see cref="AssemblyName.Name" /> of the assemblies to not match the given <paramref name="pattern" />.
29+
/// </summary>
30+
/// <param name="this">The <see cref="IRequirement{Assembly}" />.</param>
31+
/// <param name="pattern">
32+
/// The wildcard condition.
33+
/// <para />
34+
/// Supports * to match zero or more characters and ? to match exactly one character.
35+
/// </param>
36+
/// <param name="ignoreCase">Flag indicating if the comparison should be case sensitive or not.</param>
37+
public static IRequirementResult<Assembly> ShouldNotMatchName(
38+
this IRequirement<Assembly> @this,
39+
Match pattern,
40+
bool ignoreCase = false)
41+
=> @this.ShouldSatisfy(Requirement.ForAssembly(
42+
assembly => !pattern.Matches(assembly.GetName().Name, ignoreCase),
43+
assembly => new AssemblyTestError(assembly,
44+
$"Assembly '{assembly.GetName().Name}' should not match pattern '{pattern}'.")));
45+
}

Source/Testably.Architecture.Rules/Requirements/RequirementOnAssemblyExtensions.cs

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,8 @@ namespace Testably.Architecture.Rules;
88
/// <summary>
99
/// Extension methods for <see cref="IRequirement{Assembly}" />.
1010
/// </summary>
11-
public static class RequirementOnAssemblyExtensions
11+
public static partial class RequirementOnAssemblyExtensions
1212
{
13-
/// <summary>
14-
/// The assembly should not have dependencies on any assembly that matches
15-
/// the <paramref name="pattern" />.
16-
/// </summary>
17-
/// <param name="this">The <see cref="IAssemblyExpectation" />.</param>
18-
/// <param name="pattern">
19-
/// The wildcard condition.
20-
/// <para />
21-
/// Supports * to match zero or more characters and ? to match exactly one character.
22-
/// </param>
23-
/// <param name="ignoreCase">Flag indicating if the comparison should be case sensitive or not.</param>
24-
public static IRequirementResult<Assembly> ShouldNotHaveDependenciesOn(
25-
this IRequirement<Assembly> @this,
26-
Match pattern,
27-
bool ignoreCase = false)
28-
{
29-
bool FailCondition(AssemblyName referencedAssembly)
30-
{
31-
return pattern.Matches(referencedAssembly.Name, ignoreCase);
32-
}
33-
34-
return @this.ShouldSatisfy(
35-
Requirement.ForAssembly(
36-
p => !p.GetReferencedAssemblies().Any(FailCondition),
37-
p => new DependencyTestError(p,
38-
p.GetReferencedAssemblies().Where(FailCondition).ToArray())));
39-
}
40-
4113
/// <summary>
4214
/// The <see cref="Assembly" /> should satisfy the given <paramref name="condition" />.
4315
/// </summary>
@@ -48,7 +20,7 @@ public static IRequirementResult<Assembly> ShouldSatisfy(
4820
Func<Assembly, bool> compiledCondition = condition.Compile();
4921
return @this.ShouldSatisfy(
5022
Requirement.ForAssembly(compiledCondition,
51-
assembly => new TestError(
23+
assembly => new AssemblyTestError(assembly,
5224
$"Assembly '{assembly.GetName().Name}' should satisfy the required condition {condition}.")));
5325
}
5426
}

Source/Testably.Architecture.Rules/Requirements/RequirementOnTypeExtensions.MatchName.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,5 @@ public static IRequirementResult<Type> ShouldNotMatchName(
4141
=> @this.ShouldSatisfy(Requirement.ForType(
4242
type => !pattern.Matches(type.Name, ignoreCase),
4343
type => new TypeTestError(type,
44-
$"Type '{type.Name}' not match pattern '{pattern}'.")));
44+
$"Type '{type.Name}' should not match pattern '{pattern}'.")));
4545
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using Testably.Architecture.Rules.Tests.TestHelpers;
2+
using Xunit;
3+
4+
namespace Testably.Architecture.Rules.Tests.Filters;
5+
6+
public sealed partial class FilterOnAssemblyExtensionsTests
7+
{
8+
public sealed class MatchNameTests
9+
{
10+
[Theory]
11+
[InlineData("TESTABLY.Architecture.RULES", false)]
12+
[InlineData("Testably.Architecture.Rules", true)]
13+
[InlineData("T???ably.Architecture.Rules", true)]
14+
[InlineData("*Rules", true)]
15+
[InlineData("Test*", true)]
16+
[InlineData("test*", false)]
17+
[InlineData("T*s", true)]
18+
[InlineData("*rules", false)]
19+
public void WhichDoNotMatchName_CaseSensitive_ShouldReturnExpectedValue(
20+
string pattern, bool expectMatch)
21+
{
22+
ITestResult result = Expect.That.Assemblies
23+
.WhichDoNotMatchName(pattern)
24+
.ShouldAlwaysFail()
25+
.AllowEmpty()
26+
.Check.InAssemblyContaining<ArchitectureRuleViolatedException>();
27+
28+
result.ShouldBeViolatedIf(!expectMatch);
29+
}
30+
31+
[Theory]
32+
[InlineData("TESTABLY.Architecture.RULES", true)]
33+
[InlineData("Testably.Architecture.Rules", true)]
34+
[InlineData("T???ably.Architecture.Rules", true)]
35+
[InlineData("*rules", true)]
36+
[InlineData("test*", true)]
37+
[InlineData("test???", false)]
38+
[InlineData("t*s", true)]
39+
public void WhichDoNotMatchName_WithIgnoreCase_ShouldReturnExpectedValue(
40+
string pattern, bool expectMatch)
41+
{
42+
ITestResult result = Expect.That.Assemblies
43+
.WhichDoNotMatchName(pattern, true)
44+
.ShouldAlwaysFail()
45+
.AllowEmpty()
46+
.Check.InAssemblyContaining<ArchitectureRuleViolatedException>();
47+
48+
result.ShouldBeViolatedIf(!expectMatch);
49+
}
50+
51+
[Theory]
52+
[InlineData("TESTABLY.Architecture.RULES", false)]
53+
[InlineData("Testably.Architecture.Rules", true)]
54+
[InlineData("T???ably.Architecture.Rules", true)]
55+
[InlineData("*Rules", true)]
56+
[InlineData("Test*", true)]
57+
[InlineData("test*", false)]
58+
[InlineData("T*s", true)]
59+
[InlineData("*rules", false)]
60+
public void WhichMatchName_CaseSensitive_ShouldReturnExpectedValue(
61+
string pattern, bool expectMatch)
62+
{
63+
ITestResult result = Expect.That.Assemblies
64+
.WhichMatchName(pattern)
65+
.ShouldAlwaysFail()
66+
.AllowEmpty()
67+
.Check.InAssemblyContaining<ArchitectureRuleViolatedException>();
68+
69+
result.ShouldBeViolatedIf(expectMatch);
70+
}
71+
72+
[Theory]
73+
[InlineData("TESTABLY.Architecture.RULES", true)]
74+
[InlineData("Testably.Architecture.Rules", true)]
75+
[InlineData("T???ably.Architecture.Rules", true)]
76+
[InlineData("*rules", true)]
77+
[InlineData("test*", true)]
78+
[InlineData("test???", false)]
79+
[InlineData("t*s", true)]
80+
public void WhichMatchName_WithIgnoreCase_ShouldReturnExpectedValue(
81+
string pattern, bool expectMatch)
82+
{
83+
ITestResult result = Expect.That.Assemblies
84+
.WhichMatchName(pattern, true)
85+
.ShouldAlwaysFail()
86+
.AllowEmpty()
87+
.Check.InAssemblyContaining<ArchitectureRuleViolatedException>();
88+
89+
result.ShouldBeViolatedIf(expectMatch);
90+
}
91+
}
92+
}

Tests/Testably.Architecture.Rules.Tests/Filters/FilterOnAssemblyExtensionsTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace Testably.Architecture.Rules.Tests.Filters;
66

7-
public sealed class FilterOnAssemblyExtensionsTests
7+
public sealed partial class FilterOnAssemblyExtensionsTests
88
{
99
[Fact]
1010
public void And_WithContraryConditions_ShouldReturnEmptyArray()
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using FluentAssertions;
2+
using System.Linq;
3+
using System.Reflection;
4+
using Testably.Abstractions.Testing;
5+
using Testably.Architecture.Rules.Tests.TestHelpers;
6+
using Xunit;
7+
8+
namespace Testably.Architecture.Rules.Tests.Requirements;
9+
10+
public sealed partial class RequirementOnAssemblyExtensionsTests
11+
{
12+
public sealed class HaveDependenciesOnTests
13+
{
14+
[Fact]
15+
public void ShouldNotHaveDependenciesOn_CaseSensitivity_ShouldDefaultToSensitive()
16+
{
17+
IRule rule = Expect.That.Assemblies
18+
.ShouldNotHaveDependenciesOn("testably.*");
19+
20+
ITestResult result = rule.Check
21+
.InAssemblyContaining<MockFileSystem>();
22+
23+
result.ShouldNotBeViolated();
24+
}
25+
26+
[Fact]
27+
public void ShouldNotHaveDependenciesOn_ErrorsShouldIncludeNameOfAllFailedReferences()
28+
{
29+
Assembly testAssembly = typeof(MockFileSystem).Assembly;
30+
string[] expectedReferences = testAssembly.GetReferencedAssemblies()
31+
.Select(x => x.FullName)
32+
.ToArray();
33+
34+
IRule rule = Expect.That.Assemblies
35+
.ShouldNotHaveDependenciesOn("*");
36+
37+
ITestResult result = rule.Check
38+
.InAssemblyContaining<MockFileSystem>();
39+
40+
result.ShouldBeViolated();
41+
42+
result.Errors.Length.Should().Be(1);
43+
TestError error = result.Errors.Single();
44+
error.Should().BeOfType<DependencyTestError>();
45+
DependencyTestError dependencyTestError = (DependencyTestError)error;
46+
47+
dependencyTestError.Assembly.Should().BeSameAs(testAssembly);
48+
dependencyTestError.AssemblyReferences.Length.Should()
49+
.Be(expectedReferences.Length);
50+
foreach (string reference in expectedReferences)
51+
{
52+
dependencyTestError.AssemblyReferences
53+
.Select(x => x.FullName)
54+
.Should().Contain(reference);
55+
}
56+
}
57+
58+
[Fact]
59+
public void ShouldNotHaveDependenciesOn_WithDependencyStartingWithPrefix_ShouldReturnFalse()
60+
{
61+
IRule rule = Expect.That.Assemblies
62+
.ShouldNotHaveDependenciesOn("Testably.*");
63+
64+
ITestResult result = rule.Check
65+
.InAssemblyContaining<MockFileSystem>();
66+
67+
result.ShouldBeViolated();
68+
}
69+
70+
[Theory]
71+
[InlineData(false)]
72+
[InlineData(true)]
73+
public void ShouldNotHaveDependenciesOn_WithIgnoreCaseParameter_ShouldConsiderCaseSensitivity(
74+
bool ignoreCase)
75+
{
76+
IRule rule = Expect.That.Assemblies
77+
.ShouldNotHaveDependenciesOn("testably.*", ignoreCase);
78+
79+
ITestResult result = rule.Check
80+
.InAssemblyContaining<MockFileSystem>();
81+
82+
result.ShouldBeViolatedIf(ignoreCase);
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)