diff --git a/.csharpierignore b/.csharpierignore
new file mode 100644
index 000000000..801717d9f
--- /dev/null
+++ b/.csharpierignore
@@ -0,0 +1,3 @@
+# XmlViolationStore rewrites this file without a trailing newline on every test run; CSharpier's
+# XML formatter requires one. Excluded so `csharpier check` doesn't flag the store's own output.
+ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.xml
diff --git a/ArchUnit.sln b/ArchUnit.sln
index 43206a38a..5f7c40a35 100644
--- a/ArchUnit.sln
+++ b/ArchUnit.sln
@@ -63,6 +63,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PropertyMemberAssembly", "T
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassAssembly", "TestAssemblies\ClassAssembly\ClassAssembly.csproj", "{2C04EB93-5AE0-474C-B328-145A734A5E2B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlicesTestAssembly", "TestAssemblies\SlicesTestAssembly\SlicesTestAssembly.csproj", "{E421FA12-BBB9-4A8B-BD29-8757836079A5}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A780B3D6-C525-4270-ABF1-63BA65EA1593}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
@@ -427,6 +429,18 @@ Global
{2C04EB93-5AE0-474C-B328-145A734A5E2B}.Release|x64.Build.0 = Release|Any CPU
{2C04EB93-5AE0-474C-B328-145A734A5E2B}.Release|x86.ActiveCfg = Release|Any CPU
{2C04EB93-5AE0-474C-B328-145A734A5E2B}.Release|x86.Build.0 = Release|Any CPU
+ {E421FA12-BBB9-4A8B-BD29-8757836079A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E421FA12-BBB9-4A8B-BD29-8757836079A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E421FA12-BBB9-4A8B-BD29-8757836079A5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E421FA12-BBB9-4A8B-BD29-8757836079A5}.Debug|x64.Build.0 = Debug|Any CPU
+ {E421FA12-BBB9-4A8B-BD29-8757836079A5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E421FA12-BBB9-4A8B-BD29-8757836079A5}.Debug|x86.Build.0 = Debug|Any CPU
+ {E421FA12-BBB9-4A8B-BD29-8757836079A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E421FA12-BBB9-4A8B-BD29-8757836079A5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E421FA12-BBB9-4A8B-BD29-8757836079A5}.Release|x64.ActiveCfg = Release|Any CPU
+ {E421FA12-BBB9-4A8B-BD29-8757836079A5}.Release|x64.Build.0 = Release|Any CPU
+ {E421FA12-BBB9-4A8B-BD29-8757836079A5}.Release|x86.ActiveCfg = Release|Any CPU
+ {E421FA12-BBB9-4A8B-BD29-8757836079A5}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -444,6 +458,7 @@ Global
{05F40E4D-94EC-4DF7-B0FA-1BCBFDF56088} = {B1191F18-91CB-4387-B775-A5EB64D3AC30}
{1D0187EB-9D04-4CF3-AE63-0C0E97FCB49C} = {B1191F18-91CB-4387-B775-A5EB64D3AC30}
{2C04EB93-5AE0-474C-B328-145A734A5E2B} = {B1191F18-91CB-4387-B775-A5EB64D3AC30}
+ {E421FA12-BBB9-4A8B-BD29-8757836079A5} = {B1191F18-91CB-4387-B775-A5EB64D3AC30}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D1A8274D-32D6-44DB-9BB3-1A5B273709AF}
diff --git a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSlice.cs b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSlice.cs
index 342ccbba6..ad4aa0405 100644
--- a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSlice.cs
+++ b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSlice.cs
@@ -112,6 +112,7 @@ private StringBuilder BuildStringC4Style()
if (Namespace == null)
{
result.Append("Container(" + _name + ", " + _name + ")");
+ return result;
}
var namespc = Namespace.Remove(Namespace.Length - 1);
diff --git a/ArchUnitNETTests/ArchUnitNET/Storage/CustomPathFrozenRules.json b/ArchUnitNETTests/ArchUnitNET/Storage/CustomPathFrozenRules.json
index f42d52c8b..9216791d3 100644
--- a/ArchUnitNETTests/ArchUnitNET/Storage/CustomPathFrozenRules.json
+++ b/ArchUnitNETTests/ArchUnitNET/Storage/CustomPathFrozenRules.json
@@ -13,7 +13,7 @@
]
},
{
- "ArchRuleDescription": "Slices matching \"TestAssembly.Slices.(**)\" should not depend on each other",
+ "ArchRuleDescription": "Slices matching \"SlicesTestAssembly.MultipleSubnamespaces.(**)\" should not depend on each other",
"Violations": [
"Slice3",
"Slice3.Group1",
diff --git a/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.json b/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.json
index 06bbca207..5e4531c65 100644
--- a/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.json
+++ b/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.json
@@ -19,7 +19,7 @@
]
},
{
- "ArchRuleDescription": "Slices matching \"TestAssembly.Slices.(**)\" should not depend on each other",
+ "ArchRuleDescription": "Slices matching \"SlicesTestAssembly.MultipleSubnamespaces.(**)\" should not depend on each other",
"Violations": [
"Slice3",
"Slice3.Group1",
@@ -30,7 +30,7 @@
]
},
{
- "ArchRuleDescription": "Slices matching \"TestAssembly.Slices.(*)..\" should not depend on each other",
+ "ArchRuleDescription": "Slices matching \"SlicesTestAssembly.MultipleSubnamespaces.(*)..\" should not depend on each other",
"Violations": []
}
]
\ No newline at end of file
diff --git a/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.xml b/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.xml
index a174b04db..c0905ff78 100644
--- a/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.xml
+++ b/ArchUnitNETTests/ArchUnitNET/Storage/FrozenRules.xml
@@ -3,7 +3,7 @@
ArchUnitNETTests.Fluent.FreezeTests+Violation
-
+
ArchUnitNETTests.Fluent.FreezeTests+Violation
ArchUnitNETTests.Fluent.FreezeTests+Violation2
@@ -11,7 +11,7 @@
ArchUnitNETTests.Fluent.FreezeTests+Violation
-
+
Slice3
Slice3.Group1
Slice2
@@ -19,4 +19,4 @@
Slice1
Slice1.Service
-
+
\ No newline at end of file
diff --git a/ArchUnitNETTests/ArchUnitNETTests.csproj b/ArchUnitNETTests/ArchUnitNETTests.csproj
index 8fb6edaf1..c56d20a5a 100644
--- a/ArchUnitNETTests/ArchUnitNETTests.csproj
+++ b/ArchUnitNETTests/ArchUnitNETTests.csproj
@@ -35,6 +35,7 @@
+
diff --git a/ArchUnitNETTests/AssemblyTestHelper/AssemblyTestHelper.cs b/ArchUnitNETTests/AssemblyTestHelper/AssemblyTestHelper.cs
index 639b51be4..9fdfa57e8 100644
--- a/ArchUnitNETTests/AssemblyTestHelper/AssemblyTestHelper.cs
+++ b/ArchUnitNETTests/AssemblyTestHelper/AssemblyTestHelper.cs
@@ -15,11 +15,26 @@ namespace ArchUnitNETTests.AssemblyTestHelper;
public abstract class AssemblyTestHelper
{
private readonly StringBuilder _snapshot = new StringBuilder();
+ private bool _recordSnapshot = true;
public readonly string NonExistentObjectName = "NotTheNameOfAnyObject";
public abstract Architecture Architecture { get; }
+ public AssemblyTestHelper WithoutSnapshot()
+ {
+ _recordSnapshot = false;
+ return this;
+ }
+
+ private void Record(string output)
+ {
+ if (_recordSnapshot)
+ {
+ _snapshot.Append(output);
+ }
+ }
+
public void AddSnapshotHeader(string header)
{
_snapshot.AppendLine("===== " + header + " =====\n");
@@ -62,15 +77,45 @@ private static bool HasNoRealResults(IEnumerable results)
return results.Any(result => result.EvaluatedObject is ICanBeEvaluated);
}
+ ///
+ /// Asserts the rule's verdict -- -- rather than the
+ /// shape of its results. The distinction matters for
+ /// : its verdict applies the conjunction, while its
+ /// merely concatenates both operands' results and
+ /// never applies it. A passing Or whose first operand fails therefore has failing
+ /// results, which is why this assertion cannot be expressed as
+ /// results.All(r => r.Passed), and why and
+ /// -- which do read the results -- say nothing about a
+ /// combinator.
+ ///
public void AssertNoViolations(IArchRule rule)
{
var results = rule.Evaluate(Architecture).ToList();
var output = FormatSnapshot(rule, results);
- if (!results.All(result => result.Passed))
+ if (!rule.HasNoViolations(Architecture))
{
Assert.Fail(output);
}
- _snapshot.Append(output);
+ Record(output);
+ }
+
+ ///
+ /// The exact negation of , and like it a statement about the
+ /// verdict, not the results -- see there for why the two differ. Use it wherever the point of
+ /// the test is that a rule fails as a whole; prefer when
+ /// the point is that every evaluated object was reported, since this assertion is also
+ /// satisfied by the failing placeholder injects
+ /// for a rule whose input set is empty.
+ ///
+ public void AssertHasViolations(IArchRule rule)
+ {
+ var results = rule.Evaluate(Architecture).ToList();
+ var output = FormatSnapshot(rule, results);
+ if (rule.HasNoViolations(Architecture))
+ {
+ Assert.Fail(output);
+ }
+ Record(output);
}
public void AssertAnyViolations(IArchRule rule)
@@ -85,7 +130,7 @@ public void AssertAnyViolations(IArchRule rule)
{
Assert.Fail(output);
}
- _snapshot.Append(output);
+ Record(output);
}
public void AssertOnlyViolations(IArchRule rule)
@@ -109,7 +154,7 @@ public void AssertOnlyViolations(IArchRule rule, bool allowNoResults)
+ output
);
}
- _snapshot.Append(output);
+ Record(output);
}
public void AssertException(IArchRule rule)
@@ -123,6 +168,15 @@ public void AssertException(IArchRule rule)
public Task AssertSnapshotMatches([CallerFilePath] string sourceFile = "")
{
+ if (!_recordSnapshot)
+ {
+ Assert.Fail(
+ "This helper was created with WithoutSnapshot(), so nothing was recorded and the "
+ + "snapshot would verify as empty. Drop the WithoutSnapshot() call or the "
+ + "AssertSnapshotMatches() call."
+ );
+ }
+
return Verifier
.Verify(_snapshot.ToString(), null, sourceFile)
.DisableDiff() // Don't open diff tool during the test
diff --git a/ArchUnitNETTests/AssemblyTestHelper/AssemblyTestHelperExtensions.cs b/ArchUnitNETTests/AssemblyTestHelper/AssemblyTestHelperExtensions.cs
index c391a150a..5c03a0346 100644
--- a/ArchUnitNETTests/AssemblyTestHelper/AssemblyTestHelperExtensions.cs
+++ b/ArchUnitNETTests/AssemblyTestHelper/AssemblyTestHelperExtensions.cs
@@ -12,6 +12,11 @@ public static void AssertNoViolations(this IArchRule archRule, AssemblyTestHelpe
testHelper.AssertNoViolations(archRule);
}
+ public static void AssertHasViolations(this IArchRule archRule, AssemblyTestHelper testHelper)
+ {
+ testHelper.AssertHasViolations(archRule);
+ }
+
public static void AssertAnyViolations(this IArchRule archRule, AssemblyTestHelper testHelper)
{
testHelper.AssertAnyViolations(archRule);
diff --git a/ArchUnitNETTests/AssemblyTestHelper/SlicesAssemblyTestHelper.cs b/ArchUnitNETTests/AssemblyTestHelper/SlicesAssemblyTestHelper.cs
new file mode 100644
index 000000000..1ea4b2208
--- /dev/null
+++ b/ArchUnitNETTests/AssemblyTestHelper/SlicesAssemblyTestHelper.cs
@@ -0,0 +1,9 @@
+using ArchUnitNET.Domain;
+
+namespace ArchUnitNETTests.AssemblyTestHelper;
+
+public class SlicesAssemblyTestHelper : AssemblyTestHelper
+{
+ public sealed override Architecture Architecture =>
+ StaticTestArchitectures.SlicesTestArchitecture;
+}
diff --git a/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs b/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs
index 74d9a1045..feae85002 100644
--- a/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs
+++ b/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs
@@ -128,6 +128,14 @@ public void BuildUmlByDependenciesWithObjectsWithNoDependenciesTest()
Assert.Equal(expectedUml, uml);
}
+ [Fact]
+ public void SliceWithHyperlinkAppendsHyperlinkTest()
+ {
+ var slice = new PlantUmlSlice("Slice1", hyperlink: "https://example.com");
+ var uml = slice.GetPlantUmlString(new RenderOptions());
+ Assert.Equal("[Slice1] [[https://example.com]] " + Environment.NewLine, uml);
+ }
+
[Fact]
public void HandleIllegalComponentNamesTest()
{
diff --git a/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs b/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs
new file mode 100644
index 000000000..f069b07b5
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs
@@ -0,0 +1,353 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using ArchUnitNET.Domain;
+using ArchUnitNET.Domain.PlantUml.Export;
+using ArchUnitNET.Fluent.Slices;
+using VerifyXunit;
+using Xunit;
+
+namespace ArchUnitNETTests.Domain.PlantUml
+{
+ ///
+ /// Pins the PlantUML output for every combination of slice pattern and rendering mode.
+ /// These are characterization tests: a snapshot records what the exporter currently
+ /// produces, not necessarily what it ideally should, so a diff here is a prompt to look
+ /// rather than proof of a regression.
+ ///
+ public class PlantUmlSliceDiagramTests
+ {
+ private static readonly ArchUnitNET.Domain.Architecture Architecture =
+ StaticTestArchitectures.SlicesTestArchitecture;
+
+ private const string Root = "SlicesTestAssembly.MultipleSubnamespaces.";
+
+ // --- The asterisk count decides which slices the exporter renders ---------------
+ //
+ // Every pattern below matches the same nine slices -- SlicesTests.MatchingTest and
+ // Matching_SingleAsterisk_CapturesEveryDepth assert that. What differs is how many
+ // of them survive PlantUmlFileBuilder.RemovePatternInappropriateSlices, which drops
+ // a slice once its depth (dots in Description, less dots in NameSpace) reaches
+ // Slice.CountOfAsteriskInPattern:
+ //
+ // (*) count 1 -> 3 of 9 slices rendered
+ // (*).(*) count 2 -> 7 of 9 slices rendered
+ // (**) count null -> 9 of 9 slices rendered
+ //
+ // CountOfAsteriskInPattern has no other consumer in the library: the slice domain
+ // carries it solely so the exporter can perform that deletion. So these snapshots
+ // are the only record that asking for "(*)" hands the diagram two-thirds fewer
+ // nodes than GetObjects returned, and a reimplementation is free to drop the field
+ // from SliceIdentifier only if it also changes what these files say.
+
+ [Fact]
+ public Task BuildUmlBySlices_SingleAsterisk()
+ {
+ return VerifySlices(SliceRuleDefinition.Slices().Matching(Root + "(*)"));
+ }
+
+ [Fact]
+ public Task BuildUmlBySlices_DoubleAsterisk()
+ {
+ return VerifySlices(SliceRuleDefinition.Slices().Matching(Root + "(**)"));
+ }
+
+ // The matcher discards everything after the first "(*", so "(*).(*)" and "(*)..(*)"
+ // are indistinguishable to it and the next two snapshots are byte-identical -- as are
+ // their MatchingWithPackages counterparts below. That identity is the point: it is
+ // what a reimplementation giving the second capture group any meaning would have to
+ // change. SlicesTests.DotDot_* cover the shapes that would tell the two patterns
+ // apart.
+ //
+ // Identical snapshots do not make these tests redundant with "(*)", though. The
+ // second group is dead to the matcher but still raises CountOfAsteriskInPattern to
+ // 2, which moves the exporter's cutoff -- hence seven nodes here against three for
+ // "(*)".
+ [Fact]
+ public Task BuildUmlBySlices_MultipleCaptureGroups()
+ {
+ return VerifySlices(SliceRuleDefinition.Slices().Matching(Root + "(*).(*)"));
+ }
+
+ [Fact]
+ public Task BuildUmlBySlices_NonContiguousCaptureGroups()
+ {
+ return VerifySlices(SliceRuleDefinition.Slices().Matching(Root + "(*)..(*)"));
+ }
+
+ [Fact]
+ public Task BuildUmlBySlicesMatchingWithPackages_SingleAsterisk()
+ {
+ return VerifySlices(SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(*)"));
+ }
+
+ [Fact]
+ public Task BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk()
+ {
+ return VerifySlices(SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(**)"));
+ }
+
+ [Fact]
+ public Task BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups()
+ {
+ return VerifySlices(
+ SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(*).(*)")
+ );
+ }
+
+ [Fact]
+ public Task BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups()
+ {
+ return VerifySlices(
+ SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(*)..(*)")
+ );
+ }
+
+ [Fact]
+ public Task BuildUmlBySlicesMatchingWithPackages_LimitDependencies()
+ {
+ return VerifySlices(
+ SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(**)"),
+ new GenerationOptions { LimitDependencies = true }
+ );
+ }
+
+ [Fact]
+ public Task BuildUmlBySlicesMatchingWithPackages_C4Style()
+ {
+ return VerifySlices(
+ SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(**)"),
+ new GenerationOptions { C4Style = true }
+ );
+ }
+
+ [Fact]
+ public Task BuildUmlBySlices_C4Style()
+ {
+ // Slices produced by Matching (as opposed to MatchingWithPackages) have no namespace
+ // prefix, so this exercises PlantUmlSlice.BuildStringC4Style's no-namespace branch.
+ return VerifySlices(
+ SliceRuleDefinition.Slices().Matching(Root + "(**)"),
+ new GenerationOptions { C4Style = true }
+ );
+ }
+
+ [Fact]
+ public Task BuildUmlBySlicesFocusOn()
+ {
+ var slices = SortedSlices(
+ SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(**)")
+ );
+ var uml = new PlantUmlFileBuilder()
+ .WithDependenciesFromFocusOn(slices, Root + "Slice1")
+ .AsString();
+ return VerifyUml(uml);
+ }
+
+ [Fact]
+ public Task BuildUmlBySlicesFocusOn_Matching_ColorsFocusedSlices()
+ {
+ // Slices produced by Matching have no namespace, so the focused ones take
+ // PlantUmlSlice.BuildString's namespace-less branch with a colour set -- the only
+ // path on which the exporter renders "[Slice1] #99ffd1" rather than a package.
+ // Their Description carries no Root prefix either, hence the bare focus string.
+ var slices = SortedSlices(SliceRuleDefinition.Slices().Matching(Root + "(**)"));
+ var uml = new PlantUmlFileBuilder()
+ .WithDependenciesFromFocusOn(slices, "Slice1")
+ .AsString();
+ return VerifyUml(uml);
+ }
+
+ // --- Cyclic slice dependencies are folded into a single Circle dependency ----
+
+ [Fact]
+ public Task BuildUmlBySlices_DirectCircle_RendersCircleDependency()
+ {
+ var slices = SortedSlices(
+ SliceRuleDefinition.Slices().Matching("SlicesTestAssembly.DirectCircle.(*)")
+ );
+ var uml = new PlantUmlFileBuilder().WithDependenciesFrom(slices).AsString();
+ return VerifyUml(uml);
+ }
+
+ [Fact]
+ public Task BuildUmlBySlicesMatchingWithPackages_DirectCircle()
+ {
+ var slices = SortedSlices(
+ SliceRuleDefinition
+ .Slices()
+ .MatchingWithPackages("SlicesTestAssembly.DirectCircle.(*)")
+ );
+ var uml = new PlantUmlFileBuilder().WithDependenciesFrom(slices).AsString();
+ return VerifyUml(uml);
+ }
+
+ // --- LimitDependencies without namespaces takes the OneToOneCompact branch ----
+
+ [Fact]
+ public Task BuildUmlBySlices_LimitDependencies_Compact()
+ {
+ var slices = SortedSlices(SliceRuleDefinition.Slices().Matching(Root + "(**)"));
+ var uml = new PlantUmlFileBuilder()
+ .WithDependenciesFrom(slices, new GenerationOptions { LimitDependencies = true })
+ .AsString();
+ return VerifyUml(uml);
+ }
+
+ // --- IncludeNodesWithoutDependencies = false removes dependency-less slices ----
+
+ [Fact]
+ public Task BuildUmlBySlices_ExcludeNodesWithoutDependencies()
+ {
+ // A single-asterisk pattern would already collapse away the dependency-less slices
+ // via RemovePatternInappropriateSlices, so use "(**)" to keep them in the slice list
+ // and let IncludeNodesWithoutDependencies do the removal instead.
+ var slices = SortedSlices(SliceRuleDefinition.Slices().Matching(Root + "(**)"));
+ var uml = new PlantUmlFileBuilder()
+ .WithDependenciesFrom(
+ slices,
+ new GenerationOptions { IncludeNodesWithoutDependencies = false }
+ )
+ .AsString();
+ return VerifyUml(uml);
+ }
+
+ // --- A DependencyFilter is honoured when building slice diagrams --------------
+
+ [Fact]
+ public Task BuildUmlBySlices_WithDependencyFilter()
+ {
+ // "(*)" would already collapse Slice3.Group1 away via RemovePatternInappropriateSlices,
+ // leaving no dependency for the filter to remove, so use "(**)" instead.
+ var slices = SortedSlices(SliceRuleDefinition.Slices().Matching(Root + "(**)"));
+ var uml = new PlantUmlFileBuilder()
+ .WithDependenciesFrom(
+ slices,
+ new GenerationOptions
+ {
+ DependencyFilter = dep => !dep.Origin.FullName.Contains("Slice3.Group1"),
+ }
+ )
+ .AsString();
+ return VerifyUml(uml);
+ }
+
+ // --- WithDependenciesFromFocusOn argument validation ---------------------------
+
+ [Fact]
+ public void BuildUmlBySlicesFocusOn_EmptyPackage_Throws()
+ {
+ var slices = SliceRuleDefinition
+ .Slices()
+ .MatchingWithPackages(Root + "(**)")
+ .GetObjects(Architecture);
+ var ex = Assert.Throws(() =>
+ new PlantUmlFileBuilder().WithDependenciesFromFocusOn(slices, "")
+ );
+ Assert.Equal("Package can't be empty", ex.Message);
+ }
+
+ [Fact]
+ public void BuildUmlBySlicesFocusOn_DotOnlyPackage_Throws()
+ {
+ var slices = SliceRuleDefinition
+ .Slices()
+ .MatchingWithPackages(Root + "(**)")
+ .GetObjects(Architecture);
+ var ex = Assert.Throws(() =>
+ new PlantUmlFileBuilder().WithDependenciesFromFocusOn(slices, ".")
+ );
+ Assert.Equal("Package can't contain a single dot only", ex.Message);
+ }
+
+ [Fact]
+ public void BuildUmlBySlicesFocusOn_UnknownPackage_Throws()
+ {
+ // A single-asterisk pattern gives every slice a non-null CountOfAsteriskInPattern,
+ // which is required to reach RemovePatternInappropriateSlices' early return for a
+ // focus string that no slice contains, on the way to this exception.
+ var slices = SliceRuleDefinition
+ .Slices()
+ .MatchingWithPackages(Root + "(*)")
+ .GetObjects(Architecture);
+ var ex = Assert.Throws(() =>
+ new PlantUmlFileBuilder().WithDependenciesFromFocusOn(slices, "NotAPackage")
+ );
+ Assert.Contains("is not contained in this slice", ex.Message);
+ }
+
+ [Fact]
+ public void BuildUmlBySlicesFocusOn_TrailingDotPackage_IsTrimmed()
+ {
+ var slicesWithTrailingDot = SliceRuleDefinition
+ .Slices()
+ .MatchingWithPackages(Root + "(**)")
+ .GetObjects(Architecture);
+ var umlWithTrailingDot = new PlantUmlFileBuilder()
+ .WithDependenciesFromFocusOn(slicesWithTrailingDot, Root + "Slice1.")
+ .AsString();
+
+ var slicesWithoutTrailingDot = SliceRuleDefinition
+ .Slices()
+ .MatchingWithPackages(Root + "(**)")
+ .GetObjects(Architecture);
+ var umlWithoutTrailingDot = new PlantUmlFileBuilder()
+ .WithDependenciesFromFocusOn(slicesWithoutTrailingDot, Root + "Slice1")
+ .AsString();
+
+ Assert.Equal(umlWithoutTrailingDot, umlWithTrailingDot);
+ }
+
+ // --- RemovePatternInappropriateSlices' per-slice "continue" for FocusOn ------
+
+ [Fact]
+ public Task BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString()
+ {
+ // Focusing on the single-segment slice "Slice3" means slices whose Description
+ // doesn't contain it (e.g. "Slice1") skip the pattern-appropriateness check via
+ // RemovePatternInappropriateSlices' per-slice "continue", while "Slice3" itself is
+ // shallow enough to survive that check and keep the package reachable.
+ var slices = SortedSlices(
+ SliceRuleDefinition.Slices().MatchingWithPackages(Root + "(*)")
+ );
+ var uml = new PlantUmlFileBuilder()
+ .WithDependenciesFromFocusOn(slices, Root + "Slice3")
+ .AsString();
+ return VerifyUml(uml);
+ }
+
+ ///
+ /// GetObjects returns slices in Mono.Cecil's type-table order, which is deterministic
+ /// per build but not contractually stable across Cecil or runtime upgrades. Sorting by
+ /// description keeps the node and dependency order in the snapshots below stable
+ /// regardless of that traversal order.
+ ///
+ private static Slice[] SortedSlices(GivenSlices slices)
+ {
+ return slices
+ .GetObjects(Architecture)
+ .OrderBy(slice => slice.Description, StringComparer.Ordinal)
+ .ToArray();
+ }
+
+ private static Task VerifyUml(string uml)
+ {
+ return Verifier
+ .Verify(uml)
+ .DisableDiff() // Don't open diff tool during the test
+ .UseDirectory("Snapshots");
+ }
+
+ private static Task VerifySlices(
+ GivenSlices slices,
+ GenerationOptions generationOptions = null
+ )
+ {
+ return VerifyUml(
+ new PlantUmlFileBuilder()
+ .WithDependenciesFrom(SortedSlices(slices), generationOptions)
+ .AsString()
+ );
+ }
+ }
+}
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt
new file mode 100644
index 000000000..bf61e1915
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt
@@ -0,0 +1,31 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice1 {
+[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service #99ffd1
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice2 {
+[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice3 {
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice1 #99ffd1 {
+}
+}
+
+[SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service]
+Slice3 -[#green]> Slice1
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_Matching_ColorsFocusedSlices.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_Matching_ColorsFocusedSlices.verified.txt
new file mode 100644
index 000000000..b96ed6505
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_Matching_ColorsFocusedSlices.verified.txt
@@ -0,0 +1,15 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+[Slice1] #99ffd1
+[Slice1.Service] #99ffd1
+[Slice2]
+[Slice2.Service]
+[Slice3]
+[Slice1] --|> [Slice2]
+[Slice1.Service] --|> [Slice2.Service]
+[Slice3] --|> [Slice1]
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString.verified.txt
new file mode 100644
index 000000000..01d54e434
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString.verified.txt
@@ -0,0 +1,24 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice1 {
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice2 {
+[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+[Slice3] as SlicesTestAssembly.MultipleSubnamespaces.Slice3 #99ffd1
+}
+
+[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice3]
+[SlicesTestAssembly.MultipleSubnamespaces.Slice3] -[#red]> Slice1
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_C4Style.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_C4Style.verified.txt
new file mode 100644
index 000000000..cec8d4002
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_C4Style.verified.txt
@@ -0,0 +1,38 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+Boundary(SlicesTestAssembly.MultipleSubnamespaces, SlicesTestAssembly.MultipleSubnamespaces) {
+Boundary(Slice1, Slice1) {
+Container(SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service, Service)
+}
+}
+
+Boundary(SlicesTestAssembly.MultipleSubnamespaces, SlicesTestAssembly.MultipleSubnamespaces) {
+Boundary(Slice2, Slice2) {
+Container(SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service, Service)
+}
+}
+
+Boundary(SlicesTestAssembly.MultipleSubnamespaces, SlicesTestAssembly.MultipleSubnamespaces) {
+Boundary(Slice3, Slice3) {
+Boundary(Group1, Group1) {
+Container(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner, Inner)
+}
+}
+}
+
+Boundary(SlicesTestAssembly.MultipleSubnamespaces, SlicesTestAssembly.MultipleSubnamespaces) {
+Boundary(Slice3, Slice3) {
+Boundary(Group2, Group2) {
+Container(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner, Inner)
+}
+}
+}
+
+[SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service]
+[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> Slice3
+Slice3 -[#green]> Slice1
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DirectCircle.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DirectCircle.verified.txt
new file mode 100644
index 000000000..d1981eff7
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DirectCircle.verified.txt
@@ -0,0 +1,16 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+package SlicesTestAssembly.DirectCircle {
+[Slice1] as SlicesTestAssembly.DirectCircle.Slice1
+}
+
+package SlicesTestAssembly.DirectCircle {
+[Slice2] as SlicesTestAssembly.DirectCircle.Slice2
+}
+
+[SlicesTestAssembly.DirectCircle.Slice1] <-[#red]> [SlicesTestAssembly.DirectCircle.Slice2]
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk.verified.txt
new file mode 100644
index 000000000..2e17772dc
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk.verified.txt
@@ -0,0 +1,38 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice1 {
+[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice2 {
+[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice3 {
+package Group1 {
+[Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner
+}
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice3 {
+package Group2 {
+[Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner
+}
+}
+}
+
+[SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service]
+[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> Slice3
+Slice3 -[#green]> Slice1
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_LimitDependencies.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_LimitDependencies.verified.txt
new file mode 100644
index 000000000..44b61fc01
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_LimitDependencies.verified.txt
@@ -0,0 +1,38 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice1 {
+[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice2 {
+[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice3 {
+package Group1 {
+[Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner
+}
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice3 {
+package Group2 {
+[Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner
+}
+}
+}
+
+Slice1 ..> Slice2
+Slice2 ..> Slice3
+Slice3 ..> Slice1
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups.verified.txt
new file mode 100644
index 000000000..9416d0726
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups.verified.txt
@@ -0,0 +1,34 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice1 {
+[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice2 {
+[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice3 {
+[Group1] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice3 {
+[Group2] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2
+}
+}
+
+[SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service]
+[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> Slice3
+Slice3 -[#green]> Slice1
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups.verified.txt
new file mode 100644
index 000000000..9416d0726
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups.verified.txt
@@ -0,0 +1,34 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice1 {
+[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice2 {
+[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice3 {
+[Group1] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1
+}
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+package Slice3 {
+[Group2] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2
+}
+}
+
+[SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service]
+[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> Slice3
+Slice3 -[#green]> Slice1
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SingleAsterisk.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SingleAsterisk.verified.txt
new file mode 100644
index 000000000..6e0cbf47d
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SingleAsterisk.verified.txt
@@ -0,0 +1,22 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+[Slice1] as SlicesTestAssembly.MultipleSubnamespaces.Slice1
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+[Slice2] as SlicesTestAssembly.MultipleSubnamespaces.Slice2
+}
+
+package SlicesTestAssembly.MultipleSubnamespaces {
+[Slice3] as SlicesTestAssembly.MultipleSubnamespaces.Slice3
+}
+
+[SlicesTestAssembly.MultipleSubnamespaces.Slice1] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2]
+[SlicesTestAssembly.MultipleSubnamespaces.Slice2] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice3]
+[SlicesTestAssembly.MultipleSubnamespaces.Slice3] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice1]
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_C4Style.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_C4Style.verified.txt
new file mode 100644
index 000000000..851e24568
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_C4Style.verified.txt
@@ -0,0 +1,22 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+Container(Slice1, Slice1)
+Container(Slice1.Service, Slice1.Service)
+Container(Slice2, Slice2)
+Container(Slice2.Service, Slice2.Service)
+Container(Slice3, Slice3)
+Container(Slice3.Group1, Slice3.Group1)
+Container(Slice3.Group1.Inner, Slice3.Group1.Inner)
+Container(Slice3.Group2, Slice3.Group2)
+Container(Slice3.Group2.Inner, Slice3.Group2.Inner)
+[Slice1] --|> [Slice2]
+[Slice1.Service] --|> [Slice2.Service]
+[Slice2] --|> [Slice3]
+[Slice2.Service] --|> [Slice3]
+[Slice3] --|> [Slice1]
+[Slice3.Group1] --|> [Slice3]
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_DirectCircle_RendersCircleDependency.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_DirectCircle_RendersCircleDependency.verified.txt
new file mode 100644
index 000000000..ffc596323
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_DirectCircle_RendersCircleDependency.verified.txt
@@ -0,0 +1,10 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+[Slice1]
+[Slice2]
+[Slice1] <-[#red]> [Slice2]
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_DoubleAsterisk.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_DoubleAsterisk.verified.txt
new file mode 100644
index 000000000..b33b9dcab
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_DoubleAsterisk.verified.txt
@@ -0,0 +1,22 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+[Slice1]
+[Slice1.Service]
+[Slice2]
+[Slice2.Service]
+[Slice3]
+[Slice3.Group1]
+[Slice3.Group1.Inner]
+[Slice3.Group2]
+[Slice3.Group2.Inner]
+[Slice1] --|> [Slice2]
+[Slice1.Service] --|> [Slice2.Service]
+[Slice2] --|> [Slice3]
+[Slice2.Service] --|> [Slice3]
+[Slice3] --|> [Slice1]
+[Slice3.Group1] --|> [Slice3]
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_ExcludeNodesWithoutDependencies.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_ExcludeNodesWithoutDependencies.verified.txt
new file mode 100644
index 000000000..c5667533f
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_ExcludeNodesWithoutDependencies.verified.txt
@@ -0,0 +1,19 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+[Slice1]
+[Slice1.Service]
+[Slice2]
+[Slice2.Service]
+[Slice3]
+[Slice3.Group1]
+[Slice1] --|> [Slice2]
+[Slice1.Service] --|> [Slice2.Service]
+[Slice2] --|> [Slice3]
+[Slice2.Service] --|> [Slice3]
+[Slice3] --|> [Slice1]
+[Slice3.Group1] --|> [Slice3]
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_LimitDependencies_Compact.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_LimitDependencies_Compact.verified.txt
new file mode 100644
index 000000000..991ec5378
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_LimitDependencies_Compact.verified.txt
@@ -0,0 +1,20 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+[Slice1]
+[Slice1.Service]
+[Slice2]
+[Slice2.Service]
+[Slice3]
+[Slice3.Group1]
+[Slice3.Group1.Inner]
+[Slice3.Group2]
+[Slice3.Group2.Inner]
+[Slice1] --> [Slice2]
+[Slice1.Service] --> [Slice2.Service]
+[Slice2] --> [Slice3]
+[Slice3] --> [Slice1]
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_MultipleCaptureGroups.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_MultipleCaptureGroups.verified.txt
new file mode 100644
index 000000000..041ebd608
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_MultipleCaptureGroups.verified.txt
@@ -0,0 +1,20 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+[Slice1]
+[Slice1.Service]
+[Slice2]
+[Slice2.Service]
+[Slice3]
+[Slice3.Group1]
+[Slice3.Group2]
+[Slice1] --|> [Slice2]
+[Slice1.Service] --|> [Slice2.Service]
+[Slice2] --|> [Slice3]
+[Slice2.Service] --|> [Slice3]
+[Slice3] --|> [Slice1]
+[Slice3.Group1] --|> [Slice3]
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_NonContiguousCaptureGroups.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_NonContiguousCaptureGroups.verified.txt
new file mode 100644
index 000000000..041ebd608
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_NonContiguousCaptureGroups.verified.txt
@@ -0,0 +1,20 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+[Slice1]
+[Slice1.Service]
+[Slice2]
+[Slice2.Service]
+[Slice3]
+[Slice3.Group1]
+[Slice3.Group2]
+[Slice1] --|> [Slice2]
+[Slice1.Service] --|> [Slice2.Service]
+[Slice2] --|> [Slice3]
+[Slice2.Service] --|> [Slice3]
+[Slice3] --|> [Slice1]
+[Slice3.Group1] --|> [Slice3]
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_SingleAsterisk.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_SingleAsterisk.verified.txt
new file mode 100644
index 000000000..c37caedd7
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_SingleAsterisk.verified.txt
@@ -0,0 +1,13 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+[Slice1]
+[Slice2]
+[Slice3]
+[Slice1] --|> [Slice2]
+[Slice2] --|> [Slice3]
+[Slice3] --|> [Slice1]
+@enduml
diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_WithDependencyFilter.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_WithDependencyFilter.verified.txt
new file mode 100644
index 000000000..969b504e8
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlices_WithDependencyFilter.verified.txt
@@ -0,0 +1,21 @@
+@startuml
+
+!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
+
+HIDE_STEREOTYPE()
+
+[Slice1]
+[Slice1.Service]
+[Slice2]
+[Slice2.Service]
+[Slice3]
+[Slice3.Group1]
+[Slice3.Group1.Inner]
+[Slice3.Group2]
+[Slice3.Group2.Inner]
+[Slice1] --|> [Slice2]
+[Slice1.Service] --|> [Slice2.Service]
+[Slice2] --|> [Slice3]
+[Slice2.Service] --|> [Slice3]
+[Slice3] --|> [Slice1]
+@enduml
diff --git a/ArchUnitNETTests/Fluent/FreezeTests.cs b/ArchUnitNETTests/Fluent/FreezeTests.cs
index 968856cb3..ff8e36c84 100644
--- a/ArchUnitNETTests/Fluent/FreezeTests.cs
+++ b/ArchUnitNETTests/Fluent/FreezeTests.cs
@@ -36,13 +36,13 @@ public class FreezeTests
private readonly IArchRule _frozenSliceRule = SliceRuleDefinition
.Slices()
- .Matching("TestAssembly.Slices.(**)")
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.(**)")
.Should()
.NotDependOnEachOther();
private readonly IArchRule _failingFrozenSliceRule = SliceRuleDefinition
.Slices()
- .Matching("TestAssembly.Slices.(*)..")
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.(*)..")
.Should()
.NotDependOnEachOther();
@@ -51,8 +51,7 @@ public void PassFrozenRules()
{
Freeze(_frozenRule).Check(Architecture);
Freeze(_frozenRule2).Check(Architecture);
- Freeze(_frozenSliceRule)
- .Check(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture);
+ Freeze(_frozenSliceRule).Check(StaticTestArchitectures.SlicesTestArchitecture);
}
[Fact]
@@ -61,7 +60,7 @@ public void PassFrozenRulesUsingXmlViolationStore()
Freeze(_frozenRule, new XmlViolationStore()).Check(Architecture);
Freeze(_frozenRule2, new XmlViolationStore()).Check(Architecture);
Freeze(_frozenSliceRule, new XmlViolationStore())
- .Check(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture);
+ .Check(StaticTestArchitectures.SlicesTestArchitecture);
}
[Fact]
@@ -72,7 +71,7 @@ public void FailFrozenRule()
);
Assert.Throws(() =>
Freeze(_failingFrozenSliceRule)
- .Check(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
+ .Check(StaticTestArchitectures.SlicesTestArchitecture)
);
}
@@ -84,7 +83,7 @@ public void FailFrozenRuleUsingXmlViolationStore()
);
Assert.Throws(() =>
Freeze(_failingFrozenSliceRule, new XmlViolationStore())
- .Check(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
+ .Check(StaticTestArchitectures.SlicesTestArchitecture)
);
}
@@ -96,7 +95,7 @@ public void PassFrozenRulesWithCustomViolationStorePath()
Freeze(_frozenRule2, "../../../ArchUnitNET/Storage/CustomPathFrozenRules.json")
.Check(Architecture);
Freeze(_frozenSliceRule, "../../../ArchUnitNET/Storage/CustomPathFrozenRules.json")
- .Check(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture);
+ .Check(StaticTestArchitectures.SlicesTestArchitecture);
}
private class Violation { }
diff --git a/ArchUnitNETTests/Fluent/Slices/PatternValidationTests.cs b/ArchUnitNETTests/Fluent/Slices/PatternValidationTests.cs
new file mode 100644
index 000000000..bcc81a835
--- /dev/null
+++ b/ArchUnitNETTests/Fluent/Slices/PatternValidationTests.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Linq;
+using ArchUnitNET.Fluent.Slices;
+using Xunit;
+
+namespace ArchUnitNETTests.Fluent.Slices
+{
+ ///
+ /// Pins which slice patterns are rejected and with what message. The patterns are
+ /// validated while the slices are enumerated rather than when the rule is defined, so
+ /// every case here has to consume the result before the exception surfaces.
+ ///
+ public class PatternValidationTests
+ {
+ [Theory]
+ [InlineData("Foo.Bar")]
+ [InlineData("Foo.*")]
+ [InlineData("Foo..Bar")]
+ public void PatternWithoutCaptureGroupThrows(string pattern)
+ {
+ var ex = Assert.Throws(() =>
+ SliceRuleDefinition.Slices().Matching(pattern).GetObjects(Architecture).ToList()
+ );
+ Assert.Contains(
+ "have to contain (*) or (**)",
+ ex.Message,
+ StringComparison.OrdinalIgnoreCase
+ );
+ }
+
+ [Fact]
+ public void PatternMixingSingleAndDoubleAsteriskThrows()
+ {
+ var ex = Assert.Throws(() =>
+ SliceRuleDefinition
+ .Slices()
+ .Matching("Foo.(*).(**)")
+ .GetObjects(Architecture)
+ .ToList()
+ );
+ Assert.Contains(
+ "can't contain both (*) and (**)",
+ ex.Message,
+ StringComparison.OrdinalIgnoreCase
+ );
+ }
+
+ [Fact]
+ public void PatternWithRepeatedDoubleAsteriskThrows()
+ {
+ var ex = Assert.Throws(() =>
+ SliceRuleDefinition
+ .Slices()
+ .Matching("Foo.(**).(**)")
+ .GetObjects(Architecture)
+ .ToList()
+ );
+ Assert.Contains(
+ "can contain (**) only once",
+ ex.Message,
+ StringComparison.OrdinalIgnoreCase
+ );
+ }
+
+ // Prefix and postfix are matched independently against the full namespace
+ // string, without regard for segment boundaries. Crafting them so their required
+ // regions overlap makes the postfix check pass against the full namespace while the
+ // slice string computed after stripping the prefix no longer contains it -- this is
+ // the one real way to reach AssignFunc's "not clearly assignable" guard.
+ [Fact]
+ public void PatternWithOverlappingPrefixAndPostfix_NotClearlyAssignableThrows()
+ {
+ const string prefix = "SlicesTestAssembly.DotDotSeman";
+ const string postfix = "Assembly.DotDotSemantics.Single";
+ var ex = Assert.Throws(() =>
+ SliceRuleDefinition
+ .Slices()
+ .Matching(prefix + "(**)" + postfix)
+ .GetObjects(Architecture)
+ .ToList()
+ );
+ Assert.Contains(
+ "is not clearly assignable",
+ ex.Message,
+ StringComparison.OrdinalIgnoreCase
+ );
+ }
+
+ [Theory]
+ [InlineData("Foo.(*).Bar")]
+ [InlineData("Foo.(**).Bar")]
+ [InlineData("Foo.(**)..")]
+ [InlineData("Foo.(*)..")]
+ [InlineData("Foo.(*).(*)")]
+ public void ValidPatternDoesNotThrow(string pattern)
+ {
+ SliceRuleDefinition.Slices().Matching(pattern).GetObjects(Architecture).ToList();
+ }
+
+ private static ArchUnitNET.Domain.Architecture Architecture =>
+ StaticTestArchitectures.SlicesTestArchitecture;
+ }
+}
diff --git a/ArchUnitNETTests/Fluent/Slices/SliceDomainTests.cs b/ArchUnitNETTests/Fluent/Slices/SliceDomainTests.cs
new file mode 100644
index 000000000..0827aacf7
--- /dev/null
+++ b/ArchUnitNETTests/Fluent/Slices/SliceDomainTests.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Linq;
+using ArchUnitNET.Domain;
+using ArchUnitNET.Domain.Extensions;
+using ArchUnitNET.Fluent.Slices;
+using SlicesTestAssembly.MixedTypes;
+using Xunit;
+
+namespace ArchUnitNETTests.Fluent.Slices
+{
+ ///
+ /// Covers the fluent-API guard in and the plain
+ /// domain-type members of , and
+ /// , and that
+ /// no slice-diagram or rule-evaluation fixture happens to exercise.
+ ///
+ public class SliceDomainTests
+ {
+ private static Architecture Architecture => StaticTestArchitectures.SlicesTestArchitecture;
+
+ private const string Root = "SlicesTestAssembly.MultipleSubnamespaces.";
+
+ // --- SliceRuleCreator.GetSlices() without a slice assignment ------------------
+
+ [Fact]
+ public void GetSlices_WithoutSliceAssignment_Throws()
+ {
+ var creator = new SliceRuleCreator();
+ var ex = Assert.Throws(() =>
+ creator.GetSlices(Architecture)
+ );
+ Assert.Equal(
+ "The Slice Assignment has to be set before GetSlices() can be called.",
+ ex.Message
+ );
+ }
+
+ // --- Domain.Slice members ------------------------------------------------------
+
+ [Fact]
+ public void Slice_ClassesAndInterfaces_FilterByType()
+ {
+ var classType = Architecture.GetClassOfType(typeof(MixedTypesClass));
+ var interfaceType = Architecture.GetInterfaceOfType(typeof(IMixedTypesInterface));
+ var slice = new Slice(
+ SliceIdentifier.Of("Mixed"),
+ new IType[] { classType, interfaceType }
+ );
+
+ Assert.Equal(new[] { classType }, slice.Classes);
+ Assert.Equal(new[] { interfaceType }, slice.Interfaces);
+ }
+
+ [Fact]
+ public void Slice_BackwardsDependencies_AggregatesFromTypes()
+ {
+ var type = Architecture.Classes.First(c => c.BackwardsDependencies.Any());
+ var slice = new Slice(SliceIdentifier.Of("Backwards"), new IType[] { type });
+
+ Assert.Equal(type.BackwardsDependencies, slice.BackwardsDependencies);
+ }
+
+ [Fact]
+ public void Slice_ToString_ReturnsDescription()
+ {
+ var slice = new Slice(SliceIdentifier.Of("SomeSlice"), Array.Empty());
+
+ Assert.Equal("SomeSlice", slice.ToString());
+ Assert.Equal(slice.Description, slice.ToString());
+ }
+
+ ///
+ /// Slice.Equals compares its Types collection by reference, not by content, so
+ /// differentTypesInstance below is unequal despite also being empty. That
+ /// relies on the two new IType[0] expressions yielding distinct references --
+ /// true today, but it would silently stop testing anything if empty arrays were ever
+ /// interned, so the two instances are kept deliberately separate and named for it.
+ ///
+ [Fact]
+ public void Slice_Equals_Branches()
+ {
+ var types = new IType[0];
+ var equalButDistinctTypes = new IType[0];
+ Assert.NotSame(types, equalButDistinctTypes);
+
+ var slice = new Slice(SliceIdentifier.Of("A"), types);
+ var sameIdentifierAndTypes = new Slice(SliceIdentifier.Of("A"), types);
+ var differentTypesInstance = new Slice(SliceIdentifier.Of("A"), equalButDistinctTypes);
+ var differentIdentifier = new Slice(SliceIdentifier.Of("B"), types);
+
+ Assert.False(slice.Equals(null));
+ Assert.True(slice.Equals(slice));
+ Assert.False(slice.Equals("not a slice"));
+ Assert.True(slice.Equals(sameIdentifierAndTypes));
+ Assert.False(slice.Equals(differentTypesInstance));
+ Assert.False(slice.Equals(differentIdentifier));
+ }
+
+ // --- SliceIdentifier / SliceIdentifierComparer equality -----------------------
+
+ [Fact]
+ public void SliceIdentifier_CompareTo_Null_ReturnsFalse()
+ {
+ Assert.False(SliceIdentifier.Of("A").CompareTo(null));
+ }
+
+ [Fact]
+ public void SliceIdentifier_Equals_Branches()
+ {
+ var identifier = SliceIdentifier.Of("A");
+
+ Assert.False(identifier.Equals(null));
+ Assert.True(identifier.Equals(identifier));
+ Assert.False(identifier.Equals("not an identifier"));
+ Assert.True(identifier.Equals(SliceIdentifier.Of("A")));
+ Assert.False(identifier.Equals(SliceIdentifier.Of("B")));
+ }
+
+ [Fact]
+ public void SliceIdentifierComparer_Equals_NullFirstArgument_ReturnsFalse()
+ {
+ Assert.False(SliceIdentifier.Comparer.Equals(null, SliceIdentifier.Of("A")));
+ Assert.True(
+ SliceIdentifier.Comparer.Equals(SliceIdentifier.Of("A"), SliceIdentifier.Of("A"))
+ );
+ }
+
+ // --- GivenSlices description plumbing -----------------------------------------
+
+ [Fact]
+ public void GivenSlices_Description_ReflectsRuleCreatorDescription()
+ {
+ var slices = SliceRuleDefinition.Slices().Matching(Root + "(*)");
+
+ Assert.Equal("Slices matching \"" + Root + "(*)\"", slices.Description);
+ }
+
+ [Fact]
+ public void GivenSlices_FormatDescription_PrependsMultipleDescription()
+ {
+ var slices = SliceRuleDefinition.Slices().Matching(Root + "(*)");
+
+ var formatted = slices.FormatDescription("empty", "single", "multiple");
+
+ Assert.Equal("multiple " + slices.Description, formatted);
+ }
+ }
+}
diff --git a/ArchUnitNETTests/Fluent/Slices/SliceRuleCombinationTests.cs b/ArchUnitNETTests/Fluent/Slices/SliceRuleCombinationTests.cs
new file mode 100644
index 000000000..b62f9bdd4
--- /dev/null
+++ b/ArchUnitNETTests/Fluent/Slices/SliceRuleCombinationTests.cs
@@ -0,0 +1,158 @@
+using System.Threading.Tasks;
+using ArchUnitNET.Fluent;
+using ArchUnitNET.Fluent.Slices;
+using ArchUnitNETTests.AssemblyTestHelper;
+using Xunit;
+using static ArchUnitNET.Fluent.ArchRuleDefinition;
+
+namespace ArchUnitNETTests.Fluent.Slices
+{
+ ///
+ /// Covers 's four combinators. Each is exercised for both evaluation
+ /// outcome and the composed Description, which
+ /// builds by concatenating the two rules'
+ /// descriptions with the combinator's conjunction; the snapshot's Query: line pins that
+ /// description verbatim, so no separate assertion on it is needed.
+ ///
+ ///
+ /// Outcomes are asserted with
+ /// and , the two helper
+ /// assertions that read a rule's verdict. The result-shaped AssertAnyViolations and
+ /// AssertOnlyViolations would be wrong here: a combined rule's results are a plain
+ /// concatenation of its operands', so "the results contain a failure" holds for a passing
+ /// Or just as it does for a failing And, and would say nothing about the
+ /// combinator.
+ ///
+ public class SliceRuleCombinationTests
+ {
+ private const string Root = "SlicesTestAssembly.MultipleSubnamespaces.";
+
+ ///
+ /// Deliberately narrow: the bare Types().Should().Exist() that reads most naturally
+ /// here yields one passing result per type in the assembly, which would bury the point of
+ /// each snapshot and churn whenever a fixture type is added.
+ ///
+ private const string SinglePassingTypeName =
+ "SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass";
+
+ private static SliceRule CyclicSliceRule =>
+ SliceRuleDefinition.Slices().Matching(Root + "(**)").Should().BeFreeOfCycles();
+
+ // "(**).." on MultipleSubnamespaces is acyclic too, but only because it folds away the
+ // types that would form the cycle (see SlicesTests.SubnamespaceCycleDetection_
+ // FoldedIntoParent_MissesCycle) -- a fixture that is genuinely acyclic keeps this test
+ // stable once that fold is reimplemented.
+ private static SliceRule AcyclicSliceRule =>
+ SliceRuleDefinition
+ .Slices()
+ .Matching("SlicesTestAssembly.SubnamespaceCircle.(**)")
+ .Should()
+ .BeFreeOfCycles();
+
+ private static IArchRule SinglePassingTypeRule =>
+ Types().That().HaveFullName(SinglePassingTypeName).Should().Exist();
+
+ private static IArchRule FailingTypesRule(SlicesAssemblyTestHelper helper) =>
+ Types().That().HaveName(helper.NonExistentObjectName).Should().Exist();
+
+ [Fact]
+ public async Task And_FluentDefinition_CombinesEvaluationAndDescription()
+ {
+ var helper = new SlicesAssemblyTestHelper();
+
+ helper.AddSnapshotHeader("Slice operand on its own");
+ AcyclicSliceRule.AssertNoViolations(helper);
+
+ helper.AddSnapshotHeader("Both operands pass");
+ AcyclicSliceRule
+ .And()
+ .Types()
+ .That()
+ .HaveFullName(SinglePassingTypeName)
+ .Should()
+ .Exist()
+ .AssertNoViolations(helper);
+
+ helper.AddSnapshotHeader("Slice operand fails");
+ CyclicSliceRule
+ .And()
+ .Types()
+ .That()
+ .HaveFullName(SinglePassingTypeName)
+ .Should()
+ .Exist()
+ .AssertHasViolations(helper);
+
+ await helper.AssertSnapshotMatches();
+ }
+
+ [Fact]
+ public async Task Or_FluentDefinition_CombinesEvaluationAndDescription()
+ {
+ var helper = new SlicesAssemblyTestHelper();
+
+ helper.AddSnapshotHeader("Slice operand on its own");
+ CyclicSliceRule.AssertHasViolations(helper);
+
+ helper.AddSnapshotHeader("Second operand rescues the failing slice operand");
+ CyclicSliceRule
+ .Or()
+ .Types()
+ .That()
+ .HaveFullName(SinglePassingTypeName)
+ .Should()
+ .Exist()
+ .AssertNoViolations(helper);
+
+ helper.AddSnapshotHeader("Both operands fail");
+ CyclicSliceRule
+ .Or()
+ .Types()
+ .That()
+ .HaveName(helper.NonExistentObjectName)
+ .Should()
+ .Exist()
+ .AssertHasViolations(helper);
+
+ await helper.AssertSnapshotMatches();
+ }
+
+ [Fact]
+ public async Task And_WithArchRule_CombinesEvaluationAndDescription()
+ {
+ var helper = new SlicesAssemblyTestHelper();
+
+ helper.AddSnapshotHeader("Both operands pass");
+ AcyclicSliceRule.And(SinglePassingTypeRule).AssertNoViolations(helper);
+
+ helper.AddSnapshotHeader("Second operand fails");
+ AcyclicSliceRule.And(FailingTypesRule(helper)).AssertHasViolations(helper);
+
+ helper.AddSnapshotHeader("Both operands fail");
+ CyclicSliceRule.And(FailingTypesRule(helper)).AssertHasViolations(helper);
+
+ await helper.AssertSnapshotMatches();
+ }
+
+ [Fact]
+ public async Task Or_WithArchRule_CombinesEvaluationAndDescription()
+ {
+ var helper = new SlicesAssemblyTestHelper();
+
+ // The verdict passes while the recorded results still report violations, and the
+ // rendered message announces a failure for a rule that passed: CombinedArchRule
+ // applies the conjunction in HasNoViolations but never in Evaluate, so the cyclic
+ // operand's failures survive into the message. Pinned as current behaviour, not
+ // endorsed -- a user calling Evaluate directly sees violations for a rule that Check
+ // accepts. See AssemblyTestHelper.AssertNoViolations for what this forced on the
+ // helper.
+ helper.AddSnapshotHeader("One operand passes, and the other's violations still show");
+ CyclicSliceRule.Or(SinglePassingTypeRule).AssertNoViolations(helper);
+
+ helper.AddSnapshotHeader("Both operands fail");
+ CyclicSliceRule.Or(FailingTypesRule(helper)).AssertHasViolations(helper);
+
+ await helper.AssertSnapshotMatches();
+ }
+ }
+}
diff --git a/ArchUnitNETTests/Fluent/Slices/SlicesTests.cs b/ArchUnitNETTests/Fluent/Slices/SlicesTests.cs
index 8fa7c0dc2..143d03af5 100644
--- a/ArchUnitNETTests/Fluent/Slices/SlicesTests.cs
+++ b/ArchUnitNETTests/Fluent/Slices/SlicesTests.cs
@@ -1,6 +1,8 @@
-using System.Linq;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
using ArchUnitNET.Fluent.Slices;
-using ArchUnitNET.xUnit;
+using ArchUnitNETTests.AssemblyTestHelper;
using Xunit;
namespace ArchUnitNETTests.Fluent.Slices
@@ -10,80 +12,111 @@ public class SlicesTests
[Fact]
public void CycleDetectionTest()
{
- Assert.Throws(() =>
- SliceRuleDefinition
- .Slices()
- .Matching("TestAssembly.Slices.(**)")
- .Should()
- .BeFreeOfCycles()
- .Check(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
- );
- Assert.False(
- SliceRuleDefinition
- .Slices()
- .Matching("TestAssembly.Slices.(**)")
- .Should()
- .BeFreeOfCycles()
- .HasNoViolations(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
- );
- Assert.True(
- SliceRuleDefinition
- .Slices()
- .Matching("TestAssembly.Slices.(**)..")
- .Should()
- .BeFreeOfCycles()
- .HasNoViolations(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
- );
+ var helper = new SlicesAssemblyTestHelper().WithoutSnapshot();
+ SliceRuleDefinition
+ .Slices()
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.(**)")
+ .Should()
+ .BeFreeOfCycles()
+ .AssertOnlyViolations(helper);
+ SliceRuleDefinition
+ .Slices()
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.(**)..")
+ .Should()
+ .BeFreeOfCycles()
+ .AssertNoViolations(helper);
+ }
+
+ // --- Rule evaluation driven by MatchingWithPackages (not just Matching) -------
+
+ [Fact]
+ public void BeFreeOfCycles_MatchingWithPackages_DetectsCycle()
+ {
+ var helper = new SlicesAssemblyTestHelper().WithoutSnapshot();
+ SliceRuleDefinition
+ .Slices()
+ .MatchingWithPackages("SlicesTestAssembly.MultipleSubnamespaces.(**)")
+ .Should()
+ .BeFreeOfCycles()
+ .AssertOnlyViolations(helper);
+ }
+
+ // SubnamespaceCircle has real edges (Slice1 -> Slice2, Slice2.Inner -> Slice1) but no
+ // cycle, unlike DotDotSemantics whose fixtures have no members and thus no edges at all --
+ // an empty graph would pass here even if BeFreeOfCycles always returned "no cycles".
+ [Fact]
+ public void BeFreeOfCycles_MatchingWithPackages_WhenAcyclic_Passes()
+ {
+ var helper = new SlicesAssemblyTestHelper().WithoutSnapshot();
+ SliceRuleDefinition
+ .Slices()
+ .MatchingWithPackages("SlicesTestAssembly.SubnamespaceCircle.(**)")
+ .Should()
+ .BeFreeOfCycles()
+ .AssertNoViolations(helper);
+ }
+
+ [Fact]
+ public Task NotDependOnEachOther_MatchingWithPackages_ReportsViolations()
+ {
+ var helper = new SlicesAssemblyTestHelper();
+ var rule = SliceRuleDefinition
+ .Slices()
+ .MatchingWithPackages("SlicesTestAssembly.MultipleSubnamespaces.(**)..")
+ .Should()
+ .NotDependOnEachOther();
+ rule.AssertAnyViolations(helper);
+ return helper.AssertSnapshotMatches();
}
[Fact]
public void MatchingTest()
{
Assert.Equal(
- 7,
+ 9,
SliceRuleDefinition
.Slices()
- .Matching("TestAssembly.Slices.(*)")
- .GetObjects(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.(*)")
+ .GetObjects(StaticTestArchitectures.SlicesTestArchitecture)
.Count()
);
Assert.Equal(
- 7,
+ 9,
SliceRuleDefinition
.Slices()
- .Matching("TestAssembly.Slices.(**)")
- .GetObjects(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.(**)")
+ .GetObjects(StaticTestArchitectures.SlicesTestArchitecture)
.Count()
);
Assert.Equal(
- 7,
+ 9,
SliceRuleDefinition
.Slices()
- .Matching("TestAssembly.Slices.(*)..")
- .GetObjects(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.(*)..")
+ .GetObjects(StaticTestArchitectures.SlicesTestArchitecture)
.Count()
);
Assert.Equal(
3,
SliceRuleDefinition
.Slices()
- .Matching("TestAssembly.Slices.(**)..")
- .GetObjects(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.(**)..")
+ .GetObjects(StaticTestArchitectures.SlicesTestArchitecture)
.Count()
);
Assert.Equal(
- 2,
+ 4,
SliceRuleDefinition
.Slices()
- .Matching("TestAssembly.Slices.Slice3.(*)")
- .GetObjects(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.Slice3.(*)")
+ .GetObjects(StaticTestArchitectures.SlicesTestArchitecture)
.Count()
);
Assert.False(
SliceRuleDefinition
.Slices()
- .Matching("TestAssembly.Slices.Service.(*)")
- .GetObjects(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.Service.(*)")
+ .GetObjects(StaticTestArchitectures.SlicesTestArchitecture)
.Any()
);
}
@@ -91,58 +124,342 @@ public void MatchingTest()
[Fact]
public void NotDependOnEachOtherTest()
{
+ var helper = new SlicesAssemblyTestHelper().WithoutSnapshot();
+ SliceRuleDefinition
+ .Slices()
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.Slice3.(*)")
+ .Should()
+ .NotDependOnEachOther()
+ .AssertNoViolations(helper);
+ SliceRuleDefinition
+ .Slices()
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.Slice1.(*)")
+ .Should()
+ .NotDependOnEachOther()
+ .AssertNoViolations(helper);
+ SliceRuleDefinition
+ .Slices()
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.(**)")
+ .Should()
+ .NotDependOnEachOther()
+ .AssertAnyViolations(helper);
SliceRuleDefinition
.Slices()
- .Matching("TestAssembly.Slices.Slice3.(*)")
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.(*)..")
.Should()
.NotDependOnEachOther()
- .Check(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture);
+ .AssertAnyViolations(helper);
SliceRuleDefinition
.Slices()
- .Matching("TestAssembly.Slices.Slice1.(*)")
+ .Matching("SlicesTestAssembly.MultipleSubnamespaces.(**)..")
.Should()
.NotDependOnEachOther()
- .Check(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture);
- Assert.True(
+ .AssertAnyViolations(helper);
+ }
+
+ private const string Root = "SlicesTestAssembly.MultipleSubnamespaces.";
+ private const string DotDot = "SlicesTestAssembly.DotDotSemantics.";
+
+ [Fact]
+ public void DirectCycleDetectionTest()
+ {
+ var helper = new SlicesAssemblyTestHelper().WithoutSnapshot();
+ foreach (var pattern in new[] { "(*)", "(*)..", "(**)" })
+ {
SliceRuleDefinition
.Slices()
- .Matching("TestAssembly.Slices.Slice1.(*)")
+ .Matching("SlicesTestAssembly.DirectCircle." + pattern)
.Should()
- .NotDependOnEachOther()
- .HasNoViolations(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
- );
- Assert.Throws(() =>
+ .BeFreeOfCycles()
+ .AssertOnlyViolations(helper);
+ }
+ }
+
+ // None of these patterns folds Slice2.Inner into Slice2, so Slice1 -> Slice2 and
+ // Slice2.Inner -> Slice1 stay dependencies between three distinct slices and there is
+ // genuinely no cycle to find.
+ [Fact]
+ public void SubnamespaceCycleDetectionTest()
+ {
+ var helper = new SlicesAssemblyTestHelper().WithoutSnapshot();
+ foreach (var pattern in new[] { "(*)", "(*)..", "(**)" })
+ {
SliceRuleDefinition
.Slices()
- .Matching("TestAssembly.Slices.(**)")
+ .Matching("SlicesTestAssembly.SubnamespaceCircle." + pattern)
.Should()
- .NotDependOnEachOther()
- .Check(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
+ .BeFreeOfCycles()
+ .AssertNoViolations(helper);
+ }
+ }
+
+ // See: https://github.com/TNG/ArchUnitNET/issues/208 -- "(**).." is the one pattern that
+ // folds Slice2.Inner into Slice2, which should surface the cycle
+ // Slice1 -> Slice2 -> (Slice2.Inner) -> Slice1. It currently does not: the fold drops the
+ // types sitting directly in the captured namespace (see
+ // Matching_DoubleAsteriskDotDot_DropsTypesDirectlyInCapturedNamespace), so Slice1Class
+ // never makes it into a slice and the cycle has no starting edge. Pinned as-is; this
+ // assertion is expected to flip once the matcher is reimplemented.
+ [Fact]
+ public void SubnamespaceCycleDetection_FoldedIntoParent_MissesCycle()
+ {
+ var helper = new SlicesAssemblyTestHelper().WithoutSnapshot();
+ SliceRuleDefinition
+ .Slices()
+ .Matching("SlicesTestAssembly.SubnamespaceCircle.(**)..")
+ .Should()
+ .BeFreeOfCycles()
+ .AssertNoViolations(helper);
+ }
+
+ [Fact]
+ public void Matching_SingleAsterisk_CapturesEveryDepth()
+ {
+ Assert.Equal(
+ new[]
+ {
+ "Slice1",
+ "Slice1.Service",
+ "Slice2",
+ "Slice2.Service",
+ "Slice3",
+ "Slice3.Group1",
+ "Slice3.Group1.Inner",
+ "Slice3.Group2",
+ "Slice3.Group2.Inner",
+ },
+ Descriptions(Root + "(*)")
);
- Assert.False(
- SliceRuleDefinition
- .Slices()
- .Matching("TestAssembly.Slices.(**)")
- .Should()
- .NotDependOnEachOther()
- .HasNoViolations(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
+ }
+
+ [Fact]
+ public void Matching_SingleAsteriskDotDot_CapturesEveryDepth()
+ {
+ Assert.Equal(Descriptions(Root + "(*)"), Descriptions(Root + "(*).."));
+ }
+
+ [Fact]
+ public void Matching_DoubleAsteriskDotDot_FoldsSubnamespacesIntoParent()
+ {
+ Assert.Equal(new[] { "Slice1", "Slice2", "Slice3" }, Descriptions(Root + "(**).."));
+ }
+
+ ///
+ /// The descriptions above only say which slices exist; this pins what ends up inside
+ /// them, which is where "(**).." is at its most surprising. Folding keeps the types
+ /// from the sub-namespaces but drops the ones sitting directly in the captured
+ /// namespace, so Slice1Class/Slice2Class/Slice3Class are in no slice at all. That is
+ /// what makes the NotDependOnEachOther snapshots report Slice2 and Slice3 as
+ /// depending on nothing, and what hides the cycle in
+ /// .
+ ///
+ [Fact]
+ public void Matching_DoubleAsteriskDotDot_DropsTypesDirectlyInCapturedNamespace()
+ {
+ var types = SliceRuleDefinition
+ .Slices()
+ .Matching(Root + "(**)..")
+ .GetObjects(StaticTestArchitectures.SlicesTestArchitecture)
+ .ToDictionary(
+ slice => slice.Description,
+ slice =>
+ slice
+ .Types.Select(type => type.FullName)
+ .OrderBy(name => name, StringComparer.Ordinal)
+ .ToArray()
+ );
+
+ Assert.Equal(new[] { Root + "Slice1.Service.Service1Class" }, types["Slice1"]);
+ Assert.Equal(new[] { Root + "Slice2.Service.Service2Class" }, types["Slice2"]);
+ Assert.Equal(
+ new[]
+ {
+ Root + "Slice3.Group1.Group1Class",
+ Root + "Slice3.Group1.Inner.Inner1Class",
+ Root + "Slice3.Group2.Group2Class",
+ Root + "Slice3.Group2.Inner.Inner2Class",
+ },
+ types["Slice3"]
);
- Assert.False(
- SliceRuleDefinition
- .Slices()
- .Matching("TestAssembly.Slices.(*)..")
- .Should()
- .NotDependOnEachOther()
- .HasNoViolations(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
+ }
+
+ [Fact]
+ public void Matching_TrailingLiteralAfterCapture_MatchesNothing()
+ {
+ Assert.Empty(Descriptions(Root + "(**).Service.."));
+ }
+
+ [Fact]
+ public void Matching_Alternation_MatchesNothing()
+ {
+ Assert.Empty(Descriptions(Root + "(**).[Service|Inner]"));
+ }
+
+ [Fact]
+ public void Matching_LeadingDotDot_CapturesEveryDepth()
+ {
+ Assert.Equal(Descriptions(Root + "(*)"), Descriptions("..MultipleSubnamespaces.(*)"));
+ }
+
+ // The fixture's namespace contains "DuplicatePrefix.Sub" twice
+ // (SlicesTestAssembly.DuplicatePrefix.Sub.DuplicatePrefix.Sub), so a leading ".." pattern
+ // for that prefix has two candidate starting positions to strip from. This pins that
+ // AssignFunc's IndexOf (leftmost match) picks the first occurrence, leaving the second
+ // "DuplicatePrefix.Sub" -- and the one leading dot "..DuplicatePrefix.Sub." keeps after
+ // stripping only one of its two literal dots -- in the description. With an absolute
+ // (non-"..") prefix, the prefix can only ever be found at index 0, so that variant of this
+ // test would pass even if IndexOf were replaced by a hardcoded 0.
+ [Fact]
+ public void MatchingWithPackages_DuplicatePrefixSegment_UsesFirstOccurrenceAsPrefix()
+ {
+ var slices = SliceRuleDefinition
+ .Slices()
+ .MatchingWithPackages("..DuplicatePrefix.Sub.(*)")
+ .GetObjects(StaticTestArchitectures.SlicesTestArchitecture)
+ .ToList();
+
+ Assert.Single(slices);
+ Assert.Equal(".DuplicatePrefix.Sub.DuplicatePrefix.Sub", slices[0].Description);
+ }
+
+ // --- ".." semantics -------------------------------------------------------------
+
+ [Fact]
+ public void DotDot_BetweenCaptureGroups_CapturesEveryDepth()
+ {
+ Assert.Equal(
+ new[]
+ {
+ "Alpha.Service",
+ "AlphaService",
+ "Outer.Inner",
+ "Outer.Mid.Inner",
+ "Single",
+ },
+ Descriptions(DotDot + "(*)..(*)")
);
- Assert.False(
- SliceRuleDefinition
- .Slices()
- .Matching("TestAssembly.Slices.(**)..")
- .Should()
- .NotDependOnEachOther()
- .HasNoViolations(StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture)
+ }
+
+ [Fact]
+ public void DotDot_BeforeLiteral_CapturesEveryDepth()
+ {
+ Assert.Equal(
+ new[]
+ {
+ "Alpha.Service",
+ "AlphaService",
+ "Outer.Inner",
+ "Outer.Mid.Inner",
+ "Single",
+ },
+ Descriptions(DotDot + "(*)..Service")
);
}
+
+ [Fact]
+ public void DotDot_BeforeLiteral_GivesAlphaServiceAndAlphaDotServiceDistinctSlices()
+ {
+ var slices = SliceRuleDefinition
+ .Slices()
+ .Matching(DotDot + "(*)..Service")
+ .GetObjects(StaticTestArchitectures.SlicesTestArchitecture)
+ .ToList();
+
+ // "AlphaService" is one segment and "Alpha.Service" is two, but that distinction is
+ // never actually evaluated: the "..Service" postfix is discarded (see
+ // AlphaServiceClass.cs), so both namespaces just get their own unfolded slice, each
+ // holding exactly the one type it's made of. This is not evidence the matcher tells
+ // the two shapes apart -- it would look identical if it ignored segment boundaries
+ // entirely.
+ Assert.Single(slices.Single(slice => slice.Description == "AlphaService").Types);
+ Assert.Single(slices.Single(slice => slice.Description == "Alpha.Service").Types);
+ }
+
+ // --- failure messages -----------------------------------------------------------
+
+ [Fact]
+ public Task BeFreeOfCycles_WithCycle_ReturnsDescriptiveCycleMessage()
+ {
+ var helper = new SlicesAssemblyTestHelper();
+ var rule = SliceRuleDefinition
+ .Slices()
+ .Matching("SlicesTestAssembly.DirectCircle.(*)")
+ .Should()
+ .BeFreeOfCycles();
+ rule.AssertOnlyViolations(helper);
+ return helper.AssertSnapshotMatches();
+ }
+
+ // MultipleSubnamespaces is the only fixture whose failing results are asserted purely via
+ // HasNoViolations, which short-circuits on the first violation and therefore never forces
+ // the rest of the lazily evaluated sequence. These two pin the complete result set.
+ // DirectCircle needs no equivalent: its three patterns collapse to the same cycle, whose
+ // wording BeFreeOfCycles_WithCycle_ReturnsDescriptiveCycleMessage already pins.
+ [Fact]
+ public Task BeFreeOfCycles_MultipleSubnamespaces_ReportsEveryCycle()
+ {
+ var helper = new SlicesAssemblyTestHelper();
+ var rule = SliceRuleDefinition
+ .Slices()
+ .Matching(Root + "(**)")
+ .Should()
+ .BeFreeOfCycles();
+ rule.AssertOnlyViolations(helper);
+ return helper.AssertSnapshotMatches();
+ }
+
+ [Fact]
+ public Task NotDependOnEachOther_MultipleSubnamespaces_ReportsEveryDependency()
+ {
+ var helper = new SlicesAssemblyTestHelper();
+ var rule = SliceRuleDefinition
+ .Slices()
+ .Matching(Root + "(**)")
+ .Should()
+ .NotDependOnEachOther();
+ rule.AssertAnyViolations(helper);
+ return helper.AssertSnapshotMatches();
+ }
+
+ // SubnamespaceCircle has real edges (Slice1 -> Slice2, Slice2.Inner -> Slice1) but no
+ // cycle, unlike DotDotSemantics whose fixtures have no members and thus no edges at all --
+ // an empty graph would pass here even if BeFreeOfCycles always returned "no cycles".
+ [Fact]
+ public Task BeFreeOfCycles_WhenNoCycles_ReportsAllSlicesFreeOfCycles()
+ {
+ var helper = new SlicesAssemblyTestHelper();
+ var rule = SliceRuleDefinition
+ .Slices()
+ .Matching("SlicesTestAssembly.SubnamespaceCircle.(**)")
+ .Should()
+ .BeFreeOfCycles();
+ rule.AssertNoViolations(helper);
+ return helper.AssertSnapshotMatches();
+ }
+
+ [Fact]
+ public Task NotDependOnEachOther_ReturnsDescriptiveDependencyMessage()
+ {
+ var helper = new SlicesAssemblyTestHelper();
+ var rule = SliceRuleDefinition
+ .Slices()
+ .Matching(Root + "(**)..")
+ .Should()
+ .NotDependOnEachOther();
+ // Mixed on purpose: the snapshot then shows both the passing and the failing wording.
+ rule.AssertAnyViolations(helper);
+ return helper.AssertSnapshotMatches();
+ }
+
+ private static string[] Descriptions(string pattern)
+ {
+ return SliceRuleDefinition
+ .Slices()
+ .Matching(pattern)
+ .GetObjects(StaticTestArchitectures.SlicesTestArchitecture)
+ .Select(slice => slice.Description)
+ .OrderBy(description => description, StringComparer.Ordinal)
+ .ToArray();
+ }
}
}
diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.And_FluentDefinition_CombinesEvaluationAndDescription.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.And_FluentDefinition_CombinesEvaluationAndDescription.verified.txt
new file mode 100644
index 000000000..15d5cc49f
--- /dev/null
+++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.And_FluentDefinition_CombinesEvaluationAndDescription.verified.txt
@@ -0,0 +1,45 @@
+===== Slice operand on its own =====
+
+Query: Slices matching "SlicesTestAssembly.SubnamespaceCircle.(**)" should be free of cycles
+Result: True
+Description: All Slices are free of cycles.
+Message:
+All Evaluations passed
+
+===== Both operands pass =====
+
+Query: Slices matching "SlicesTestAssembly.SubnamespaceCircle.(**)" should be free of cycles and Types that have full name "SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass" should exist
+Result: True
+Description: All Slices are free of cycles.
+Result: True
+Description: SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass passed
+Message:
+All Evaluations passed
+
+===== Slice operand fails =====
+
+Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles and Types that have full name "SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass" should exist
+Result: False
+Description: Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+Result: True
+Description: SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass passed
+Message:
+"Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed:
+ Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+
+
+
diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.And_WithArchRule_CombinesEvaluationAndDescription.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.And_WithArchRule_CombinesEvaluationAndDescription.verified.txt
new file mode 100644
index 000000000..c22e38343
--- /dev/null
+++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.And_WithArchRule_CombinesEvaluationAndDescription.verified.txt
@@ -0,0 +1,53 @@
+===== Both operands pass =====
+
+Query: Slices matching "SlicesTestAssembly.SubnamespaceCircle.(**)" should be free of cycles and Types that have full name "SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass" should exist
+Result: True
+Description: All Slices are free of cycles.
+Result: True
+Description: SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass passed
+Message:
+All Evaluations passed
+
+===== Second operand fails =====
+
+Query: Slices matching "SlicesTestAssembly.SubnamespaceCircle.(**)" should be free of cycles and Types that have name "NotTheNameOfAnyObject" should exist
+Result: True
+Description: All Slices are free of cycles.
+Result: False
+Description: There are no objects matching the criteria
+Message:
+"Types that have name "NotTheNameOfAnyObject" should exist" failed:
+ There are no objects matching the criteria
+
+
+
+===== Both operands fail =====
+
+Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles and Types that have name "NotTheNameOfAnyObject" should exist
+Result: False
+Description: Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+Result: False
+Description: There are no objects matching the criteria
+Message:
+"Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed:
+ Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+
+"Types that have name "NotTheNameOfAnyObject" should exist" failed:
+ There are no objects matching the criteria
+
+
+
diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.Or_FluentDefinition_CombinesEvaluationAndDescription.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.Or_FluentDefinition_CombinesEvaluationAndDescription.verified.txt
new file mode 100644
index 000000000..14faa3188
--- /dev/null
+++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.Or_FluentDefinition_CombinesEvaluationAndDescription.verified.txt
@@ -0,0 +1,82 @@
+===== Slice operand on its own =====
+
+Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles
+Result: False
+Description: Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+Message:
+"Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed:
+ Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+
+
+
+===== Second operand rescues the failing slice operand =====
+
+Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles or Types that have full name "SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass" should exist
+Result: False
+Description: Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+Result: True
+Description: SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass passed
+Message:
+"Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed:
+ Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+
+
+
+===== Both operands fail =====
+
+Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles or Types that have name "NotTheNameOfAnyObject" should exist
+Result: False
+Description: Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+Result: False
+Description: There are no objects matching the criteria
+Message:
+"Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed:
+ Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+
+"Types that have name "NotTheNameOfAnyObject" should exist" failed:
+ There are no objects matching the criteria
+
+
+
diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.Or_WithArchRule_CombinesEvaluationAndDescription.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.Or_WithArchRule_CombinesEvaluationAndDescription.verified.txt
new file mode 100644
index 000000000..42340d70c
--- /dev/null
+++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SliceRuleCombinationTests.Or_WithArchRule_CombinesEvaluationAndDescription.verified.txt
@@ -0,0 +1,57 @@
+===== One operand passes, and the other's violations still show =====
+
+Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles or Types that have full name "SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass" should exist
+Result: False
+Description: Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+Result: True
+Description: SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner.MidInnerClass passed
+Message:
+"Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed:
+ Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+
+
+
+===== Both operands fail =====
+
+Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles or Types that have name "NotTheNameOfAnyObject" should exist
+Result: False
+Description: Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+Result: False
+Description: There are no objects matching the criteria
+Message:
+"Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed:
+ Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+
+"Types that have name "NotTheNameOfAnyObject" should exist" failed:
+ There are no objects matching the criteria
+
+
+
diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_MultipleSubnamespaces_ReportsEveryCycle.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_MultipleSubnamespaces_ReportsEveryCycle.verified.txt
new file mode 100644
index 000000000..b15623362
--- /dev/null
+++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_MultipleSubnamespaces_ReportsEveryCycle.verified.txt
@@ -0,0 +1,23 @@
+Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles
+Result: False
+Description: Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+Message:
+"Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should be free of cycles" failed:
+ Cycle found:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+
+
+
diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_WhenNoCycles_ReportsAllSlicesFreeOfCycles.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_WhenNoCycles_ReportsAllSlicesFreeOfCycles.verified.txt
new file mode 100644
index 000000000..ef163ef79
--- /dev/null
+++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_WhenNoCycles_ReportsAllSlicesFreeOfCycles.verified.txt
@@ -0,0 +1,6 @@
+Query: Slices matching "SlicesTestAssembly.SubnamespaceCircle.(**)" should be free of cycles
+Result: True
+Description: All Slices are free of cycles.
+Message:
+All Evaluations passed
+
diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_WithCycle_ReturnsDescriptiveCycleMessage.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_WithCycle_ReturnsDescriptiveCycleMessage.verified.txt
new file mode 100644
index 000000000..03a2b704c
--- /dev/null
+++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.BeFreeOfCycles_WithCycle_ReturnsDescriptiveCycleMessage.verified.txt
@@ -0,0 +1,19 @@
+Query: Slices matching "SlicesTestAssembly.DirectCircle.(*)" should be free of cycles
+Result: False
+Description: Cycle found:
+Slice1 -> Slice2
+ SlicesTestAssembly.DirectCircle.Slice1.Slice1Class -> SlicesTestAssembly.DirectCircle.Slice2.Slice2Class
+Slice2 -> Slice1
+ SlicesTestAssembly.DirectCircle.Slice2.Slice2Class -> SlicesTestAssembly.DirectCircle.Slice1.Slice1Class
+
+Message:
+"Slices matching "SlicesTestAssembly.DirectCircle.(*)" should be free of cycles" failed:
+ Cycle found:
+Slice1 -> Slice2
+ SlicesTestAssembly.DirectCircle.Slice1.Slice1Class -> SlicesTestAssembly.DirectCircle.Slice2.Slice2Class
+Slice2 -> Slice1
+ SlicesTestAssembly.DirectCircle.Slice2.Slice2Class -> SlicesTestAssembly.DirectCircle.Slice1.Slice1Class
+
+
+
+
diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_MatchingWithPackages_ReportsViolations.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_MatchingWithPackages_ReportsViolations.verified.txt
new file mode 100644
index 000000000..ae856ef18
--- /dev/null
+++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_MatchingWithPackages_ReportsViolations.verified.txt
@@ -0,0 +1,19 @@
+Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**).." should not depend on each other
+Result: False
+Description: SlicesTestAssembly.MultipleSubnamespaces.Slice1 does depend on other slices:
+SlicesTestAssembly.MultipleSubnamespaces.Slice1 -> SlicesTestAssembly.MultipleSubnamespaces.Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service.Service1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class
+
+Result: True
+Description: SlicesTestAssembly.MultipleSubnamespaces.Slice2 does not depend on another slice.
+Result: True
+Description: SlicesTestAssembly.MultipleSubnamespaces.Slice3 does not depend on another slice.
+Message:
+"Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**).." should not depend on each other" failed:
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1 does depend on other slices:
+SlicesTestAssembly.MultipleSubnamespaces.Slice1 -> SlicesTestAssembly.MultipleSubnamespaces.Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service.Service1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class
+
+
+
+
diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_MultipleSubnamespaces_ReportsEveryDependency.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_MultipleSubnamespaces_ReportsEveryDependency.verified.txt
new file mode 100644
index 000000000..0a801d01b
--- /dev/null
+++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_MultipleSubnamespaces_ReportsEveryDependency.verified.txt
@@ -0,0 +1,66 @@
+Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should not depend on each other
+Result: False
+Description: Slice1 does depend on other slices:
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+
+Result: False
+Description: Slice1.Service does depend on other slices:
+Slice1.Service -> Slice2.Service
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service.Service1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class
+
+Result: False
+Description: Slice2 does depend on other slices:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+
+Result: False
+Description: Slice2.Service does depend on other slices:
+Slice2.Service -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+
+Result: False
+Description: Slice3 does depend on other slices:
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+Result: False
+Description: Slice3.Group1 does depend on other slices:
+Slice3.Group1 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Group1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+
+Result: True
+Description: Slice3.Group1.Inner does not depend on another slice.
+Result: True
+Description: Slice3.Group2 does not depend on another slice.
+Result: True
+Description: Slice3.Group2.Inner does not depend on another slice.
+Message:
+"Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**)" should not depend on each other" failed:
+ Slice1 does depend on other slices:
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class
+
+ Slice1.Service does depend on other slices:
+Slice1.Service -> Slice2.Service
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service.Service1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class
+
+ Slice2 does depend on other slices:
+Slice2 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Slice2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+
+ Slice2.Service does depend on other slices:
+Slice2.Service -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+
+ Slice3 does depend on other slices:
+Slice3 -> Slice1
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class
+
+ Slice3.Group1 does depend on other slices:
+Slice3.Group1 -> Slice3
+ SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Group1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice3.Slice3Class
+
+
+
+
diff --git a/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_ReturnsDescriptiveDependencyMessage.verified.txt b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_ReturnsDescriptiveDependencyMessage.verified.txt
new file mode 100644
index 000000000..448e70e4b
--- /dev/null
+++ b/ArchUnitNETTests/Fluent/Slices/Snapshots/SlicesTests.NotDependOnEachOther_ReturnsDescriptiveDependencyMessage.verified.txt
@@ -0,0 +1,19 @@
+Query: Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**).." should not depend on each other
+Result: False
+Description: Slice1 does depend on other slices:
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service.Service1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class
+
+Result: True
+Description: Slice2 does not depend on another slice.
+Result: True
+Description: Slice3 does not depend on another slice.
+Message:
+"Slices matching "SlicesTestAssembly.MultipleSubnamespaces.(**).." should not depend on each other" failed:
+ Slice1 does depend on other slices:
+Slice1 -> Slice2
+ SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service.Service1Class -> SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service.Service2Class
+
+
+
+
diff --git a/ArchUnitNETTests/StaticTestArchitectures.cs b/ArchUnitNETTests/StaticTestArchitectures.cs
index 39246cccc..b9dcef78b 100644
--- a/ArchUnitNETTests/StaticTestArchitectures.cs
+++ b/ArchUnitNETTests/StaticTestArchitectures.cs
@@ -96,6 +96,12 @@ public static class StaticTestArchitectures
.LoadAssemblies(typeof(ClassNamespace.RegularClass).Assembly)
.Build();
+ public static readonly Architecture SlicesTestArchitecture = new ArchLoader()
+ .LoadAssemblies(
+ typeof(SlicesTestAssembly.MultipleSubnamespaces.Slice1.Slice1Class).Assembly
+ )
+ .Build();
+
public static readonly Architecture ArchUnitNETTestAssemblyArchitecture = new ArchLoader()
.LoadAssemblies(typeof(Class1).Assembly)
.Build();
diff --git a/TestAssemblies/SlicesTestAssembly/DirectCircle/Slice1/Slice1Class.cs b/TestAssemblies/SlicesTestAssembly/DirectCircle/Slice1/Slice1Class.cs
new file mode 100644
index 000000000..7088f2e7a
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/DirectCircle/Slice1/Slice1Class.cs
@@ -0,0 +1,8 @@
+using SlicesTestAssembly.DirectCircle.Slice2;
+
+namespace SlicesTestAssembly.DirectCircle.Slice1;
+
+public class Slice1Class
+{
+ public Slice2Class Reference = null!;
+}
diff --git a/TestAssemblies/SlicesTestAssembly/DirectCircle/Slice2/Slice2Class.cs b/TestAssemblies/SlicesTestAssembly/DirectCircle/Slice2/Slice2Class.cs
new file mode 100644
index 000000000..c35904236
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/DirectCircle/Slice2/Slice2Class.cs
@@ -0,0 +1,8 @@
+using SlicesTestAssembly.DirectCircle.Slice1;
+
+namespace SlicesTestAssembly.DirectCircle.Slice2;
+
+public class Slice2Class
+{
+ public Slice1Class Reference = null!;
+}
diff --git a/TestAssemblies/SlicesTestAssembly/DotDotSemantics/Alpha/Service/AlphaServiceSegmentClass.cs b/TestAssemblies/SlicesTestAssembly/DotDotSemantics/Alpha/Service/AlphaServiceSegmentClass.cs
new file mode 100644
index 000000000..1144250df
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/DotDotSemantics/Alpha/Service/AlphaServiceSegmentClass.cs
@@ -0,0 +1,4 @@
+namespace SlicesTestAssembly.DotDotSemantics.Alpha.Service;
+
+// "Service" as a segment of its own: the case "DotDotSemantics.(*)..Service" is meant to match.
+public class AlphaServiceSegmentClass { }
diff --git a/TestAssemblies/SlicesTestAssembly/DotDotSemantics/AlphaService/AlphaServiceClass.cs b/TestAssemblies/SlicesTestAssembly/DotDotSemantics/AlphaService/AlphaServiceClass.cs
new file mode 100644
index 000000000..feedab11d
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/DotDotSemantics/AlphaService/AlphaServiceClass.cs
@@ -0,0 +1,7 @@
+namespace SlicesTestAssembly.DotDotSemantics.AlphaService;
+
+// A single segment that ends with the literal "Service". Parse() discards everything after the
+// first "(*" for single-asterisk patterns, so "DotDotSemantics.(*)..Service" never actually
+// applies the "..Service" postfix: this namespace matches it just the same as Alpha/Service below,
+// even though "AlphaService" is one segment and not "Alpha" + "Service".
+public class AlphaServiceClass { }
diff --git a/TestAssemblies/SlicesTestAssembly/DotDotSemantics/Outer/Inner/InnerClass.cs b/TestAssemblies/SlicesTestAssembly/DotDotSemantics/Outer/Inner/InnerClass.cs
new file mode 100644
index 000000000..d9e18519b
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/DotDotSemantics/Outer/Inner/InnerClass.cs
@@ -0,0 +1,4 @@
+namespace SlicesTestAssembly.DotDotSemantics.Outer.Inner;
+
+// Two segments below DotDotSemantics, with nothing skipped in between.
+public class InnerClass { }
diff --git a/TestAssemblies/SlicesTestAssembly/DotDotSemantics/Outer/Mid/Inner/MidInnerClass.cs b/TestAssemblies/SlicesTestAssembly/DotDotSemantics/Outer/Mid/Inner/MidInnerClass.cs
new file mode 100644
index 000000000..cc23a0591
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/DotDotSemantics/Outer/Mid/Inner/MidInnerClass.cs
@@ -0,0 +1,4 @@
+namespace SlicesTestAssembly.DotDotSemantics.Outer.Mid.Inner;
+
+// Three segments below DotDotSemantics: "Mid" is the segment a ".." is expected to skip.
+public class MidInnerClass { }
diff --git a/TestAssemblies/SlicesTestAssembly/DotDotSemantics/Single/SingleClass.cs b/TestAssemblies/SlicesTestAssembly/DotDotSemantics/Single/SingleClass.cs
new file mode 100644
index 000000000..c59cbd8e4
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/DotDotSemantics/Single/SingleClass.cs
@@ -0,0 +1,5 @@
+namespace SlicesTestAssembly.DotDotSemantics.Single;
+
+// One segment below DotDotSemantics: a pattern capturing two segments must not match this
+// namespace by splitting "Single" into two parts.
+public class SingleClass { }
diff --git a/TestAssemblies/SlicesTestAssembly/DuplicatePrefix/Sub/DuplicatePrefix/Sub/LeafClass.cs b/TestAssemblies/SlicesTestAssembly/DuplicatePrefix/Sub/DuplicatePrefix/Sub/LeafClass.cs
new file mode 100644
index 000000000..f93a29898
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/DuplicatePrefix/Sub/DuplicatePrefix/Sub/LeafClass.cs
@@ -0,0 +1,5 @@
+namespace SlicesTestAssembly.DuplicatePrefix.Sub.DuplicatePrefix.Sub;
+
+// The full slice prefix "DuplicatePrefix.Sub" occurs twice in this namespace: matching it with a
+// leading ".." (Contains-based) pattern is ambiguous about which occurrence is "the" prefix.
+public class LeafClass { }
diff --git a/TestAssemblies/SlicesTestAssembly/MixedTypes/IMixedTypesInterface.cs b/TestAssemblies/SlicesTestAssembly/MixedTypes/IMixedTypesInterface.cs
new file mode 100644
index 000000000..0215eca05
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/MixedTypes/IMixedTypesInterface.cs
@@ -0,0 +1,6 @@
+namespace SlicesTestAssembly.MixedTypes;
+
+// The only interface in this assembly. It lives in a namespace that no slice pattern in the
+// tests matches, so it stays out of every slice-assignment and diagram fixture and exists
+// purely so Slice's Classes/Interfaces filters can be exercised on a mixed type list.
+public interface IMixedTypesInterface { }
diff --git a/TestAssemblies/SlicesTestAssembly/MixedTypes/MixedTypesClass.cs b/TestAssemblies/SlicesTestAssembly/MixedTypes/MixedTypesClass.cs
new file mode 100644
index 000000000..ba25a327b
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/MixedTypes/MixedTypesClass.cs
@@ -0,0 +1,4 @@
+namespace SlicesTestAssembly.MixedTypes;
+
+// See IMixedTypesInterface: the class half of the mixed type list.
+public class MixedTypesClass : IMixedTypesInterface { }
diff --git a/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice1/Service/Service1Class.cs b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice1/Service/Service1Class.cs
new file mode 100644
index 000000000..31076cef8
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice1/Service/Service1Class.cs
@@ -0,0 +1,8 @@
+using SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service;
+
+namespace SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service;
+
+public class Service1Class
+{
+ public Service2Class Reference = null!;
+}
diff --git a/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice1/Slice1Class.cs b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice1/Slice1Class.cs
new file mode 100644
index 000000000..72880102a
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice1/Slice1Class.cs
@@ -0,0 +1,8 @@
+using SlicesTestAssembly.MultipleSubnamespaces.Slice2;
+
+namespace SlicesTestAssembly.MultipleSubnamespaces.Slice1;
+
+public class Slice1Class
+{
+ public Slice2Class Reference = null!;
+}
diff --git a/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice2/Service/Service2Class.cs b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice2/Service/Service2Class.cs
new file mode 100644
index 000000000..1e6d1f501
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice2/Service/Service2Class.cs
@@ -0,0 +1,8 @@
+using SlicesTestAssembly.MultipleSubnamespaces.Slice3;
+
+namespace SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service;
+
+public class Service2Class
+{
+ public Slice3Class Reference = null!;
+}
diff --git a/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice2/Slice2Class.cs b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice2/Slice2Class.cs
new file mode 100644
index 000000000..3dd170cb8
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice2/Slice2Class.cs
@@ -0,0 +1,8 @@
+using SlicesTestAssembly.MultipleSubnamespaces.Slice3;
+
+namespace SlicesTestAssembly.MultipleSubnamespaces.Slice2;
+
+public class Slice2Class
+{
+ public Slice3Class Reference = null!;
+}
diff --git a/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Group1/Group1Class.cs b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Group1/Group1Class.cs
new file mode 100644
index 000000000..2a5730b74
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Group1/Group1Class.cs
@@ -0,0 +1,6 @@
+namespace SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1;
+
+public class Group1Class
+{
+ public Slice3Class Dependency = null!;
+}
diff --git a/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Group1/Inner/Inner1Class.cs b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Group1/Inner/Inner1Class.cs
new file mode 100644
index 000000000..d49ea72dd
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Group1/Inner/Inner1Class.cs
@@ -0,0 +1,3 @@
+namespace SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner;
+
+public class Inner1Class { }
diff --git a/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Group2/Group2Class.cs b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Group2/Group2Class.cs
new file mode 100644
index 000000000..b93159d41
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Group2/Group2Class.cs
@@ -0,0 +1,3 @@
+namespace SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2;
+
+public class Group2Class { }
diff --git a/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Group2/Inner/Inner2Class.cs b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Group2/Inner/Inner2Class.cs
new file mode 100644
index 000000000..80bfb05a2
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Group2/Inner/Inner2Class.cs
@@ -0,0 +1,3 @@
+namespace SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner;
+
+public class Inner2Class { }
diff --git a/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Slice3Class.cs b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Slice3Class.cs
new file mode 100644
index 000000000..04de2226e
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/MultipleSubnamespaces/Slice3/Slice3Class.cs
@@ -0,0 +1,8 @@
+using SlicesTestAssembly.MultipleSubnamespaces.Slice1;
+
+namespace SlicesTestAssembly.MultipleSubnamespaces.Slice3;
+
+public class Slice3Class
+{
+ public Slice1Class Reference = null!;
+}
diff --git a/TestAssemblies/SlicesTestAssembly/SlicesTestAssembly.csproj b/TestAssemblies/SlicesTestAssembly/SlicesTestAssembly.csproj
new file mode 100644
index 000000000..51fae4aaa
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/SlicesTestAssembly.csproj
@@ -0,0 +1,12 @@
+
+
+ net10.0
+ enable
+ enable
+ true
+ false
+ True
+ ..\..\strongNameKey.snk
+ 1.0.0.0
+
+
diff --git a/TestAssemblies/SlicesTestAssembly/SubnamespaceCircle/Slice1/Slice1Class.cs b/TestAssemblies/SlicesTestAssembly/SubnamespaceCircle/Slice1/Slice1Class.cs
new file mode 100644
index 000000000..dc9aeb136
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/SubnamespaceCircle/Slice1/Slice1Class.cs
@@ -0,0 +1,8 @@
+using SlicesTestAssembly.SubnamespaceCircle.Slice2;
+
+namespace SlicesTestAssembly.SubnamespaceCircle.Slice1;
+
+public class Slice1Class
+{
+ public Slice2Class Reference = null!;
+}
diff --git a/TestAssemblies/SlicesTestAssembly/SubnamespaceCircle/Slice2/Inner/Slice2InnerClass.cs b/TestAssemblies/SlicesTestAssembly/SubnamespaceCircle/Slice2/Inner/Slice2InnerClass.cs
new file mode 100644
index 000000000..f3db26f37
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/SubnamespaceCircle/Slice2/Inner/Slice2InnerClass.cs
@@ -0,0 +1,6 @@
+namespace SlicesTestAssembly.SubnamespaceCircle.Slice2.Inner;
+
+public class Slice2InnerClass
+{
+ public Slice1.Slice1Class Reference = null!;
+}
diff --git a/TestAssemblies/SlicesTestAssembly/SubnamespaceCircle/Slice2/Slice2Class.cs b/TestAssemblies/SlicesTestAssembly/SubnamespaceCircle/Slice2/Slice2Class.cs
new file mode 100644
index 000000000..83071b022
--- /dev/null
+++ b/TestAssemblies/SlicesTestAssembly/SubnamespaceCircle/Slice2/Slice2Class.cs
@@ -0,0 +1,3 @@
+namespace SlicesTestAssembly.SubnamespaceCircle.Slice2;
+
+public class Slice2Class { }
diff --git a/TestAssembly/Slices/Slice1/Service/Service1Class.cs b/TestAssembly/Slices/Slice1/Service/Service1Class.cs
deleted file mode 100644
index 553582c9b..000000000
--- a/TestAssembly/Slices/Slice1/Service/Service1Class.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using TestAssembly.Slices.Slice2.Service;
-
-namespace TestAssembly.Slices.Slice1.Service
-{
- public class Service1Class
- {
- public Service2Class Reference;
- }
-}
diff --git a/TestAssembly/Slices/Slice1/Slice1Class.cs b/TestAssembly/Slices/Slice1/Slice1Class.cs
deleted file mode 100644
index 901d6140e..000000000
--- a/TestAssembly/Slices/Slice1/Slice1Class.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using TestAssembly.Slices.Slice2;
-
-namespace TestAssembly.Slices.Slice1
-{
- public class Slice1Class
- {
- public Slice2Class Reference;
- }
-}
diff --git a/TestAssembly/Slices/Slice2/Service/Service2Class.cs b/TestAssembly/Slices/Slice2/Service/Service2Class.cs
deleted file mode 100644
index 7f088c16c..000000000
--- a/TestAssembly/Slices/Slice2/Service/Service2Class.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using TestAssembly.Slices.Slice3;
-
-namespace TestAssembly.Slices.Slice2.Service
-{
- public class Service2Class
- {
- public Slice3Class Reference;
- }
-}
diff --git a/TestAssembly/Slices/Slice2/Slice2Class.cs b/TestAssembly/Slices/Slice2/Slice2Class.cs
deleted file mode 100644
index aac88867b..000000000
--- a/TestAssembly/Slices/Slice2/Slice2Class.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using TestAssembly.Slices.Slice3;
-
-namespace TestAssembly.Slices.Slice2
-{
- public class Slice2Class
- {
- public Slice3Class Reference;
- }
-}
diff --git a/TestAssembly/Slices/Slice3/Group1/Group1Class.cs b/TestAssembly/Slices/Slice3/Group1/Group1Class.cs
deleted file mode 100644
index f6dc0664d..000000000
--- a/TestAssembly/Slices/Slice3/Group1/Group1Class.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace TestAssembly.Slices.Slice3.Group1
-{
- public class Group1Class
- {
- public Slice3Class Dependency;
- }
-}
diff --git a/TestAssembly/Slices/Slice3/Group2/Group2Class.cs b/TestAssembly/Slices/Slice3/Group2/Group2Class.cs
deleted file mode 100644
index cffad2c95..000000000
--- a/TestAssembly/Slices/Slice3/Group2/Group2Class.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace TestAssembly.Slices.Slice3.Group2
-{
- public class Group2Class { }
-}
diff --git a/TestAssembly/Slices/Slice3/Slice3Class.cs b/TestAssembly/Slices/Slice3/Slice3Class.cs
deleted file mode 100644
index fd1b7f971..000000000
--- a/TestAssembly/Slices/Slice3/Slice3Class.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using TestAssembly.Slices.Slice1;
-
-namespace TestAssembly.Slices.Slice3
-{
- public class Slice3Class
- {
- public Slice1Class Reference;
- }
-}