Skip to content
2 changes: 2 additions & 0 deletions build-tools/create-packs/Microsoft.Android.Sdk.proj
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ core workload SDK packs imported by WorkloadManifest.targets.
<FilesToPackage Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Android.Run.pdb" TargetPath="..\tools" IsSymbolFile="true" />
<FilesToPackage Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Android.Run.runtimeconfig.json" TargetPath="tools" />
<FilesToPackage Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Testing.Platform.dll" TargetPath="tools" />
<FilesToPackage Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Testing.Extensions.TrxReport.dll" TargetPath="tools" />
<FilesToPackage Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Testing.Extensions.TrxReport.Abstractions.dll" TargetPath="tools" />
<FilesToPackage Include="@(VersionFiles)" TargetPath="tools" />
<FilesToPackage Include="$(XamarinAndroidSourcePath)src\Xamarin.Android.Build.Tasks\Microsoft.Android.Sdk\Sdk\**" TargetPath="Sdk" />
<FilesToPackage Include="$(XamarinAndroidSourcePath)src\Microsoft.Android.Sdk.ILLink\PreserveLists\**" TargetPath="PreserveLists" />
Expand Down
48 changes: 35 additions & 13 deletions src/Microsoft.Android.Run/AndroidTestAdapter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Xml.Linq;
using Microsoft.Testing.Extensions.TrxReport.Abstractions;
using Microsoft.Testing.Platform.Capabilities.TestFramework;
using Microsoft.Testing.Platform.Extensions;
using Microsoft.Testing.Platform.Extensions.Messages;
Expand Down Expand Up @@ -76,17 +77,30 @@ async Task RunAndReportAsync (ExecuteRequestContext context, SessionUid sessionU

var testResults = ParseTrxFile (localTrxPath);
foreach (var result in testResults) {
// Build the failure message including stack trace for non-TRX consumers
var failureMessage = result.ErrorMessage ?? "Test failed";
if (!string.IsNullOrEmpty (result.StackTrace))
failureMessage += "\n" + result.StackTrace;

var stateProperty = result.Outcome switch {
TrxOutcome.Passed => (IProperty) new PassedTestNodeStateProperty (),
TrxOutcome.Failed => new FailedTestNodeStateProperty (result.ErrorMessage ?? "Test failed"),
TrxOutcome.Failed => new FailedTestNodeStateProperty (failureMessage),
TrxOutcome.NotExecuted => new SkippedTestNodeStateProperty (result.ErrorMessage),
_ => new PassedTestNodeStateProperty (),
};

var properties = new List<IProperty> { stateProperty };

// Add TRX report properties required by ITrxReportCapability
if (!string.IsNullOrEmpty (result.ClassName))
properties.Add (new TrxFullyQualifiedTypeNameProperty (result.ClassName));
if (result.Outcome == TrxOutcome.Failed && (!string.IsNullOrEmpty (result.ErrorMessage) || !string.IsNullOrEmpty (result.StackTrace)))
properties.Add (new TrxExceptionProperty (result.ErrorMessage, result.StackTrace));
Comment thread
jonathanpeppers marked this conversation as resolved.

var testNode = new TestNode {
Uid = new TestNodeUid (result.FullyQualifiedName),
DisplayName = result.TestName,
Properties = new PropertyBag (stateProperty),
Properties = new PropertyBag (properties.ToArray ()),
};

await context.MessageBus.PublishAsync (this, new TestNodeUpdateMessage (sessionUid, testNode));
Expand Down Expand Up @@ -235,18 +249,14 @@ static List<TrxTestResult> ParseTrxFile (string trxPath)
? $"{className}.{testName}"
: testName;

// Extract error message if present
// Extract error message and stack trace if present
string? errorMessage = null;
string? stackTrace = null;
var outputElement = unitTestResult.Element (ns + "Output");
var errorInfo = outputElement?.Element (ns + "ErrorInfo");
if (errorInfo != null) {
var message = errorInfo.Element (ns + "Message")?.Value;
var stackTrace = errorInfo.Element (ns + "StackTrace")?.Value;
errorMessage = message;
if (!string.IsNullOrEmpty (stackTrace))
errorMessage = string.IsNullOrEmpty (errorMessage)
? stackTrace
: $"{errorMessage}\n{stackTrace}";
errorMessage = errorInfo.Element (ns + "Message")?.Value;
stackTrace = errorInfo.Element (ns + "StackTrace")?.Value;
}
Comment thread
jonathanpeppers marked this conversation as resolved.

var trxOutcome = outcome switch {
Expand All @@ -256,7 +266,7 @@ static List<TrxTestResult> ParseTrxFile (string trxPath)
_ => TrxOutcome.Passed,
};

results.Add (new TrxTestResult (fullyQualifiedName, testName, trxOutcome, errorMessage));
results.Add (new TrxTestResult (fullyQualifiedName, testName, className, trxOutcome, errorMessage, stackTrace));
}
}

