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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v3.1.5+1b188a7b0a (64-bit .NET 10.0.8) +[xUnit.net 00:00:00.08] Discovering: TrxLib.Tests +[xUnit.net 00:00:00.13] Discovered: TrxLib.Tests +[xUnit.net 00:00:00.15] Starting: TrxLib.Tests +[xUnit.net 00:00:00.20] Finished: TrxLib.Tests + + + + \ 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; } }