Skip to content

Commit

Permalink
Stabilize tests pt2 (#15779)
Browse files Browse the repository at this point in the history
  • Loading branch information
pinzart90 authored Jan 30, 2025
1 parent 5ef1f7b commit 9cdd9f3
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 183 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ async void InitializeAsync()
//This indicates in which location will be created the WebView2 cache folder
documentationBrowser.CreationProperties = new CoreWebView2CreationProperties()
{
UserDataFolder = WebBrowserUserDataFolder
UserDataFolder = DynamoModel.IsTestMode ? TestUtilities.UserDataFolderDuringTests(nameof(DocumentationBrowserView)) : WebBrowserUserDataFolder
};
}

Expand Down
5 changes: 3 additions & 2 deletions src/DynamoCoreWpf/Utilities/ResourceUtilities.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using Dynamo.Logging;
using Dynamo.Models;
using Dynamo.Wpf.Properties;
using Dynamo.Wpf.UI.GuidedTour;
using Dynamo.Wpf.Utilities;
using DynamoUtilities;
using Microsoft.Web.WebView2.Wpf;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -598,7 +599,7 @@ internal static async Task<object> ExecuteJSFunction(UIElement MainWindow, objec
//This indicates in which location will be created the WebView2 cache folder
webBrowserComponent.CreationProperties = new CoreWebView2CreationProperties()
{
UserDataFolder = userDataFolder
UserDataFolder = DynamoModel.IsTestMode ? TestUtilities.UserDataFolderDuringTests(nameof(ResourceUtilities)) : userDataFolder
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ private async void UserControl_Loaded(object sender, System.Windows.RoutedEventA

dynWebView.CreationProperties = new CoreWebView2CreationProperties
{
UserDataFolder = webBrowserUserDataFolder.FullName
UserDataFolder = DynamoModel.IsTestMode ? TestUtilities.UserDataFolderDuringTests(nameof(HomePage)) : webBrowserUserDataFolder.FullName
};

//ContentRendered ensures that the webview2 component is visible.
Expand Down
2 changes: 1 addition & 1 deletion src/DynamoCoreWpf/Views/SplashScreen/SplashScreen.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ protected override async void OnContentRendered(EventArgs e)

webView.CreationProperties = new CoreWebView2CreationProperties
{
UserDataFolder = webBrowserUserDataFolder.FullName
UserDataFolder = DynamoModel.IsTestMode ? TestUtilities.UserDataFolderDuringTests(nameof(SplashScreen)) : webBrowserUserDataFolder.FullName
};

//ContentRendered ensures that the webview2 component is visible.
Expand Down
9 changes: 9 additions & 0 deletions src/DynamoUtilities/TestUtilities.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

Expand All @@ -11,5 +14,11 @@ internal static class TestUtilities
{
// Simple string that we can store in DynamoWebView2 instances so that we can track them down more easily
internal static string WebView2Tag;

internal static string UserDataFolderDuringTests(string appName)
{
var directory = new DirectoryInfo(Assembly.GetExecutingAssembly().Location);
return Path.Combine(directory.Parent.Parent.Parent.FullName, "test", $"webview2_{Environment.ProcessId}_{appName}_appdata");
}
}
}
2 changes: 1 addition & 1 deletion src/LibraryViewExtensionWebView2/LibraryViewController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ async void InitializeAsync()
//This indicates in which location will be created the WebView2 cache folder
this.browser.CreationProperties = new CoreWebView2CreationProperties()
{
UserDataFolder = WebBrowserUserDataFolder
UserDataFolder = DynamoModel.IsTestMode ? TestUtilities.UserDataFolderDuringTests(nameof(LibraryViewController)) : WebBrowserUserDataFolder
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/Notifications/NotificationCenterController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ private async void InitializeBrowserAsync(object sender, RoutedEventArgs e)
//This indicates in which location will be created the WebView2 cache folder
notificationUIPopup.webView.CreationProperties = new CoreWebView2CreationProperties()
{
UserDataFolder = webBrowserUserDataFolder.FullName
UserDataFolder = DynamoModel.IsTestMode ? TestUtilities.UserDataFolderDuringTests(nameof(NotificationCenterController)) : webBrowserUserDataFolder.FullName
};
}
notificationUIPopup.webView.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted;
Expand Down
54 changes: 40 additions & 14 deletions test/DynamoCoreTests/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -602,9 +602,10 @@ public override bool Equals(object obj)
[TestFixture, Category("Serialization")]
public class SerializationTests : DynamoModelTestBase
{
public static string jsonNonGuidFolderName = "json_nonGuidIds";
public static string jsonFolderName = "json";
public static string jsonFolderNameDifferentCulture = "json_differentCulture";
private static Dictionary<string, string> testFilesCache = [];
public static string jsonNonGuidFolderName = $"json_nonGuidIds_{Environment.ProcessId}";
public static string jsonFolderName = $"json_{Environment.ProcessId}";
public static string jsonFolderNameDifferentCulture = $"json_differentCulture_{Environment.ProcessId}";
private const int MAXNUM_SERIALIZATIONTESTS_TOEXECUTE = 300;

// Filter out dyns that change during testing
Expand Down Expand Up @@ -660,6 +661,7 @@ public void FixtureSetup()
Console.WriteLine(e.Message);
}
}
CacheTestFiles();
}

