1 // -----------------------------------------------------------------------
\r
2 // Copyright (c) Microsoft Corporation. All rights reserved.
\r
3 // -----------------------------------------------------------------------
\r
7 using System.Collections.Generic;
\r
8 using System.ComponentModel.Composition;
\r
9 using System.ComponentModel.Composition.Factories;
\r
10 using System.ComponentModel.Composition.Hosting;
\r
11 using Microsoft.VisualStudio.TestTools.UnitTesting;
\r
12 using System.UnitTesting;
\r
14 namespace Tests.Integration
\r
17 public class RejectionTests
\r
19 public interface IExtension
\r
21 int Id { get; set; }
\r
25 public class MyImporter
\r
27 [ImportMany(AllowRecomposition = true)]
\r
28 public IExtension[] Extensions { get; set; }
\r
31 [Export(typeof(IExtension))]
\r
32 public class Extension1 : IExtension
\r
34 [Import("IExtension.IdValue")]
\r
35 public int Id { get; set; }
\r
38 [Export(typeof(IExtension))]
\r
39 public class Extension2 : IExtension
\r
41 [Import("IExtension.IdValue2")]
\r
42 public int Id { get; set; }
\r
46 public void Rejection_ExtensionLightUp_AddedViaBatch()
\r
48 var container = ContainerFactory.CreateWithAttributedCatalog(
\r
51 typeof(Extension2));
\r
53 var importer = container.GetExportedValue<MyImporter>();
\r
55 Assert.AreEqual(0, importer.Extensions.Length, "Should have 0 extensions");
\r
57 container.ComposeExportedValue<int>("IExtension.IdValue", 10);
\r
59 Assert.AreEqual(1, importer.Extensions.Length, "Should have 1 extension");
\r
60 Assert.AreEqual(10, importer.Extensions[0].Id);
\r
62 container.ComposeExportedValue<int>("IExtension.IdValue2", 20);
\r
64 Assert.AreEqual(2, importer.Extensions.Length, "Should have 2 extension");
\r
65 Assert.AreEqual(10, importer.Extensions[0].Id);
\r
66 Assert.AreEqual(20, importer.Extensions[1].Id);
\r
69 public class ExtensionValues
\r
71 [Export("IExtension.IdValue")]
\r
72 public int Value = 10;
\r
74 [Export("IExtension.IdValue2")]
\r
75 public int Value2 = 20;
\r
79 public void Rejection_ExtensionLightUp_AddedViaCatalog()
\r
81 var ext1Cat = CatalogFactory.CreateAttributed(typeof(Extension1));
\r
82 var ext2Cat = CatalogFactory.CreateAttributed(typeof(Extension2));
\r
83 var hostCat = CatalogFactory.CreateAttributed(typeof(MyImporter));
\r
84 var valueCat = CatalogFactory.CreateAttributed(typeof(ExtensionValues));
\r
86 var catalog = new AggregateCatalog();
\r
87 catalog.Catalogs.Add(hostCat);
\r
89 var container = ContainerFactory.Create(catalog);
\r
91 var importer = container.GetExportedValue<MyImporter>();
\r
93 Assert.AreEqual(0, importer.Extensions.Length, "Should have 0 extensions");
\r
95 catalog.Catalogs.Add(ext1Cat);
\r
97 Assert.AreEqual(0, importer.Extensions.Length, "Should have 0 extensions after ext1 added without dependency");
\r
99 catalog.Catalogs.Add(ext2Cat);
\r
101 Assert.AreEqual(0, importer.Extensions.Length, "Should have 0 extensions after ext2 added without dependency");
\r
103 catalog.Catalogs.Add(valueCat);
\r
105 Assert.AreEqual(2, importer.Extensions.Length, "Should have 2 extension");
\r
106 Assert.AreEqual(10, importer.Extensions[0].Id);
\r
107 Assert.AreEqual(20, importer.Extensions[1].Id);
\r
110 public interface IMissing { }
\r
111 public interface ISingle { }
\r
112 public interface IMultiple { }
\r
113 public interface IConditional { }
\r
114 public class SingleImpl : ISingle { }
\r
115 public class MultipleImpl : IMultiple { }
\r
117 public class NoImportPart
\r
119 public NoImportPart()
\r
121 SingleExport = new SingleImpl();
\r
122 MultipleExport1 = new MultipleImpl();
\r
123 MultipleExport2 = new MultipleImpl();
\r
127 public ISingle SingleExport { private set; get; }
\r
130 public IMultiple MultipleExport1 { private set; get; }
\r
133 public IMultiple MultipleExport2 { private set; get; }
\r
142 public ISingle SingleImport { get; set; }
\r
146 public void Rejection_Resurrection()
\r
148 var container = ContainerFactory.CreateWithAttributedCatalog(typeof(Needy));
\r
150 var exports1 = container.GetExportedValues<Needy>();
\r
152 Assert.AreEqual(0, exports1.Count(), "Catalog entry should be rejected");
\r
154 container.ComposeParts(new NoImportPart());
\r
156 var exports2 = container.GetExportedValues<Needy>();
\r
157 Assert.AreEqual(1, exports2.Count(), "Catalog entry should be ressurrected");
\r
161 public void Rejection_BatchSatisfiesBatch()
\r
163 var container = ContainerFactory.Create();
\r
164 var needy = new Needy();
\r
165 container.ComposeParts(needy, new NoImportPart());
\r
166 Assert.IsInstanceOfType(needy.SingleImport, typeof(SingleImpl), "Import not satisifed as expected");
\r
170 public void Rejection_BatchSatisfiesBatchReversed()
\r
172 var container = ContainerFactory.Create();
\r
173 var needy = new Needy();
\r
174 container.ComposeParts(new NoImportPart(), needy);
\r
175 Assert.IsInstanceOfType(needy.SingleImport, typeof(SingleImpl), "Import not satisifed as expected");
\r
179 public void Rejection_CatalogSatisfiesBatch()
\r
181 var container = ContainerFactory.CreateWithAttributedCatalog(typeof(NoImportPart));
\r
182 var needy = new Needy();
\r
183 container.ComposeParts(needy);
\r
184 Assert.IsInstanceOfType(needy.SingleImport, typeof(SingleImpl), "Import not satisifed as expected");
\r
188 public void Rejection_TransitiveDependenciesSatisfied()
\r
190 var container = ContainerFactory.CreateWithAttributedCatalog(typeof(Needy), typeof(NoImportPart));
\r
191 var needy = container.GetExportedValue<Needy>();
\r
192 Assert.IsNotNull(needy);
\r
193 Assert.IsInstanceOfType(needy.SingleImport, typeof(SingleImpl), "Import not satisifed as expected");
\r
197 public void Rejection_TransitiveDependenciesUnsatisfied_ShouldThrowCardinalityMismatch()
\r
199 var container = ContainerFactory.CreateWithAttributedCatalog(typeof(Needy), typeof(MissingImportPart));
\r
201 ExceptionAssert.Throws<ImportCardinalityMismatchException>(() =>
\r
202 container.GetExportedValue<Needy>());
\r
205 public class MissingImportPart : NoImportPart
\r
208 public IMissing MissingImport { set; get; }
\r
212 public void Rejection_BatchRevert()
\r
214 var container = ContainerFactory.Create();
\r
216 ExceptionAssert.Throws<ChangeRejectedException>(() =>
\r
217 container.ComposeParts(new MissingImportPart()));
\r
221 public void Rejection_DefendPromisesOnceMade()
\r
223 var container = ContainerFactory.CreateWithAttributedCatalog(typeof(Needy));
\r
225 var addBatch = new CompositionBatch();
\r
226 var removeBatch = new CompositionBatch();
\r
227 var addedPart = addBatch.AddPart(new NoImportPart());
\r
228 removeBatch.RemovePart(addedPart);
\r
230 // Add then remove should be fine as long as exports aren't used yet.
\r
231 container.Compose(addBatch);
\r
232 container.Compose(removeBatch);
\r
234 // Add the dependencies
\r
235 container.Compose(addBatch);
\r
237 // Retrieve needy which uses an export from addedPart
\r
238 var export = container.GetExportedValue<Needy>();
\r
240 // Should not be able to remove the addedPart because someone depends on it.
\r
241 ExceptionAssert.Throws<ChangeRejectedException>(() =>
\r
242 container.Compose(removeBatch));
\r
246 public void Rejection_DefendPromisesLazily()
\r
248 var container = ContainerFactory.CreateWithAttributedCatalog(typeof(Needy));
\r
250 // Add the missing dependency for Needy
\r
251 container.ComposeParts(new NoImportPart());
\r
253 // This change should succeed since the component "Needy" hasn't been fully composed
\r
254 // and one way of satisfying its needs is as good ask another
\r
255 var export = container.GetExport<Needy>();
\r
257 // Cannot add another import because it would break existing promised compositions
\r
258 ExceptionAssert.Throws<ChangeRejectedException>(() =>
\r
259 container.ComposeParts(new NoImportPart()));
\r
261 // Instansitate the object
\r
262 var needy = export.Value;
\r
264 // Cannot add another import because it would break existing compositions
\r
265 ExceptionAssert.Throws<ChangeRejectedException>(() =>
\r
266 container.ComposeParts(new NoImportPart()));
\r
271 public void Rejection_SwitchPromiseFromManualToCatalog()
\r
273 // This test shows how the priority list in the AggregateCatalog can actually play with
\r
274 // the rejection work. Until the actual object is actually pulled on and satisfied the
\r
275 // promise can be moved around even for not-recomposable imports but once the object is
\r
276 // pulled on it is fixed from that point on.
\r
278 var container = ContainerFactory.CreateWithAttributedCatalog(typeof(Needy), typeof(NoImportPart));
\r
280 // Add the missing dependency for Needy
\r
281 container.ComposeParts(new NoImportPart());
\r
283 // This change should succeed since the component "Needy" hasn't been fully composed
\r
284 // and one way of satisfying its needs is as good as another
\r
285 var export = container.GetExport<Needy>();
\r
287 // Adding more exports doesn't fail because we push the promise to use the NoImportPart from the catalog
\r
288 // using the priorities from the AggregateExportProvider
\r
289 container.ComposeParts(new NoImportPart());
\r
291 // Instansitate the object
\r
292 var needy = export.Value;
\r
294 // Cannot add another import because it would break existing compositions
\r
295 ExceptionAssert.Throws<ChangeRejectedException>(() =>
\r
296 container.ComposeParts(new NoImportPart()));
\r
299 public interface ILoopA { }
\r
300 public interface ILoopB { }
\r
302 [Export(typeof(ILoopA))]
\r
303 public class LoopA1 : ILoopA
\r
306 public ILoopB LoopB { set; get; }
\r
309 [Export(typeof(ILoopA))]
\r
310 public class LoopA2 : ILoopA
\r
313 public ILoopB LoopB { set; get; }
\r
316 [Export(typeof(ILoopB))]
\r
317 public class LoopB1 : ILoopB
\r
320 public ILoopA LoopA { set; get; }
\r
323 [Export(typeof(ILoopB))]
\r
324 public class LoopB2 : ILoopB
\r
327 public ILoopA LoopA { set; get; }
\r
330 // This is an interesting situation. There are several possible self-consistent outcomes:
\r
331 // - All parts involved in the loop are rejected
\r
332 // - A consistent subset are not rejected (exactly one of LoopA1/LoopA2 and one of LoopB1/LoopB2
\r
334 // Both have desireable and undesirable characteristics. The first case is non-discriminatory but
\r
335 // rejects more parts than are necessary, the second minimizes rejection but must choose a subset
\r
336 // on somewhat arbitary grounds.
\r
338 public void Rejection_TheClemensLoop()
\r
340 var catalog = new TypeCatalog(new Type[] { typeof(LoopA1), typeof(LoopA2), typeof(LoopB1), typeof(LoopB2) });
\r
341 var container = new CompositionContainer(catalog);
\r
342 var exportsA = container.GetExportedValues<ILoopA>();
\r
343 var exportsB = container.GetExportedValues<ILoopB>();
\r
345 // These assertions would prove solution one
\r
346 Assert.AreEqual(0, exportsA.Count(), "Catalog ILoopA entries should be rejected");
\r
347 Assert.AreEqual(0, exportsB.Count(), "Catalog ILoopB entries should be rejected");
\r
349 // These assertions would prove solution two
\r
350 //Assert.AreEqual(1, exportsA.Count, "Only noe ILoopA entry should not be rejected");
\r
351 //Assert.AreEqual(1, exportsB.Count, "Only noe ILoopB entry should not be rejected");
\r
354 public interface IWorkItem
\r
356 string Id { get; set; }
\r
360 public class AllWorkItems
\r
362 [ImportMany(AllowRecomposition = true)]
\r
363 public Lazy<IWorkItem>[] WorkItems { get; set; }
\r
366 [Export(typeof(IWorkItem))]
\r
367 public class WorkItem : IWorkItem
\r
369 [Import("WorkItem.Id", AllowRecomposition = true)]
\r
370 public string Id { get; set; }
\r
375 [Export("WorkItem.Id")]
\r
376 public string Id = "MyId";
\r
381 public void AppliedStateNotCompleteedYet()
\r
383 var container = ContainerFactory.CreateWithAttributedCatalog(typeof(AllWorkItems));
\r
385 container.ComposeExportedValue<string>("WorkItem.Id", "A");
\r
387 var workItems = container.GetExportedValue<AllWorkItems>();
\r
389 Assert.AreEqual(0, workItems.WorkItems.Length);
\r
391 container.ComposeParts(new WorkItem());
\r
393 Assert.AreEqual(1, workItems.WorkItems.Length);
\r
394 Assert.AreEqual("A", workItems.WorkItems[0].Value.Id);
\r
398 public class ClassWithMissingImport
\r
401 private string _importNotFound = null;
\r
405 public void AppliedStateStored_ShouldRevertStateOnFailure()
\r
407 var container = ContainerFactory.CreateWithAttributedCatalog(typeof(AllWorkItems), typeof(WorkItem), typeof(Ids));
\r
409 var workItems = container.GetExportedValue<AllWorkItems>();
\r
411 Assert.AreEqual(1, workItems.WorkItems.Length);
\r
413 var batch = new CompositionBatch();
\r
415 batch.AddExportedValue("WorkItem.Id", "B");
\r
416 batch.AddPart(new ClassWithMissingImport());
\r
418 ExceptionAssert.Throws<ChangeRejectedException>(() =>
\r
419 container.Compose(batch));
\r
421 Assert.AreEqual("MyId", workItems.WorkItems[0].Value.Id);
\r
425 public class OptionalImporter
\r
427 [Import(AllowDefault = true)]
\r
428 public ClassWithMissingImport Import { get; set; }
\r
432 public void OptionalImportWithMissingDependency_ShouldRejectAndComposeFine()
\r
434 var container = ContainerFactory.CreateWithAttributedCatalog(typeof(OptionalImporter), typeof(ClassWithMissingImport));
\r
436 var importer = container.GetExportedValue<OptionalImporter>();
\r
438 Assert.IsNull(importer.Import);
\r
444 [Import(AllowDefault = true, AllowRecomposition = true)]
\r
445 public PartB ImportB { get; set; }
\r
452 public PartC ImportC { get; set; }
\r
459 public PartB ImportB { get; set; }
\r
464 public void PartAOptionalDependsOnPartB_PartBGetAddedLater()
\r
466 var container = new CompositionContainer(new TypeCatalog(typeof(PartC), typeof(PartA)));
\r
467 var partA = container.GetExportedValue<PartA>();
\r
469 Assert.IsNull(partA.ImportB);
\r
471 var partB = new PartB();
\r
472 container.ComposeParts(partB);
\r
474 Assert.AreEqual(partA.ImportB, partB);
\r
475 Assert.IsNotNull(partB.ImportC);
\r
479 public class PartA2
\r
481 [Import(AllowDefault = true, AllowRecomposition = true)]
\r
482 public PartB ImportB { get; set; }
\r
484 [Import(AllowDefault = true, AllowRecomposition = true)]
\r
485 public PartC ImportC { get; set; }
\r
490 public void PartAOptionalDependsOnPartBAndPartC_PartCGetRecurrected()
\r
492 var container = new CompositionContainer(new TypeCatalog(typeof(PartA2), typeof(PartB)));
\r
493 var partA = container.GetExportedValue<PartA2>();
\r
495 Assert.IsNull(partA.ImportB);
\r
496 Assert.IsNull(partA.ImportC);
\r
498 var partC = new PartC();
\r
499 container.ComposeParts(partC);
\r
501 Assert.AreEqual(partA.ImportB, partC.ImportB);
\r
502 Assert.AreEqual(partA.ImportC, partC);
\r