diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/SynchronizedSingleSessionVSTestAndTestAnywhereAdapter.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/SynchronizedSingleSessionVSTestAndTestAnywhereAdapter.cs
index 0789698909..e2d903229e 100644
--- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/SynchronizedSingleSessionVSTestAndTestAnywhereAdapter.cs
+++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/SynchronizedSingleSessionVSTestAndTestAnywhereAdapter.cs
@@ -150,7 +150,7 @@ protected sealed override Task ExecuteRequestAsync(TestExecutionRequest request,
=> ExecuteRequestWithRequestCountGuardAsync(async () =>
{
#pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file
- string[] testAssemblyPaths = [.. _getTestAssemblies().Select(x => x.Location)];
+ string[] testAssemblyPaths = [.. _getTestAssemblies().Select(GetAssemblyPath)];
#pragma warning restore IL3000 // Avoid accessing Assembly file path when publishing as a single file
switch (request)
{
@@ -179,6 +179,33 @@ public void Dispose()
GC.SuppressFinalize(this);
}
+ ///
+ /// Gets the path of an assembly, falling back to the assembly name when
+ /// returns an empty string (e.g. on Android CoreCLR
+ /// where assemblies are memory-mapped).
+ ///
+#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
+ if (!string.IsNullOrEmpty(location))
+ {
+ return location;
+ }
+
+ // On platforms like Android CoreCLR, assemblies may be memory-mapped and
+ // Assembly.Location returns an empty string. Use the assembly name as a
+ // synthetic path since the downstream code (on .NET Core) loads assemblies
+ // by name via Assembly.Load, not by file path.
+ string name = assembly.GetName().Name
+ ?? throw new InvalidOperationException($"Cannot determine the name of assembly '{assembly}'.");
+
+ return name + ".dll";
+ }
+#pragma warning restore IL3000 // Avoid accessing Assembly file path when publishing as a single file
+
private async Task ExecuteRequestWithRequestCountGuardAsync(Func asyncFunc)
{
_incomingRequestCounter.AddCount();
diff --git a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/SynchronizedSingleSessionVSTestBridgedTestFrameworkTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/SynchronizedSingleSessionVSTestBridgedTestFrameworkTests.cs
new file mode 100644
index 0000000000..ec336f40d8
--- /dev/null
+++ b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/SynchronizedSingleSessionVSTestBridgedTestFrameworkTests.cs
@@ -0,0 +1,105 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Reflection;
+using System.Reflection.Emit;
+
+using Moq;
+
+namespace Microsoft.Testing.Extensions.VSTestBridge.UnitTests;
+
+[TestClass]
+public sealed class SynchronizedSingleSessionVSTestBridgedTestFrameworkTests
+{
+#if NETCOREAPP
+ [TestMethod]
+ public void GetAssemblyPath_WhenLocationIsNonEmpty_ReturnsLocation()
+ {
+ // Arrange - Assembly.Location is virtual on .NET Core, so Moq can mock it
+ var assembly = new Mock();
+ assembly.Setup(a => a.Location).Returns(@"C:\path\to\MyTests.dll");
+
+ // Act
+ string result = SynchronizedSingleSessionVSTestBridgedTestFramework.GetAssemblyPath(assembly.Object);
+
+ // Assert
+ Assert.AreEqual(@"C:\path\to\MyTests.dll", result);
+ }
+
+ [TestMethod]
+ public void GetAssemblyPath_WhenLocationIsEmpty_ReturnsSyntheticPathFromAssemblyName()
+ {
+ // Arrange - simulate Android CoreCLR where Assembly.Location returns ""
+ var assembly = new Mock();
+ assembly.Setup(a => a.Location).Returns(string.Empty);
+ assembly.Setup(a => a.GetName()).Returns(new AssemblyName("MyTests"));
+
+ // Act
+ string result = SynchronizedSingleSessionVSTestBridgedTestFramework.GetAssemblyPath(assembly.Object);
+
+ // Assert
+ Assert.AreEqual("MyTests.dll", result);
+ }
+
+ [TestMethod]
+ public void GetAssemblyPath_WhenLocationIsNull_ReturnsSyntheticPathFromAssemblyName()
+ {
+ // Arrange
+ var assembly = new Mock();
+ assembly.Setup(a => a.Location).Returns((string)null!);
+ assembly.Setup(a => a.GetName()).Returns(new AssemblyName("MyTests"));
+
+ // Act
+ string result = SynchronizedSingleSessionVSTestBridgedTestFramework.GetAssemblyPath(assembly.Object);
+
+ // Assert
+ Assert.AreEqual("MyTests.dll", result);
+ }
+
+ [TestMethod]
+ public void GetAssemblyPath_WhenLocationIsEmpty_AndAssemblyNameIsNull_Throws()
+ {
+ // Arrange
+ var assembly = new Mock();
+ assembly.Setup(a => a.Location).Returns(string.Empty);
+ assembly.Setup(a => a.GetName()).Returns(new AssemblyName());
+
+ // Act & Assert
+ Assert.ThrowsExactly(
+ () => SynchronizedSingleSessionVSTestBridgedTestFramework.GetAssemblyPath(assembly.Object));
+ }
+
+ [TestMethod]
+ public void GetAssemblyPath_WithDynamicInMemoryAssembly_ReturnsSyntheticPath()
+ {
+ // Arrange - create a real in-memory assembly that has empty Location
+ var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(
+ new AssemblyName("InMemoryTestAssembly"),
+ AssemblyBuilderAccess.Run);
+
+ // Verify our assumption: dynamic assemblies have empty Location
+ Assert.AreEqual(string.Empty, assemblyBuilder.Location);
+
+ // Act
+ string result = SynchronizedSingleSessionVSTestBridgedTestFramework.GetAssemblyPath(assemblyBuilder);
+
+ // Assert
+ Assert.AreEqual("InMemoryTestAssembly.dll", result);
+ }
+#endif
+
+ [TestMethod]
+ public void GetAssemblyPath_WithRealAssembly_ReturnsActualLocation()
+ {
+ // Arrange - use the currently executing assembly which has a real file-backed location
+ Assembly assembly = typeof(SynchronizedSingleSessionVSTestBridgedTestFrameworkTests).Assembly;
+
+ // Act
+ string result = SynchronizedSingleSessionVSTestBridgedTestFramework.GetAssemblyPath(assembly);
+
+ // Assert - should return the real path ending with .dll or .exe
+ Assert.AreEqual(assembly.Location, result);
+ Assert.IsTrue(
+ result.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || result.EndsWith(".exe", StringComparison.OrdinalIgnoreCase));
+ }
+}