diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index c45f3ac..397afc2 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -54,4 +54,11 @@ jobs:
run: dotnet build -p:ContinuousIntegrationBuild=True --no-restore --configuration Release
- name: Test
- run: dotnet test --no-build --configuration Release --verbosity normal
\ No newline at end of file
+ run: dotnet test --no-build --configuration Release --verbosity normal
+
+ - name: AOT Publish Validation
+ run: |
+ $rid = if ($env:RUNNER_OS -eq 'Windows') { 'win-x64' } else { 'linux-x64' }
+ dotnet publish TrxLib.AotSample/TrxLib.AotSample.csproj -r $rid -c Release --self-contained
+ $ext = if ($env:RUNNER_OS -eq 'Windows') { '.exe' } else { '' }
+ & "TrxLib.AotSample/bin/Release/net10.0/$rid/publish/TrxLib.AotSample$ext"
\ No newline at end of file
diff --git a/TrxLib.AotSample/Program.cs b/TrxLib.AotSample/Program.cs
new file mode 100644
index 0000000..a1ec8e5
--- /dev/null
+++ b/TrxLib.AotSample/Program.cs
@@ -0,0 +1,20 @@
+using TrxLib;
+
+var sampleRoot = Path.Combine(AppContext.BaseDirectory, "SampleTrxFiles");
+if (!Directory.Exists(sampleRoot))
+ throw new DirectoryNotFoundException($"Sample TRX directory not found: {sampleRoot}");
+
+var sampleFiles = Directory
+ .EnumerateFiles(sampleRoot, "*.trx", SearchOption.AllDirectories)
+ .OrderBy(path => path, StringComparer.Ordinal)
+ .ToArray();
+
+if (sampleFiles.Length == 0)
+ throw new InvalidOperationException($"No TRX sample files found under: {sampleRoot}");
+
+foreach (var file in sampleFiles)
+{
+ _ = TrxParser.Parse(new FileInfo(file));
+}
+
+Console.WriteLine($"TrxLib AOT validation passed. Parsed {sampleFiles.Length} sample files.");
diff --git a/TrxLib.AotSample/TrxLib.AotSample.csproj b/TrxLib.AotSample/TrxLib.AotSample.csproj
new file mode 100644
index 0000000..ef22c20
--- /dev/null
+++ b/TrxLib.AotSample/TrxLib.AotSample.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net10.0
+ true
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TrxLib.Tests/SampleTrxFiles/theory-tests.trx b/TrxLib.Tests/SampleTrxFiles/theory-tests.trx
new file mode 100644
index 0000000..3d49985
--- /dev/null
+++ b/TrxLib.Tests/SampleTrxFiles/theory-tests.trx
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TrxLib.Tests/TrxParserTests.cs b/TrxLib.Tests/TrxParserTests.cs
index 9b56fd8..eb83079 100644
--- a/TrxLib.Tests/TrxParserTests.cs
+++ b/TrxLib.Tests/TrxParserTests.cs
@@ -168,6 +168,34 @@ private static bool NotWindows()
return !RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
}
+ [Fact]
+ public void Parse_TheoryTestsTrx_AppendsSuffixToFqtnForParameterizedTests()
+ {
+ var results = TrxParser.Parse(new FileInfo(GetSampleFilePath("theory-tests.trx")));
+ results.Select(r => r.FullyQualifiedTestName)
+ .Should()
+ .Contain("Acme.Tests.MathTests.AddNumbers(left: 1, right: 2)");
+ results.Select(r => r.FullyQualifiedTestName)
+ .Should()
+ .Contain("Acme.Tests.MathTests.AddNumbers(left: 0, right: 0)");
+ }
+
+ [Fact]
+ public void Parse_TheoryTestsTrx_DoesNotAppendSuffixForNonParameterizedTest()
+ {
+ var results = TrxParser.Parse(new FileInfo(GetSampleFilePath("theory-tests.trx")));
+ results.Single(r => r.FullyQualifiedTestName == "Acme.Tests.MathTests.PlainTest")
+ .Should().NotBeNull();
+ }
+
+ [Fact]
+ public void Parse_TheoryTestsTrx_ParsesAllThreeTestResults()
+ {
+ var results = TrxParser.Parse(new FileInfo(GetSampleFilePath("theory-tests.trx")));
+ results.Should().HaveCount(3);
+ results.Count(r => r.Outcome == TestOutcome.Passed).Should().Be(3);
+ }
+
[ConditionalFact(nameof(NotWindows))]
public void Parse_Example1OSXTrx_ParsesCodebaseCorrectly()
{
@@ -355,4 +383,4 @@ public void Parse_Example1OSXTrx_ParsesFinishTimeCorrectly()
var results = TrxParser.Parse(new FileInfo(GetSampleFilePath(Path.Combine("1", "example1_OSX.trx"))));
results.CompletedTime.Should().Be(DateTimeOffset.Parse("2017-01-17T10:39:57.1294340-08:00"));
}
-}
\ No newline at end of file
+}
diff --git a/TrxLib.slnx b/TrxLib.slnx
index 0523140..18f741c 100644
--- a/TrxLib.slnx
+++ b/TrxLib.slnx
@@ -8,6 +8,7 @@
+
diff --git a/TrxLib/Counters.cs b/TrxLib/Counters.cs
index fe023da..e84ee0d 100644
--- a/TrxLib/Counters.cs
+++ b/TrxLib/Counters.cs
@@ -1,74 +1,55 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
-/// Represents the test result counters from the ResultSummary element of a TRX file.
-/// Contains the authoritative vstest-computed counts for each outcome category.
+/// Represents the vstest-authoritative test run counters from the ResultSummary element.
///
public class Counters
{
/// Gets or sets the total number of tests.
- [XmlAttribute("total")]
public int Total { get; set; }
/// Gets or sets the number of tests that were executed.
- [XmlAttribute("executed")]
public int Executed { get; set; }
/// Gets or sets the number of tests that passed.
- [XmlAttribute("passed")]
public int Passed { get; set; }
/// Gets or sets the number of tests that failed.
- [XmlAttribute("failed")]
public int Failed { get; set; }
- /// Gets or sets the number of tests that encountered a system error.
- [XmlAttribute("error")]
+ /// Gets or sets the number of tests that produced an error.
public int Error { get; set; }
/// Gets or sets the number of tests that timed out.
- [XmlAttribute("timeout")]
public int Timeout { get; set; }
/// Gets or sets the number of tests that were aborted.
- [XmlAttribute("aborted")]
public int Aborted { get; set; }
- /// Gets or sets the number of tests with inconclusive results.
- [XmlAttribute("inconclusive")]
+ /// Gets or sets the number of tests that were inconclusive.
public int Inconclusive { get; set; }
/// Gets or sets the number of tests that passed but the run was aborted.
- [XmlAttribute("passedButRunAborted")]
public int PassedButRunAborted { get; set; }
/// Gets or sets the number of tests that were not runnable.
- [XmlAttribute("notRunnable")]
public int NotRunnable { get; set; }
/// Gets or sets the number of tests that were not executed.
- [XmlAttribute("notExecuted")]
public int NotExecuted { get; set; }
- /// Gets or sets the number of tests that were disconnected.
- [XmlAttribute("disconnected")]
+ /// Gets or sets the number of disconnected tests.
public int Disconnected { get; set; }
- /// Gets or sets the number of tests with a warning outcome.
- [XmlAttribute("warning")]
+ /// Gets or sets the number of tests that produced warnings.
public int Warning { get; set; }
/// Gets or sets the number of completed tests.
- [XmlAttribute("completed")]
public int Completed { get; set; }
- /// Gets or sets the number of tests currently in progress.
- [XmlAttribute("inProgress")]
+ /// Gets or sets the number of tests in progress.
public int InProgress { get; set; }
- /// Gets or sets the number of tests that are pending.
- [XmlAttribute("pending")]
+ /// Gets or sets the number of pending tests.
public int Pending { get; set; }
}
diff --git a/TrxLib/Deployment.cs b/TrxLib/Deployment.cs
index ba14d07..20b4836 100644
--- a/TrxLib/Deployment.cs
+++ b/TrxLib/Deployment.cs
@@ -1,5 +1,3 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
@@ -11,6 +9,5 @@ public class Deployment
///
/// Gets or sets the root directory path where test run files are deployed.
///
- [XmlAttribute("runDeploymentRoot")]
public string? RunDeploymentRoot { get; set; }
}
\ No newline at end of file
diff --git a/TrxLib/ErrorInfo.cs b/TrxLib/ErrorInfo.cs
index 24bda98..1262300 100644
--- a/TrxLib/ErrorInfo.cs
+++ b/TrxLib/ErrorInfo.cs
@@ -1,5 +1,3 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
@@ -11,13 +9,11 @@ public class ErrorInfo
///
/// Gets or sets the error message describing why the test failed.
///
- [XmlElement("Message")]
public string? Message { get; set; }
///
/// Gets or sets the stack trace information from the test failure.
/// Contains the call stack at the point of the exception that caused the test to fail.
///
- [XmlElement("StackTrace")]
public string? StackTrace { get; set; }
}
diff --git a/TrxLib/Execution.cs b/TrxLib/Execution.cs
index c1bdd4b..c5e172e 100644
--- a/TrxLib/Execution.cs
+++ b/TrxLib/Execution.cs
@@ -1,17 +1,13 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
-/// Represents the execution element of a unit test definition in a TRX file.
-/// Contains the execution ID that links a test definition to its result.
+/// Represents the Execution element inside a UnitTest definition.
+/// Links the test definition to its execution record and result.
///
public class Execution
{
///
- /// Gets or sets the execution identifier. Links this test definition to the
- /// corresponding UnitTestResult via its executionId attribute.
+ /// Gets or sets the execution identifier.
///
- [XmlAttribute("id")]
public string? Id { get; set; }
}
diff --git a/TrxLib/Output.cs b/TrxLib/Output.cs
index 79a2a2f..cb0b004 100644
--- a/TrxLib/Output.cs
+++ b/TrxLib/Output.cs
@@ -1,5 +1,3 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
@@ -12,12 +10,10 @@ public class Output
/// Gets or sets the error information if the test failed.
/// Contains the error message and stack trace details.
///
- [XmlElement("ErrorInfo")]
public ErrorInfo? ErrorInfo { get; set; }
///
/// Gets or sets the standard output text captured during test execution.
///
- [XmlElement("StdOut")]
public string? StdOut { get; set; }
}
diff --git a/TrxLib/ResultSummary.cs b/TrxLib/ResultSummary.cs
index 91f1259..7b700db 100644
--- a/TrxLib/ResultSummary.cs
+++ b/TrxLib/ResultSummary.cs
@@ -1,28 +1,23 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
-/// Represents the ResultSummary element of a TRX file.
-/// Contains the overall run outcome, authoritative vstest-computed counters, and run-level output.
+/// Represents the ResultSummary element of a TRX file, containing the overall
+/// outcome and vstest-authoritative test counters for the run.
///
public class ResultSummary
{
///
- /// Gets or sets the overall outcome of the test run (e.g. "Passed", "Failed", "Completed").
+ /// Gets or sets the overall outcome of the test run (e.g., "Passed", "Failed").
///
- [XmlAttribute("outcome")]
public string? Outcome { get; set; }
///
- /// Gets or sets the test result counters for the run.
+ /// Gets or sets the authoritative test counters computed by vstest.
///
- [XmlElement("Counters")]
public Counters? Counters { get; set; }
///
- /// Gets or sets the run-level output (e.g. run-level stdout written by the test host).
+ /// Gets or sets the run-level output (e.g., stdout from the test host).
///
- [XmlElement("Output")]
public Output? Output { get; set; }
}
diff --git a/TrxLib/Results.cs b/TrxLib/Results.cs
index aaec770..1172e0f 100644
--- a/TrxLib/Results.cs
+++ b/TrxLib/Results.cs
@@ -1,5 +1,3 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
@@ -11,6 +9,5 @@ public class Results
///
/// Gets or sets the list of unit test results from the test run.
///
- [XmlElement("UnitTestResult")]
public List? UnitTestResults { get; set; }
}
diff --git a/TrxLib/TestDefinitions.cs b/TrxLib/TestDefinitions.cs
index a3df25f..65ec1b7 100644
--- a/TrxLib/TestDefinitions.cs
+++ b/TrxLib/TestDefinitions.cs
@@ -1,5 +1,3 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
@@ -11,6 +9,5 @@ public class TestDefinitions
///
/// Gets or sets the list of unit test definitions.
///
- [XmlElement("UnitTest")]
public List? UnitTests { get; set; }
}
diff --git a/TrxLib/TestEntries.cs b/TrxLib/TestEntries.cs
index b3cda4e..e203843 100644
--- a/TrxLib/TestEntries.cs
+++ b/TrxLib/TestEntries.cs
@@ -1,14 +1,10 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
-/// Represents the TestEntries element of a TRX file.
-/// Contains the execution index linking test IDs to execution IDs and test list categories.
+/// Represents the TestEntries element, which indexes test definitions to their execution records.
///
public class TestEntries
{
- /// Gets or sets the individual test entry records.
- [XmlElement("TestEntry")]
+ /// Gets or sets the collection of test entry records.
public List? Items { get; set; }
}
diff --git a/TrxLib/TestEntry.cs b/TrxLib/TestEntry.cs
index 584f8ec..6818313 100644
--- a/TrxLib/TestEntry.cs
+++ b/TrxLib/TestEntry.cs
@@ -1,22 +1,16 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
-/// Represents a single TestEntry in the TestEntries section of a TRX file.
-/// Links a test definition (testId) to its execution record (executionId) and list category (testListId).
+/// Represents a single TestEntry that links a test definition to its execution record.
///
public class TestEntry
{
/// Gets or sets the test definition identifier.
- [XmlAttribute("testId")]
public string? TestId { get; set; }
/// Gets or sets the execution identifier.
- [XmlAttribute("executionId")]
public string? ExecutionId { get; set; }
- /// Gets or sets the test list identifier.
- [XmlAttribute("testListId")]
+ /// Gets or sets the test list identifier this entry belongs to.
public string? TestListId { get; set; }
}
diff --git a/TrxLib/TestList.cs b/TrxLib/TestList.cs
index 0e2c2b1..10f7b13 100644
--- a/TrxLib/TestList.cs
+++ b/TrxLib/TestList.cs
@@ -1,18 +1,13 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
-/// Represents a single TestList entry in the TestLists section of a TRX file.
-/// vstest always writes two default lists: "Results Not in a List" and "All Loaded Results".
+/// Represents a single TestList entry used to categorize test results.
///
public class TestList
{
- /// Gets or sets the display name of the test list.
- [XmlAttribute("name")]
+ /// Gets or sets the name of the test list.
public string? Name { get; set; }
/// Gets or sets the unique identifier of the test list.
- [XmlAttribute("id")]
public string? Id { get; set; }
}
diff --git a/TrxLib/TestLists.cs b/TrxLib/TestLists.cs
index 7363b2f..388778b 100644
--- a/TrxLib/TestLists.cs
+++ b/TrxLib/TestLists.cs
@@ -1,14 +1,10 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
-/// Represents the TestLists element of a TRX file.
-/// Contains the list categories used to group test results.
+/// Represents the TestLists element containing the list categories for a test run.
///
public class TestLists
{
- /// Gets or sets the individual test list entries.
- [XmlElement("TestList")]
+ /// Gets or sets the collection of test lists.
public List? Items { get; set; }
}
diff --git a/TrxLib/TestMethod.cs b/TrxLib/TestMethod.cs
index 0466b65..ba7cd31 100644
--- a/TrxLib/TestMethod.cs
+++ b/TrxLib/TestMethod.cs
@@ -1,5 +1,3 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
@@ -11,24 +9,20 @@ public class TestMethod
///
/// Gets or sets the path to the assembly containing the test method.
///
- [XmlAttribute("codeBase")]
public string? CodeBase { get; set; }
///
/// Gets or sets the fully qualified name of the class containing the test method.
///
- [XmlAttribute("className")]
public string? ClassName { get; set; }
///
/// Gets or sets the name of the test method.
///
- [XmlAttribute("name")]
public string? Name { get; set; }
///
/// Gets or sets the fully qualified name of the test adapter used to run the test.
///
- [XmlAttribute("adapterTypeName")]
public string? AdapterTypeName { get; set; }
}
diff --git a/TrxLib/TestRun.cs b/TrxLib/TestRun.cs
index a86241d..2dd9971 100644
--- a/TrxLib/TestRun.cs
+++ b/TrxLib/TestRun.cs
@@ -1,72 +1,59 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
/// Represents the root element of a TRX (Test Results XML) file.
/// Contains all test definitions, results, and metadata about the test run.
///
-[XmlRoot("TestRun", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")]
public class TestRun
{
///
/// Gets or sets the collection of test results from the test run.
///
- [XmlElement("Results")]
public Results? Results { get; set; }
///
/// Gets or sets the collection of test definitions used in the test run.
///
- [XmlElement("TestDefinitions")]
public TestDefinitions? TestDefinitions { get; set; }
///
/// Gets or sets the name of the test run.
///
- [XmlAttribute("name")]
public string? Name { get; set; }
///
/// Gets or sets the unique identifier of the test run.
///
- [XmlAttribute("id")]
public string? Id { get; set; }
///
/// Gets or sets the timing information for the test run.
///
- [XmlElement("Times")]
public Times? Times { get; set; }
///
/// Gets or sets the configuration settings used for the test run.
///
- [XmlElement("TestSettings")]
public TestSettings? TestSettings { get; set; }
///
- /// Gets or sets the user account that initiated the test run.
+ /// Gets or sets the user account that initiated the test run (the runUser attribute).
///
- [XmlAttribute("runUser")]
public string? RunUser { get; set; }
///
/// Gets or sets the result summary for the test run, including the overall outcome
/// and authoritative vstest-computed counters.
///
- [XmlElement("ResultSummary")]
public ResultSummary? ResultSummary { get; set; }
///
/// Gets or sets the test list categories used to group results.
///
- [XmlElement("TestLists")]
public TestLists? TestLists { get; set; }
///
/// Gets or sets the test entries index linking test IDs to execution IDs.
///
- [XmlElement("TestEntries")]
public TestEntries? TestEntries { get; set; }
}
diff --git a/TrxLib/TestSettings.cs b/TrxLib/TestSettings.cs
index 716e41d..bfd38fa 100644
--- a/TrxLib/TestSettings.cs
+++ b/TrxLib/TestSettings.cs
@@ -1,5 +1,3 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
@@ -11,19 +9,16 @@ public class TestSettings
///
/// Gets or sets the name of the test settings configuration.
///
- [XmlAttribute("name")]
public string? Name { get; set; }
///
/// Gets or sets the unique identifier of the test settings.
///
- [XmlAttribute("id")]
public string? Id { get; set; }
///
/// Gets or sets the deployment information for the test run.
/// Contains details about where test files are deployed.
///
- [XmlElement("Deployment")]
public Deployment? Deployment { get; set; }
}
\ No newline at end of file
diff --git a/TrxLib/Times.cs b/TrxLib/Times.cs
index 873d868..da41144 100644
--- a/TrxLib/Times.cs
+++ b/TrxLib/Times.cs
@@ -1,5 +1,3 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
@@ -11,24 +9,20 @@ public class Times
///
/// Gets or sets the timestamp when the test run was created.
///
- [XmlAttribute("creation")]
public string? Creation { get; set; }
///
/// Gets or sets the timestamp when the test run was queued for execution.
///
- [XmlAttribute("queuing")]
public string? Queuing { get; set; }
///
/// Gets or sets the timestamp when the test run started execution.
///
- [XmlAttribute("start")]
public string? Start { get; set; }
///
/// Gets or sets the timestamp when the test run finished execution.
///
- [XmlAttribute("finish")]
public string? Finish { get; set; }
}
\ No newline at end of file
diff --git a/TrxLib/TrxLib.csproj b/TrxLib/TrxLib.csproj
index 7ae2424..ed740ea 100644
--- a/TrxLib/TrxLib.csproj
+++ b/TrxLib/TrxLib.csproj
@@ -1,7 +1,8 @@
- netstandard2.1
+ netstandard2.1;net8.0;net10.0
+ true
true
true
true
diff --git a/TrxLib/TrxParser.cs b/TrxLib/TrxParser.cs
index 4a66e9d..1bba8bb 100644
--- a/TrxLib/TrxParser.cs
+++ b/TrxLib/TrxParser.cs
@@ -1,4 +1,4 @@
-using System.Xml.Serialization;
+using System.Xml.Linq;
namespace TrxLib;
@@ -7,6 +7,8 @@ namespace TrxLib;
///
public class TrxParser
{
+ private static readonly XNamespace TrxNs = XNamespace.Get("http://microsoft.com/schemas/VisualStudio/TeamTest/2010");
+
///
/// Parses a TRX file and converts it into a TestResultSet containing structured test results.
///
@@ -15,8 +17,7 @@ public class TrxParser
public static TestResultSet Parse(FileInfo trxFile)
{
using var stream = trxFile.OpenRead();
- var serializer = new XmlSerializer(typeof(TestRun));
- TestRun? testRun = serializer.Deserialize(stream) as TestRun;
+ TestRun? testRun = DeserializeTestRun(stream);
if (testRun == null)
return new TestResultSet();
@@ -66,19 +67,29 @@ public static TestResultSet Parse(FileInfo trxFile)
{
if (!string.IsNullOrEmpty(testMethodDomain.ClassName) && !string.IsNullOrEmpty(testMethodDomain.Name))
{
- // If Name already contains the full FQTN (starts with ClassName), use Name as-is
- // as the base; otherwise build it from ClassName.Name.
- var baseFqtn = testMethodDomain.Name.StartsWith(testMethodDomain.ClassName + ".", StringComparison.Ordinal)
+ string baseFqtn = testMethodDomain.Name.StartsWith(testMethodDomain.ClassName + ".", StringComparison.Ordinal)
? testMethodDomain.Name
: $"{testMethodDomain.ClassName}.{testMethodDomain.Name}";
- // Preserve theory-test parameter suffixes: if testName starts with the base
- // FQTN and has additional content (e.g. "(param: value)"), use testName
- // directly so each parameterized invocation has a unique FQTN.
- if (result.TestName != null && result.TestName.StartsWith(baseFqtn, StringComparison.Ordinal))
- fullyQualifiedTestName = result.TestName;
- else
- fullyQualifiedTestName = baseFqtn;
+ // For parameterized/theory tests, testName carries the suffix (e.g. "(arg1, arg2)").
+ // Extract the short method name and append the suffix from testName if present.
+ var methodShortName = testMethodDomain.Name.Contains('.')
+ ? testMethodDomain.Name.Substring(testMethodDomain.Name.LastIndexOf('.') + 1)
+ : testMethodDomain.Name;
+ var paramSuffix = string.Empty;
+ if (!string.IsNullOrEmpty(result.TestName))
+ {
+ string? candidate = null;
+
+ if (result.TestName.StartsWith(baseFqtn, StringComparison.Ordinal))
+ candidate = result.TestName.Substring(baseFqtn.Length);
+ else if (result.TestName.StartsWith(methodShortName, StringComparison.Ordinal))
+ candidate = result.TestName.Substring(methodShortName.Length);
+
+ if (candidate?.StartsWith("(", StringComparison.Ordinal) == true)
+ paramSuffix = candidate;
+ }
+ fullyQualifiedTestName = baseFqtn + paramSuffix;
}
else
{
@@ -142,8 +153,10 @@ public static TestResultSet Parse(FileInfo trxFile)
var testResultSet = new TestResultSet(results)
{
TestRunName = testRun.Name ?? string.Empty,
- TestRunId = testRun.Id ?? string.Empty,
TestFilePath = trxFile.FullName,
+ TestRunId = testRun.Id ?? string.Empty,
+ DeploymentRoot = testRun.TestSettings?.Deployment?.RunDeploymentRoot ?? string.Empty,
+ TestSettingsName = testRun.TestSettings?.Name ?? string.Empty,
OriginalTestRun = testRun
};
@@ -159,4 +172,196 @@ public static TestResultSet Parse(FileInfo trxFile)
return testResultSet;
}
+
+ private static TestRun? DeserializeTestRun(Stream stream)
+ {
+ XDocument doc;
+ try
+ {
+ doc = XDocument.Load(stream);
+ }
+ catch (System.Xml.XmlException)
+ {
+ return null;
+ }
+
+ var root = doc.Root;
+ if (root == null)
+ return null;
+
+ var testRun = new TestRun
+ {
+ Name = (string?)root.Attribute("name"),
+ Id = (string?)root.Attribute("id"),
+ RunUser = (string?)root.Attribute("runUser"),
+ };
+
+ var timesEl = root.Element(TrxNs + "Times");
+ if (timesEl != null)
+ {
+ testRun.Times = new Times
+ {
+ Creation = (string?)timesEl.Attribute("creation"),
+ Queuing = (string?)timesEl.Attribute("queuing"),
+ Start = (string?)timesEl.Attribute("start"),
+ Finish = (string?)timesEl.Attribute("finish"),
+ };
+ }
+
+ var testSettingsEl = root.Element(TrxNs + "TestSettings");
+ if (testSettingsEl != null)
+ {
+ testRun.TestSettings = new TestSettings
+ {
+ Name = (string?)testSettingsEl.Attribute("name"),
+ Id = (string?)testSettingsEl.Attribute("id"),
+ Deployment = testSettingsEl.Element(TrxNs + "Deployment") is XElement deployEl
+ ? new Deployment { RunDeploymentRoot = (string?)deployEl.Attribute("runDeploymentRoot") }
+ : null,
+ };
+ }
+
+ var testDefsEl = root.Element(TrxNs + "TestDefinitions");
+ if (testDefsEl != null)
+ {
+ testRun.TestDefinitions = new TestDefinitions
+ {
+ UnitTests = testDefsEl.Elements(TrxNs + "UnitTest").Select(ut =>
+ {
+ var tmEl = ut.Element(TrxNs + "TestMethod");
+ return new UnitTest
+ {
+ Id = (string?)ut.Attribute("id"),
+ Name = (string?)ut.Attribute("name"),
+ Storage = (string?)ut.Attribute("storage"),
+ Execution = ut.Element(TrxNs + "Execution") is XElement execEl
+ ? new Execution { Id = (string?)execEl.Attribute("id") }
+ : null,
+ TestMethod = tmEl != null ? new TestMethod
+ {
+ CodeBase = (string?)tmEl.Attribute("codeBase"),
+ ClassName = (string?)tmEl.Attribute("className"),
+ Name = (string?)tmEl.Attribute("name"),
+ AdapterTypeName = (string?)tmEl.Attribute("adapterTypeName"),
+ } : null,
+ };
+ }).ToList(),
+ };
+ }
+
+ var resultsEl = root.Element(TrxNs + "Results");
+ if (resultsEl != null)
+ {
+ testRun.Results = new Results
+ {
+ UnitTestResults = resultsEl.Elements(TrxNs + "UnitTestResult").Select(ur =>
+ {
+ Output? output = null;
+ if (ur.Element(TrxNs + "Output") is XElement outputEl)
+ {
+ ErrorInfo? errorInfo = null;
+ if (outputEl.Element(TrxNs + "ErrorInfo") is XElement errorInfoEl)
+ {
+ errorInfo = new ErrorInfo
+ {
+ Message = (string?)errorInfoEl.Element(TrxNs + "Message"),
+ StackTrace = (string?)errorInfoEl.Element(TrxNs + "StackTrace"),
+ };
+ }
+ output = new Output
+ {
+ StdOut = (string?)outputEl.Element(TrxNs + "StdOut"),
+ ErrorInfo = errorInfo,
+ };
+ }
+ return new UnitTestResult
+ {
+ TestId = (string?)ur.Attribute("testId"),
+ TestName = (string?)ur.Attribute("testName"),
+ Outcome = (string?)ur.Attribute("outcome"),
+ StartTime = (string?)ur.Attribute("startTime"),
+ EndTime = (string?)ur.Attribute("endTime"),
+ Duration = (string?)ur.Attribute("duration"),
+ ComputerName = (string?)ur.Attribute("computerName"),
+ ExecutionId = (string?)ur.Attribute("executionId"),
+ TestListId = (string?)ur.Attribute("testListId"),
+ TestType = (string?)ur.Attribute("testType"),
+ RelativeResultsDirectory = (string?)ur.Attribute("relativeResultsDirectory"),
+ Output = output,
+ };
+ }).ToList(),
+ };
+ }
+
+ var resultSummaryEl = root.Element(TrxNs + "ResultSummary");
+ if (resultSummaryEl != null)
+ {
+ Counters? counters = null;
+ if (resultSummaryEl.Element(TrxNs + "Counters") is XElement countersEl)
+ {
+ counters = new Counters
+ {
+ Total = (int?)countersEl.Attribute("total") ?? 0,
+ Executed = (int?)countersEl.Attribute("executed") ?? 0,
+ Passed = (int?)countersEl.Attribute("passed") ?? 0,
+ Failed = (int?)countersEl.Attribute("failed") ?? 0,
+ Error = (int?)countersEl.Attribute("error") ?? 0,
+ Timeout = (int?)countersEl.Attribute("timeout") ?? 0,
+ Aborted = (int?)countersEl.Attribute("aborted") ?? 0,
+ Inconclusive = (int?)countersEl.Attribute("inconclusive") ?? 0,
+ PassedButRunAborted = (int?)countersEl.Attribute("passedButRunAborted") ?? 0,
+ NotRunnable = (int?)countersEl.Attribute("notRunnable") ?? 0,
+ NotExecuted = (int?)countersEl.Attribute("notExecuted") ?? 0,
+ Disconnected = (int?)countersEl.Attribute("disconnected") ?? 0,
+ Warning = (int?)countersEl.Attribute("warning") ?? 0,
+ Completed = (int?)countersEl.Attribute("completed") ?? 0,
+ InProgress = (int?)countersEl.Attribute("inProgress") ?? 0,
+ Pending = (int?)countersEl.Attribute("pending") ?? 0,
+ };
+ }
+ Output? summaryOutput = null;
+ if (resultSummaryEl.Element(TrxNs + "Output") is XElement summaryOutputEl)
+ {
+ summaryOutput = new Output
+ {
+ StdOut = (string?)summaryOutputEl.Element(TrxNs + "StdOut"),
+ };
+ }
+ testRun.ResultSummary = new ResultSummary
+ {
+ Outcome = (string?)resultSummaryEl.Attribute("outcome"),
+ Counters = counters,
+ Output = summaryOutput,
+ };
+ }
+
+ var testListsEl = root.Element(TrxNs + "TestLists");
+ if (testListsEl != null)
+ {
+ testRun.TestLists = new TestLists
+ {
+ Items = testListsEl.Elements(TrxNs + "TestList").Select(tl => new TestList
+ {
+ Name = (string?)tl.Attribute("name"),
+ Id = (string?)tl.Attribute("id"),
+ }).ToList(),
+ };
+ }
+
+ var testEntriesEl = root.Element(TrxNs + "TestEntries");
+ if (testEntriesEl != null)
+ {
+ testRun.TestEntries = new TestEntries
+ {
+ Items = testEntriesEl.Elements(TrxNs + "TestEntry").Select(te => new TestEntry
+ {
+ TestId = (string?)te.Attribute("testId"),
+ ExecutionId = (string?)te.Attribute("executionId"),
+ TestListId = (string?)te.Attribute("testListId"),
+ }).ToList(),
+ };
+ }
+
+ return testRun;
+ }
}
diff --git a/TrxLib/UnitTest.cs b/TrxLib/UnitTest.cs
index ad7c4de..3c02707 100644
--- a/TrxLib/UnitTest.cs
+++ b/TrxLib/UnitTest.cs
@@ -1,5 +1,3 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
@@ -12,31 +10,26 @@ public class UnitTest
/// Gets or sets the unique identifier of the unit test.
/// This ID is referenced by UnitTestResult elements.
///
- [XmlAttribute("id")]
public string? Id { get; set; }
///
/// Gets or sets the name of the unit test.
///
- [XmlAttribute("name")]
public string? Name { get; set; }
///
/// Gets or sets the test method information for this unit test.
/// Contains details about the method that implements the test.
///
- [XmlElement("TestMethod")]
public TestMethod? TestMethod { get; set; }
///
/// Gets or sets the path to the test assembly. Stored as a lowercased path by vstest.
///
- [XmlAttribute("storage")]
public string? Storage { get; set; }
///
/// Gets or sets the execution element containing the execution ID for this test definition.
///
- [XmlElement("Execution")]
public Execution? Execution { get; set; }
}
diff --git a/TrxLib/UnitTestResult.cs b/TrxLib/UnitTestResult.cs
index 05b3a9e..3c26654 100644
--- a/TrxLib/UnitTestResult.cs
+++ b/TrxLib/UnitTestResult.cs
@@ -1,5 +1,3 @@
-using System.Xml.Serialization;
-
namespace TrxLib;
///
@@ -12,73 +10,61 @@ public class UnitTestResult
/// Gets or sets the unique identifier of the test definition referenced by this test result.
/// This ID maps to a UnitTest element in the TestDefinitions section.
///
- [XmlAttribute("testId")]
public string? TestId { get; set; }
///
/// Gets or sets the name of the test that was executed.
///
- [XmlAttribute("testName")]
public string? TestName { get; set; }
///
/// Gets or sets the outcome of the test execution.
/// Common values include "Passed", "Failed", "NotExecuted", "Inconclusive", "Timeout", and "Pending".
///
- [XmlAttribute("outcome")]
public string? Outcome { get; set; }
///
/// Gets or sets the start time of the test execution in ISO 8601 format.
///
- [XmlAttribute("startTime")]
public string? StartTime { get; set; }
///
/// Gets or sets the end time of the test execution in ISO 8601 format.
///
- [XmlAttribute("endTime")]
public string? EndTime { get; set; }
///
/// Gets or sets the duration of the test execution, typically in the format "hh:mm:ss.fffffff".
///
- [XmlAttribute("duration")]
public string? Duration { get; set; }
///
/// Gets or sets the name of the computer where the test was executed.
///
- [XmlAttribute("computerName")]
public string? ComputerName { get; set; }
///
/// Gets or sets the output information of the test execution, including error information and standard output.
///
- [XmlElement("Output")]
public Output? Output { get; set; }
///
/// Gets or sets the execution identifier. Links this result to its TestEntry and UnitTest/Execution records.
///
- [XmlAttribute("executionId")]
public string? ExecutionId { get; set; }
///
/// Gets or sets the test list identifier. References a TestList in the TestLists section.
///
- [XmlAttribute("testListId")]
public string? TestListId { get; set; }
///
/// Gets or sets the test type GUID identifying the kind of test (e.g. unit test adapter GUID).
///
- [XmlAttribute("testType")]
public string? TestType { get; set; }
///
/// Gets or sets the relative results directory for this test result's attachments.
///
- [XmlAttribute("relativeResultsDirectory")]
public string? RelativeResultsDirectory { get; set; }
}