RecompositionTests.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. // -----------------------------------------------------------------------
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. // -----------------------------------------------------------------------
  4. using System;
  5. using System.Collections.Generic;
  6. using System.ComponentModel.Composition;
  7. using System.ComponentModel.Composition.Hosting;
  8. using System.ComponentModel.Composition.UnitTesting;
  9. using Microsoft.VisualStudio.TestTools.UnitTesting;
  10. using System.ComponentModel.Composition.Factories;
  11. using System.UnitTesting;
  12. using System.Linq;
  13. using System.ComponentModel.Composition.Primitives;
  14. namespace Tests.Integration
  15. {
  16. [TestClass]
  17. public class RecompositionTests
  18. {
  19. public class Class_OptIn_AllowRecompositionImports
  20. {
  21. [Import("Value", AllowRecomposition = true)]
  22. public int Value { get; set; }
  23. }
  24. [TestMethod]
  25. public void Import_OptIn_AllowRecomposition()
  26. {
  27. var container = new CompositionContainer();
  28. var importer = new Class_OptIn_AllowRecompositionImports();
  29. CompositionBatch batch = new CompositionBatch();
  30. batch.AddPart(importer);
  31. var valueKey = batch.AddExportedValue("Value", 21);
  32. container.Compose(batch);
  33. // Initial compose Value should be 21
  34. Assert.AreEqual(21, importer.Value);
  35. // Recompose Value to be 42
  36. batch = new CompositionBatch();
  37. batch.RemovePart(valueKey);
  38. batch.AddExportedValue("Value", 42);
  39. container.Compose(batch);
  40. Assert.AreEqual(42, importer.Value, "Value should have changed!");
  41. }
  42. public class Class_OptOut_AllowRecompositionImports
  43. {
  44. [Import("Value", AllowRecomposition = false)]
  45. public int Value { get; set; }
  46. }
  47. [TestMethod]
  48. public void Import_OptOut_AllowRecomposition()
  49. {
  50. var container = new CompositionContainer();
  51. var importer = new Class_OptOut_AllowRecompositionImports();
  52. CompositionBatch batch = new CompositionBatch();
  53. batch.AddPart(importer);
  54. var valueKey = batch.AddExportedValue("Value", 21);
  55. container.Compose(batch);
  56. // Initial compose Value should be 21
  57. Assert.AreEqual(21, importer.Value);
  58. // Reset value to ensure it doesn't get set to same value again
  59. importer.Value = -21;
  60. // Recompose Value to be 42
  61. batch = new CompositionBatch();
  62. batch.RemovePart(valueKey);
  63. batch.AddExportedValue("Value", 42);
  64. // After rejection batch failures throw ChangeRejectedException to indicate that
  65. // the failure did not affect the container
  66. CompositionAssert.ThrowsChangeRejectedError(ErrorId.ImportEngine_PreventedByExistingImport, () =>
  67. {
  68. container.Compose(batch);
  69. });
  70. Assert.AreEqual(-21, importer.Value, "Value should NOT have changed!");
  71. }
  72. public class Class_Default_AllowRecompositionImports
  73. {
  74. [Import("Value")]
  75. public int Value { get; set; }
  76. }
  77. [TestMethod]
  78. public void Import_Default_AllowRecomposition()
  79. {
  80. var container = new CompositionContainer();
  81. var importer = new Class_Default_AllowRecompositionImports();
  82. CompositionBatch batch = new CompositionBatch();
  83. batch.AddPart(importer);
  84. var valueKey = batch.AddExportedValue("Value", 21);
  85. container.Compose(batch);
  86. // Initial compose Value should be 21
  87. Assert.AreEqual(21, importer.Value);
  88. // Reset value to ensure it doesn't get set to same value again
  89. importer.Value = -21;
  90. // Recompose Value to be 42
  91. batch = new CompositionBatch();
  92. batch.RemovePart(valueKey);
  93. batch.AddExportedValue("Value", 42);
  94. // After rejection batch failures throw ChangeRejectedException to indicate that
  95. // the failure did not affect the container
  96. CompositionAssert.ThrowsChangeRejectedError(ErrorId.ImportEngine_PreventedByExistingImport, () =>
  97. {
  98. container.Compose(batch);
  99. });
  100. Assert.AreEqual(-21, importer.Value, "Value should NOT have changed!");
  101. }
  102. public class Class_BothOptInAndOptOutRecompositionImports
  103. {
  104. [Import("Value", AllowRecomposition = true)]
  105. public int RecomposableValue { get; set; }
  106. [Import("Value", AllowRecomposition = false)]
  107. public int NonRecomposableValue { get; set; }
  108. }
  109. [TestMethod]
  110. public void Import_BothOptInAndOptOutRecomposition()
  111. {
  112. var container = new CompositionContainer();
  113. var importer = new Class_BothOptInAndOptOutRecompositionImports();
  114. CompositionBatch batch = new CompositionBatch();
  115. batch.AddPart(importer);
  116. var valueKey = batch.AddExportedValue("Value", 21);
  117. container.Compose(batch);
  118. // Initial compose values should be 21
  119. Assert.AreEqual(21, importer.RecomposableValue);
  120. Assert.AreEqual(21, importer.NonRecomposableValue);
  121. // Reset value to ensure it doesn't get set to same value again
  122. importer.NonRecomposableValue = -21;
  123. importer.RecomposableValue = -21;
  124. // Recompose Value to be 42
  125. batch = new CompositionBatch();
  126. batch.RemovePart(valueKey);
  127. batch.AddExportedValue("Value", 42);
  128. // After rejection batch failures throw ChangeRejectedException to indicate that
  129. // the failure did not affect the container
  130. CompositionAssert.ThrowsChangeRejectedError(ErrorId.ImportEngine_PreventedByExistingImport, () =>
  131. {
  132. container.Compose(batch);
  133. });
  134. Assert.AreEqual(-21, importer.NonRecomposableValue, "Value should NOT have changed!");
  135. // The batch rejection means that the recomposable value shouldn't change either
  136. Assert.AreEqual(-21, importer.RecomposableValue, "Value should NOT have changed!");
  137. }
  138. public class Class_MultipleOptInRecompositionImportsWithDifferentContracts
  139. {
  140. [Import("Value1", AllowRecomposition = true)]
  141. public int Value1 { get; set; }
  142. [Import("Value2", AllowRecomposition = true)]
  143. public int Value2 { get; set; }
  144. }
  145. [TestMethod]
  146. public void Import_OptInRecomposition_Multlple()
  147. {
  148. var container = new CompositionContainer();
  149. var importer = new Class_MultipleOptInRecompositionImportsWithDifferentContracts();
  150. CompositionBatch batch = new CompositionBatch();
  151. batch.AddPart(importer);
  152. var value1Key = batch.AddExportedValue("Value1", 21);
  153. var value2Key = batch.AddExportedValue("Value2", 23);
  154. container.Compose(batch);
  155. Assert.AreEqual(21, importer.Value1);
  156. Assert.AreEqual(23, importer.Value2);
  157. // Reset value to ensure it doesn't get set to same value again
  158. importer.Value1 = -21;
  159. importer.Value2 = -23;
  160. // Recompose Value to be 42
  161. batch = new CompositionBatch();
  162. batch.RemovePart(value1Key);
  163. batch.AddExportedValue("Value1", 42);
  164. container.Compose(batch);
  165. Assert.AreEqual(42, importer.Value1, "Value should have changed!");
  166. Assert.AreEqual(-23, importer.Value2, "Value should NOT have changed because Value2 contract should not be updated.");
  167. }
  168. [PartNotDiscoverable]
  169. public class MyName
  170. {
  171. public MyName(string name)
  172. {
  173. this.Name = name;
  174. }
  175. [Export("Name")]
  176. public string Name { get; private set; }
  177. }
  178. [PartNotDiscoverable]
  179. public class Spouse
  180. {
  181. public Spouse(string name)
  182. {
  183. this.Name = name;
  184. }
  185. [Export("Friend")]
  186. [ExportMetadata("Relationship", "Wife")]
  187. public string Name { get; private set; }
  188. }
  189. [PartNotDiscoverable]
  190. public class Child
  191. {
  192. public Child(string name)
  193. {
  194. this.Name = name;
  195. }
  196. [Export("Child")]
  197. public string Name { get; private set; }
  198. }
  199. [PartNotDiscoverable]
  200. public class Job
  201. {
  202. public Job(string name)
  203. {
  204. this.Name = name;
  205. }
  206. [Export("Job")]
  207. public string Name { get; private set; }
  208. }
  209. [PartNotDiscoverable]
  210. public class Friend
  211. {
  212. public Friend(string name)
  213. {
  214. this.Name = name;
  215. }
  216. [Export("Friend")]
  217. public string Name { get; private set; }
  218. }
  219. public interface IRelationshipView
  220. {
  221. string Relationship { get; }
  222. }
  223. [PartNotDiscoverable]
  224. public class Me
  225. {
  226. [Import("Name", AllowRecomposition = true)]
  227. public string Name { get; set; }
  228. [Import("Job", AllowDefault = true, AllowRecomposition = true)]
  229. public string Job { get; set; }
  230. [ImportMany("Child")]
  231. public string[] Children { get; set; }
  232. [ImportMany("Friend")]
  233. public Lazy<string, IRelationshipView>[] Relatives { get; set; }
  234. [ImportMany("Friend", AllowRecomposition = true)]
  235. public string[] Friends { get; set; }
  236. }
  237. [TestMethod]
  238. public void Recomposition_IntegrationTest()
  239. {
  240. var container = new CompositionContainer();
  241. var batch = new CompositionBatch();
  242. var me = new Me();
  243. batch.AddPart(me);
  244. var namePart = batch.AddPart(new MyName("Blake"));
  245. batch.AddPart(new Spouse("Barbara"));
  246. batch.AddPart(new Friend("Steve"));
  247. batch.AddPart(new Friend("Joyce"));
  248. container.Compose(batch);
  249. Assert.AreEqual(me.Name, "Blake", "Name in initial composition incorrect");
  250. Assert.AreEqual(me.Job, null, "Job should have the default value");
  251. Assert.AreEqual(me.Friends.Length, 3, "Number of friends in initial composition incorrect");
  252. Assert.AreEqual(me.Relatives.Length, 1, "Number of relatives in initial composition incorrect");
  253. Assert.AreEqual(me.Children.Length, 0, "Number of children in initial composition incorrect");
  254. // Can only have one name
  255. ExceptionAssert.Throws<ChangeRejectedException>(() =>
  256. container.ComposeParts(new MyName("Blayke")));
  257. batch = new CompositionBatch();
  258. batch.AddPart(new MyName("Blayke"));
  259. batch.RemovePart(namePart);
  260. container.Compose(batch);
  261. Assert.AreEqual(me.Name, "Blayke", "Name after recomposition incorrect");
  262. batch = new CompositionBatch();
  263. var jobPart = batch.AddPart(new Job("Architect"));
  264. container.Compose(batch);
  265. Assert.AreEqual(me.Job, "Architect", "Job after recomposition incorrect");
  266. batch = new CompositionBatch();
  267. batch.AddPart(new Job("Chimney Sweep"));
  268. container.Compose(batch);
  269. Assert.IsTrue(me.Job == null, "More than one of an optional import should result in the default value");
  270. batch = new CompositionBatch();
  271. batch.RemovePart(jobPart);
  272. container.Compose(batch);
  273. Assert.AreEqual(me.Job, "Chimney Sweep", "Job after re-recomposition incorrect");
  274. batch = new CompositionBatch();
  275. // Can only have one spouse because they aren't recomposable
  276. ExceptionAssert.Throws<ChangeRejectedException>(() =>
  277. container.ComposeParts(new Spouse("Cameron")));
  278. Assert.AreEqual(me.Relatives.Length, 1, "Number of relatives shouldn't be affected by rolled back composition");
  279. batch = new CompositionBatch();
  280. batch.AddPart(new Friend("Graham"));
  281. container.Compose(batch);
  282. Assert.AreEqual(me.Friends.Length, 4, "Number of friends after recomposition incorrect");
  283. Assert.AreEqual(me.Relatives.Length, 1, "Number of relatives shouldn't be affected by rolled back composition");
  284. }
  285. public class FooWithOptionalImport
  286. {
  287. private FooWithSimpleImport _optionalImport;
  288. [Import(AllowDefault=true, AllowRecomposition=true)]
  289. public FooWithSimpleImport OptionalImport
  290. {
  291. get
  292. {
  293. return this._optionalImport;
  294. }
  295. set
  296. {
  297. if (value != null)
  298. {
  299. this._optionalImport = value;
  300. Assert.IsTrue(!string.IsNullOrEmpty(this._optionalImport.SimpleValue), "Value should have it's imports satisfied");
  301. }
  302. }
  303. }
  304. }
  305. [Export]
  306. public class FooWithSimpleImport
  307. {
  308. [Import("FooSimpleImport")]
  309. public string SimpleValue { get; set; }
  310. }
  311. [TestMethod]
  312. public void PartsShouldHaveImportsSatisfiedBeforeBeingUsedToSatisfyRecomposableImports()
  313. {
  314. var container = new CompositionContainer();
  315. var fooOptional = new FooWithOptionalImport();
  316. container.ComposeParts(fooOptional);
  317. container.ComposeExportedValue<string>("FooSimpleImport", "NotNullOrEmpty");
  318. container.ComposeParts(new FooWithSimpleImport());
  319. Assert.IsTrue(!string.IsNullOrEmpty(fooOptional.OptionalImport.SimpleValue));
  320. }
  321. [Export]
  322. public class RootImportRecomposable
  323. {
  324. [Import(AllowDefault = true, AllowRecomposition = true)]
  325. public NonSharedImporter Importer { get; set; }
  326. }
  327. [Export]
  328. [PartCreationPolicy(CreationPolicy.NonShared)]
  329. public class NonSharedImporter
  330. {
  331. [Import]
  332. public SimpleImport Import { get; set; }
  333. }
  334. [Export]
  335. public class RootImporter
  336. {
  337. [Import]
  338. public SimpleImport Import { get; set; }
  339. }
  340. [Export]
  341. public class SimpleImport
  342. {
  343. public int Property { get { return 42; } }
  344. }
  345. [TestMethod]
  346. [Ignore]
  347. [WorkItem(733533)]
  348. public void RemoveCatalogWithNonSharedPartWithRequiredImport()
  349. {
  350. var typeCatalog = new TypeCatalog(typeof(NonSharedImporter), typeof(SimpleImport));
  351. var aggCatalog = new AggregateCatalog();
  352. var container = new CompositionContainer(aggCatalog);
  353. aggCatalog.Catalogs.Add(typeCatalog);
  354. aggCatalog.Catalogs.Add(new TypeCatalog(typeof(RootImportRecomposable)));
  355. var rootExport = container.GetExport<RootImportRecomposable>();
  356. var root = rootExport.Value;
  357. Assert.AreEqual(42, root.Importer.Import.Property);
  358. aggCatalog.Catalogs.Remove(typeCatalog);
  359. Assert.IsNull(root.Importer);
  360. }
  361. [TestMethod]
  362. [Ignore]
  363. [WorkItem(734123)]
  364. public void GetExportResultShouldBePromise()
  365. {
  366. var typeCatalog = new TypeCatalog(typeof(RootImporter), typeof(SimpleImport));
  367. var aggCatalog = new AggregateCatalog();
  368. var container = new CompositionContainer(aggCatalog);
  369. aggCatalog.Catalogs.Add(typeCatalog);
  370. var root = container.GetExport<RootImporter>();
  371. ExceptionAssert.Throws<ChangeRejectedException>(() =>
  372. aggCatalog.Catalogs.Remove(typeCatalog)
  373. );
  374. var value = root.Value;
  375. Assert.AreEqual(42, value.Import.Property);
  376. }
  377. [TestMethod]
  378. [WorkItem(789269)]
  379. public void TestRemovingAndReAddingMultipleDefinitionsFromCatalog()
  380. {
  381. var fixedParts = new TypeCatalog(typeof(RootMultipleImporter), typeof(ExportedService));
  382. var changingParts = new TypeCatalog(typeof(Exporter1), typeof(Exporter2));
  383. var catalog = new AggregateCatalog();
  384. catalog.Catalogs.Add(fixedParts);
  385. catalog.Catalogs.Add(changingParts);
  386. var container = new CompositionContainer(catalog);
  387. var root = container.GetExport<RootMultipleImporter>().Value;
  388. Assert.AreEqual(2, root.Imports.Length);
  389. catalog.Catalogs.Remove(changingParts);
  390. Assert.AreEqual(0, root.Imports.Length);
  391. catalog.Catalogs.Add(changingParts);
  392. Assert.AreEqual(2, root.Imports.Length);
  393. }
  394. [Export]
  395. public class RootMultipleImporter
  396. {
  397. [ImportMany(AllowRecomposition=true)]
  398. public IExportedInterface[] Imports { get; set; }
  399. }
  400. public interface IExportedInterface
  401. {
  402. }
  403. [Export(typeof(IExportedInterface))]
  404. public class Exporter1 : IExportedInterface
  405. {
  406. [Import]
  407. public ExportedService Service { get; set; }
  408. }
  409. [Export(typeof(IExportedInterface))]
  410. public class Exporter2 : IExportedInterface
  411. {
  412. [Import]
  413. public ExportedService Service { get; set; }
  414. }
  415. [Export]
  416. public class ExportedService
  417. {
  418. }
  419. [TestMethod]
  420. [WorkItem(762215)]
  421. [Ignore]
  422. public void TestPartCreatorResurrection()
  423. {
  424. var container = new CompositionContainer(new TypeCatalog(typeof(NonDisposableImportsDisposable), typeof(PartImporter<NonDisposableImportsDisposable>)));
  425. var exports = container.GetExports<PartImporter<NonDisposableImportsDisposable>>();
  426. Assert.AreEqual(0, exports.Count());
  427. container.ComposeParts(new DisposablePart());
  428. exports = container.GetExports<PartImporter<NonDisposableImportsDisposable>>();
  429. Assert.AreEqual(1, exports.Count());
  430. }
  431. [Export]
  432. public class PartImporter<PartType>
  433. {
  434. [Import]
  435. public PartType Creator { get; set; }
  436. }
  437. [Export]
  438. public class NonDisposableImportsDisposable
  439. {
  440. [Import]
  441. public DisposablePart Part { get; set; }
  442. }
  443. [Export]
  444. public class Part
  445. {
  446. }
  447. [Export]
  448. [PartCreationPolicy(CreationPolicy.NonShared)]
  449. public class DisposablePart : Part, IDisposable
  450. {
  451. public bool Disposed { get; private set; }
  452. public void Dispose()
  453. {
  454. Disposed = true;
  455. }
  456. }
  457. }
  458. }