diff --git a/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs b/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs
index d75ca4f76..ae0018484 100644
--- a/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs
+++ b/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs
@@ -348,13 +348,19 @@ ConditionResult Condition(TRuleType ruleType)
.Concat(classDiagramAssociation.GetTargetNamespaceIdentifiers(ruleType))
.ToList());
+
var pass = true;
var dynamicFailDescription = "does depend on";
- foreach (var dependency in ruleType.GetTypeDependencies())
+
+ //Prevent failDescriptions like "does depend on X and does depend on X and does depend on Y and does depend on Y
+ var ruleTypeDependencies = ruleType.GetTypeDependencies().GroupBy(p => p.FullName).Select(g => g.First());
+ foreach (var dependency in ruleTypeDependencies)
{
- if (classDiagramAssociation.Contains(dependency) && !allAllowedTargets.Any(pattern => dependency.FullNameMatches(pattern, true)))
+ if (classDiagramAssociation.Contains(dependency)
+ && !allAllowedTargets.Any(pattern => dependency.FullNameMatches(pattern, true)))
{
dynamicFailDescription += pass ? " " + dependency.FullName : " and " + dependency.FullName;
+
pass = false;
}
}
diff --git a/ArchUnitNETTests/ArchUnitNETTests.csproj b/ArchUnitNETTests/ArchUnitNETTests.csproj
index 4ac977511..fce6557f1 100644
--- a/ArchUnitNETTests/ArchUnitNETTests.csproj
+++ b/ArchUnitNETTests/ArchUnitNETTests.csproj
@@ -8,14 +8,20 @@
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+ PreserveNewest
+
diff --git a/ArchUnitNETTests/Domain/PlantUml/PlantUmlErrorMessagesCheck.cs b/ArchUnitNETTests/Domain/PlantUml/PlantUmlErrorMessagesCheck.cs
new file mode 100644
index 000000000..787d4c372
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/PlantUmlErrorMessagesCheck.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using ArchUnitNET.Domain;
+using ArchUnitNET.Fluent;
+using ArchUnitNET.xUnit;
+using Xunit;
+using static ArchUnitNET.Fluent.ArchRuleDefinition;
+
+namespace ArchUnitNETTests.Domain.PlantUml
+{
+ public class PlantUmlErrorMessagesCheck
+ {
+ private static readonly Architecture Architecture = StaticTestArchitectures.ArchUnitNETTestAssemblyArchitecture;
+ private readonly string _umlFile;
+
+ public PlantUmlErrorMessagesCheck()
+ {
+ _umlFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Domain", "PlantUml",
+ "zzz_test_version_with_errors.puml");
+ }
+
+ [Fact]
+ public void NoDuplicatesInErrorMessageTest()
+ {
+ var testPassed = CheckByPuml(out var rawErrormessage);
+ Assert.False(testPassed);
+
+ //CheckForDuplicates returns false when errormessage contains duplicates or is empty
+ var containsNoDuplicates = ContainsNoDuplicates(rawErrormessage, out var explainErrormessage);
+
+ var errormessage = "\nOriginal (ArchUnitNet) Exception:\n" + rawErrormessage +
+ "\n\nAssert Error:\n" + explainErrormessage + "\n";
+
+ Assert.True(containsNoDuplicates, errormessage);
+ }
+
+ private bool CheckByPuml(out string errormessage)
+ {
+ errormessage = null;
+
+ try
+ {
+ IArchRule adhereToPlantUmlDiagram = Types().Should().AdhereToPlantUmlDiagram(_umlFile);
+ adhereToPlantUmlDiagram.Check(Architecture);
+ }
+ //xUnit
+ catch (FailedArchRuleException exception)
+ {
+ errormessage = exception.Message;
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool ContainsNoDuplicates(string uncutMessage, out string errormessage)
+ {
+ if (string.IsNullOrWhiteSpace(uncutMessage))
+ {
+ errormessage = "Error message is empty.";
+ return false;
+ }
+
+ var sources = new List();
+
+ var errors = uncutMessage.Split('\n').Skip(1);
+
+ foreach (var error in errors.Where(e => !string.IsNullOrWhiteSpace(e)))
+ {
+ var splitError = error.Split(" does depend on ");
+ var source = splitError[0].Trim();
+ var targets = splitError[1].Trim().Split(" and ");
+ if (sources.Contains(source))
+ {
+ errormessage = $"Two errors with {source} as source found.";
+ return false;
+ }
+
+ sources.Add(source);
+ if (targets.Distinct().Count() < targets.Length)
+ {
+ errormessage = $"The error \"{error}\" contains duplicate targets.";
+ return false;
+ }
+ }
+
+ errormessage = "No duplicates found.";
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ArchUnitNETTests/Domain/PlantUml/zzz_test_version_with_errors.puml b/ArchUnitNETTests/Domain/PlantUml/zzz_test_version_with_errors.puml
new file mode 100644
index 000000000..f585911e7
--- /dev/null
+++ b/ArchUnitNETTests/Domain/PlantUml/zzz_test_version_with_errors.puml
@@ -0,0 +1,50 @@
+@startuml
+
+
+' !!! Test Version !!!
+' This PlantUML Diagram will cause errors when you run the ArchUnitNet PLantUML Test
+' It is made to check the errormessages that you will get when the codebase architecture does not match the architecture of the PUML
+' Unlogical or wrong dependencies are made on purpose
+
+skinparam componentStyle uml2
+skinparam component {
+ BorderColor #grey
+ BackgroundColor #white
+}
+
+[Addresses] <>
+[Customers] <>
+[Orders] <>
+[Products] <>
+[Product Catalog] <> as catalog
+[Product Import] <> as import
+
+' Could be some random comment
+[XML] <> <> as xml
+
+'Causes Error
+[Addresses] <-[#blue]- catalog
+
+'Causes Error
+[Orders] <-[#blue]- [Customers] : is placed by
+[Orders] --> [Products]
+[Orders] --> [Addresses]
+
+'Causes Error
+[Customers] <-[#blue]- [Addresses]
+[Customers] --> [Products]
+
+
+[Products] <--[#green]- catalog
+
+'Causes Error
+catalog <-[#blue]- [Orders]
+
+import -left-> catalog : parse products
+import --> xml
+'Causes Error
+import <-[#blue]- [Customers]
+
+note top on link #lightgreen: is responsible for translating XML to csharp classes
+
+@enduml
\ No newline at end of file
diff --git a/TestAssembly/PlantUml/Addresses/Address.cs b/TestAssembly/PlantUml/Addresses/Address.cs
new file mode 100644
index 000000000..4db7a327b
--- /dev/null
+++ b/TestAssembly/PlantUml/Addresses/Address.cs
@@ -0,0 +1,11 @@
+using TestAssembly.PlantUml.Catalog;
+
+namespace TestAssembly.PlantUml.Addresses
+{
+ public class Address
+ {
+#pragma warning disable CS0169
+ private ProductCatalog productCatalog;
+#pragma warning restore CS0169
+ }
+}
\ No newline at end of file
diff --git a/TestAssembly/PlantUml/Catalog/ProductCatalog.cs b/TestAssembly/PlantUml/Catalog/ProductCatalog.cs
new file mode 100644
index 000000000..56642c9de
--- /dev/null
+++ b/TestAssembly/PlantUml/Catalog/ProductCatalog.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using TestAssembly.PlantUml.Orders;
+using TestAssembly.PlantUml.Products;
+
+namespace TestAssembly.PlantUml.Catalog
+{
+ public class ProductCatalog
+ {
+ private readonly List _allProducts = new List();
+
+ internal void GonnaDoSomethingIllegalWithOrder()
+ {
+ var order = new Order();
+ foreach (var product in _allProducts)
+ {
+ product.Register();
+ }
+
+ order.AddProducts(_allProducts);
+ }
+ }
+}
\ No newline at end of file
diff --git a/TestAssembly/PlantUml/Customers/Customer.cs b/TestAssembly/PlantUml/Customers/Customer.cs
new file mode 100644
index 000000000..efd00e015
--- /dev/null
+++ b/TestAssembly/PlantUml/Customers/Customer.cs
@@ -0,0 +1,15 @@
+using TestAssembly.PlantUml.Addresses;
+using TestAssembly.PlantUml.Orders;
+
+namespace TestAssembly.PlantUml.Customers
+{
+ public class Customer
+ {
+ public Address Address { get; set; }
+
+ internal void AddOrder(Order order)
+ {
+ // simply having such a parameter violates the specified UML diagram
+ }
+ }
+}
\ No newline at end of file
diff --git a/TestAssembly/PlantUml/Importer/ProductImport.cs b/TestAssembly/PlantUml/Importer/ProductImport.cs
new file mode 100644
index 000000000..16f19d413
--- /dev/null
+++ b/TestAssembly/PlantUml/Importer/ProductImport.cs
@@ -0,0 +1,21 @@
+using TestAssembly.PlantUml.Catalog;
+using TestAssembly.PlantUml.Customers;
+using TestAssembly.PlantUml.Xml.Processor;
+using TestAssembly.PlantUml.Xml.Types;
+
+namespace TestAssembly.PlantUml.Importer
+{
+ public class ProductImport
+ {
+ public ProductCatalog productCatalog;
+ public XmlTypes xmlType;
+ public XmlProcessor xmlProcessor;
+
+ public Customer Customer => new Customer(); // violates diagram -> product import may not directly know Customer
+
+ private ProductCatalog Parse(byte[] xml)
+ {
+ return new ProductCatalog();
+ }
+ }
+}
\ No newline at end of file
diff --git a/TestAssembly/PlantUml/Orders/Order.cs b/TestAssembly/PlantUml/Orders/Order.cs
new file mode 100644
index 000000000..f13a0bf16
--- /dev/null
+++ b/TestAssembly/PlantUml/Orders/Order.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using TestAssembly.PlantUml.Addresses;
+using TestAssembly.PlantUml.Customers;
+using TestAssembly.PlantUml.Products;
+
+namespace TestAssembly.PlantUml.Orders
+{
+ public class Order
+ {
+ public Customer _customer;
+ private readonly List _products = new List();
+
+ public void AddProducts(IList products)
+ {
+ _products.AddRange(products);
+ }
+
+ internal void Report()
+ {
+ Report(_customer.Address);
+ foreach (var product in _products)
+ {
+ product.Report();
+ }
+ }
+
+ private void Report(Address address)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/TestAssembly/PlantUml/Products/Product.cs b/TestAssembly/PlantUml/Products/Product.cs
new file mode 100644
index 000000000..1a8074e1a
--- /dev/null
+++ b/TestAssembly/PlantUml/Products/Product.cs
@@ -0,0 +1,21 @@
+using TestAssembly.PlantUml.Customers;
+using TestAssembly.PlantUml.Orders;
+
+namespace TestAssembly.PlantUml.Products
+{
+ public class Product
+ {
+ public Customer _customer;
+
+ internal Order Order => null; // the return type violates the specified UML diagram
+
+
+ public void Register()
+ {
+ }
+
+ public void Report()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/TestAssembly/PlantUml/Xml/Processor/XmlProcessor.cs b/TestAssembly/PlantUml/Xml/Processor/XmlProcessor.cs
new file mode 100644
index 000000000..d3a3648dc
--- /dev/null
+++ b/TestAssembly/PlantUml/Xml/Processor/XmlProcessor.cs
@@ -0,0 +1,6 @@
+namespace TestAssembly.PlantUml.Xml.Processor
+{
+ public class XmlProcessor
+ {
+ }
+}
\ No newline at end of file
diff --git a/TestAssembly/PlantUml/Xml/Types/XmlTypes.cs b/TestAssembly/PlantUml/Xml/Types/XmlTypes.cs
new file mode 100644
index 000000000..8575a8ce5
--- /dev/null
+++ b/TestAssembly/PlantUml/Xml/Types/XmlTypes.cs
@@ -0,0 +1,6 @@
+namespace TestAssembly.PlantUml.Xml.Types
+{
+ public class XmlTypes
+ {
+ }
+}
\ No newline at end of file
diff --git a/TestAssembly/PlantUml/Xml/Utils/XmlUtils.cs b/TestAssembly/PlantUml/Xml/Utils/XmlUtils.cs
new file mode 100644
index 000000000..6ce6f4988
--- /dev/null
+++ b/TestAssembly/PlantUml/Xml/Utils/XmlUtils.cs
@@ -0,0 +1,6 @@
+namespace TestAssembly.PlantUml.Xml.Utils
+{
+ public class XmlUtils
+ {
+ }
+}
\ No newline at end of file