Expand Down Expand Up @@ -284,10 +294,22 @@ enum TrxOutcome
record TrxTestResult (
string FullyQualifiedName,
string TestName,
string? ClassName,
TrxOutcome Outcome,
string? ErrorMessage);
string? ErrorMessage,
string? StackTrace);

class AndroidTestCapabilities : ITestFrameworkCapabilities
{
public IReadOnlyCollection<ITestFrameworkCapability> Capabilities { get; } = [];
public IReadOnlyCollection<ITestFrameworkCapability> Capabilities { get; } = [new AndroidTrxReportCapability ()];
}

class AndroidTrxReportCapability : ITrxReportCapability
{
public bool IsSupported => true;

public void Enable ()
{
// No-op: TRX properties are always added to test nodes.
}
}
1 change: 1 addition & 0 deletions src/Microsoft.Android.Run/Microsoft.Android.Run.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
<PackageReference Include="Microsoft.Testing.Platform" Version="$(MicrosoftTestingPlatformVersion)" />
<PackageReference Include="Microsoft.Testing.Extensions.TrxReport" Version="$(MicrosoftTestingPlatformVersion)" />
<PackageReference Include="Mono.Options" Version="$(MonoOptionsVersion)" />
</ItemGroup>

Expand Down
10 changes: 10 additions & 0 deletions src/Microsoft.Android.Run/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics;
using Microsoft.Testing.Extensions;
using Mono.Options;
using Xamarin.Android.Tools;

Expand Down Expand Up @@ -317,6 +318,13 @@ async Task<int> RunDotnetTestAsync (List<string> mtpArgs)
// since MTP needs them to set up the test communication channel.
mtpArgs.AddRange (["--server", "dotnettestcli", "--dotnet-test-pipe", validatedDotnetTestPipe]);

// MTP defaults its working directory to the DLL location (SDK tools directory),
// not Environment.CurrentDirectory. Pass --results-directory explicitly so TRX
// reports are written to the project directory, matching dotnet test conventions.
if (!mtpArgs.Contains ("--results-directory")) {
mtpArgs.AddRange (["--results-directory", Path.Combine (Environment.CurrentDirectory, "TestResults")]);
}

var testApplicationBuilder = await Microsoft.Testing.Platform.Builder.TestApplication.CreateBuilderAsync (mtpArgs.ToArray ());

var adapter = new AndroidTestAdapter (
Expand All @@ -330,6 +338,8 @@ async Task<int> RunDotnetTestAsync (List<string> mtpArgs)
_ => new AndroidTestCapabilities (),
(_, _) => adapter);

testApplicationBuilder.AddTrxReportProvider ();

using var testApplication = await testApplicationBuilder.BuildAsync ();
return await testApplication.RunAsync ();
}
Expand Down
20 changes: 17 additions & 3 deletions tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2443,10 +2443,13 @@ public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime)
Assert.IsTrue (dotnet.Build (target: "Install", parameters: buildParameters.ToArray ()), "`dotnet build -t:Install` should succeed");

// Run based on mode
var runParameters = buildParameters.Select (p => $"/p:{p}").ToArray ();
var runParameters = buildParameters.Select (p => $"/p:{p}").ToList ();
if (mode == "test")
runParameters.Add ("--report-trx");

using var process = mode == "run"
? dotnet.StartRun (waitForExit: true, parameters: runParameters)
: dotnet.StartTest (parameters: runParameters);
? dotnet.StartRun (waitForExit: true, parameters: runParameters.ToArray ())
: dotnet.StartTest (parameters: runParameters.ToArray ());

var locker = new Lock ();
var output = new StringBuilder ();
Expand Down Expand Up @@ -2499,6 +2502,17 @@ public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime)
StringAssert.Contains ("succeeded: 1", outputText, $"Output should report 1 passed test. See {logPath} for details.");
StringAssert.Contains ("failed: 1", outputText, $"Output should report 1 failed test. See {logPath} for details.");
StringAssert.Contains ("skipped: 1", outputText, $"Output should report 1 skipped test. See {logPath} for details.");

// Verify that a TRX file was produced by --report-trx
var trxFiles = Directory.GetFiles (projectDirectory, "*.trx", SearchOption.AllDirectories);
Assert.IsTrue (trxFiles.Length > 0, $"Expected at least one .trx file in {projectDirectory}. See {logPath} for details.");

TestContext.AddTestAttachment (trxFiles [0]);

var trxDoc = XDocument.Load (trxFiles [0]);
var trxNs = trxDoc.Root?.Name.Namespace ?? XNamespace.None;
var resultSummary = trxDoc.Root?.Element (trxNs + "ResultSummary");
Assert.IsNotNull (resultSummary, $"TRX file should contain a ResultSummary element. File: {trxFiles [0]}");
}
}

Expand Down