From b95836ab511846a6f55ec06b1080d4b5e85101b0 Mon Sep 17 00:00:00 2001 From: Konstantin Novitsky Date: Wed, 15 Apr 2026 11:41:28 -0400 Subject: [PATCH] Add SafeMode option to test settings for Excel launch Introduce SafeMode property to ExcelFactAttribute, ExcelTestSettingsAttribute, ITestSettings, and ExcelTestSettings, allowing tests to specify launching Excel in safe mode (/safe). Update ExcelRunner and ExcelTestAssemblyRunner to support this option, and ensure SafeMode is serialized in ExcelTestCase. Improves test isolation and reliability by enabling safe mode for specific test cases. Documentation updated accordingly. Add support for running tests in Excel Safe Mode Introduce a SafeMode property to test settings, attributes, and runners, enabling Excel to be launched with the /safe switch for selected tests. Update process launching, test case serialization, and test discovery to support Safe Mode. Add a sample test using Safe Mode to improve test isolation and reliability. --- ExcelDna.Testing/Examples/InProcessTests.cs | 2 +- .../ExcelDna.Testing/ExcelFactAttribute.cs | 3 +++ .../ExcelDna.Testing/ExcelFactDiscoverer.cs | 3 ++- ExcelDna.Testing/ExcelDna.Testing/ExcelRunner.cs | 8 +++++++- .../ExcelDna.Testing/ExcelTestAssemblyRunner.cs | 11 +++++++---- ExcelDna.Testing/ExcelDna.Testing/ExcelTestCase.cs | 4 +++- .../ExcelDna.Testing/ExcelTestSettings.cs | 5 ++++- .../ExcelDna.Testing/ExcelTestSettingsAttribute.cs | 3 +++ ExcelDna.Testing/ExcelDna.Testing/ITestSettings.cs | 5 +++++ 9 files changed, 35 insertions(+), 9 deletions(-) diff --git a/ExcelDna.Testing/Examples/InProcessTests.cs b/ExcelDna.Testing/Examples/InProcessTests.cs index 19f9f05..da2fed0 100644 --- a/ExcelDna.Testing/Examples/InProcessTests.cs +++ b/ExcelDna.Testing/Examples/InProcessTests.cs @@ -63,7 +63,7 @@ public void AsyncFunctionTest() Assert.Equal("Completed", targetRange.Value); } - [ExcelFact(Workbook = "", AddIn = @"..\..\..\..\ExampleAddinNET6\bin\Debug\net6.0-windows\ExampleAddinNET6-AddIn")] + [ExcelFact(Workbook = "", AddIn = @"..\..\..\..\ExampleAddinNET6\bin\Debug\net6.0-windows\ExampleAddinNET6-AddIn", SafeMode = true)] public void FunctionTestNET6() { Range targetRange = (ExcelDna.Testing.Util.Workbook.Sheets[1] as Worksheet).Range["A1"]; diff --git a/ExcelDna.Testing/ExcelDna.Testing/ExcelFactAttribute.cs b/ExcelDna.Testing/ExcelDna.Testing/ExcelFactAttribute.cs index 9f79b7b..8dd8461 100644 --- a/ExcelDna.Testing/ExcelDna.Testing/ExcelFactAttribute.cs +++ b/ExcelDna.Testing/ExcelDna.Testing/ExcelFactAttribute.cs @@ -16,5 +16,8 @@ public class ExcelFactAttribute : FactAttribute, ITestSettings /// public string AddIn { get; set; } + + /// + public bool SafeMode { get; set; } } } diff --git a/ExcelDna.Testing/ExcelDna.Testing/ExcelFactDiscoverer.cs b/ExcelDna.Testing/ExcelDna.Testing/ExcelFactDiscoverer.cs index a7d24c9..52b34b2 100644 --- a/ExcelDna.Testing/ExcelDna.Testing/ExcelFactDiscoverer.cs +++ b/ExcelDna.Testing/ExcelDna.Testing/ExcelFactDiscoverer.cs @@ -24,7 +24,8 @@ private static ExcelTestSettings GetSettings(ITestMethod testMethod) return new ExcelTestSettings( GetSetting(testMethod, nameof(ExcelFactAttribute.OutOfProcess)), GetSetting(testMethod, nameof(ExcelFactAttribute.Workbook)), - GetSetting(testMethod, nameof(ExcelFactAttribute.AddIn))); + GetSetting(testMethod, nameof(ExcelFactAttribute.AddIn)), + GetSetting(testMethod, nameof(ExcelFactAttribute.SafeMode))); } private static T GetSetting(ITestMethod testMethod, string name) diff --git a/ExcelDna.Testing/ExcelDna.Testing/ExcelRunner.cs b/ExcelDna.Testing/ExcelDna.Testing/ExcelRunner.cs index 15508a5..73f02e5 100644 --- a/ExcelDna.Testing/ExcelDna.Testing/ExcelRunner.cs +++ b/ExcelDna.Testing/ExcelDna.Testing/ExcelRunner.cs @@ -13,13 +13,19 @@ public ExcelRunner() excelDetected = excelDetector.TryFindLatestExcel(out excelExePath) && excelDetector.TryFindExcelBitness(excelExePath, out bitness); } - public Process Start(string addinAssemblyPath, IEnumerable addins) + public Process Start(string addinAssemblyPath, IEnumerable addins, bool safeMode = false) { if (!excelDetected) throw new ApplicationException("Can't find an installed version of Excel."); string addinAssemblyDirectory = Path.GetDirectoryName(addinAssemblyPath); string arguments = ""; + + if (safeMode) + { + arguments += "/safe "; + } + foreach (string externalAddinRelativePath in addins) { arguments += Quote(GetXllPath(addinAssemblyDirectory, externalAddinRelativePath, bitness)) + " "; diff --git a/ExcelDna.Testing/ExcelDna.Testing/ExcelTestAssemblyRunner.cs b/ExcelDna.Testing/ExcelDna.Testing/ExcelTestAssemblyRunner.cs index 6cf44e0..744bf58 100644 --- a/ExcelDna.Testing/ExcelDna.Testing/ExcelTestAssemblyRunner.cs +++ b/ExcelDna.Testing/ExcelDna.Testing/ExcelTestAssemblyRunner.cs @@ -35,14 +35,17 @@ public ExcelTestAssemblyRunner(ITestAssembly testAssembly, IEnumerable RunTestCollectionsAsync(IMessageBus messageBus, CancellationTokenSource cancellationTokenSource) { IEnumerable localTestCases = TestCases.Except(TestCases.OfType()); - IEnumerable excelInProcessTestCases = TestCases.OfType().Where(i => !i.Settings.OutOfProcess); + IEnumerable excelInProcessTestCases = TestCases.OfType().Where(i => !i.Settings.OutOfProcess && !i.Settings.SafeMode); + IEnumerable excelInProcessSafeTestCases = TestCases.OfType().Where(i => !i.Settings.OutOfProcess && i.Settings.SafeMode); IEnumerable excelOutOfProcessTestCases = TestCases.OfType().Where(i => i.Settings.OutOfProcess); var result = await LocalRunTestCasesAsync(localTestCases, messageBus, cancellationTokenSource); if (excelOutOfProcessTestCases.Count() > 0) result.Aggregate(await COMRunTestCasesAsync(excelOutOfProcessTestCases, messageBus, cancellationTokenSource)); if (excelInProcessTestCases.Count() > 0) - result.Aggregate(await RemoteRunTestCasesAsync(excelInProcessTestCases, messageBus, cancellationTokenSource)); + result.Aggregate(await RemoteRunTestCasesAsync(excelInProcessTestCases, messageBus, cancellationTokenSource, false)); + if (excelInProcessSafeTestCases.Count() > 0) + result.Aggregate(await RemoteRunTestCasesAsync(excelInProcessSafeTestCases, messageBus, cancellationTokenSource, true)); CleanupReferences(); @@ -58,13 +61,13 @@ private async Task LocalRunTestCasesAsync(IEnumerable RemoteRunTestCasesAsync(IEnumerable testCases, IMessageBus messageBus, CancellationTokenSource cancellationTokenSource) + private async Task RemoteRunTestCasesAsync(IEnumerable testCases, IMessageBus messageBus, CancellationTokenSource cancellationTokenSource, bool safeMode) { RunSummary result = new RunSummary(); try { ExcelStartupEvent.Create(); - Process excelProcess = excelRunner.Start(testAssembly.Assembly.AssemblyPath, GetAddins(testCases)); + Process excelProcess = excelRunner.Start(testAssembly.Assembly.AssemblyPath, GetAddins(testCases), safeMode); if (!ExcelStartupEvent.Wait(30000)) throw new System.ApplicationException("Excel startup failed."); diff --git a/ExcelDna.Testing/ExcelDna.Testing/ExcelTestCase.cs b/ExcelDna.Testing/ExcelDna.Testing/ExcelTestCase.cs index 98b004d..3571a59 100644 --- a/ExcelDna.Testing/ExcelDna.Testing/ExcelTestCase.cs +++ b/ExcelDna.Testing/ExcelDna.Testing/ExcelTestCase.cs @@ -35,6 +35,7 @@ public override void Serialize(IXunitSerializationInfo info) info.AddValue(nameof(testSettings.OutOfProcess), testSettings.OutOfProcess); info.AddValue(nameof(testSettings.Workbook), testSettings.Workbook); info.AddValue(nameof(testSettings.AddIn), testSettings.AddIn); + info.AddValue(nameof(testSettings.SafeMode), testSettings.SafeMode); } public override void Deserialize(IXunitSerializationInfo info) @@ -43,7 +44,8 @@ public override void Deserialize(IXunitSerializationInfo info) testSettings = new ExcelTestSettings( info.GetValue(nameof(testSettings.OutOfProcess)), info.GetValue(nameof(testSettings.Workbook)), - info.GetValue(nameof(testSettings.AddIn))); + info.GetValue(nameof(testSettings.AddIn)), + info.GetValue(nameof(testSettings.SafeMode))); } public string SerializeToString() diff --git a/ExcelDna.Testing/ExcelDna.Testing/ExcelTestSettings.cs b/ExcelDna.Testing/ExcelDna.Testing/ExcelTestSettings.cs index 44f66b3..adbd94d 100644 --- a/ExcelDna.Testing/ExcelDna.Testing/ExcelTestSettings.cs +++ b/ExcelDna.Testing/ExcelDna.Testing/ExcelTestSettings.cs @@ -2,11 +2,12 @@ { public class ExcelTestSettings : ITestSettings { - public ExcelTestSettings(bool outOfProcess, string workbook, string addin) + public ExcelTestSettings(bool outOfProcess, string workbook, string addin, bool safeMode = false) { OutOfProcess = outOfProcess; Workbook = workbook; AddIn = addin; + SafeMode = safeMode; } public bool OutOfProcess { get; set; } @@ -14,5 +15,7 @@ public ExcelTestSettings(bool outOfProcess, string workbook, string addin) public string Workbook { get; set; } public string AddIn { get; set; } + + public bool SafeMode { get; set; } } } diff --git a/ExcelDna.Testing/ExcelDna.Testing/ExcelTestSettingsAttribute.cs b/ExcelDna.Testing/ExcelDna.Testing/ExcelTestSettingsAttribute.cs index d93a282..27387eb 100644 --- a/ExcelDna.Testing/ExcelDna.Testing/ExcelTestSettingsAttribute.cs +++ b/ExcelDna.Testing/ExcelDna.Testing/ExcelTestSettingsAttribute.cs @@ -14,5 +14,8 @@ public class ExcelTestSettingsAttribute : Attribute, ITestSettings /// public string AddIn { get; set; } + + /// + public bool SafeMode { get; set; } } } diff --git a/ExcelDna.Testing/ExcelDna.Testing/ITestSettings.cs b/ExcelDna.Testing/ExcelDna.Testing/ITestSettings.cs index 54ba645..fd82ef3 100644 --- a/ExcelDna.Testing/ExcelDna.Testing/ITestSettings.cs +++ b/ExcelDna.Testing/ExcelDna.Testing/ITestSettings.cs @@ -16,5 +16,10 @@ internal interface ITestSettings /// A relative path to .xll add-in to load. Without bitness and .xll extension. /// string AddIn { get; set; } + + /// + /// Whether to start Excel in safe mode using the /safe command-line switch. + /// + bool SafeMode { get; set; } } }