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