Handle empty Assembly.Location on Android CoreCLR#7772
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses a runtime-specific issue in the VSTest bridge where Assembly.Location can be an empty string (notably on Android CoreCLR with memory-mapped assemblies), which previously caused MSTest discovery/execution to fail due to source extension validation.
Changes:
- Updated VSTest bridge assembly source collection to use a helper that tolerates empty
Assembly.Location. - Added
GetAssemblyPath(Assembly)fallback logic to synthesize a “.dll” path from the assembly name whenLocationis empty. - Added unit tests covering non-empty, empty, null
Location, missing assembly name, dynamic in-memory assemblies, and real file-backed assemblies.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/Platform/Microsoft.Testing.Extensions.VSTestBridge/SynchronizedSingleSessionVSTestAndTestAnywhereAdapter.cs |
Uses GetAssemblyPath instead of Assembly.Location directly, and introduces the fallback helper. |
test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/SynchronizedSingleSessionVSTestBridgedTestFrameworkTests.cs |
Adds coverage for the new GetAssemblyPath behavior across key scenarios. |
91a6af0 to
82b4b0b
Compare
When Assembly.Location returns empty string (e.g. on Android CoreCLR where assemblies are memory-mapped), fall back to using the assembly name with a .dll extension as a synthetic path. This allows AreValidSources validation to pass and downstream Assembly.Load by name to find the already-loaded assembly. Fixes #7769 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
82b4b0b to
a831875
Compare
simonrozsival
left a comment
There was a problem hiding this comment.
This change will unblock us in dotnet/android
Evangelink
left a comment
There was a problem hiding this comment.
Looks good! The fix correctly solves the reported issue with minimal risk. The synthetic path approach works because on .NET Core, FileOperations.DoesFileExist always returns true and FileOperations.LoadAssembly resolves by assembly name (not file path), so the synthetic name.dll path is just a token to pass AreValidSources validation.
A few minor suggestions below — none are blocking.
Summary of suggestions:
- Remove the now-redundant
#pragma warning disable IL3000at the call site — the pragma has moved intoGetAssemblyPath, so the outer pragma is no longer needed. - Use method group
Select(GetAssemblyPath)instead ofSelect(x => GetAssemblyPath(x)). - Narrow the pragma scope inside
GetAssemblyPathto just theassembly.Locationline. - The hardcoded
.dllextension is fine for the Android CoreCLR scenario (test assemblies are always DLLs there), but a brief comment documenting this assumption would be helpful for future readers.
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (1)
src/Platform/Microsoft.Testing.Extensions.VSTestBridge/SynchronizedSingleSessionVSTestAndTestAnywhereAdapter.cs:192
GetAssemblyPathcurrently has nested IL3000 suppressions (one wrapping the whole method and another aroundassembly.Location). This is redundant and broadens the suppression scope more than needed. Consider keeping a single suppression scoped as narrowly as possible (e.g., only around theassembly.Locationaccess).
#pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file
internal static string GetAssemblyPath(Assembly assembly)
{
#pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file
string location = assembly.Location;
#pragma warning restore IL3000 // Avoid accessing Assembly file path when publishing as a single file
- Files reviewed: 2/2 changed files
- Comments generated: 2
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.<OnStart>b__2_0() INSTRUMENTATION_CODE: 0 Related: dotnet/android#11195, #7769 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Assembly.Locationis empty on Android CoreCLR (assemblies are memory-mapped), which makesAreValidSourcesthrowNotSupportedExceptionbecause empty string is neither.dllnor.exe.Fall back to
<AssemblyName>.dllwhenLocationis empty. Works on .NET Core becauseFileOperations.DoesFileExistalways returnstruethere, andFileOperations.LoadAssemblyloads by name, not by path — so the already-loaded assembly is found.Added tests for empty / null
Location, null name, a real dynamic in-memory assembly, and a file-backed one.Fixes #7769