RejectionTests.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. // -----------------------------------------------------------------------
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. // -----------------------------------------------------------------------
  4. using System;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Collections.Generic;
  8. using System.ComponentModel.Composition;
  9. using System.ComponentModel.Composition.Factories;
  10. using System.ComponentModel.Composition.Hosting;
  11. using Microsoft.VisualStudio.TestTools.UnitTesting;
  12. using System.UnitTesting;
  13. namespace Tests.Integration
  14. {
  15. [TestClass]
  16. public class RejectionTests
  17. {
  18. public interface IExtension
  19. {
  20. int Id { get; set; }
  21. }
  22. [Export]
  23. public class MyImporter
  24. {
  25. [ImportMany(AllowRecomposition = true)]
  26. public IExtension[] Extensions { get; set; }
  27. }
  28. [Export(typeof(IExtension))]
  29. public class Extension1 : IExtension
  30. {
  31. [Import("IExtension.IdValue")]
  32. public int Id { get; set; }
  33. }
  34. [Export(typeof(IExtension))]
  35. public class Extension2 : IExtension
  36. {
  37. [Import("IExtension.IdValue2")]
  38. public int Id { get; set; }
  39. }
  40. [TestMethod]
  41. public void Rejection_ExtensionLightUp_AddedViaBatch()
  42. {
  43. var container = ContainerFactory.CreateWithAttributedCatalog(
  44. typeof(MyImporter),
  45. typeof(Extension1),
  46. typeof(Extension2));
  47. var importer = container.GetExportedValue<MyImporter>();
  48. Assert.AreEqual(0, importer.Extensions.Length, "Should have 0 extensions");
  49. container.ComposeExportedValue<int>("IExtension.IdValue", 10);
  50. Assert.AreEqual(1, importer.Extensions.Length, "Should have 1 extension");
  51. Assert.AreEqual(10, importer.Extensions[0].Id);
  52. container.ComposeExportedValue<int>("IExtension.IdValue2", 20);
  53. Assert.AreEqual(2, importer.Extensions.Length, "Should have 2 extension");
  54. Assert.AreEqual(10, importer.Extensions[0].Id);
  55. Assert.AreEqual(20, importer.Extensions[1].Id);
  56. }
  57. public class ExtensionValues
  58. {
  59. [Export("IExtension.IdValue")]
  60. public int Value = 10;
  61. [Export("IExtension.IdValue2")]
  62. public int Value2 = 20;
  63. }
  64. [TestMethod]
  65. public void Rejection_ExtensionLightUp_AddedViaCatalog()
  66. {
  67. var ext1Cat = CatalogFactory.CreateAttributed(typeof(Extension1));
  68. var ext2Cat = CatalogFactory.CreateAttributed(typeof(Extension2));
  69. var hostCat = CatalogFactory.CreateAttributed(typeof(MyImporter));
  70. var valueCat = CatalogFactory.CreateAttributed(typeof(ExtensionValues));
  71. var catalog = new AggregateCatalog();
  72. catalog.Catalogs.Add(hostCat);
  73. var container = ContainerFactory.Create(catalog);
  74. var importer = container.GetExportedValue<MyImporter>();
  75. Assert.AreEqual(0, importer.Extensions.Length, "Should have 0 extensions");
  76. catalog.Catalogs.Add(ext1Cat);
  77. Assert.AreEqual(0, importer.Extensions.Length, "Should have 0 extensions after ext1 added without dependency");
  78. catalog.Catalogs.Add(ext2Cat);
  79. Assert.AreEqual(0, importer.Extensions.Length, "Should have 0 extensions after ext2 added without dependency");
  80. catalog.Catalogs.Add(valueCat);
  81. Assert.AreEqual(2, importer.Extensions.Length, "Should have 2 extension");
  82. Assert.AreEqual(10, importer.Extensions[0].Id);
  83. Assert.AreEqual(20, importer.Extensions[1].Id);
  84. }
  85. public interface IMissing { }
  86. public interface ISingle { }
  87. public interface IMultiple { }
  88. public interface IConditional { }
  89. public class SingleImpl : ISingle { }
  90. public class MultipleImpl : IMultiple { }
  91. public class NoImportPart
  92. {
  93. public NoImportPart()
  94. {
  95. SingleExport = new SingleImpl();
  96. MultipleExport1 = new MultipleImpl();
  97. MultipleExport2 = new MultipleImpl();
  98. }
  99. [Export]
  100. public ISingle SingleExport { private set; get; }
  101. [Export]
  102. public IMultiple MultipleExport1 { private set; get; }
  103. [Export]
  104. public IMultiple MultipleExport2 { private set; get; }
  105. }
  106. [Export]
  107. public class Needy
  108. {
  109. public Needy() { }
  110. [Import]
  111. public ISingle SingleImport { get; set; }
  112. }
  113. [TestMethod]
  114. public void Rejection_Resurrection()
  115. {
  116. var container = ContainerFactory.CreateWithAttributedCatalog(typeof(Needy));
  117. var exports1 = container.GetExportedValues<Needy>();
  118. Assert.AreEqual(0, exports1.Count(), "Catalog entry should be rejected");
  119. container.ComposeParts(new NoImportPart());
  120. var exports2 = container.GetExportedValues<Needy>();
  121. Assert.AreEqual(1, exports2.Count(), "Catalog entry should be ressurrected");
  122. }
  123. [TestMethod]
  124. public void Rejection_BatchSatisfiesBatch()
  125. {
  126. var container = ContainerFactory.Create();
  127. var needy = new Needy();
  128. container.ComposeParts(needy, new NoImportPart());
  129. Assert.IsInstanceOfType(needy.SingleImport, typeof(SingleImpl), "Import not satisifed as expected");
  130. }
  131. [TestMethod]
  132. public void Rejection_BatchSatisfiesBatchReversed()
  133. {
  134. var container = ContainerFactory.Create();
  135. var needy = new Needy();
  136. container.ComposeParts(new NoImportPart(), needy);
  137. Assert.IsInstanceOfType(needy.SingleImport, typeof(SingleImpl), "Import not satisifed as expected");
  138. }
  139. [TestMethod]
  140. public void Rejection_CatalogSatisfiesBatch()
  141. {
  142. var container = ContainerFactory.CreateWithAttributedCatalog(typeof(NoImportPart));
  143. var needy = new Needy();
  144. container.ComposeParts(needy);
  145. Assert.IsInstanceOfType(needy.SingleImport, typeof(SingleImpl), "Import not satisifed as expected");
  146. }
  147. [TestMethod]
  148. public void Rejection_TransitiveDependenciesSatisfied()
  149. {
  150. var container = ContainerFactory.CreateWithAttributedCatalog(typeof(Needy), typeof(NoImportPart));
  151. var needy = container.GetExportedValue<Needy>();
  152. Assert.IsNotNull(needy);
  153. Assert.IsInstanceOfType(needy.SingleImport, typeof(SingleImpl), "Import not satisifed as expected");
  154. }
  155. [TestMethod]
  156. public void Rejection_TransitiveDependenciesUnsatisfied_ShouldThrowCardinalityMismatch()
  157. {
  158. var container = ContainerFactory.CreateWithAttributedCatalog(typeof(Needy), typeof(MissingImportPart));
  159. ExceptionAssert.Throws<ImportCardinalityMismatchException>(() =>
  160. container.GetExportedValue<Needy>());
  161. }
  162. public class MissingImportPart : NoImportPart
  163. {
  164. [Import]
  165. public IMissing MissingImport { set; get; }
  166. }
  167. [TestMethod]
  168. public void Rejection_BatchRevert()
  169. {
  170. var container = ContainerFactory.Create();
  171. ExceptionAssert.Throws<ChangeRejectedException>(() =>
  172. container.ComposeParts(new MissingImportPart()));
  173. }
  174. [TestMethod]
  175. public void Rejection_DefendPromisesOnceMade()
  176. {
  177. var container = ContainerFactory.CreateWithAttributedCatalog(typeof(Needy));
  178. var addBatch = new CompositionBatch();
  179. var removeBatch = new CompositionBatch();
  180. var addedPart = addBatch.AddPart(new NoImportPart());
  181. removeBatch.RemovePart(addedPart);
  182. // Add then remove should be fine as long as exports aren't used yet.
  183. container.Compose(addBatch);
  184. container.Compose(removeBatch);
  185. // Add the dependencies
  186. container.Compose(addBatch);
  187. // Retrieve needy which uses an export from addedPart
  188. var export = container.GetExportedValue<Needy>();
  189. // Should not be able to remove the addedPart because someone depends on it.
  190. ExceptionAssert.Throws<ChangeRejectedException>(() =>
  191. container.Compose(removeBatch));
  192. }
  193. [TestMethod]
  194. public void Rejection_DefendPromisesLazily()
  195. {
  196. var container = ContainerFactory.CreateWithAttributedCatalog(typeof(Needy));
  197. // Add the missing dependency for Needy
  198. container.ComposeParts(new NoImportPart());
  199. // This change should succeed since the component "Needy" hasn't been fully composed
  200. // and one way of satisfying its needs is as good ask another
  201. var export = container.GetExport<Needy>();
  202. // Cannot add another import because it would break existing promised compositions
  203. ExceptionAssert.Throws<ChangeRejectedException>(() =>
  204. container.ComposeParts(new NoImportPart()));
  205. // Instansitate the object
  206. var needy = export.Value;
  207. // Cannot add another import because it would break existing compositions
  208. ExceptionAssert.Throws<ChangeRejectedException>(() =>
  209. container.ComposeParts(new NoImportPart()));
  210. }
  211. [TestMethod]
  212. public void Rejection_SwitchPromiseFromManualToCatalog()
  213. {
  214. // This test shows how the priority list in the AggregateCatalog can actually play with
  215. // the rejection work. Until the actual object is actually pulled on and satisfied the
  216. // promise can be moved around even for not-recomposable imports but once the object is
  217. // pulled on it is fixed from that point on.
  218. var container = ContainerFactory.CreateWithAttributedCatalog(typeof(Needy), typeof(NoImportPart));
  219. // Add the missing dependency for Needy
  220. container.ComposeParts(new NoImportPart());
  221. // This change should succeed since the component "Needy" hasn't been fully composed
  222. // and one way of satisfying its needs is as good as another
  223. var export = container.GetExport<Needy>();
  224. // Adding more exports doesn't fail because we push the promise to use the NoImportPart from the catalog
  225. // using the priorities from the AggregateExportProvider
  226. container.ComposeParts(new NoImportPart());
  227. // Instansitate the object
  228. var needy = export.Value;
  229. // Cannot add another import because it would break existing compositions
  230. ExceptionAssert.Throws<ChangeRejectedException>(() =>
  231. container.ComposeParts(new NoImportPart()));
  232. }
  233. public interface ILoopA { }
  234. public interface ILoopB { }
  235. [Export(typeof(ILoopA))]
  236. public class LoopA1 : ILoopA
  237. {
  238. [Import]
  239. public ILoopB LoopB { set; get; }
  240. }
  241. [Export(typeof(ILoopA))]
  242. public class LoopA2 : ILoopA
  243. {
  244. [Import]
  245. public ILoopB LoopB { set; get; }
  246. }
  247. [Export(typeof(ILoopB))]
  248. public class LoopB1 : ILoopB
  249. {
  250. [Import]
  251. public ILoopA LoopA { set; get; }
  252. }
  253. [Export(typeof(ILoopB))]
  254. public class LoopB2 : ILoopB
  255. {
  256. [Import]
  257. public ILoopA LoopA { set; get; }
  258. }
  259. // This is an interesting situation. There are several possible self-consistent outcomes:
  260. // - All parts involved in the loop are rejected
  261. // - A consistent subset are not rejected (exactly one of LoopA1/LoopA2 and one of LoopB1/LoopB2
  262. //
  263. // Both have desireable and undesirable characteristics. The first case is non-discriminatory but
  264. // rejects more parts than are necessary, the second minimizes rejection but must choose a subset
  265. // on somewhat arbitary grounds.
  266. [TestMethod]
  267. public void Rejection_TheClemensLoop()
  268. {
  269. var catalog = new TypeCatalog(new Type[] { typeof(LoopA1), typeof(LoopA2), typeof(LoopB1), typeof(LoopB2) });
  270. var container = new CompositionContainer(catalog);
  271. var exportsA = container.GetExportedValues<ILoopA>();
  272. var exportsB = container.GetExportedValues<ILoopB>();
  273. // These assertions would prove solution one
  274. Assert.AreEqual(0, exportsA.Count(), "Catalog ILoopA entries should be rejected");
  275. Assert.AreEqual(0, exportsB.Count(), "Catalog ILoopB entries should be rejected");
  276. // These assertions would prove solution two
  277. //Assert.AreEqual(1, exportsA.Count, "Only noe ILoopA entry should not be rejected");
  278. //Assert.AreEqual(1, exportsB.Count, "Only noe ILoopB entry should not be rejected");
  279. }
  280. public interface IWorkItem
  281. {
  282. string Id { get; set; }
  283. }
  284. [Export]
  285. public class AllWorkItems
  286. {
  287. [ImportMany(AllowRecomposition = true)]
  288. public Lazy<IWorkItem>[] WorkItems { get; set; }
  289. }
  290. [Export(typeof(IWorkItem))]
  291. public class WorkItem : IWorkItem
  292. {
  293. [Import("WorkItem.Id", AllowRecomposition = true)]
  294. public string Id { get; set; }
  295. }
  296. public class Ids
  297. {
  298. [Export("WorkItem.Id")]
  299. public string Id = "MyId";
  300. }
  301. [TestMethod]
  302. public void AppliedStateNotCompleteedYet()
  303. {
  304. var container = ContainerFactory.CreateWithAttributedCatalog(typeof(AllWorkItems));
  305. container.ComposeExportedValue<string>("WorkItem.Id", "A");
  306. var workItems = container.GetExportedValue<AllWorkItems>();
  307. Assert.AreEqual(0, workItems.WorkItems.Length);
  308. container.ComposeParts(new WorkItem());
  309. Assert.AreEqual(1, workItems.WorkItems.Length);
  310. Assert.AreEqual("A", workItems.WorkItems[0].Value.Id);
  311. }
  312. [Export]
  313. public class ClassWithMissingImport
  314. {
  315. [Import]
  316. private string _importNotFound = null;
  317. }
  318. [TestMethod]
  319. public void AppliedStateStored_ShouldRevertStateOnFailure()
  320. {
  321. var container = ContainerFactory.CreateWithAttributedCatalog(typeof(AllWorkItems), typeof(WorkItem), typeof(Ids));
  322. var workItems = container.GetExportedValue<AllWorkItems>();
  323. Assert.AreEqual(1, workItems.WorkItems.Length);
  324. var batch = new CompositionBatch();
  325. batch.AddExportedValue("WorkItem.Id", "B");
  326. batch.AddPart(new ClassWithMissingImport());
  327. ExceptionAssert.Throws<ChangeRejectedException>(() =>
  328. container.Compose(batch));
  329. Assert.AreEqual("MyId", workItems.WorkItems[0].Value.Id);
  330. }
  331. [Export]
  332. public class OptionalImporter
  333. {
  334. [Import(AllowDefault = true)]
  335. public ClassWithMissingImport Import { get; set; }
  336. }
  337. [TestMethod]
  338. public void OptionalImportWithMissingDependency_ShouldRejectAndComposeFine()
  339. {
  340. var container = ContainerFactory.CreateWithAttributedCatalog(typeof(OptionalImporter), typeof(ClassWithMissingImport));
  341. var importer = container.GetExportedValue<OptionalImporter>();
  342. Assert.IsNull(importer.Import);
  343. }
  344. [Export]
  345. public class PartA
  346. {
  347. [Import(AllowDefault = true, AllowRecomposition = true)]
  348. public PartB ImportB { get; set; }
  349. }
  350. [Export]
  351. public class PartB
  352. {
  353. [Import]
  354. public PartC ImportC { get; set; }
  355. }
  356. [Export]
  357. public class PartC
  358. {
  359. [Import]
  360. public PartB ImportB { get; set; }
  361. }
  362. [TestMethod]
  363. [WorkItem(684510)]
  364. public void PartAOptionalDependsOnPartB_PartBGetAddedLater()
  365. {
  366. var container = new CompositionContainer(new TypeCatalog(typeof(PartC), typeof(PartA)));
  367. var partA = container.GetExportedValue<PartA>();
  368. Assert.IsNull(partA.ImportB);
  369. var partB = new PartB();
  370. container.ComposeParts(partB);
  371. Assert.AreEqual(partA.ImportB, partB);
  372. Assert.IsNotNull(partB.ImportC);
  373. }
  374. [Export]
  375. public class PartA2
  376. {
  377. [Import(AllowDefault = true, AllowRecomposition = true)]
  378. public PartB ImportB { get; set; }
  379. [Import(AllowDefault = true, AllowRecomposition = true)]
  380. public PartC ImportC { get; set; }
  381. }
  382. [TestMethod]
  383. [WorkItem(684510)]
  384. public void PartAOptionalDependsOnPartBAndPartC_PartCGetRecurrected()
  385. {
  386. var container = new CompositionContainer(new TypeCatalog(typeof(PartA2), typeof(PartB)));
  387. var partA = container.GetExportedValue<PartA2>();
  388. Assert.IsNull(partA.ImportB);
  389. Assert.IsNull(partA.ImportC);
  390. var partC = new PartC();
  391. container.ComposeParts(partC);
  392. Assert.AreEqual(partA.ImportB, partC.ImportB);
  393. Assert.AreEqual(partA.ImportC, partC);
  394. }
  395. }
  396. }