Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,13 @@ public void WillConnect (UIScene scene, UISceneSession session, UISceneConnectio
var documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
var resultsPath = Path.Combine (documentsPath, "TestResults");

// Forward args from 'dotnet test', which sends the MTP protocol config
// (--server / --dotnet-test-pipe) via the command line.
// Environment.GetCommandLineArgs()[0] is the executable path, not an argument.
string [] cliArgs = Environment.GetCommandLineArgs ();
string [] mtpArgs = cliArgs.Length > 1 ? cliArgs [1..] : [];
var builder = await TestApplication.CreateBuilderAsync ([
.. mtpArgs,
"--results-directory", resultsPath,
"--report-trx"
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,13 @@ public override void DidFinishLaunching (NSNotification notification)
var documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
var resultsPath = Path.Combine (documentsPath, "TestResults");

// Forward args from 'dotnet test', which sends the MTP protocol config
// (--server / --dotnet-test-pipe) via the command line.
// Environment.GetCommandLineArgs()[0] is the executable path, not an argument.
string [] cliArgs = Environment.GetCommandLineArgs ();
string [] mtpArgs = cliArgs.Length > 1 ? cliArgs [1..] : [];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to drop the first argument, but I can't see any explanation/comment of why this is necessary?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment noting that Environment.GetCommandLineArgs()[0] is the executable path, not an argument.

var builder = await TestApplication.CreateBuilderAsync ([
.. mtpArgs,
"--results-directory", resultsPath,
"--report-trx"
]);
Expand Down
8 changes: 8 additions & 0 deletions dotnet/targets/Xamarin.Shared.Sdk.MSTest.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
<GenerateTestingPlatformEntryPoint Condition="'$(GenerateTestingPlatformEntryPoint)' == ''">false</GenerateTestingPlatformEntryPoint>
<!-- Suppress trimmer warnings from test framework dependencies (e.g. Microsoft.ApplicationInsights) -->
<SuppressTrimAnalysisWarnings Condition="'$(SuppressTrimAnalysisWarnings)' == ''">true</SuppressTrimAnalysisWarnings>
<!--
On macOS and Mac Catalyst 'dotnet run/test' launches the app via 'open' by default,
which goes through LaunchServices and does not propagate the parent process's
environment variables or reliably forward CLI arguments to the app. That breaks
the Microsoft.Testing.Platform (MTP) handshake that 'dotnet test' relies on.
Launching the app binary directly avoids the problem.
-->
<RunWithOpen Condition="'$(RunWithOpen)' == ''">false</RunWithOpen>
</PropertyGroup>
<ItemGroup>
<!-- Disable ANSI escape codes in MSTest output, as they appear as garbled text in simulator/device logs -->
Expand Down
40 changes: 20 additions & 20 deletions tests/dotnet/UnitTests/DotNetTestTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ public class DotNetTestTest : TestBaseClass {
[Test]
[TestCase (ApplePlatform.iOS, "iostest")]
[TestCase (ApplePlatform.TVOS, "tvostest")]
// macOS and Mac Catalyst don't use mlaunch (they use 'open' via Desktop.targets),
// so MTP support requires a different approach for these platforms.
// [TestCase (ApplePlatform.MacCatalyst, "maccatalysttest")]
// [TestCase (ApplePlatform.MacOSX, "macostest")]
[TestCase (ApplePlatform.MacCatalyst, "maccatalysttest")]
[TestCase (ApplePlatform.MacOSX, "macostest")]
public void DotNetTest (ApplePlatform platform, string template)
{
Configuration.IgnoreIfIgnoredPlatform (platform);
Expand All @@ -26,24 +24,21 @@ public void DotNetTest (ApplePlatform platform, string template)
DotNet.AssertNew (outputDir, template);
var proj = Path.Combine (outputDir, $"{template}.csproj");

// Boot a simulator so that ComputeRunArguments can find a device
var isDesktop = platform == ApplePlatform.MacOSX || platform == ApplePlatform.MacCatalyst;
var log = ConsoleLogger.Instance;
var simService = new SimulatorService (log);
var runtimeService = new RuntimeService (log);
var device = GetOrCreateDevice (platform, simService, runtimeService);
SimulatorDeviceInfo? device = null;

if (!device.IsBooted)
Assert.That (simService.Boot (device.Udid), Is.True, $"Failed to boot simulator {device.Udid}.");
if (!isDesktop) {
// Boot a simulator so that ComputeRunArguments can find a device
var runtimeService = new RuntimeService (log);
device = GetOrCreateDevice (platform, simService, runtimeService);

try {
// dotnet test internally calls ComputeRunArguments via MSBuild API without
// forwarding /p: properties, so we must set them in the project file directly.
var csproj = File.ReadAllText (proj);
csproj = csproj.Replace (
"</PropertyGroup>",
$" <UseFloatingTargetPlatformVersion>true</UseFloatingTargetPlatformVersion>\n <Device>{device.Udid}</Device>\n </PropertyGroup>");
File.WriteAllText (proj, csproj);
if (!device.IsBooted)
Assert.That (simService.Boot (device.Udid), Is.True, $"Failed to boot simulator {device.Udid}.");
}

try {
// Replace generated tests with a single passing test
var testFile = Path.Combine (outputDir, "Test1.cs");
File.WriteAllText (testFile, $@"namespace {template};
Expand All @@ -59,21 +54,26 @@ public void TestMethod1 ()

// Run 'dotnet test' directly using Execution.RunAsync.
// dotnet test's MTP flow doesn't forward /p: properties to its internal
// ComputeRunArguments MSBuild API call, so properties must be in the csproj.
// ComputeRunArguments MSBuild API call, so pass properties as environment
// variables instead — MSBuild picks those up as global properties in the
// child process.
var env = new Dictionary<string, string?> ();
env ["MSBuildSDKsPath"] = null;
env ["MSBUILD_EXE_PATH"] = null;
env ["UseFloatingTargetPlatformVersion"] = "true";
if (device is not null)
env ["Device"] = device.Udid;
var binlog = Path.Combine (outputDir, "log-test.binlog");
var testArgs = new List<string> {
"test",
proj,
$"--device:{device.Udid}",
$"/bl:{binlog}"
};
var testResult = Execution.RunAsync (DotNet.Executable, testArgs, env, Console.Out, workingDirectory: outputDir, timeout: TimeSpan.FromMinutes (10)).Result;
Assert.That (testResult.ExitCode, Is.EqualTo (0), $"'dotnet test' failed with exit code {testResult.ExitCode}.\nBinlog: {binlog}\nOutput:\n{testResult.Output.MergedOutput}");
} finally {
simService.Shutdown (device.Udid);
if (device is not null)
simService.Shutdown (device.Udid);
}
}

Expand Down
Loading