From 2813a3eb23024de2d49d1a83350f056ceb2713d7 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 23 Apr 2026 15:49:36 -0500 Subject: [PATCH 1/2] Fix TestRunDirectories crash on Android with relative/empty paths Continuation of #7772. On .NET for Android, `Assembly.Location` returns a relative path like `"AndroidTest1.dll"` (MonoVM) or an empty string (CoreCLR). `Path.GetDirectoryName` returns `""` for these inputs, and then `Directory.CreateDirectory("")` throws `ArgumentException`. This would fail in Release mode on Android, or if the "fast deployment" feature is disabled. Guard against empty/relative paths by checking the result of `Path.GetDirectoryName` and falling back to the standard `RootDeploymentDirectory/Out` path. Full stack trace from AZDO build (https://dev.azure.com/dnceng-public/public/_build/results?buildId=1392743): INSTRUMENTATION_RESULT: error=System.ArgumentException: The value cannot be an empty string. (Parameter 'path') at System.ArgumentException.ThrowNullOrEmptyException(String argument, String paramName) at System.ArgumentException.ThrowIfNullOrEmpty(String argument, String paramName) at System.IO.Directory.CreateDirectory(String path) at Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities.FileUtility.CreateDirectoryIfNotExists(String directory) at Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities.DeploymentUtilityBase.CreateDeploymentDirectories(IRunContext runContext, String firstTestSource) at Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.TestDeployment.Deploy(IEnumerable`1 testCases, IRunContext runContext, IFrameworkHandle frameworkHandle, ITestSourceHandler testSourceHandler, TestRunCancellationToken cancellationToken) at Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.MSTestExecutor.RunTestsAsync(...) at Microsoft.VisualStudio.TestTools.UnitTesting.MSTestBridgedTestFramework.SynchronizedRunTestsAsync(...) at Microsoft.Testing.Extensions.VSTestBridge.SynchronizedSingleSessionVSTestBridgedTestFramework.ExecuteRequestAsync(...) at Microsoft.Testing.Platform.Builder.TestApplication.RunAsync() at DotNetNewAndroidTestMonoVM.TestInstrumentation.b__2_0() INSTRUMENTATION_CODE: 0 Related: dotnet/android#11195, #7769 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Deployment/TestRunDirectories.cs | 8 ++++++-- .../Deployment/TestRunDirectoriesTests.cs | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Deployment/TestRunDirectories.cs b/src/Adapter/MSTestAdapter.PlatformServices/Deployment/TestRunDirectories.cs index 22c6ba3714..2926d7f964 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Deployment/TestRunDirectories.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Deployment/TestRunDirectories.cs @@ -45,8 +45,12 @@ public TestRunDirectories(string rootDirectory, string? firstTestSource, bool is RootDeploymentDirectory = rootDirectory; InDirectory = Path.Combine(RootDeploymentDirectory, DeploymentInDirectorySuffix); - OutDirectory = isAppDomainCreationDisabled && firstTestSource is not null - ? Path.GetDirectoryName(firstTestSource)! + string? testSourceDirectory = isAppDomainCreationDisabled && !StringEx.IsNullOrEmpty(firstTestSource) + ? Path.GetDirectoryName(firstTestSource) + : null; + + OutDirectory = !StringEx.IsNullOrEmpty(testSourceDirectory) + ? testSourceDirectory! : Path.Combine(RootDeploymentDirectory, DeploymentOutDirectorySuffix); InMachineNameDirectory = Path.Combine(InDirectory, Environment.MachineName); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Deployment/TestRunDirectoriesTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Deployment/TestRunDirectoriesTests.cs index 1fbf566525..e72d3c06f0 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Deployment/TestRunDirectoriesTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Deployment/TestRunDirectoriesTests.cs @@ -21,4 +21,19 @@ public void OutDirectoryShouldReturnCorrectDirectory() public void InMachineNameDirectoryShouldReturnMachineSpecificDeploymentDirectory() => _testRunDirectories.InMachineNameDirectory.Should().Be(Path.Combine(@"C:\temp\In", Environment.MachineName)); + + public void OutDirectoryShouldFallBackWhenFirstTestSourceIsEmpty() + { + // Simulates Android CoreCLR where Assembly.Location returns "" (before synthetic path). + var directories = new TestRunDirectories(@"C:\temp", firstTestSource: string.Empty, isAppDomainCreationDisabled: true); + directories.OutDirectory.Should().Be(@"C:\temp\Out"); + } + + public void OutDirectoryShouldFallBackWhenFirstTestSourceIsRelativePath() + { + // Simulates Android MonoVM / CoreCLR after PR #7772 where Assembly.Location + // is a relative filename like "MyTests.dll" with no directory component. + var directories = new TestRunDirectories(@"C:\temp", firstTestSource: "MyTests.dll", isAppDomainCreationDisabled: true); + directories.OutDirectory.Should().Be(@"C:\temp\Out"); + } } From b1443efc1e5ac43be5e846dbaada77551bb82b48 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 23 Apr 2026 16:50:30 -0500 Subject: [PATCH 2/2] Fix where copilot used null forgiving `!` --- .../Deployment/TestRunDirectories.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Deployment/TestRunDirectories.cs b/src/Adapter/MSTestAdapter.PlatformServices/Deployment/TestRunDirectories.cs index 2926d7f964..96c37f0eae 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Deployment/TestRunDirectories.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Deployment/TestRunDirectories.cs @@ -49,9 +49,9 @@ public TestRunDirectories(string rootDirectory, string? firstTestSource, bool is ? Path.GetDirectoryName(firstTestSource) : null; - OutDirectory = !StringEx.IsNullOrEmpty(testSourceDirectory) - ? testSourceDirectory! - : Path.Combine(RootDeploymentDirectory, DeploymentOutDirectorySuffix); + OutDirectory = StringEx.IsNullOrEmpty(testSourceDirectory) + ? Path.Combine(RootDeploymentDirectory, DeploymentOutDirectorySuffix) + : testSourceDirectory; InMachineNameDirectory = Path.Combine(InDirectory, Environment.MachineName); }