1 // -----------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 // -----------------------------------------------------------------------
5 using System
.Collections
.Generic
;
6 using System
.Diagnostics
;
7 using System
.ComponentModel
.Composition
.Diagnostics
;
8 using System
.ComponentModel
.Composition
.Factories
;
9 using System
.ComponentModel
.Composition
.Hosting
;
10 using System
.ComponentModel
.Composition
.Primitives
;
12 using System
.UnitTesting
;
13 using Microsoft
.VisualStudio
.TestTools
.UnitTesting
;
15 namespace System
.ComponentModel
.Composition
18 public class ComposablePartCatalogExportProviderTests
21 public void Constructor_NullAsCatalogArgument_ShouldThrowArgumentNull()
23 ExceptionAssert
.ThrowsArgument
<ArgumentNullException
>("catalog", () =>
25 new CatalogExportProvider((ComposablePartCatalog
)null);
30 public void Constructor_ValueAsCatalogArgument_ShouldSetCatalogPropertyToEmpty()
32 var expectations
= Expectations
.GetCatalogs();
34 foreach (var e
in expectations
)
36 var provider
= new CatalogExportProvider(e
);
38 Assert
.AreSame(e
, provider
.Catalog
);
43 public void Catalog_WhenDisposed_ShouldThrowObjectDisposed()
45 var provider
= CreateCatalogExportProvider();
48 ExceptionAssert
.ThrowsDisposed(provider
, () =>
50 var catalog
= provider
.Catalog
;
55 public void SourceProvider_NullAsValueArgument_ShouldThrowArgumentNull()
57 var provider
= CreateCatalogExportProvider();
59 ExceptionAssert
.ThrowsArgument
<ArgumentNullException
>("value", () =>
61 provider
.SourceProvider
= null;
66 public void GetExports_WhenRejectedDefinitionRequiredImportIsAdded_ShouldBeResurrected()
68 var part
= PartFactory
.CreateImporterExporter("Export", "Import");
70 var provider
= CreateCatalogExportProvider(part
);
71 var sourceProvider
= ExportProviderFactory
.CreateRecomposable();
72 provider
.SourceProvider
= sourceProvider
;
74 var exports
= provider
.GetExports
<object>("Export");
76 EnumerableAssert
.IsEmpty(exports
, "definition should have been rejected.");
78 // Resurrect the definition
79 sourceProvider
.AddExport("Import", new object());
81 exports
= provider
.GetExports
<object>("Export");
83 Assert
.AreEqual(1, exports
.Count(), "definition should have been resurrected.");
87 public void GetExports_WhenMultipleRejectedDefinitionsRequiredImportsAreAdded_ShouldBeResurrected()
89 var part1
= PartFactory
.CreateImporterExporter("Export", "Import");
90 var part2
= PartFactory
.CreateImporterExporter("Export", "Import");
92 var provider
= CreateCatalogExportProvider(part1
, part2
);
93 var sourceProvider
= ExportProviderFactory
.CreateRecomposable();
94 provider
.SourceProvider
= sourceProvider
;
96 var exports
= provider
.GetExports
<object>("Export");
98 EnumerableAssert
.IsEmpty(exports
, "definition1 and definition2 should have been rejected.");
100 // Resurrect both definitions
101 sourceProvider
.AddExport("Import", new object());
103 exports
= provider
.GetExports
<object>("Export");
105 Assert
.AreEqual(2, exports
.Count(), "definition1 and definition2 should have been resurrected.");
110 public void GetExports_AfterResurrectedDefinitionHasBeenRemovedAndReaddedToCatalog_ShouldNotBeTreatedAsRejected()
112 var definition1
= PartDefinitionFactory
.Create(PartFactory
.CreateImporterExporter("Export", "Import"));
113 var definition2
= PartDefinitionFactory
.Create(PartFactory
.CreateImporterExporter("Export", "Import"));
114 var catalog
= CatalogFactory
.CreateMutable(definition1
, definition2
);
116 var provider
= CreateCatalogExportProvider(catalog
);
117 var sourceProvider
= ExportProviderFactory
.CreateRecomposable();
118 provider
.SourceProvider
= sourceProvider
;
120 var exports
= provider
.GetExports
<object>("Export");
122 EnumerableAssert
.IsEmpty(exports
, "definition1 and definition2 should have been rejected.");
124 // Resurrect both definitions
125 sourceProvider
.AddExport("Import", new object());
127 exports
= provider
.GetExports
<object>("Export");
129 Assert
.AreEqual(2, exports
.Count(), "definition1 and definition2 should have been resurrected.");
131 catalog
.RemoveDefinition(definition1
);
133 exports
= provider
.GetExports
<object>("Export");
134 Assert
.AreEqual(1, exports
.Count(), "definition1 should have been removed.");
136 catalog
.AddDefinition(definition1
);
138 exports
= provider
.GetExports
<object>("Export");
140 Assert
.AreEqual(2, exports
.Count(), "definition1 and definition2 should be both present.");
146 public void GetExports_WhenDefinitionIsRejected_ShouldTraceWarning()
148 using (TraceContext context
= new TraceContext(SourceLevels
.Warning
))
150 var part
= PartFactory
.CreateImporterExporter("Export", "Import");
151 var provider
= CreateCatalogExportProvider(part
);
152 provider
.SourceProvider
= ExportProviderFactory
.CreateRecomposable();
154 ExceptionAssert
.Throws
<ImportCardinalityMismatchException
>(() =>
156 provider
.GetExport
<object>("Export");
160 Assert
.IsNotNull(context
.LastTraceEvent
);
161 Assert
.AreEqual(context
.LastTraceEvent
.EventType
, TraceEventType
.Warning
);
162 Assert
.AreEqual(context
.LastTraceEvent
.Id
, TraceId
.Rejection_DefinitionRejected
);
167 public void GetExports_WhenDefinitionIsResurrected_ShouldTraceInformation()
169 using (TraceContext context
= new TraceContext(SourceLevels
.Information
))
171 var part
= PartFactory
.CreateImporterExporter("Export", "Import");
172 var sourceProvider
= ExportProviderFactory
.CreateRecomposable();
173 var provider
= CreateCatalogExportProvider(part
);
174 provider
.SourceProvider
= sourceProvider
;
176 ExceptionAssert
.Throws
<ImportCardinalityMismatchException
>(() =>
178 provider
.GetExport
<object>("Export");
181 // Add the required export to the source provider 'resurrect' the part
182 sourceProvider
.AddExport("Import", "Value");
184 provider
.GetExport
<object>("Export");
186 Assert
.IsNotNull(context
.LastTraceEvent
);
187 Assert
.AreEqual(context
.LastTraceEvent
.EventType
, TraceEventType
.Information
);
188 Assert
.AreEqual(context
.LastTraceEvent
.Id
, TraceId
.Rejection_DefinitionResurrected
);
193 public void GetExports_WhenDefinitionsAreResurrected_ShouldTraceInformation()
195 using (TraceContext context
= new TraceContext(SourceLevels
.Information
))
197 var part1
= PartFactory
.CreateImporterExporter("Export", "Import");
198 var part2
= PartFactory
.CreateImporterExporter("Export", "Import");
200 var sourceProvider
= ExportProviderFactory
.CreateRecomposable();
201 var provider
= CreateCatalogExportProvider(part1
, part2
);
202 provider
.SourceProvider
= sourceProvider
;
204 EnumerableAssert
.IsEmpty(provider
.GetExports
<object>("Export"));
206 // Add the required export to the source provider 'resurrect' the part
207 sourceProvider
.AddExport("Import", "Value");
209 provider
.GetExports
<object>("Export");
211 Assert
.AreEqual(4, context
.TraceEvents
.Count
); // 2 for rejection, 2 for resurrection
212 Assert
.AreEqual(context
.TraceEvents
[2].EventType
, TraceEventType
.Information
);
213 Assert
.AreEqual(context
.TraceEvents
[3].EventType
, TraceEventType
.Information
);
214 Assert
.AreEqual(context
.TraceEvents
[2].Id
, TraceId
.Rejection_DefinitionResurrected
);
215 Assert
.AreEqual(context
.TraceEvents
[3].Id
, TraceId
.Rejection_DefinitionResurrected
);
221 [TestProperty("Type", "Integration")]
222 public void BasicTest()
224 var catalog
= CatalogFactory
.CreateDefaultAttributed();
225 var catalogExportProvider
= new CatalogExportProvider(catalog
);
226 catalogExportProvider
.SourceProvider
= catalogExportProvider
;
227 var testName
= AttributedModelServices
.GetContractName(typeof(CatalogComponentTest
));
228 var testNameNonComponent
= AttributedModelServices
.GetContractName(typeof(CatalogComponentTestNonComponentPart
));
229 var testInterfaceName
= AttributedModelServices
.GetContractName(typeof(ICatalogComponentTest
));
231 Assert
.AreEqual(1, catalogExportProvider
.GetExports(ImportFromContract(testName
)).Count());
232 Assert
.AreEqual(0, catalogExportProvider
.GetExports(ImportFromContract(testNameNonComponent
)).Count());
234 var exports
= catalogExportProvider
.GetExports(ImportFromContract(testInterfaceName
));
235 Assert
.AreEqual(2, exports
.Count(), "There should be 2 of them");
237 foreach (var i
in exports
)
238 Assert
.IsNotNull(i
.Value
, "Should get a value");
243 [TestProperty("Type", "Integration")]
244 public void BasicTestWithRequiredMetadata_NoTypeConstraint()
246 var catalog
= CatalogFactory
.CreateDefaultAttributed();
247 var catalogExportProvider
= new CatalogExportProvider(catalog
);
248 catalogExportProvider
.SourceProvider
= catalogExportProvider
;
250 Assert
.AreEqual(0, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithNoFoo", new string[] { "Foo" }
, new Type
[] {typeof(object)}
)).Count());
252 Assert
.AreEqual(1, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo" }
, new Type
[] { typeof(object) }
)).Count());
253 Assert
.AreEqual(0, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo", "Bar" }
, new Type
[] { typeof(object), typeof(object) }
)).Count());
255 Assert
.AreEqual(0, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithNoFoo", new string[] { "Foo" }
, new Type
[] { typeof(object) }
)).Count());
256 Assert
.AreEqual(1, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo" }
, new Type
[] { typeof(object) }
)).Count());
260 [TestProperty("Type", "Integration")]
261 public void BasicTestWithRequiredMetadata_TypeConstraint()
263 var catalog
= CatalogFactory
.CreateDefaultAttributed();
264 var catalogExportProvider
= new CatalogExportProvider(catalog
);
265 catalogExportProvider
.SourceProvider
= catalogExportProvider
;
267 Assert
.AreEqual(0, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithNoFoo", new string[] { "Foo" }
, new Type
[] { typeof(string) }
)).Count());
269 Assert
.AreEqual(1, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo" }
, new Type
[] { typeof(string) }
)).Count());
270 Assert
.AreEqual(0, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo", "Bar" }
, new Type
[] { typeof(string), typeof(string) }
)).Count());
272 Assert
.AreEqual(0, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithNoFoo", new string[] { "Foo" }
, new Type
[] { typeof(string) }
)).Count());
273 Assert
.AreEqual(1, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo" }
, new Type
[] { typeof(string) }
)).Count());
278 [TestProperty("Type", "Integration")]
279 public void BasicTestWithRequiredMetadata_WrongTypeConstraint()
281 var catalog
= CatalogFactory
.CreateDefaultAttributed();
282 var catalogExportProvider
= new CatalogExportProvider(catalog
);
283 catalogExportProvider
.SourceProvider
= catalogExportProvider
;
285 Assert
.AreEqual(0, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithNoFoo", new string[] { "Foo" }
, new Type
[] { typeof(int) }
)).Count());
287 Assert
.AreEqual(0, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo" }
, new Type
[] { typeof(int) }
)).Count());
288 Assert
.AreEqual(0, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo", "Bar" }
, new Type
[] { typeof(int), typeof(int) }
)).Count());
290 Assert
.AreEqual(0, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithNoFoo", new string[] { "Foo" }
, new Type
[] { typeof(int) }
)).Count());
291 Assert
.AreEqual(0, catalogExportProvider
.GetExports(ImportFromContractAndMetadata("MyExporterWithFoo", new string[] { "Foo" }
, new Type
[] { typeof(int) }
)).Count());
296 [TestProperty("Type", "Integration")]
297 public void ComponentCatalogResolverGetStaticExport()
299 var catalog
= CatalogFactory
.CreateDefaultAttributed();
300 var catalogExportProvider
= new CatalogExportProvider(catalog
);
301 catalogExportProvider
.SourceProvider
= catalogExportProvider
;
303 var exports
= catalogExportProvider
.GetExports(ImportFromContract("StaticString"));
304 Assert
.AreEqual(1, exports
.Count());
305 Assert
.AreEqual("StaticString", exports
.First().Value
);
309 [TestProperty("Type", "Integration")]
310 public void ComponentCatalogResolverComponentCatalogExportReference()
312 var catalog
= CatalogFactory
.CreateDefaultAttributed();
313 var catalogExportProvider
= new CatalogExportProvider(catalog
);
314 catalogExportProvider
.SourceProvider
= catalogExportProvider
;
316 var exports
= catalogExportProvider
.GetExports(ImportFromContract(AttributedModelServices
.GetContractName(typeof(MyExporterWithValidMetadata
))));
318 Assert
.AreEqual(1, exports
.Count());
320 var export
= exports
.First();
321 Assert
.AreEqual("world", export
.Metadata
["hello"]);
323 Assert
.IsInstanceOfType(export
.Value
, typeof(MyExporterWithValidMetadata
));
327 [TestProperty("Type", "Integration")]
328 public void ValueTypeFromCatalog()
330 var catalog
= CatalogFactory
.CreateDefaultAttributed();
331 var container
= new CompositionContainer(catalog
);
332 int singletonResult
= container
.GetExportedValue
<int>("{AssemblyCatalogResolver}SingletonValueType");
333 Assert
.AreEqual(17, singletonResult
, "expecting value type resolved from catalog");
334 int factoryResult
= container
.GetExportedValue
<int>("{AssemblyCatalogResolver}FactoryValueType");
335 Assert
.AreEqual(18, factoryResult
, "expecting value type resolved from catalog");
339 [PartCreationPolicy(CreationPolicy
.Any
)]
340 public class CreationPolicyAny
346 public void CreationPolicyAny_MultipleCallsReturnSameInstance()
348 var catalog
= CatalogFactory
.CreateAttributed(typeof (CreationPolicyAny
));
349 var provider
= new CatalogExportProvider(catalog
);
350 provider
.SourceProvider
= ContainerFactory
.Create();
352 var export
= provider
.GetExportedValue
<CreationPolicyAny
>();
354 for (int i
= 0; i
< 5; i
++) // 5 is arbitrarily chosen
356 var export1
= provider
.GetExportedValue
<CreationPolicyAny
>();
358 Assert
.AreEqual(export
, export1
);
363 [PartCreationPolicy(CreationPolicy
.Shared
)]
364 public class CreationPolicyShared
370 public void CreationPolicyShared_MultipleCallsReturnSameInstance()
372 var catalog
= CatalogFactory
.CreateAttributed(typeof(CreationPolicyShared
));
373 var provider
= new CatalogExportProvider(catalog
);
374 provider
.SourceProvider
= ContainerFactory
.Create();
376 var export
= provider
.GetExportedValue
<CreationPolicyShared
>();
378 for (int i
= 0; i
< 5; i
++) // 5 is arbitrarily chosen
380 var export1
= provider
.GetExportedValue
<CreationPolicyShared
>();
382 Assert
.AreEqual(export
, export1
);
387 [PartCreationPolicy(CreationPolicy
.NonShared
)]
388 public class CreationPolicyNonShared
394 public void CreationPolicyNonShared_MultipleCallsReturnsDifferentInstances()
396 var catalog
= CatalogFactory
.CreateAttributed(typeof(CreationPolicyNonShared
));
397 var provider
= new CatalogExportProvider(catalog
);
398 provider
.SourceProvider
= ContainerFactory
.Create();
400 List
<CreationPolicyNonShared
> list
= new List
<CreationPolicyNonShared
>();
401 var export
= provider
.GetExportedValue
<CreationPolicyNonShared
>();
404 for (int i
= 0; i
< 5; i
++) // 5 is arbitrarily chosen
406 export
= provider
.GetExportedValue
<CreationPolicyNonShared
>();
408 CollectionAssert
.DoesNotContain(list
, export
);
415 public void GetExports_NoSourceProvider_ShouldThrowInvalidOperation()
417 var catalog
= CatalogFactory
.CreateAttributed();
418 var provider
= new CatalogExportProvider(catalog
);
420 ExceptionAssert
.Throws
<InvalidOperationException
>(() =>
421 provider
.GetExports(ImportFromContract("Foo")));
425 [TestProperty("Type", "Integration")]
428 public void Recomposition_PartDefWithRecomposableImportIsRemoved_ExportsMatchingImportChanged_ShouldNotBeRecomposed()
430 string dependencyContractName
= "dependency";
431 var exportValue
= new object();
433 var exporterPart
= PartFactory
.CreateExporter(dependencyContractName
, exportValue
);
434 var importerPart
= PartFactory
.CreateImporter(dependencyContractName
, true);
436 var exporterCatalog
= CatalogFactory
.Create(exporterPart
);
437 var importerCatalog
= CatalogFactory
.Create(importerPart
);
439 var aggregateCatalog
= CatalogFactory
.CreateAggregateCatalog(importerCatalog
, exporterCatalog
);
441 var provider
= new CatalogExportProvider(aggregateCatalog
);
442 provider
.SourceProvider
= provider
;
444 var exports
= provider
.GetExports(importerPart
.ImportDefinitions
.Single());
445 Assert
.AreEqual(exportValue
, importerPart
.Value
, "Importer was not composed");
447 aggregateCatalog
.Catalogs
.Remove(importerCatalog
);
448 aggregateCatalog
.Catalogs
.Remove(exporterCatalog
);
450 Assert
.AreEqual(exportValue
, importerPart
.Value
, "Importer was unexpectedly recomposed");
454 [TestProperty("Type", "Integration")]
457 public void Recomposition_PartDefWithNonRecomposableImportIsRemoved_ExportsMatchingImportChanged_ShouldNotBeRejected()
459 string dependencyContractName
= "dependency";
460 var exportValue
= new object();
462 var exporterPart
= PartFactory
.CreateExporter(dependencyContractName
, exportValue
);
463 var importerPart
= PartFactory
.CreateImporter(dependencyContractName
, false);
465 var exporterCatalog
= CatalogFactory
.Create(exporterPart
);
466 var importerCatalog
= CatalogFactory
.Create(importerPart
);
468 var aggregateCatalog
= CatalogFactory
.CreateAggregateCatalog(importerCatalog
, exporterCatalog
);
470 var provider
= new CatalogExportProvider(aggregateCatalog
);
471 provider
.SourceProvider
= provider
;
473 var exports
= provider
.GetExports(importerPart
.ImportDefinitions
.Single());
474 Assert
.AreEqual(exportValue
, importerPart
.Value
, "Importer was not composed");
476 aggregateCatalog
.Catalogs
.Remove(importerCatalog
);
477 aggregateCatalog
.Catalogs
.Remove(exporterCatalog
);
479 Assert
.AreEqual(exportValue
, importerPart
.Value
, "Importer was unexpectedly recomposed");
483 public void CanBeCollectedAfterDispose()
485 AggregateExportProvider sourceExportProvider
= new AggregateExportProvider();
486 var catalog
= new AggregateCatalog(CatalogFactory
.CreateDefaultAttributed());
487 var catalogExportProvider
= new CatalogExportProvider(catalog
);
488 catalogExportProvider
.SourceProvider
= sourceExportProvider
;
490 WeakReference weakCatalogExportProvider
= new WeakReference(catalogExportProvider
);
491 catalogExportProvider
.Dispose();
492 catalogExportProvider
= null;
495 GC
.WaitForPendingFinalizers();
497 Assert
.IsFalse(weakCatalogExportProvider
.IsAlive
);
499 GC
.KeepAlive(sourceExportProvider
);
500 GC
.KeepAlive(catalog
);
504 public void RemovingAndReAddingMultipleDefinitionsFromCatalog()
506 var fixedParts
= new TypeCatalog(typeof(RootMultipleImporter
), typeof(ExportedService
));
507 var changingParts
= new TypeCatalog(typeof(Exporter1
), typeof(Exporter2
));
508 var catalog
= new AggregateCatalog();
509 catalog
.Catalogs
.Add(fixedParts
);
510 catalog
.Catalogs
.Add(changingParts
);
511 var catalogExportProvider
= new CatalogExportProvider(catalog
);
512 catalogExportProvider
.SourceProvider
= catalogExportProvider
;
514 var root
= catalogExportProvider
.GetExport
<RootMultipleImporter
>().Value
;
515 Assert
.AreEqual(2, root
.Imports
.Length
);
517 catalog
.Catalogs
.Remove(changingParts
);
518 Assert
.AreEqual(0, root
.Imports
.Length
);
520 catalog
.Catalogs
.Add(changingParts
);
521 Assert
.AreEqual(2, root
.Imports
.Length
);
525 public class RootMultipleImporter
527 [ImportMany(AllowRecomposition
= true)]
528 public IExportedInterface
[] Imports { get; set; }
530 public interface IExportedInterface
533 [Export(typeof(IExportedInterface
))]
534 public class Exporter1
: IExportedInterface
537 public ExportedService Service { get; set; }
539 [Export(typeof(IExportedInterface
))]
540 public class Exporter2
: IExportedInterface
543 public ExportedService Service { get; set; }
546 public class ExportedService
552 private static ImportDefinition
ImportFromContract(string contractName
)
554 return ImportDefinitionFactory
.CreateDefault(contractName
,
556 ImportCardinality
.ZeroOrMore
,
561 private static ImportDefinition
ImportFromContractAndMetadata(string contractName
, string[] metadataKeys
, Type
[] metadataValues
)
563 Assert
.AreEqual(metadataKeys
.Length
, metadataValues
.Length
);
564 Dictionary
<string, Type
> requiredMetadata
= new Dictionary
<string, Type
>();
565 for (int i
= 0; i
< metadataKeys
.Length
; i
++)
567 requiredMetadata
.Add(metadataKeys
[i
], metadataValues
[i
]);
570 return new ContractBasedImportDefinition(contractName
,
573 ImportCardinality
.ZeroOrMore
,
579 private static CatalogExportProvider
CreateCatalogExportProvider()
581 return CreateCatalogExportProvider(CatalogFactory
.Create());
584 private static CatalogExportProvider
CreateCatalogExportProvider(params ComposablePartDefinition
[] definitions
)
586 return CreateCatalogExportProvider(CatalogFactory
.Create(definitions
));
589 private static CatalogExportProvider
CreateCatalogExportProvider(params ComposablePart
[] parts
)
591 return CreateCatalogExportProvider(CatalogFactory
.Create(parts
));
594 private static CatalogExportProvider
CreateCatalogExportProvider(ComposablePartCatalog catalog
)
596 return new CatalogExportProvider(catalog
);