1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.ComponentModel.Composition.Factories;
6 using System.ComponentModel.Composition.Hosting;
7 using System.ComponentModel.Composition.Primitives;
8 using System.Linq;
9 using Xunit;
10 
11 namespace System.ComponentModel.Composition
12 {
13     public class CompositionContainerCycleTests
14     {
15         // There are nine possible scenarios that cause a part to have a dependency on another part, some of which
16         // are legal and some not. For example, below, is not legal for a part, A, to have a prerequisite dependency
17         // on a part, B, which has also has a prerequisite dependency on A. In contrast, however, it is legal for
18         // part A and B to have a non-prerequisite (Post) dependency on each other.
19         //
20         // ------------------------------
21         // |        |         B         |
22         // |        | Pre | Post | None |
23         // |--------|-----|------|------|
24         // |   Pre  |  X  |    X |    v |
25         // | A Post |  X  |    v |    v |
26         // |   None |  v  |    v |    v |
27         // ------------------------------
28         //
29 
30         [Fact]
APrerequisiteDependsOnBPrerequisite_ShouldThrowComposition()31         public void APrerequisiteDependsOnBPrerequisite_ShouldThrowComposition()
32         {
33             AssertCycle(Dependency.Prerequisite,
34                         Dependency.Prerequisite);
35         }
36 
37         [Fact]
APrerequisiteDependsOnBPost_ShouldThrowComposition()38         public void APrerequisiteDependsOnBPost_ShouldThrowComposition()
39         {
40             AssertCycle(Dependency.Prerequisite,
41                         Dependency.Post);
42         }
43 
44         [Fact]
APrerequisiteDependsOnBNone_ShouldNotThrow()45         public void APrerequisiteDependsOnBNone_ShouldNotThrow()
46         {
47             AssertNotCycle(Dependency.Prerequisite,
48                            Dependency.None);
49         }
50 
51         [Fact]
APostDependsOnBPrerequisite_ShouldThrowComposition()52         public void APostDependsOnBPrerequisite_ShouldThrowComposition()
53         {
54             AssertCycle(Dependency.Post,
55                         Dependency.Prerequisite);
56         }
57 
58         [Fact]
APostDependsOnBPost_ShouldNotThrow()59         public void APostDependsOnBPost_ShouldNotThrow()
60         {
61             AssertNotCycle(Dependency.Post,
62                            Dependency.Post);
63         }
64 
65         [Fact]
APostDependsOnBNone_ShouldNotThrow()66         public void APostDependsOnBNone_ShouldNotThrow()
67         {
68             AssertNotCycle(Dependency.Post,
69                            Dependency.None);
70         }
71 
72         [Fact]
BPrerequisiteDependsOnANone_ShouldNotThrow()73         public void BPrerequisiteDependsOnANone_ShouldNotThrow()
74         {
75             AssertNotCycle(Dependency.None,
76                            Dependency.Prerequisite);
77         }
78 
79         [Fact]
BPostDependsOnANone_ShouldNotThrow()80         public void BPostDependsOnANone_ShouldNotThrow()
81         {
82             AssertNotCycle(Dependency.None,
83                            Dependency.Post);
84         }
85 
86         [Fact]
ANoneWithBNone_ShouldNotThrow()87         public void ANoneWithBNone_ShouldNotThrow()
88         {
89             AssertNotCycle(Dependency.None,
90                            Dependency.None);
91         }
92 
AssertCycle(Dependency partADependency, Dependency partBDependency)93         private static void AssertCycle(Dependency partADependency, Dependency partBDependency)
94         {
95             var exportA = GetExport("A", partADependency, partBDependency);
96 
97             Assert.Throws<CompositionException>(() =>
98             {
99                 var value = exportA.Value;
100             });
101 
102             var exportB = GetExport("B", partADependency, partBDependency);
103 
104             Assert.Throws<CompositionException>(() =>
105             {
106                 var value = exportB.Value;
107             });
108         }
109 
AssertNotCycle(Dependency partADependency, Dependency partBDependency)110         private static void AssertNotCycle(Dependency partADependency, Dependency partBDependency)
111         {
112             var exportA = GetExport("A", partADependency, partBDependency);
113             var exportB = GetExport("B", partADependency, partBDependency);
114 
115             Assert.Equal("A", exportA.Value);
116             Assert.Equal("B", exportB.Value);
117         }
118 
GetExport(string contractName, Dependency partADependency, Dependency partBDependency)119         private static Lazy<object, object> GetExport(string contractName, Dependency partADependency, Dependency partBDependency)
120         {
121             var container = GetContainer(partADependency, partBDependency);
122 
123             return container.GetExports(typeof(object), null, contractName).Single();
124         }
125 
GetContainer(Dependency partADependency, Dependency partBDependency)126         private static CompositionContainer GetContainer(Dependency partADependency, Dependency partBDependency)
127         {
128             var partA = CreatePartA(partADependency);
129             var partB = CreatePartB(partBDependency);
130 
131             var catalog = CatalogFactory.Create(partA, partB);
132 
133             return ContainerFactory.Create(catalog);
134         }
135 
CreatePartA(Dependency dependency)136         private static ComposablePart CreatePartA(Dependency dependency)
137         {
138             return CreatePart(dependency, "A", "B");
139         }
140 
CreatePartB(Dependency dependency)141         private static ComposablePart CreatePartB(Dependency dependency)
142         {
143             return CreatePart(dependency, "B", "A");
144         }
145 
CreatePart(Dependency dependency, string exportContractName, string importContractName)146         private static ComposablePart CreatePart(Dependency dependency, string exportContractName, string importContractName)
147         {
148             ConcreteComposablePart part = new ConcreteComposablePart();
149             part.AddExport(exportContractName, exportContractName);
150 
151             if (dependency != Dependency.None)
152             {
153                 part.AddImport(importContractName, ImportCardinality.ExactlyOne, false, dependency == Dependency.Prerequisite);
154             }
155 
156             return part;
157         }
158 
159         private enum Dependency
160         {
161             Prerequisite,
162             Post,
163             None,
164         }
165     }
166 }
167