[OneTimeTearDown]
Expand Down Expand Up @@ -938,23 +940,41 @@ public void AllTypesSerialize()
serializationTestUtils.SaveWorkspaceComparisonData);
}

public static object[] FindWorkspaces()
private void CacheTestFiles()
{
testFilesCache.Clear();
var di = new DirectoryInfo(TestDirectory);
var fis = di.GetFiles("*.dyn", SearchOption.AllDirectories);
return fis.Where(fi => !filterOutFromSerializationTests.Contains(fi.Name)).Select(fi => fi.FullName).Take(MAXNUM_SERIALIZATIONTESTS_TOEXECUTE).ToArray();
var testFiles = fis.Where(fi => !filterOutFromSerializationTests.Contains(fi.Name)).Select(fi => fi.FullName).Take(MAXNUM_SERIALIZATIONTESTS_TOEXECUTE).ToArray();

foreach (var testFile in testFiles) {
testFilesCache.Add(Guid.NewGuid().ToString(), testFile);
}
}

internal static string GetTestFileNameFromGuid(string guid)
{
return testFilesCache.GetValueOrDefault(guid);
}

public static object[] FindWorkspaces()
{
return [.. testFilesCache.Keys];
}

/// <summary>
/// This parameterized test finds all .dyn files in directories within
/// the test directory, opens them and executes, then converts them to
/// json and executes again, comparing the values from the two runs.
/// </summary>
/// <param name="filePath">The path to a .dyn file. This parameter is supplied
/// by the test framework.</param>
/// <param name="fileId">A random guid assigned to a .dyn file. This parameter is supplied
/// by the test framework. You can get the file path by calling GetTestFileNameFromGuid(fileId).</param>
[Test, TestCaseSource(nameof(FindWorkspaces)), Category("JsonTestExclude")]
public void SerializationTest(string filePath)
public void SerializationTest(string fileId)
{
string filePath = GetTestFileNameFromGuid(fileId);
Console.WriteLine($"Running test {TestContext.CurrentContext.Test.ClassName}.{TestContext.CurrentContext.Test.MethodName} with file {filePath}");

modelsGuidToIdMap.Clear();
DoWorkspaceOpenAndCompare(filePath, jsonFolderName, ConvertCurrentWorkspaceToJsonAndSave,
serializationTestUtils.CompareWorkspaceModels,
Expand All @@ -967,11 +987,14 @@ public void SerializationTest(string filePath)
/// json and executes again, comparing the values from the two runs
/// while being in a different culture.
/// </summary>
/// <param name="filePath">The path to a .dyn file. This parameter is supplied
/// by the test framework.</param>
/// <param name="fileId">A random guid assigned to a .dyn file. This parameter is supplied
/// by the test framework. You can get the file path by calling GetTestFileNameFromGuid(fileId).</param>
[Test, TestCaseSource(nameof(FindWorkspaces)), Category("JsonTestExclude")]
public void SerializationInDifferentCultureTest(string filePath)
public void SerializationInDifferentCultureTest(string fileId)
{
string filePath = GetTestFileNameFromGuid(fileId);
Console.WriteLine($"Running test {TestContext.CurrentContext.Test.ClassName}.{TestContext.CurrentContext.Test.MethodName} with file {filePath}");

var frCulture = CultureInfo.CreateSpecificCulture("fr-FR");

// Save current culture - usually "en-US"
Expand All @@ -998,11 +1021,14 @@ public void SerializationInDifferentCultureTest(string filePath)
/// This set of tests has slightly modified json where the id properties
/// are altered when serialized to test deserialization of non-guid ids.
/// </summary>
/// <param name="filePath">The path to a .dyn file. This parameter is supplied
/// by the test framework.</param>
/// <param name="fileId">A random guid assigned to a .dyn file. This parameter is supplied
/// by the test framework. You can get the file path by calling GetTestFileNameFromGuid(fileId).</param>
[Test, TestCaseSource(nameof(FindWorkspaces)), Category("JsonTestExclude")]
public void SerializationNonGuidIdsTest(string filePath)
public void SerializationNonGuidIdsTest(string fileId)
{
string filePath = GetTestFileNameFromGuid(fileId);
Console.WriteLine($"Running test {TestContext.CurrentContext.Test.ClassName}.{TestContext.CurrentContext.Test.MethodName} with file {filePath}");

modelsGuidToIdMap.Clear();
DoWorkspaceOpenAndCompare(filePath, jsonNonGuidFolderName,
ConvertCurrentWorkspaceToNonGuidJsonAndSave,
Expand Down
146 changes: 89 additions & 57 deletions test/DynamoCoreWpfTests/DynamoTestUIBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,82 @@

namespace DynamoCoreWpfTests
{
internal class TestDiagnostics
{
internal int DispatcherOpsCounter = 0;
// Use this flag to skip trying to execute all the dispatched operations during the test lifetime.
// This flag should only be used very sparingly
internal bool SkipDispatcherFlush = false;

private void Hooks_OperationPosted(object sender, DispatcherHookEventArgs e)
{
e.Operation.Task.ContinueWith((t) => {
Interlocked.Decrement(ref DispatcherOpsCounter);
}, TaskScheduler.Default);
Interlocked.Increment(ref DispatcherOpsCounter);
}

private void PrettyPrint(object obj)
{
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();

Console.WriteLine("{");
foreach (var property in properties)
{
try
{
Console.WriteLine($" {property.Name}: {property.GetValue(obj)}");
}
catch (Exception e)
{
Console.WriteLine($" {property.Name}: {e.Message}");
}
}
Console.WriteLine("}");
}

internal void SetupStartupDiagnostics()
{
System.Console.WriteLine($"PID {Process.GetCurrentProcess().Id} Start test: {TestContext.CurrentContext.Test.Name}");
TestUtilities.WebView2Tag = TestContext.CurrentContext.Test.Name;

SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext());

Dispatcher.CurrentDispatcher.Hooks.OperationPosted += Hooks_OperationPosted;
}

internal void SetupBeforeCleanupDiagnostics()
{
Dispatcher.CurrentDispatcher.Hooks.OperationPosted -= Hooks_OperationPosted;
if (!SkipDispatcherFlush)
{
DispatcherUtil.DoEventsLoop(() => DispatcherOpsCounter == 0);
}
}

internal void SetupAfterCleanupDiagnostics()
{
TestUtilities.WebView2Tag = string.Empty;
using (var currentProc = Process.GetCurrentProcess())
{
int id = currentProc.Id;
var name = TestContext.CurrentContext.Test.Name;
System.Console.WriteLine($"PID {id} Finished test: {name} with DispatcherOpsCounter = {DispatcherOpsCounter}");
System.Console.WriteLine($"PID {id} Finished test: {name} with WorkingSet = {currentProc.WorkingSet64}");
System.Console.WriteLine($"PID {id} Finished test: {name} with PrivateBytes = {currentProc.PrivateMemorySize64}");
System.Console.Write($"PID {id} Finished test: {name} with GC Memory Info: ");
PrettyPrint(GC.GetGCMemoryInfo());
}
}

internal void SetupCleanupDiagnostics()
{
SetupBeforeCleanupDiagnostics();
SetupAfterCleanupDiagnostics();
}
}

public class DynamoTestUIBase
{
protected Preloader preloader;
Expand All @@ -34,8 +110,17 @@ public class DynamoTestUIBase

// 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 bool SkipDispatcherFlush {
get => testDiagnostics.SkipDispatcherFlush;
set => testDiagnostics.SkipDispatcherFlush = value;
}

[Obsolete("This property will be deprecated as it is for internal use only.")]
protected int DispatcherOpsCounter {
get => testDiagnostics.DispatcherOpsCounter;
}

private TestDiagnostics testDiagnostics = new();

protected string ExecutingDirectory
{
Expand All @@ -47,9 +132,7 @@ protected string ExecutingDirectory
[SetUp]
public virtual void Start()
{
System.Console.WriteLine($"PID {Process.GetCurrentProcess().Id} Start test: {TestContext.CurrentContext.Test.Name}");
TestUtilities.WebView2Tag = TestContext.CurrentContext.Test.Name;

testDiagnostics.SetupStartupDiagnostics();
var assemblyPath = Assembly.GetExecutingAssembly().Location;
preloader = new Preloader(Path.GetDirectoryName(assemblyPath));
preloader.Preload();
Expand Down Expand Up @@ -87,11 +170,6 @@ public virtual void Start()
//create the view
View = new DynamoView(ViewModel);
View.Show();

SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext());

Dispatcher.CurrentDispatcher.Hooks.OperationPosted += Hooks_OperationPosted;

}

protected static void RaiseLoadedEvent(FrameworkElement element)
Expand All @@ -104,14 +182,6 @@ 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);
}

/// <summary>
/// Derived test classes can override this method to provide different configurations.
/// </summary>
Expand All @@ -130,13 +200,6 @@ protected virtual DynamoModel.IStartConfiguration CreateStartConfiguration(IPath
[TearDown]
public void Exit()
{

Dispatcher.CurrentDispatcher.Hooks.OperationPosted -= Hooks_OperationPosted;
if (!SkipDispatcherFlush)
{
DispatcherUtil.DoEventsLoop(() => DispatcherOpsCounter == 0);
}

//Ensure that we leave the workspace marked as
//not having changes.
ViewModel.HomeSpace.HasUnsavedChanges = false;
Expand Down Expand Up @@ -166,38 +229,7 @@ public void Exit()
{
Console.WriteLine(ex.StackTrace);
}

TestUtilities.WebView2Tag = string.Empty;
using (var currentProc = Process.GetCurrentProcess())
{
int id = currentProc.Id;
var name = TestContext.CurrentContext.Test.Name;
System.Console.WriteLine($"PID {id} Finished test: {name} with DispatcherOpsCounter = {DispatcherOpsCounter}");
System.Console.WriteLine($"PID {id} Finished test: {name} with WorkingSet = {currentProc.WorkingSet64}");
System.Console.WriteLine($"PID {id} Finished test: {name} with PrivateBytes = {currentProc.PrivateMemorySize64}");
System.Console.Write($"PID {id} Finished test: {name} with GC Memory Info: ");
PrettyPrint(GC.GetGCMemoryInfo());
}
}

private static void PrettyPrint(object obj)
{
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();

Console.WriteLine("{");
foreach (var property in properties)
{
try
{
Console.WriteLine($" {property.Name}: {property.GetValue(obj)}");
}
catch (Exception e)
{
Console.WriteLine($" {property.Name}: {e.Message}");
}
}
Console.WriteLine("}");
testDiagnostics.SetupAfterCleanupDiagnostics();
}

protected virtual void GetLibrariesToPreload(List<string> libraries)
Expand Down
Loading

0 comments on commit 9cdd9f3

Please sign in to comment.