diff --git a/src/DynamoCore/Models/DynamoModel.cs b/src/DynamoCore/Models/DynamoModel.cs index c13014e3ccf..f01b99610b5 100644 --- a/src/DynamoCore/Models/DynamoModel.cs +++ b/src/DynamoCore/Models/DynamoModel.cs @@ -723,7 +723,7 @@ protected DynamoModel(IStartConfiguration config) //run process startup/reading on another thread so we don't block dynamo startup. //if we end up needing to control aspects of dynamo model or view startup that we can't make //event based/async then just run this on main thread - ie get rid of the Task.Run() - var mainThreadSyncContext = new SynchronizationContext(); + var mainThreadSyncContext = SynchronizationContext.Current ?? new SynchronizationContext(); Task.Run(() => { try diff --git a/src/DynamoCore/Scheduler/AsyncTaskExtensions.cs b/src/DynamoCore/Scheduler/AsyncTaskExtensions.cs index d80ff1d7b38..bab83a64845 100644 --- a/src/DynamoCore/Scheduler/AsyncTaskExtensions.cs +++ b/src/DynamoCore/Scheduler/AsyncTaskExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -27,7 +27,7 @@ public static IDisposable Then(this AsyncTask task, AsyncTaskCompletedHandler ac /// An IDisposable representing the event subscription public static IDisposable ThenPost(this AsyncTask task, AsyncTaskCompletedHandler action, SynchronizationContext context = null) { - if (context == null) context = new SynchronizationContext(); // uses the default + if (context == null) context = SynchronizationContext.Current ?? new SynchronizationContext(); // uses the current or a default return task.Then((t) => context.Post((_) => action(task), null)); } @@ -38,7 +38,7 @@ public static IDisposable ThenPost(this AsyncTask task, AsyncTaskCompletedHandle /// An IDisposable representing the event subscription public static IDisposable ThenSend(this AsyncTask task, AsyncTaskCompletedHandler action, SynchronizationContext context = null) { - if (context == null) context = new SynchronizationContext(); // uses the default + if (context == null) context = SynchronizationContext.Current ?? new SynchronizationContext(); // uses the current or a default return task.Then((t) => context.Send((_) => action(task), null)); } diff --git a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs index 8e9474c70b9..45e305152ee 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs @@ -1115,7 +1115,9 @@ private void UnsubscribeDispatcherEvents() } #endregion - private void InitializeAutomationSettings(string commandFilePath) + // InitializeAutomationSettings initializes the Dynamo automationSettings object. + // This method is meant to be accessed only within this class and within tests. + internal void InitializeAutomationSettings(string commandFilePath) { if (String.IsNullOrEmpty(commandFilePath) || !File.Exists(commandFilePath)) commandFilePath = null; diff --git a/src/DynamoUtilities/DynamoFeatureFlagsManager.cs b/src/DynamoUtilities/DynamoFeatureFlagsManager.cs index de0614bedff..32be9566ca6 100644 --- a/src/DynamoUtilities/DynamoFeatureFlagsManager.cs +++ b/src/DynamoUtilities/DynamoFeatureFlagsManager.cs @@ -69,7 +69,7 @@ internal void CacheAllFlags() try { AllFlagsCache = JsonConvert.DeserializeObject>(dataFromCLI); - //invoke the flags retrieved event on the sync context which should be the main ui thread. + // Invoke the flags retrieved event on the sync context which should be the main ui thread (if in Dynamo with UI) or the default SyncContext (if in headless mode). syncContext?.Send((_) => { FlagsRetrieved?.Invoke(); diff --git a/test/DynamoCoreTests/UnitsOfMeasureTests.cs b/test/DynamoCoreTests/UnitsOfMeasureTests.cs index 3f6a6796fe1..ae20ee162fb 100644 --- a/test/DynamoCoreTests/UnitsOfMeasureTests.cs +++ b/test/DynamoCoreTests/UnitsOfMeasureTests.cs @@ -11,6 +11,7 @@ internal class UnitsOfMeasureTests : UnitTestBase [SetUp] public override void Setup() { + base.Setup(); Display.PrecisionFormat = "f4"; } @@ -758,12 +759,6 @@ public void CanMapOverUnits() } internal class ForgeUnitsTests : UnitTestBase { - [SetUp] - public override void Setup() - { - - } - const string milimeters = "autodesk.unit.unit:millimeters"; const string meters = "autodesk.unit.unit:meters"; diff --git a/test/DynamoCoreWpfTests/DynamoTestUIBase.cs b/test/DynamoCoreWpfTests/DynamoTestUIBase.cs index f71bcbbbd33..d0e57ad255b 100644 --- a/test/DynamoCoreWpfTests/DynamoTestUIBase.cs +++ b/test/DynamoCoreWpfTests/DynamoTestUIBase.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection; using System.Threading; +using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; using Dynamo.Configuration; @@ -15,7 +16,6 @@ using Dynamo.Nodes; using Dynamo.Scheduler; using Dynamo.ViewModels; -using Dynamo.Wpf.Utilities; using DynamoCoreWpfTests.Utility; using DynamoShapeManager; using DynamoUtilities; @@ -32,6 +32,11 @@ public class DynamoTestUIBase protected DynamoView View { get; set; } protected DynamoModel Model { get; set; } + // Use this flag to skip trying to execute all the dispatched operations during the test lifetime. + // This flag should only be used very sparingly + protected bool SkipDispatcherFlush = false; + protected int DispatcherOpsCounter = 0; + protected string ExecutingDirectory { get { return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); } @@ -84,6 +89,11 @@ public virtual void Start() View.Show(); SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext()); + + if (!SkipDispatcherFlush) + { + Dispatcher.CurrentDispatcher.Hooks.OperationPosted += Hooks_OperationPosted; + } } protected static void RaiseLoadedEvent(FrameworkElement element) @@ -96,6 +106,14 @@ protected static void RaiseLoadedEvent(FrameworkElement element) eventMethod.Invoke(element, new object[] { args }); } + private void Hooks_OperationPosted(object sender, DispatcherHookEventArgs e) + { + e.Operation.Task.ContinueWith((t) => { + Interlocked.Decrement(ref DispatcherOpsCounter); + }, TaskScheduler.Default); + Interlocked.Increment(ref DispatcherOpsCounter); + } + /// /// Derived test classes can override this method to provide different configurations. /// @@ -114,6 +132,13 @@ protected virtual DynamoModel.IStartConfiguration CreateStartConfiguration(IPath [TearDown] public void Exit() { + if (!SkipDispatcherFlush) + { + Dispatcher.CurrentDispatcher.Hooks.OperationPosted -= Hooks_OperationPosted; + + DispatcherUtil.DoEventsLoop(() => DispatcherOpsCounter == 0); + } + //Ensure that we leave the workspace marked as //not having changes. ViewModel.HomeSpace.HasUnsavedChanges = false; diff --git a/test/DynamoCoreWpfTests/DynamoViewModelUnitTest.cs b/test/DynamoCoreWpfTests/DynamoViewModelUnitTest.cs index 8705685c2ef..f061ed034c4 100644 --- a/test/DynamoCoreWpfTests/DynamoViewModelUnitTest.cs +++ b/test/DynamoCoreWpfTests/DynamoViewModelUnitTest.cs @@ -30,6 +30,7 @@ public class DynamoViewModelUnitTest : DSEvaluationUnitTestBase { protected DynamoViewModel ViewModel; protected Preloader preloader; + protected string CommandFilePath; protected override DynamoModel GetModel() { @@ -131,6 +132,7 @@ protected void StartDynamo() new DynamoViewModel.StartConfiguration() { DynamoModel = model, + CommandFilePath = CommandFilePath, Watch3DViewModel = new DefaultWatch3DViewModel(null, watch3DViewParams) }); diff --git a/test/DynamoCoreWpfTests/PreviewBubbleTests.cs b/test/DynamoCoreWpfTests/PreviewBubbleTests.cs index a38ccb58421..b3d5900ec6f 100644 --- a/test/DynamoCoreWpfTests/PreviewBubbleTests.cs +++ b/test/DynamoCoreWpfTests/PreviewBubbleTests.cs @@ -102,12 +102,9 @@ public void PreviewBubble_ListMargin() nodeView.PreviewControl.RaiseEvent(new RoutedEventArgs(FrameworkElement.LoadedEvent)); // Fire transition on dynamo main ui thread. - View.Dispatcher.Invoke(() => - { - nodeView.PreviewControl.BindToDataSource(); - nodeView.PreviewControl.TransitionToState(Dynamo.UI.Controls.PreviewControl.State.Condensed); - nodeView.PreviewControl.TransitionToState(Dynamo.UI.Controls.PreviewControl.State.Expanded); - }); + nodeView.PreviewControl.BindToDataSource(); + nodeView.PreviewControl.TransitionToState(Dynamo.UI.Controls.PreviewControl.State.Condensed); + nodeView.PreviewControl.TransitionToState(Dynamo.UI.Controls.PreviewControl.State.Expanded); DispatcherUtil.DoEvents(); var watchTree = nodeView.PreviewControl.ChildOfType(); @@ -124,12 +121,9 @@ public void PreviewBubble_NumberMargin() nodeView.PreviewControl.RaiseEvent(new RoutedEventArgs(FrameworkElement.LoadedEvent)); // Fire transition on dynamo main ui thread. - View.Dispatcher.Invoke(() => - { - nodeView.PreviewControl.BindToDataSource(); - nodeView.PreviewControl.TransitionToState(Dynamo.UI.Controls.PreviewControl.State.Condensed); - nodeView.PreviewControl.TransitionToState(Dynamo.UI.Controls.PreviewControl.State.Expanded); - }); + nodeView.PreviewControl.BindToDataSource(); + nodeView.PreviewControl.TransitionToState(Dynamo.UI.Controls.PreviewControl.State.Condensed); + nodeView.PreviewControl.TransitionToState(Dynamo.UI.Controls.PreviewControl.State.Expanded); DispatcherUtil.DoEvents(); var watchTree = nodeView.PreviewControl.ChildOfType(); @@ -145,11 +139,9 @@ public void PreviewBubble_CloseWhenFocusInCodeBlock() var nodeView = NodeViewWithGuid("1382aaf9-9432-4cf0-86ae-c586d311767e"); nodeView.PreviewControl.RaiseEvent(new RoutedEventArgs(FrameworkElement.LoadedEvent)); - View.Dispatcher.Invoke(() => - { - nodeView.PreviewControl.BindToDataSource(); - nodeView.PreviewControl.TransitionToState(Dynamo.UI.Controls.PreviewControl.State.Condensed); - }); + nodeView.PreviewControl.BindToDataSource(); + nodeView.PreviewControl.TransitionToState(Dynamo.UI.Controls.PreviewControl.State.Condensed); + DispatcherUtil.DoEvents(); nodeView.ChildOfType().Focus(); diff --git a/test/DynamoCoreWpfTests/RecordedTests.cs b/test/DynamoCoreWpfTests/RecordedTests.cs index de704a9a9e6..a3dbf63b1b8 100644 --- a/test/DynamoCoreWpfTests/RecordedTests.cs +++ b/test/DynamoCoreWpfTests/RecordedTests.cs @@ -51,42 +51,16 @@ public class RecordedUnitTestBase : DynamoViewModelUnitTest public override void Setup() { + base.Setup(); + // Fixed seed randomizer for predictability. randomizer = new System.Random(123456); - SetupDirectories(); - - // We do not call "base.Init()" here because we want to be able - // to create our own copy of Controller here with command file path. - // - // base.Init(); } public override void Cleanup() - { - base.Cleanup(); - Exit(); - } - - protected void Exit() { commandCallback = null; - if (this.ViewModel != null) - { - // There are exceptions made to certain test cases where async evaluation - // needs to be permitted. IsTestMode is marked as false for these test cases - // to emulate the real UI async scenario. Since the UI takes care of shutting down - // the Model in such a case, we need to make sure it is not shut down twice - // by checking for IsTestMode here as well - if (DynamoModel.IsTestMode) - { - var shutdownParams = new DynamoViewModel.ShutdownParams( - shutdownHost: false, allowCancellation: false); - ViewModel.PerformShutdownSequence(shutdownParams); - } - this.ViewModel = null; - } - - preloader = null; // Invalid preloader object for the test. + base.Cleanup(); } #endregion @@ -157,60 +131,12 @@ protected void RunCommandsFromFile(string commandFileName, CommandCallback comma commandFilePath = Path.Combine(commandFilePath, @"core\recorded\"); commandFilePath = Path.Combine(commandFilePath, commandFileName); - if (this.ViewModel != null) - { - var message = "Multiple DynamoViewModel instances detected!"; - throw new InvalidOperationException(message); - } - - var geometryFactoryPath = string.Empty; - //preloadGeometry = true; - if (preloadGeometry && (preloader == null)) - { - var assemblyPath = Assembly.GetExecutingAssembly().Location; - preloader = new Preloader(Path.GetDirectoryName(assemblyPath)); - preloader.Preload(); - - geometryFactoryPath = preloader.GeometryFactoryPath; - preloadGeometry = false; - } - - TestPathResolver pathResolver = null; - var preloadedLibraries = new List(); - GetLibrariesToPreload(preloadedLibraries); - - if (preloadedLibraries.Any()) - { - // Only when any library needs preloading will a path resolver be - // created, otherwise DynamoModel gets created without preloading - // any library. - // - pathResolver = new TestPathResolver(); - foreach (var preloadedLibrary in preloadedLibraries.Distinct()) - { - pathResolver.AddPreloadLibraryPath(preloadedLibrary); - } - } - - var model = DynamoModel.Start( - new DynamoModel.DefaultStartConfiguration() - { - StartInTestMode = true, - PathResolver = pathResolver, - GeometryFactoryPath = geometryFactoryPath, - ProcessMode = TaskProcessMode.Synchronous - }); - - // Create the DynamoViewModel to control the view - this.ViewModel = DynamoViewModel.Start( - new DynamoViewModel.StartConfiguration() - { - CommandFilePath = commandFilePath, - DynamoModel = model - }); + CommandFilePath = commandFilePath; ViewModel.HomeSpace.RunSettings.RunType = RunType.Automatic; + ViewModel.InitializeAutomationSettings(CommandFilePath); + // Load all custom nodes if there is any specified for this test. if (this.customNodesToBeLoaded != null) { @@ -228,6 +154,7 @@ protected void RunCommandsFromFile(string commandFileName, CommandCallback comma RegisterCommandCallback(commandCallback); // Create the view. + // dynamoView will be closed by the ViewModel's automationSettings object. dynamoView = new DynamoView(this.ViewModel); dynamoView.ShowDialog(); @@ -236,6 +163,12 @@ protected void RunCommandsFromFile(string commandFileName, CommandCallback comma Assert.IsNotNull(this.ViewModel.Model.CurrentWorkspace); workspace = this.ViewModel.Model.CurrentWorkspace; workspaceViewModel = this.ViewModel.CurrentSpaceViewModel; + + var automation = this.ViewModel.Automation; + if (automation != null) + { + automation.PlaybackStateChanged -= OnAutomationPlaybackStateChanged; + } } private void RegisterCommandCallback(CommandCallback commandCallback) @@ -1857,7 +1790,8 @@ public void UpdatingCbnShouldCauseDownstreamExecution() // a code block node causes downstream addition node to result in "null" // instead of retaining its value of "16". Modifying the code block node // in this case should not have caused such issue. - // + // + using (Disposable.Create(() => { DynamoModel.IsTestMode = true; })) RunCommandsFromFile("UpdatingCbnShouldCauseDownstreamExecution.xml", (commandTag) => { switch (commandTag) @@ -2450,24 +2384,29 @@ public void TestCBNWithNodeToCode() // Run playback is recorded in command file RunCommandsFromFile("TestCBNOperationWithoutNodeToCode.xml"); AssertValue("c_089fbe21a34547759592b683550558dd", 8); + } - // Reset current test case - Exit(); - Setup(); - + [Test, Apartment(ApartmentState.STA)] + [Category("RegressionTests")] + public void TestCBNWithNodeToCode_2() + { + Assert.Inconclusive("Node To Code feature has been removed"); // Run playback is recorded in command file RunCommandsFromFile("TestCBNOperationWithNodeToCode.xml"); AssertValue("c_089fbe21a34547759592b683550558dd", 8); + } - // Reset current test case - Exit(); - Setup(); - + [Test, Apartment(ApartmentState.STA)] + [Category("RegressionTests")] + public void TestCBNWithNodeToCode_3() + { + Assert.Inconclusive("Node To Code feature has been removed"); // Run playback is recorded in command file RunCommandsFromFile("TestCBNOperationWithNodeToCodeUndo.xml"); AssertValue("c_089fbe21a34547759592b683550558dd", 8); } + [Test, Apartment(ApartmentState.STA)] [Category("RegressionTests")] public void Defect_MAGN_902() @@ -2555,13 +2494,15 @@ public void DS_FunctionRedef01() AssertValue("e_babc481696e6495c84897a650d1bfb25", 1); AssertValue("p_d4d53e201514434983e17cb5c533a3e0", 0); - - Exit(); - Setup(); - + } + + [Test] + [Category("RegressionTests")] + public void DS_FunctionRedef01_2() + { // redefine function - test if the CBN reexecuted RunCommandsFromFile("Function_redef01a.xml"); - + AssertValue("e_babc481696e6495c84897a650d1bfb25", 3); AssertValue("p_d4d53e201514434983e17cb5c533a3e0", 0); } @@ -2578,10 +2519,12 @@ public void DS_FunctionRedef02() AssertValue("d_0ce2353ce5d6445f83b72db7e3861ce0", 1); AssertValue("p_c9827e41855647f68e9d6c600a2e45ee", 0); + } - Exit(); - Setup(); - + [Test] + [Category("RegressionTests")] + public void DS_FunctionRedef02_2() + { // redefine function call - CBN with function definition is not expected to be executed RunCommandsFromFile("Function_redef02a.xml"); @@ -2601,9 +2544,12 @@ public void DS_FunctionRedef03() AssertValue("c_f34e01e225e446349eb8e815e8ee580d", 0); AssertValue("d_f34e01e225e446349eb8e815e8ee580d", 1); + } - Exit(); - Setup(); + [Test] + [Category("RegressionTests")] + public void DS_FunctionRedef03_2() + { // redefine function call - CBN with function definition is not expected to be executed RunCommandsFromFile("Function_redef03a.xml"); @@ -2623,13 +2569,15 @@ public void DS_FunctionRedef04() AssertValue("c_275d7a3d2b984f0e808d2aba03c6ff4f", 1); AssertValue("b_9b638b99d63145838b82662a60cdf6bc", 0); - - Exit(); - Setup(); - + } + + [Test] + [Category("RegressionTests")] + public void DS_FunctionRedef04_2() + { // redefine function call - change type of argument RunCommandsFromFile("Function_redef04a.xml"); - + AssertValue("c_275d7a3d2b984f0e808d2aba03c6ff4f", new object[] { 1, 2, 3 }); AssertValue("b_9b638b99d63145838b82662a60cdf6bc", 0); } @@ -3721,6 +3669,7 @@ public void Defect_MAGN_3212() [Category("RegressionTests"), Category("Failure")] public void TestCancelExecution() { + using (Disposable.Create(() => { DynamoModel.IsTestMode = true; })) RunCommandsFromFile("TestCancelExecutionFunctionCall.xml", (commandTag) => { // We need to run asynchronously for this test case as we need to @@ -3742,15 +3691,14 @@ public void TestCancelExecution() { AssertNullValues(); } - }); - } [Test, Apartment(ApartmentState.STA)] [Category("Failure")] public void TestCancelExecutionWhileLoop() { + using (Disposable.Create(() => { DynamoModel.IsTestMode = true; })) RunCommandsFromFile("TestCancelExecutionWhileLoop.xml", (commandTag) => { // We need to run asynchronously for this test case as we need to diff --git a/test/DynamoCoreWpfTests/Utility/DispatcherUtil.cs b/test/DynamoCoreWpfTests/Utility/DispatcherUtil.cs index 18812562d00..3ded9cbe004 100644 --- a/test/DynamoCoreWpfTests/Utility/DispatcherUtil.cs +++ b/test/DynamoCoreWpfTests/Utility/DispatcherUtil.cs @@ -17,7 +17,9 @@ public static class DispatcherUtil public static void DoEvents() { var frame = new DispatcherFrame(); - Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, + + // Invoke with the lowest priority possible so that other tasks (with higher priority) can get a chance to finish. + Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.SystemIdle, new DispatcherOperationCallback(ExitFrame), frame); Dispatcher.PushFrame(frame); } diff --git a/test/DynamoCoreWpfTests/ViewExtensions/NodeManipulatorExtensionTests.cs b/test/DynamoCoreWpfTests/ViewExtensions/NodeManipulatorExtensionTests.cs index 2462208e45c..22a1499250e 100644 --- a/test/DynamoCoreWpfTests/ViewExtensions/NodeManipulatorExtensionTests.cs +++ b/test/DynamoCoreWpfTests/ViewExtensions/NodeManipulatorExtensionTests.cs @@ -1,4 +1,4 @@ -using Dynamo.Graph.Nodes; +using Dynamo.Graph.Nodes; using Dynamo.Graph.Nodes.ZeroTouch; using Dynamo.Manipulation; using Dynamo.Models; @@ -27,6 +27,15 @@ protected override IEnumerable GetGizmos(bool createOrUpdate) } public class NodeManipulatorExtensionTests : DynamoTestUIBase { + [SetUp] + public virtual void Start() + { + // Forcing the dispatcher to execute all of its tasks within these tests causes crashes in Helix. + // For now just skip it. + SkipDispatcherFlush = true; + base.Start(); + } + protected override void GetLibrariesToPreload(List libraries) { libraries.Add("ProtoGeometry.dll"); diff --git a/test/Engine/FFITarget/TestObjects.cs b/test/Engine/FFITarget/TestObjects.cs index 6a082a330e6..0cfab398914 100644 --- a/test/Engine/FFITarget/TestObjects.cs +++ b/test/Engine/FFITarget/TestObjects.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Autodesk.DesignScript.Runtime; diff --git a/test/Libraries/PackageManagerTests/PackageLoaderCustomTest.cs b/test/Libraries/PackageManagerTests/PackageLoaderCustomTest.cs index bdd9f5c5821..a00c7125895 100644 --- a/test/Libraries/PackageManagerTests/PackageLoaderCustomTest.cs +++ b/test/Libraries/PackageManagerTests/PackageLoaderCustomTest.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using Dynamo.Configuration; @@ -11,6 +11,8 @@ class PackageLoaderCustomTest : DynamoModelTestBase [SetUp] public override void Setup() { + // setup temp folder fro the new settings file. + // temp folder will be regenerated when calling base.Setup() SetupDirectories(); var settings = new PreferenceSettings(); @@ -29,9 +31,9 @@ public override void Setup() var settingFilePath = Path.Combine(TempFolder, "DynamoSettings.xml"); settings.Save(settingFilePath); - var settingsLoadedFromFile = PreferenceSettings.Load(settingFilePath); + this.dynamoSettings = PreferenceSettings.Load(settingFilePath); - StartDynamo(settingsLoadedFromFile); + base.Setup(); } [Test]