PageRenderTime 58ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/Labs/Microsoft.Activities.Extensions/Microsoft.Activities.Extensions.Tests/StateMachineTrackerTest.cs

#
C# | 1432 lines | 848 code | 156 blank | 428 comment | 57 complexity | 1e74afaa221785a828cbea4bab683328 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. namespace Microsoft.Activities.Extensions.Tests
  2. {
  3. using System;
  4. using System.Activities;
  5. using System.Activities.DurableInstancing;
  6. using System.Activities.Statements;
  7. using System.Diagnostics;
  8. using System.Linq;
  9. using System.ServiceModel;
  10. using System.ServiceModel.Activities;
  11. using System.ServiceModel.Activities.Description;
  12. using Microsoft.Activities.Extensions.ServiceModel;
  13. using Microsoft.Activities.Extensions.Tests.ServiceReference1;
  14. using Microsoft.Activities.Extensions.Tracking;
  15. using Microsoft.Activities.UnitTesting;
  16. using Microsoft.Activities.UnitTesting.Activities;
  17. using Microsoft.Activities.UnitTesting.DurableInstancing;
  18. using Microsoft.VisualStudio.TestTools.UnitTesting;
  19. using TrackingStateMachine.Activities;
  20. /// <summary>
  21. /// This is a test class for StateMachineTrackerTest and is intended
  22. /// to contain all StateMachineTrackerTest Unit Tests
  23. /// </summary>
  24. /// <remarks>
  25. /// TODO: What happens when tracked activity is versioned? Can dynamic update work?
  26. /// TODO: What happens if more than one PersistenceParticipant is used?
  27. /// </remarks>
  28. [TestClass]
  29. public class StateMachineTrackerTest
  30. {
  31. #region Public Methods and Operators
  32. /// <summary>
  33. /// Initializes the test class
  34. /// </summary>
  35. /// <param name="context"> The context. </param>
  36. /// <remarks>
  37. /// Since this class uses databases, this initialization
  38. /// will attempt to remove databases leftover from previous
  39. /// test runs
  40. /// </remarks>
  41. [ClassInitialize]
  42. public static void ClassInitialize(TestContext context)
  43. {
  44. SqlDatabaseTest.TryDropDatabasesWithPrefix();
  45. }
  46. /// <summary>
  47. /// Given
  48. /// * A WorkflowApplication
  49. /// * A SqlWorkflowInstanceStore
  50. /// When
  51. /// * Attach is invoked
  52. /// Then
  53. /// * A StateMachineStateTracker is returned
  54. /// * The InstanceStore is added to the WorkflowApplicaiton
  55. /// * A StateMachineStateTracker is added to the Extensions
  56. /// * A StateTrackerPersistence is created and added to the extensions
  57. /// </summary>
  58. [TestMethod]
  59. public void AttachNullWorkflowApplicationShouldThrow()
  60. {
  61. using (var testdb = new SqlWorkflowInstanceStoreTest())
  62. {
  63. // Arrange
  64. var store = new SqlWorkflowInstanceStore(testdb.ConnectionString);
  65. // Act / Assert
  66. AssertHelper.Throws<ArgumentNullException>(
  67. () => StateMachineStateTracker.Attach((WorkflowApplication)null, store));
  68. }
  69. }
  70. /// <summary>
  71. /// Given
  72. /// * A WorkflowServiceHost
  73. /// * A SqlWorkflowInstanceStore
  74. /// When
  75. /// * Attach is invoked
  76. /// Then
  77. /// * A StateMachineStateTracker is returned
  78. /// * The InstanceStore is added to the WorkflowApplicaiton
  79. /// * A StateMachineStateTracker is added to the Extensions
  80. /// * A StateTrackerPersistence is created and added to the extensions
  81. /// </summary>
  82. [TestMethod]
  83. public void AttachNullWorkflowServiceHostShouldThrow()
  84. {
  85. using (var testdb = new SqlWorkflowInstanceStoreTest())
  86. {
  87. // Arrange
  88. var store = new SqlWorkflowInstanceStore(testdb.ConnectionString);
  89. // Act / Assert
  90. AssertHelper.Throws<ArgumentNullException>(
  91. () => StateMachineStateTracker.Attach((WorkflowServiceHost)null, store));
  92. }
  93. }
  94. /// <summary>
  95. /// Given
  96. /// * A WorkflowApplication
  97. /// * A SqlWorkflowInstanceStore
  98. /// When
  99. /// * Attach is invoked
  100. /// Then
  101. /// * A StateMachineStateTracker is returned
  102. /// * The InstanceStore is added to the WorkflowApplicaiton
  103. /// * A StateMachineStateTracker is added to the Extensions
  104. /// * A StateTrackerPersistence is created and added to the extensions
  105. /// </summary>
  106. [TestMethod]
  107. public void AttachShouldAttachToWorkflowApplication()
  108. {
  109. using (var testdb = new SqlWorkflowInstanceStoreTest())
  110. {
  111. // Arrange
  112. var host = new WorkflowApplication(new Sequence());
  113. var store = testdb.CreateInstanceStore();
  114. // Act
  115. var tracker = StateMachineStateTracker.Attach(host, store);
  116. // Assert
  117. Assert.IsNotNull(tracker);
  118. Assert.AreEqual(store, host.InstanceStore);
  119. Assert.IsTrue(
  120. host.Extensions.GetSingletonExtensions().Contains(tracker),
  121. "The tracker was not added to the extensions");
  122. Assert.IsTrue(
  123. host.Extensions.GetSingletonExtensions().Any(o => o.GetType() == typeof(StateTrackerPersistence)));
  124. }
  125. }
  126. /// <summary>
  127. /// Given
  128. /// * A WorkflowServiceHost
  129. /// * A SqlWorkflowInstanceStore
  130. /// When
  131. /// * Attach is invoked
  132. /// Then
  133. /// * A StateMachineStateTracker is returned
  134. /// * The InstanceStore is added to the WorkflowApplicaiton
  135. /// * A StateMachineStateTracker is added to the Extensions
  136. /// * A StateTrackerPersistence is created and added to the extensions
  137. /// </summary>
  138. [TestMethod]
  139. public void AttachShouldAttachToWorkflowServiceHost()
  140. {
  141. using (var testdb = new SqlWorkflowInstanceStoreTest())
  142. {
  143. // Arrange
  144. var host = new WorkflowServiceHost(new Sequence());
  145. var store = testdb.CreateInstanceStore();
  146. // Act
  147. var tracker = StateMachineStateTracker.Attach(host, store);
  148. // Assert
  149. Assert.IsNotNull(tracker);
  150. Assert.AreEqual(store, host.DurableInstancingOptions.InstanceStore);
  151. Assert.IsTrue(
  152. host.WorkflowExtensions.GetSingletonExtensions().Contains(tracker),
  153. "The tracker was not added to the extensions");
  154. Assert.IsTrue(
  155. host.WorkflowExtensions.GetSingletonExtensions().Any(
  156. o => o.GetType() == typeof(StateTrackerPersistence)));
  157. }
  158. }
  159. /// <summary>
  160. /// Given
  161. /// * A WorkflowApplication that has run
  162. /// * A SqlWorkflowInstanceStore
  163. /// When
  164. /// * Attach is invoked
  165. /// Then
  166. /// * An exception is thrown
  167. /// </summary>
  168. [TestMethod]
  169. public void AttachToInitializedWorkflowApplicationShouldThrow()
  170. {
  171. using (var testdb = new SqlWorkflowInstanceStoreTest())
  172. {
  173. // Arrange
  174. var host = new WorkflowApplication(new TestBookmark<int> { BookmarkName = "B1" });
  175. var store = testdb.CreateInstanceStore();
  176. host.RunEpisode("B1");
  177. // Act / Assert
  178. AssertHelper.Throws<InvalidOperationException>(() => StateMachineStateTracker.Attach(host, store));
  179. }
  180. }
  181. /// <summary>
  182. /// Given
  183. /// * A WorkflowServiceHost that has run
  184. /// * A SqlWorkflowInstanceStore
  185. /// When
  186. /// * Attach is invoked
  187. /// Then
  188. /// * An exception is thrown
  189. /// </summary>
  190. [TestMethod]
  191. public void AttachToInitializedWorkflowServiceHostShouldThrow()
  192. {
  193. using (var testdb = new SqlWorkflowInstanceStoreTest())
  194. {
  195. // Arrange
  196. var store = testdb.CreateInstanceStore();
  197. var host = new WorkflowServiceHost(
  198. new TestBookmark<int> { BookmarkName = "B1" }, ServiceTest.GetUniqueUri());
  199. host.DurableInstancingOptions.InstanceStore = store;
  200. host.Open();
  201. // Act / Assert
  202. AssertHelper.Throws<InvalidOperationException>(() => StateMachineStateTracker.Attach(host, store));
  203. }
  204. }
  205. /// <summary>
  206. /// Given
  207. /// * A StateMachine
  208. /// * A WorkflowApplication with the StateMachineStateTracker added as an extension
  209. /// When
  210. /// * StateMachineTracker.CurrentState is accessed
  211. /// Then
  212. /// * An InvalidOperationException is thrown
  213. /// </summary>
  214. [TestMethod]
  215. public void CurrentStateThrowWithMoreThanOneStateMachine()
  216. {
  217. var activity = new NestedStateMachineExample();
  218. var host = WorkflowApplicationTest.Create(activity);
  219. var tracker = StateMachineStateTracker.Attach(host.TestWorkflowApplication);
  220. try
  221. {
  222. host.TestWorkflowApplication.RunEpisode("T1", Constants.Timeout);
  223. // Run until bookmark "NT1" from nested state machine
  224. host.TestWorkflowApplication.ResumeEpisodeBookmark("T1", null, "NT1");
  225. Assert.AreEqual(2, tracker.TrackedStateMachines.Count);
  226. AssertHelper.Throws<InvalidOperationException>(() => AssertHelper.GetProperty(tracker.CurrentState));
  227. }
  228. finally
  229. {
  230. Debug.Assert(tracker != null, "tracker != null");
  231. tracker.Trace();
  232. Debug.Assert(host.Tracking != null, "host.Tracking != null");
  233. host.Tracking.Trace();
  234. }
  235. }
  236. /// <summary>
  237. /// Given
  238. /// * A StateTracker with no CurrentStateMachine
  239. /// When
  240. /// * Get CurrentState is invoked
  241. /// Then
  242. /// * It should return null
  243. /// </summary>
  244. [TestMethod]
  245. public void CurrentStateWithNoCurrentShouldReturnNull()
  246. {
  247. // Arrange
  248. var tracker = new StateMachineStateTracker(new Sequence());
  249. // Act / Assert
  250. Assert.IsNull(tracker.CurrentState);
  251. }
  252. /// <summary>
  253. /// Given
  254. /// * A StateMachineExampleActivity
  255. /// * A StateMachineStateTracker set to a max history of 3
  256. /// When
  257. /// * The fourth state transition occurs
  258. /// Then
  259. /// * The first state in the history is dropped from the buffer and the fourth is added to the end
  260. /// </summary>
  261. [TestMethod]
  262. public void HistoryIsCircularBuffer()
  263. {
  264. // Arrange
  265. const int MaxHistory = 3;
  266. var activity = new StateMachineExample();
  267. var host = WorkflowApplicationTest.Create(activity);
  268. var tracker = new StateMachineStateTracker(activity, MaxHistory);
  269. host.Extensions.Add(tracker);
  270. try
  271. {
  272. // Start the workflow - run to State1 with bookmark "T1"
  273. host.TestWorkflowApplication.RunEpisode("T1", Constants.Timeout);
  274. // History
  275. // State1
  276. CollectionAssert.AreEqual(tracker.StateHistory.ToList(), new[] { "State1" });
  277. // Run until State2 with bookmark "T3"
  278. host.TestWorkflowApplication.ResumeEpisodeBookmark("T1", null, "T3");
  279. // History
  280. // State1
  281. // State2
  282. CollectionAssert.AreEqual(tracker.StateHistory.ToList(), new[] { "State1", "State2" });
  283. // Run until State1 with bookmark "T1"
  284. host.TestWorkflowApplication.ResumeEpisodeBookmark("T3", null, "T1");
  285. // History
  286. // State1
  287. // State2
  288. // State1
  289. CollectionAssert.AreEqual(tracker.StateHistory.ToList(), new[] { "State1", "State2", "State1" });
  290. // Run until State2 with bookmark "T3"
  291. host.TestWorkflowApplication.ResumeEpisodeBookmark("T1", null, "T3");
  292. // History
  293. // State1 <- Dropped from buffer
  294. // State2
  295. // State1
  296. // State2
  297. Assert.AreEqual(MaxHistory, tracker.StateHistory.Count());
  298. CollectionAssert.AreEqual(tracker.StateHistory.ToList(), new[] { "State2", "State1", "State2" });
  299. }
  300. finally
  301. {
  302. tracker.Trace();
  303. Debug.Assert(host.Tracking != null, "host.Tracking != null");
  304. host.Tracking.Trace();
  305. }
  306. }
  307. /// <summary>
  308. /// A null WorkflowServiceHost should throw with a valid instance store
  309. /// </summary>
  310. [TestMethod]
  311. public void InstanceStoreAndNullHostShouldThrow()
  312. {
  313. // Arrange
  314. using (var testdb = new SqlWorkflowInstanceStoreTest())
  315. {
  316. var store = new SqlWorkflowInstanceStore(testdb.ConnectionString);
  317. // Act / Assert
  318. AssertHelper.Throws<ArgumentNullException>(
  319. () => StateMachineStateTracker.Attach((WorkflowServiceHost)null, store));
  320. }
  321. }
  322. /// <summary>
  323. /// LoadInstance with an empty connection string should throw
  324. /// </summary>
  325. [TestMethod]
  326. public void LoadInstanceEmptyConnStringShouldThrow()
  327. {
  328. AssertHelper.Throws<ArgumentNullException>(
  329. () => StateMachineStateTracker.LoadInstance(Guid.NewGuid(), new Sequence(), string.Empty));
  330. }
  331. /// <summary>
  332. /// LoadInstance with a null activity should throw
  333. /// </summary>
  334. [TestMethod]
  335. public void LoadInstanceNullActivityShouldThrow()
  336. {
  337. using (var testdb = new SqlWorkflowInstanceStoreTest())
  338. {
  339. var conn = testdb.ConnectionString;
  340. AssertHelper.Throws<ArgumentNullException>(
  341. () => StateMachineStateTracker.LoadInstance(Guid.NewGuid(), null, conn));
  342. }
  343. }
  344. /// <summary>
  345. /// LoadInstance with a bad extension should throw
  346. /// </summary>
  347. [TestMethod]
  348. public void LoadInstancesBadNameExtShouldThrow()
  349. {
  350. using (var testdb = new SqlWorkflowInstanceStoreTest())
  351. {
  352. var conn = testdb.ConnectionString;
  353. AssertHelper.Throws<ArgumentException>(
  354. () => StateMachineStateTracker.LoadInstances("BadName.bad", "foo", conn));
  355. }
  356. }
  357. /// <summary>
  358. /// LoadInstance with a bad file name no extension should throw
  359. /// </summary>
  360. [TestMethod]
  361. public void LoadInstancesBadNameXamlShouldThrow()
  362. {
  363. using (var testdb = new SqlWorkflowInstanceStoreTest())
  364. {
  365. var conn = testdb.ConnectionString;
  366. AssertHelper.Throws<ArgumentException>(
  367. () => StateMachineStateTracker.LoadInstances("BadName", "foo", conn));
  368. }
  369. }
  370. /// <summary>
  371. /// LoadInstance with an empty connection string should throw
  372. /// </summary>
  373. [TestMethod]
  374. public void LoadInstancesEmptyConnStringShouldThrow()
  375. {
  376. AssertHelper.Throws<ArgumentNullException>(
  377. () =>
  378. StateMachineStateTracker.LoadInstances(Constants.StateMachineServiceExampleXamlx, "foo", string.Empty));
  379. }
  380. /// <summary>
  381. /// LoadInstance with an empty displayName should throw
  382. /// </summary>
  383. [TestMethod]
  384. public void LoadInstancesEmptyDisplayNameShouldThrow()
  385. {
  386. using (var testdb = new SqlWorkflowInstanceStoreTest())
  387. {
  388. var conn = testdb.ConnectionString;
  389. AssertHelper.Throws<ArgumentNullException>(
  390. () =>
  391. StateMachineStateTracker.LoadInstances(
  392. Constants.StateMachineServiceExampleXamlx, string.Empty, conn));
  393. }
  394. }
  395. /// <summary>
  396. /// LoadInstance with an empty xaml should throw
  397. /// </summary>
  398. [TestMethod]
  399. public void LoadInstancesEmptyXamlShouldThrow()
  400. {
  401. using (var testdb = new SqlWorkflowInstanceStoreTest())
  402. {
  403. var conn = testdb.ConnectionString;
  404. AssertHelper.Throws<ArgumentNullException>(
  405. () => StateMachineStateTracker.LoadInstances(string.Empty, "foo", conn));
  406. }
  407. }
  408. /// <summary>
  409. /// A null activity should throw
  410. /// </summary>
  411. [TestMethod]
  412. public void NullActivityShouldThrow()
  413. {
  414. AssertHelper.Throws<ArgumentNullException>(() => new StateMachineStateTracker(null));
  415. }
  416. /// <summary>
  417. /// A null WorkflowServiceHost and null instnace store should throw
  418. /// </summary>
  419. [TestMethod]
  420. public void NullInstanceStoreAndHostShouldThrow()
  421. {
  422. AssertHelper.Throws<ArgumentNullException>(
  423. () => StateMachineStateTracker.Attach((WorkflowServiceHost)null, null));
  424. }
  425. /// <summary>
  426. /// A null WorkflowApplication should throw
  427. /// </summary>
  428. [TestMethod]
  429. public void NullWorkflowApplicationShouldThrow()
  430. {
  431. AssertHelper.Throws<ArgumentNullException>(() => StateMachineStateTracker.Attach((WorkflowApplication)null));
  432. }
  433. /// <summary>
  434. /// A null WorkflowServiceHost should throw
  435. /// </summary>
  436. [TestMethod]
  437. public void NullWorkflowServiceHostShouldThrow()
  438. {
  439. AssertHelper.Throws<ArgumentNullException>(() => StateMachineStateTracker.Attach((WorkflowServiceHost)null));
  440. }
  441. /// <summary>
  442. /// Given
  443. /// * A StateMachine
  444. /// * A WorkflowApplication with the StateMachineStateTracker added as an extension
  445. /// When
  446. /// * StateMachineTracker.PossibleTransitions is accessed
  447. /// Then
  448. /// * An InvalidOperationException is thrown
  449. /// </summary>
  450. [TestMethod]
  451. public void PossibleTransitionsThrowWithMoreThanOneStateMachine()
  452. {
  453. var activity = new NestedStateMachineExample();
  454. var host = WorkflowApplicationTest.Create(activity);
  455. var tracker = StateMachineStateTracker.Attach(host.TestWorkflowApplication);
  456. try
  457. {
  458. host.TestWorkflowApplication.RunEpisode("T1", Constants.Timeout);
  459. // Run until bookmark "NT1" from nested state machine
  460. host.TestWorkflowApplication.ResumeEpisodeBookmark("T1", null, "NT1");
  461. Assert.AreEqual(2, tracker.TrackedStateMachines.Count);
  462. AssertHelper.Throws<InvalidOperationException>(
  463. () => AssertHelper.GetProperty(tracker.PossibleTransitions));
  464. }
  465. finally
  466. {
  467. Debug.Assert(tracker != null, "tracker != null");
  468. tracker.Trace();
  469. Debug.Assert(host.Tracking != null, "host.Tracking != null");
  470. host.Tracking.Trace();
  471. }
  472. }
  473. /// <summary>
  474. /// Given
  475. /// * A StateTracker with no CurrentStateMachine
  476. /// When
  477. /// * Get PossibleTransitions is invoked
  478. /// Then
  479. /// * A null string is returned
  480. /// </summary>
  481. [TestMethod]
  482. public void PossibleTransitionsWithNoCurrentShouldReturnNull()
  483. {
  484. // Arrange
  485. var tracker = new StateMachineStateTracker(new Sequence());
  486. // Act / Assert
  487. Assert.IsNull(tracker.PossibleTransitions);
  488. }
  489. /// <summary>
  490. /// Given
  491. /// * A StateMachine
  492. /// * A WorkflowApplication with the StateMachineStateTracker added as an extension
  493. /// When
  494. /// * StateMachineTracker.PreviousState is accessed
  495. /// Then
  496. /// * An InvalidOperationException is thrown
  497. /// </summary>
  498. [TestMethod]
  499. public void PreviousStateThrowWithMoreThanOneStateMachine()
  500. {
  501. var activity = new NestedStateMachineExample();
  502. var host = WorkflowApplicationTest.Create(activity);
  503. var tracker = StateMachineStateTracker.Attach(host.TestWorkflowApplication);
  504. try
  505. {
  506. host.TestWorkflowApplication.RunEpisode("T1", Constants.Timeout);
  507. // Run until bookmark "NT1" from nested state machine
  508. host.TestWorkflowApplication.ResumeEpisodeBookmark("T1", null, "NT1");
  509. Assert.AreEqual(2, tracker.TrackedStateMachines.Count);
  510. AssertHelper.Throws<InvalidOperationException>(() => AssertHelper.GetProperty(tracker.PreviousState));
  511. }
  512. finally
  513. {
  514. Debug.Assert(tracker != null, "tracker != null");
  515. tracker.Trace();
  516. Debug.Assert(host.Tracking != null, "host.Tracking != null");
  517. host.Tracking.Trace();
  518. }
  519. }
  520. /// <summary>
  521. /// Given
  522. /// * A StateMachine
  523. /// * A WorkflowApplication with the StateMachineStateTracker added as an extension
  524. /// When
  525. /// * StateMachineTracker.StateHistory is accessed
  526. /// Then
  527. /// * An InvalidOperationException is thrown
  528. /// </summary>
  529. [TestMethod]
  530. public void StateHistoryThrowWithMoreThanOneStateMachine()
  531. {
  532. var activity = new NestedStateMachineExample();
  533. var host = WorkflowApplicationTest.Create(activity);
  534. var tracker = StateMachineStateTracker.Attach(host.TestWorkflowApplication);
  535. try
  536. {
  537. host.TestWorkflowApplication.RunEpisode("T1", Constants.Timeout);
  538. // Run until bookmark "NT1" from nested state machine
  539. host.TestWorkflowApplication.ResumeEpisodeBookmark("T1", null, "NT1");
  540. Assert.AreEqual(2, tracker.TrackedStateMachines.Count);
  541. AssertHelper.Throws<InvalidOperationException>(() => AssertHelper.GetProperty(tracker.StateHistory));
  542. }
  543. finally
  544. {
  545. Debug.Assert(tracker != null, "tracker != null");
  546. tracker.Trace();
  547. Debug.Assert(host.Tracking != null, "host.Tracking != null");
  548. host.Tracking.Trace();
  549. }
  550. }
  551. /// <summary>
  552. /// Given
  553. /// * A newly created StateMachineTracker
  554. /// When
  555. /// * The TrackedStateMachines property is read
  556. /// Then
  557. /// * An empty dictionary is returned
  558. /// </summary>
  559. [TestMethod]
  560. public void TrackedStateMachinesShouldBeEmptyOnCreate()
  561. {
  562. // Arrange
  563. var tracker = new StateMachineStateTracker(new StateMachine());
  564. // Act
  565. var trackedMachines = tracker.TrackedStateMachines;
  566. // Assert
  567. Assert.IsNotNull(trackedMachines);
  568. }
  569. /// <summary>
  570. /// Given
  571. /// * A WorkflowService configured to use the StateMachineStateTracker
  572. /// When
  573. /// * StateMachineTracker.Attach is invoked
  574. /// Then
  575. /// * The list of StateMachineStateTracker instances is returned with 2 instances
  576. /// </summary>
  577. [TestMethod]
  578. [DeploymentItem(Constants.StateMachineServiceExamplePath)]
  579. public void TrackerShouldAttachWorkflowServiceHost()
  580. {
  581. // Arrange
  582. var serviceEndpoint = ServiceTest.GetUniqueEndpointAddress();
  583. using (
  584. var testHost = new WorkflowServiceTestHost(Constants.StateMachineServiceExampleXamlx, serviceEndpoint))
  585. {
  586. testHost.Host.Description.Behaviors.Add(
  587. new WorkflowIdleBehavior { TimeToPersist = TimeSpan.Zero, TimeToUnload = TimeSpan.Zero });
  588. // Act
  589. var tracker = StateMachineStateTracker.Attach(testHost.Host);
  590. testHost.Open();
  591. this.RunSampleStateMachineService(testHost.EndpointAddress, ExampleTrigger.T1, ExampleTrigger.T2);
  592. // Assert
  593. Assert.IsNotNull(tracker);
  594. Assert.AreEqual(1, tracker.TrackedStateMachines.Count);
  595. }
  596. }
  597. /// <summary>
  598. /// Given
  599. /// * A StateMachine
  600. /// * A WorkflowApplication with the StateMachineStateTracker added as an extension and run through a couple of transitions
  601. /// When
  602. /// * The state tracker is converted to XML and back again
  603. /// Then
  604. /// * Two state trackers are equal
  605. /// </summary>
  606. [TestMethod]
  607. public void TrackerShouldConvertToAndFromXml()
  608. {
  609. var activity = new StateMachineExample();
  610. var host = WorkflowApplicationTest.Create(activity);
  611. var tracker1 = new StateMachineStateTracker(activity);
  612. Debug.Assert(host != null, "host != null");
  613. Debug.Assert(host.Extensions != null, "host.Extensions != null");
  614. host.Extensions.Add(tracker1);
  615. try
  616. {
  617. host.TestWorkflowApplication.RunEpisode("T1", Constants.Timeout);
  618. host.TestWorkflowApplication.ResumeEpisodeBookmark("T1", null, "T3");
  619. var xml = tracker1.ToXml();
  620. WorkflowTrace.Information("*** Tracker1***");
  621. WorkflowTrace.Information(xml);
  622. WorkflowTrace.Information(Environment.NewLine);
  623. var tracker2 = StateMachineStateTracker.Parse(activity, xml);
  624. WorkflowTrace.Information("*** Tracker2***");
  625. WorkflowTrace.Information(tracker2.ToXml());
  626. WorkflowTrace.Information(Environment.NewLine);
  627. Assert.AreEqual(tracker1.CurrentState, tracker2.CurrentState);
  628. Assert.AreEqual(tracker1.CurrentStateMachine, tracker2.CurrentStateMachine);
  629. Assert.AreEqual(tracker1.InstanceId, tracker2.InstanceId);
  630. Assert.AreEqual(tracker1.PossibleTransitions, tracker2.PossibleTransitions);
  631. Assert.AreEqual(tracker1.PreviousState, tracker2.PreviousState);
  632. Assert.AreEqual(tracker1.PreviousStateMachine, tracker2.PreviousStateMachine);
  633. Assert.AreEqual(tracker1.RootActivity, tracker2.RootActivity);
  634. AssertHelper.AreEqual(tracker1.StateHistory, tracker2.StateHistory);
  635. AssertHelper.AreEqual(tracker1.Transitions, tracker2.Transitions);
  636. }
  637. finally
  638. {
  639. host.Tracking.Trace();
  640. }
  641. }
  642. /// <summary>
  643. /// Given
  644. /// * A StateMachine
  645. /// * A WorkflowApplication with the StateMachineStateTracker added as an extension
  646. /// When
  647. /// * The StateMachine is run and becomes idle
  648. /// Then
  649. /// * LoadInstances should load the instances
  650. /// </summary>
  651. [TestMethod]
  652. [DeploymentItem(Constants.StateMachineExamplePath)]
  653. public void TrackerShouldLoadInstancesWithActivity()
  654. {
  655. using (var testdb = new SqlWorkflowInstanceStoreTest())
  656. {
  657. // Arrange
  658. testdb.CreateInstanceStore();
  659. var id = this.RunSampleStateMachine(testdb, ExampleTrigger.T1, ExampleTrigger.T3);
  660. // Act
  661. var instance = StateMachineStateTracker.LoadInstance(
  662. id, new StateMachineExample(), testdb.ConnectionString);
  663. // Assert
  664. Assert.IsNotNull(instance);
  665. Assert.AreEqual(id, instance.InstanceId);
  666. }
  667. }
  668. /// <summary>
  669. /// Given
  670. /// * A StateMachine
  671. /// * A WorkflowApplication with the StateMachineStateTracker added as an extension
  672. /// * A StateMachineTracker which has previously accumulated history
  673. /// When
  674. /// * The StateMachine is loaded and run again
  675. /// Then
  676. /// * The history is accumulated
  677. /// </summary>
  678. [TestMethod]
  679. [DeploymentItem(Constants.StateMachineExamplePath)]
  680. public void TrackerAccumulatesHistoryAcrossLoad()
  681. {
  682. using (var testdb = new SqlWorkflowInstanceStoreTest())
  683. {
  684. // Arrange
  685. var triggers = new[] { ExampleTrigger.T1 };
  686. var activity = new StateMachineExample();
  687. var host = WorkflowApplicationTest.Create(activity);
  688. Guid id;
  689. StateMachineStateTracker.Attach(host.TestWorkflowApplication, testdb.CreateInstanceStore());
  690. // Act
  691. using (host)
  692. {
  693. try
  694. {
  695. // Start the workflow and run until it becomes idle with at least one bookmark
  696. host.TestWorkflowApplication.RunEpisode(AnyBookmark, Constants.Timeout);
  697. // Play each of the triggers
  698. foreach (var trigger in triggers)
  699. {
  700. // Resume the workflow and run until any bookmark
  701. host.TestWorkflowApplication.ResumeEpisodeBookmark(
  702. trigger.ToString(), Constants.Timeout, AnyBookmark);
  703. }
  704. host.Persist(Constants.Timeout);
  705. host.Unload(Constants.Timeout);
  706. id = host.Id;
  707. }
  708. finally
  709. {
  710. host.Tracking.Trace();
  711. }
  712. }
  713. var tracker = StateMachineStateTracker.LoadInstance(id, activity, testdb.ConnectionString);
  714. var host2 = WorkflowApplicationTest.Create(activity);
  715. StateMachineStateTracker.Attach(host2.TestWorkflowApplication, testdb.InstanceStore, tracker: tracker);
  716. host2.Load(id, Constants.Timeout);
  717. using (host2)
  718. {
  719. try
  720. {
  721. // Resume the workflow and run until any bookmark
  722. host2.TestWorkflowApplication.ResumeEpisodeBookmark(
  723. ExampleTrigger.T3.ToString(), Constants.Timeout, AnyBookmark);
  724. }
  725. finally
  726. {
  727. host.Tracking.Trace();
  728. }
  729. }
  730. // Assert
  731. Assert.IsNotNull(tracker);
  732. Assert.AreEqual(3, tracker.StateHistory.Count);
  733. }
  734. }
  735. /// <summary>
  736. /// Given
  737. /// * A StateMachine
  738. /// * A WorkflowApplication with the StateMachineStateTracker added as an extension
  739. /// When
  740. /// * The StateMachine is run and becomes idle
  741. /// Then
  742. /// * LoadInstances should load the instances
  743. /// </summary>
  744. [TestMethod]
  745. [DeploymentItem(Constants.StateMachineExamplePath)]
  746. public void TrackerShouldLoadInstancesWithXaml()
  747. {
  748. using (var testdb = new SqlWorkflowInstanceStoreTest())
  749. {
  750. // Arrange
  751. testdb.CreateInstanceStore();
  752. // Create a couple of state machines in the persistence store
  753. this.RunSampleStateMachine(testdb, ExampleTrigger.T1, ExampleTrigger.T3);
  754. this.RunSampleStateMachine(testdb, ExampleTrigger.T1, ExampleTrigger.T3, ExampleTrigger.T2);
  755. var instances = StateMachineStateTracker.LoadInstances(
  756. Constants.StateMachineExampleXaml, Constants.StateMachineExample, testdb.ConnectionString);
  757. Assert.IsNotNull(instances);
  758. Assert.AreEqual(2, instances.Count);
  759. }
  760. }
  761. /// <summary>
  762. /// Given
  763. /// * A StateMachine
  764. /// * A WorkflowApplication with the StateMachineStateTracker added as an extension
  765. /// When
  766. /// * The StateMachine is run and becomes idle
  767. /// Then
  768. /// * LoadInstances should load the instances
  769. /// </summary>
  770. [TestMethod]
  771. [DeploymentItem(Constants.StateMachineServiceExamplePath)]
  772. public void TrackerShouldLoadInstancesWithXamlx()
  773. {
  774. using (var testdb = new SqlWorkflowInstanceStoreTest())
  775. {
  776. // Arrange
  777. testdb.CreateInstanceStore();
  778. // Create a couple of state machines in the persistence store
  779. RunSampleStateMachineService(testdb, ExampleTrigger.T1, ExampleTrigger.T2);
  780. RunSampleStateMachineService(testdb, ExampleTrigger.T1, ExampleTrigger.T2, ExampleTrigger.T5);
  781. // Get the root activity of the workflow service
  782. var activity = XamlHelper.Load(Constants.StateMachineServiceExampleXamlx);
  783. var instances = StateMachineStateTracker.LoadInstances(activity, testdb.ConnectionString);
  784. Assert.IsNotNull(instances);
  785. Assert.AreEqual(2, instances.Count);
  786. }
  787. }
  788. /// <summary>
  789. /// Given
  790. /// * A StateMachine
  791. /// * A WorkflowApplication with the StateMachineStateTracker added as an extension
  792. /// When
  793. /// * The StateMachine is run and becomes idle
  794. /// Then
  795. /// * LoadInstances should load the instances
  796. /// </summary>
  797. [TestMethod]
  798. [DeploymentItem(Constants.StateMachineExamplePath)]
  799. public void TrackerShouldNotLoadInstancesWithBadId()
  800. {
  801. using (var testdb = new SqlWorkflowInstanceStoreTest())
  802. {
  803. // Arrange
  804. testdb.CreateInstanceStore();
  805. // Act
  806. var instance = StateMachineStateTracker.LoadInstance(
  807. Guid.NewGuid(), new StateMachineExample(), testdb.ConnectionString);
  808. Assert.IsNull(instance);
  809. }
  810. }
  811. /// <summary>
  812. /// Given
  813. /// * A StateMachine
  814. /// * A WorkflowApplication with the StateMachineStateTracker added as an extension
  815. /// When
  816. /// * The StateMachine is run and becomes idle
  817. /// Then
  818. /// * The StateMachineStateTracker correctly reports the current state
  819. /// * and the possible transitions
  820. /// </summary>
  821. [TestMethod]
  822. public void CurrentStateReportedWhenRun()
  823. {
  824. var activity = new StateMachineExample();
  825. var host = WorkflowApplicationTest.Create(activity);
  826. var tracker = new StateMachineStateTracker(activity);
  827. Debug.Assert(host != null, "host != null");
  828. Debug.Assert(host.Extensions != null, "host.Extensions != null");
  829. host.Extensions.Add(tracker);
  830. try
  831. {
  832. // Using Microsoft.Activities.Extensions run the workflow until a bookmark named "T1"
  833. Assert.IsInstanceOfType(
  834. host.TestWorkflowApplication.RunEpisode("T1", Constants.Timeout), typeof(WorkflowIdleEpisodeResult));
  835. WorkflowTrace.Information("First Idle");
  836. tracker.Trace();
  837. Debug.Assert(tracker.TrackedStateMachines != null, "tracker.TrackedStateMachines != null");
  838. Assert.AreEqual(1, tracker.TrackedStateMachines.Count);
  839. var stateTrackerInfo = tracker.TrackedStateMachines.Values.ElementAt(0);
  840. Debug.Assert(stateTrackerInfo != null, "stateTrackerInfo != null");
  841. Assert.AreEqual("State1", stateTrackerInfo.CurrentState);
  842. Assert.AreEqual("State1", tracker.CurrentState);
  843. Assert.AreEqual(tracker.CurrentState, stateTrackerInfo.CurrentState);
  844. Debug.Assert(stateTrackerInfo.Transitions != null, "stateTrackerInfo.Transitions != null");
  845. Assert.AreEqual(2, stateTrackerInfo.Transitions.Count);
  846. Assert.AreEqual("T1, T2", stateTrackerInfo.PossibleTransitions);
  847. // Run until bookmark "T3"
  848. Assert.IsInstanceOfType(
  849. host.TestWorkflowApplication.ResumeEpisodeBookmark("T1", null, "T3"),
  850. typeof(WorkflowIdleEpisodeResult));
  851. WorkflowTrace.Information("Second Idle");
  852. tracker.Trace();
  853. // Use the tracker operations that are forwarded to the current state
  854. Debug.Assert(tracker.TrackedStateMachines != null, "tracker.TrackedStateMachines != null");
  855. Assert.AreEqual(1, tracker.TrackedStateMachines.Count);
  856. Assert.AreEqual("State2", tracker.CurrentState);
  857. Debug.Assert(tracker.Transitions != null, "tracker.Transitions != null");
  858. Assert.AreEqual(2, tracker.Transitions.Count);
  859. Assert.AreEqual("T3, T5", tracker.PossibleTransitions);
  860. // Resume bookmark T5 and run until complete
  861. Assert.IsInstanceOfType(
  862. host.TestWorkflowApplication.ResumeEpisodeBookmark("T5", null),
  863. typeof(WorkflowCompletedEpisodeResult));
  864. // Should be no remaining state machines tracked after completion
  865. Assert.AreEqual(1, tracker.TrackedStateMachines.Count);
  866. }
  867. finally
  868. {
  869. tracker.Trace();
  870. Debug.Assert(host.Tracking != null, "host.Tracking != null");
  871. host.Tracking.Trace();
  872. }
  873. }
  874. /// <summary>
  875. /// Given
  876. /// * A StateMachine
  877. /// * A WorkflowApplication with the StateMachineStateTracker added as an extension
  878. /// When
  879. /// * The StateMachine is run and becomes idle
  880. /// Then
  881. /// * The StateMachineStateTracker correctly reports the current state
  882. /// * and the possible transitions
  883. /// </summary>
  884. [TestMethod]
  885. public void TrackerShouldReportPreviousState()
  886. {
  887. var activity = new StateMachineExample();
  888. var host = WorkflowApplicationTest.Create(activity);
  889. var tracker = new StateMachineStateTracker(activity);
  890. Debug.Assert(host != null, "host != null");
  891. Debug.Assert(host.Extensions != null, "host.Extensions != null");
  892. host.Extensions.Add(tracker);
  893. try
  894. {
  895. // Using Microsoft.Activities.Extensions run the workflow until a bookmark named "T1"
  896. Assert.IsInstanceOfType(
  897. host.TestWorkflowApplication.RunEpisode("T1", Constants.Timeout), typeof(WorkflowIdleEpisodeResult));
  898. WorkflowTrace.Information("First Idle");
  899. tracker.Trace();
  900. Debug.Assert(tracker.TrackedStateMachines != null, "tracker.TrackedStateMachines != null");
  901. Assert.AreEqual(1, tracker.TrackedStateMachines.Count);
  902. var stateTrackerInfo = tracker.TrackedStateMachines.Values.ElementAt(0);
  903. Debug.Assert(stateTrackerInfo != null, "stateTrackerInfo != null");
  904. Assert.AreEqual(null, stateTrackerInfo.PreviousState);
  905. Assert.AreEqual(null, tracker.PreviousState);
  906. Assert.AreEqual(tracker.PreviousState, stateTrackerInfo.PreviousState);
  907. Debug.Assert(stateTrackerInfo.Transitions != null, "stateTrackerInfo.Transitions != null");
  908. Assert.AreEqual(2, stateTrackerInfo.Transitions.Count);
  909. Assert.AreEqual("T1, T2", stateTrackerInfo.PossibleTransitions);
  910. // Run until bookmark "T3"
  911. Assert.IsInstanceOfType(
  912. host.TestWorkflowApplication.ResumeEpisodeBookmark("T1", null, "T3"),
  913. typeof(WorkflowIdleEpisodeResult));
  914. WorkflowTrace.Information("Second Idle");
  915. tracker.Trace();
  916. // Use the tracker operations that are forwarded to the current state
  917. Debug.Assert(tracker.TrackedStateMachines != null, "tracker.TrackedStateMachines != null");
  918. Assert.AreEqual(1, tracker.TrackedStateMachines.Count);
  919. Assert.AreEqual("State1", tracker.PreviousState);
  920. Debug.Assert(tracker.Transitions != null, "tracker.Transitions != null");
  921. Assert.AreEqual(2, tracker.Transitions.Count);
  922. Assert.AreEqual("T3, T5", tracker.PossibleTransitions);
  923. // Resume bookmark T5 and run until complete
  924. Assert.IsInstanceOfType(
  925. host.TestWorkflowApplication.ResumeEpisodeBookmark("T5", null),
  926. typeof(WorkflowCompletedEpisodeResult));
  927. Assert.AreEqual(1, tracker.TrackedStateMachines.Count);
  928. }
  929. finally
  930. {
  931. tracker.Trace();
  932. Debug.Assert(host.Tracking != null, "host.Tracking != null");
  933. host.Tracking.Trace();
  934. }
  935. }
  936. /// <summary>
  937. /// Given
  938. /// * A StateMachine
  939. /// * A WorkflowApplication with the StateMachineStateTracker added as an extension
  940. /// When
  941. /// * The StateMachine is run and becomes idle
  942. /// Then
  943. /// * The StateMachineStateTracker correctly reports the current state
  944. /// * and the possible transitions
  945. /// </summary>
  946. [TestMethod]
  947. public void TrackerWithNestedState()
  948. {
  949. var activity = new NestedStateMachineExample();
  950. var host = WorkflowApplicationTest.Create(activity);
  951. Debug.Assert(host != null, "host != null");
  952. var tracker = StateMachineStateTracker.Attach(host.TestWorkflowApplication);
  953. try
  954. {
  955. // Using Microsoft.Activities.Extensions run the workflow until a bookmark named "T1"
  956. Assert.IsInstanceOfType(
  957. host.TestWorkflowApplication.RunEpisode("T1", Constants.Timeout), typeof(WorkflowIdleEpisodeResult));
  958. WorkflowTrace.Information("First Idle");
  959. Debug.Assert(tracker != null, "tracker != null");
  960. tracker.Trace();
  961. Debug.Assert(tracker.TrackedStateMachines != null, "tracker.TrackedStateMachines != null");
  962. Assert.AreEqual(1, tracker.TrackedStateMachines.Count);
  963. var stateTrackerInfo = tracker.TrackedStateMachines.Values.ElementAt(0);
  964. Debug.Assert(stateTrackerInfo != null, "stateTrackerInfo != null");
  965. Assert.AreEqual("State1", stateTrackerInfo.CurrentState);
  966. Debug.Assert(stateTrackerInfo.Transitions != null, "stateTrackerInfo.Transitions != null");
  967. Assert.AreEqual(2, stateTrackerInfo.Transitions.Count);
  968. Assert.AreEqual("T1, T6", stateTrackerInfo.PossibleTransitions);
  969. // Run until bookmark "NT1" from nested state machine
  970. Assert.IsInstanceOfType(
  971. host.TestWorkflowApplication.ResumeEpisodeBookmark("T1", null, "NT1"),
  972. typeof(WorkflowIdleEpisodeResult));
  973. WorkflowTrace.Information("Second Idle");
  974. tracker.Trace();
  975. Debug.Assert(tracker.TrackedStateMachines != null, "tracker.TrackedStateMachines != null");
  976. Assert.AreEqual(2, tracker.TrackedStateMachines.Count);
  977. // Verify the parent state machine is already in State2
  978. stateTrackerInfo = tracker.TrackedStateMachines.Values.ElementAt(0);
  979. Debug.Assert(stateTrackerInfo != null, "stateTrackerInfo != null");
  980. Assert.AreEqual("State2", stateTrackerInfo.CurrentState);
  981. Debug.Assert(stateTrackerInfo.Transitions != null, "stateTrackerInfo.Transitions != null");
  982. Assert.AreEqual(3, stateTrackerInfo.Transitions.Count);
  983. Assert.AreEqual("T2, T3, T7", stateTrackerInfo.PossibleTransitions);
  984. // The nested state machine
  985. var nestedStateTrackerInfo = tracker.TrackedStateMachines.Values.ElementAt(1);
  986. Debug.Assert(nestedStateTrackerInfo != null, "nestedStateTrackerInfo != null");
  987. Assert.AreEqual("Nested State1", nestedStateTrackerInfo.CurrentState);
  988. Debug.Assert(nestedStateTrackerInfo.Transitions != null, "nestedStateTrackerInfo.Transitions != null");
  989. Assert.AreEqual(1, nestedStateTrackerInfo.Transitions.Count);
  990. Assert.AreEqual("NT1", nestedStateTrackerInfo.PossibleTransitions);
  991. // Resume bookmark "NT1" from nested state machine and run until bookmark "T7"
  992. Assert.IsInstanceOfType(
  993. host.TestWorkflowApplication.ResumeEpisodeBookmark("NT1", null, "T7"),
  994. typeof(WorkflowIdleEpisodeResult));
  995. // Resume bookmark T7 and run until complete
  996. Assert.IsInstanceOfType(
  997. host.TestWorkflowApplication.ResumeEpisodeBookmark("T7", null),
  998. typeof(WorkflowCompletedEpisodeResult));
  999. Assert.AreEqual(2, tracker.TrackedStateMachines.Count);
  1000. }
  1001. finally
  1002. {
  1003. Debug.Assert(tracker != null, "tracker != null");
  1004. tracker.Trace();
  1005. Debug.Assert(host.Tracking != null, "host.Tracking != null");
  1006. host.Tracking.Trace();
  1007. }
  1008. }
  1009. /// <summary>
  1010. /// Given
  1011. /// * A StateMachine
  1012. /// * A WorkflowApplication with the StateMachineStateTracker added as an extension
  1013. /// When
  1014. /// * StateMachineTracker.Transitions is accessed
  1015. /// Then
  1016. /// * An InvalidOperationException is thrown
  1017. /// </summary>
  1018. [TestMethod]
  1019. public void TransitionsThrowWithMoreThanOneStateMachine()
  1020. {
  1021. var activity = new NestedStateMachineExample();
  1022. var host = WorkflowApplicationTest.Create(activity);
  1023. var tracker = StateMachineStateTracker.Attach(host.TestWorkflowApplication);
  1024. try
  1025. {
  1026. host.TestWorkflowApplication.RunEpisode("T1", Constants.Timeout);
  1027. // Run until bookmark "NT1" from nested state machine
  1028. host.TestWorkflowApplication.ResumeEpisodeBookmark("T1", null, "NT1");
  1029. Assert.AreEqual(2, tracker.TrackedStateMachines.Count);
  1030. AssertHelper.Throws<InvalidOperationException>(() => AssertHelper.GetProperty(tracker.Transitions));
  1031. }
  1032. finally
  1033. {
  1034. Debug.Assert(tracker != null, "tracker != null");
  1035. tracker.Trace();
  1036. Debug.Assert(host.Tracking != null, "host.Tracking != null");
  1037. host.Tracking.Trace();
  1038. }
  1039. }
  1040. /// <summary>
  1041. /// Given
  1042. /// * A StateTracker with no CurrentStateMachine
  1043. /// When
  1044. /// * Get Transitions is invoked
  1045. /// Then
  1046. /// * A null is returned
  1047. /// </summary>
  1048. [TestMethod]
  1049. public void TransitionsWithNoCurrentShouldReturnNull()
  1050. {
  1051. // Arrange
  1052. var tracker = new StateMachineStateTracker(new Sequence());
  1053. // Act / Assert
  1054. Assert.IsNull(tracker.Transitions);
  1055. }
  1056. /// <summary>
  1057. /// Given
  1058. /// * A WorkflowService configured to use the StateMachineStateTracker
  1059. /// * An instance store with 2 persisted instances with state tracker information
  1060. /// When
  1061. /// * LoadInstances is called using the xamlx file
  1062. /// Then
  1063. /// * The list…

Large files files are truncated, but you can click here to view the full file