Skip to content
Merged
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
32 changes: 32 additions & 0 deletions TrxLib.Tests/TestResultTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,36 @@ public void Inferred_properties_are_not_inferred_from_fully_qualified_test_name_
testResult.Namespace.Should().BeNull();
testResult.TestName.Should().Be(fullyQualifiedTestName);
}

[Fact]
public void Theory_test_with_dotted_param_is_parsed_correctly_when_testMethod_provided()
{
// When testMethod is supplied the constructor must use testMethod.ClassName
// directly instead of splitting the FQTN on '.'. Without the fix, a param
// value like "foo.bar" causes ClassName and TestName to be corrupt.
const string className = "System.CommandLine.Tests.ParserTests";
const string methodName = "Parse_theory_method";
const string fqtn = $"{className}.{methodName}(param: \"foo.bar\")";

var testResult = new TestResult(fqtn, TestOutcome.Passed,
testMethod: new TestMethod { ClassName = className, Name = methodName });

using var _ = new AssertionScope();
testResult.FullyQualifiedTestName.Should().Be(fqtn);
testResult.FullyQualifiedClassName.Should().Be(className);
testResult.ClassName.Should().Be("ParserTests");
testResult.Namespace.Should().Be("System.CommandLine.Tests");
testResult.TestName.Should().Be($"{methodName}(param: \"foo.bar\")");
}

[Fact]
public void ToString_DoesNotThrow_ForOutcomeValueNotInEnum()
{
// TestResult.ToString() has a _ => throw arm that crashes on any enum value
// not listed in its switch expression (e.g. future additions to TestOutcome, or
// values written by vstest that TrxLib doesn't yet map, such as "Completed").
var testResult = new TestResult("some.namespace.SomeClass.SomeTest", (TestOutcome)99);
var act = () => testResult.ToString();
act.Should().NotThrow<ArgumentOutOfRangeException>();
}
}
112 changes: 112 additions & 0 deletions TrxLib.Tests/TrxParserTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Runtime.InteropServices;

using AwesomeAssertions;
using AwesomeAssertions.Execution;

namespace TrxLib.Tests;

Expand Down Expand Up @@ -175,6 +176,117 @@ public void Parse_Example1OSXTrx_ParsesCodebaseCorrectly()
test.Codebase?.FullName.Should().Be(@"/Users/josequ/dev/cli/test/Microsoft.DotNet.Cli.Utils.Tests/bin/Debug/netcoreapp1.0/Microsoft.DotNet.Cli.Utils.Tests.dll");
}

// ── Parser regression tests ──────────────────────────────────────────────────
// These cover attributes/elements present in sample TRX files that were
// previously silently discarded. All tests below should be GREEN.

[Fact]
public void Parse_OneTestFailureTrx_PopulatesTestRunId()
{
// TestResultSet.TestRunId is never assigned; parser must set it from TestRun.Id.
var results = TrxParser.Parse(new FileInfo(GetSampleFilePath("OneTestFailure.trx")));
results.TestRunId.Should().Be("a1293f2f-ba2d-4aa4-91a7-ceed97fd4735");
}

[Fact]
public void Parse_OneTestFailureTrx_ParsesRunUser()
{
// runUser attribute on <TestRun> is present in every TRX file but currently not parsed.
var results = TrxParser.Parse(new FileInfo(GetSampleFilePath("OneTestFailure.trx")));
results.OriginalTestRun.Should().NotBeNull();
results.OriginalTestRun!.RunUser.Should().Be("BenjaminMichaelis");
}

[Fact]
public void Parse_OneTestFailureTrx_ParsesResultSummaryOutcome()
{
// <ResultSummary outcome="..."> is present in every TRX file but currently not parsed.
var results = TrxParser.Parse(new FileInfo(GetSampleFilePath("OneTestFailure.trx")));
results.OriginalTestRun.Should().NotBeNull();
results.OriginalTestRun!.ResultSummary.Should().NotBeNull();
results.OriginalTestRun!.ResultSummary!.Outcome.Should().Be("Failed");
}

[Fact]
public void Parse_OneTestFailureTrx_ParsesCountersTotals()
{
// <Counters total="19" passed="18" failed="1" ...> inside <ResultSummary> but currently not parsed.
var results = TrxParser.Parse(new FileInfo(GetSampleFilePath("OneTestFailure.trx")));
results.OriginalTestRun.Should().NotBeNull();
var counters = results.OriginalTestRun!.ResultSummary?.Counters;
counters.Should().NotBeNull();
using var _ = new AssertionScope();
counters!.Total.Should().Be(19);
counters.Passed.Should().Be(18);
counters.Failed.Should().Be(1);
counters.Executed.Should().Be(19);
}

[Fact]
public void Parse_OneTestFailureTrx_ParsesTestLists()
{
// <TestLists> always has two default entries but currently not parsed.
var results = TrxParser.Parse(new FileInfo(GetSampleFilePath("OneTestFailure.trx")));
results.OriginalTestRun.Should().NotBeNull();
var testLists = results.OriginalTestRun!.TestLists?.Items;
testLists.Should().NotBeNull();
testLists.Should().HaveCount(2);
testLists.Should().ContainSingle(l => l.Name == "Results Not in a List" && l.Id == "8c84fa94-04c1-424b-9868-57a2d4851a1d");
testLists.Should().ContainSingle(l => l.Name == "All Loaded Results" && l.Id == "19431567-8539-422a-85d7-44ee4e166bda");
}

[Fact]
public void Parse_OneTestFailureTrx_ParsesTestEntries()
{
// <TestEntries> is present in every TRX file but currently not parsed.
var results = TrxParser.Parse(new FileInfo(GetSampleFilePath("OneTestFailure.trx")));
results.OriginalTestRun.Should().NotBeNull();
var entries = results.OriginalTestRun!.TestEntries?.Items;
entries.Should().NotBeNull();
entries.Should().HaveCount(19);
// Spot-check one entry links testId -> executionId -> testListId correctly.
entries.Should().ContainSingle(e =>
e.TestId == "35e9c03c-7c18-2ee8-7216-91e69cfe406e" &&
e.ExecutionId == "9682c228-090a-47f3-96d2-26ffa23c9a53" &&
e.TestListId == "8c84fa94-04c1-424b-9868-57a2d4851a1d");
}

[Fact]
public void Parse_OneTestFailureTrx_ParsesUnitTestResultExecutionId()
{
// executionId attribute on <UnitTestResult> is present in every TRX but currently not parsed.
var results = TrxParser.Parse(new FileInfo(GetSampleFilePath("OneTestFailure.trx")));
results.OriginalTestRun.Should().NotBeNull();
var rawResults = results.OriginalTestRun!.Results?.UnitTestResults;
rawResults.Should().NotBeNull();
rawResults.Should().ContainSingle(r => r.ExecutionId == "9682c228-090a-47f3-96d2-26ffa23c9a53");
}

[Fact]
public void Parse_OneTestFailureTrx_ParsesUnitTestDefinitionAttributes()
{
// Both storage and <Execution id="..."/> are on <UnitTest> but currently not parsed.
var results = TrxParser.Parse(new FileInfo(GetSampleFilePath("OneTestFailure.trx")));
var unitTests = results.OriginalTestRun?.TestDefinitions?.UnitTests;
unitTests.Should().NotBeNull();
using var _ = new AssertionScope();
unitTests.Should().Contain(u => u.Storage == @"essentialcsharp\src\chapter01.tests\bin\debug\net6.0\chapter01.tests.dll");
unitTests.Should().Contain(u => u.Execution != null && u.Execution.Id == "86ff1fe0-ef46-4281-b70a-7ecc3ed2376b");
}

[Fact]
public void Parse_ComplexTrx_FullyQualifiedTestNameIncludesTheoryParameters()
{
// The parser builds FQTN from ClassName.Name, discarding the (params) suffix from testName.
// For theory tests every invocation gets the same FQTN, making distinct runs indistinguishable.
var results = TrxParser.Parse(new FileInfo(GetSampleFilePath("complex.trx")));
const string expectedFqtn =
"System.CommandLine.Tests.ParserTests+MultiplePositions" +
".When_an_option_is_shared_between_an_outer_and_inner_command_then_specifying_in_one_does_not_result_in_error_on_other" +
"(commandLine: \"outer --the-option xyz inner\")";
results.Select(r => r.FullyQualifiedTestName).Should().Contain(expectedFqtn);
}

[Fact]
public void Parse_Example2WindowsTrx_TestsDoNotAppearWithMoreThanOneOutcome()
{
Expand Down
74 changes: 74 additions & 0 deletions TrxLib/Counters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Xml.Serialization;

namespace TrxLib;

/// <summary>
/// Represents the test result counters from the ResultSummary element of a TRX file.
/// Contains the authoritative vstest-computed counts for each outcome category.
/// </summary>
public class Counters
{
/// <summary>Gets or sets the total number of tests.</summary>
[XmlAttribute("total")]
public int Total { get; set; }

/// <summary>Gets or sets the number of tests that were executed.</summary>
[XmlAttribute("executed")]
public int Executed { get; set; }

/// <summary>Gets or sets the number of tests that passed.</summary>
[XmlAttribute("passed")]
public int Passed { get; set; }

/// <summary>Gets or sets the number of tests that failed.</summary>
[XmlAttribute("failed")]
public int Failed { get; set; }

/// <summary>Gets or sets the number of tests that encountered a system error.</summary>
[XmlAttribute("error")]
public int Error { get; set; }

/// <summary>Gets or sets the number of tests that timed out.</summary>
[XmlAttribute("timeout")]
public int Timeout { get; set; }

/// <summary>Gets or sets the number of tests that were aborted.</summary>
[XmlAttribute("aborted")]
public int Aborted { get; set; }

/// <summary>Gets or sets the number of tests with inconclusive results.</summary>
[XmlAttribute("inconclusive")]
public int Inconclusive { get; set; }

/// <summary>Gets or sets the number of tests that passed but the run was aborted.</summary>
[XmlAttribute("passedButRunAborted")]
public int PassedButRunAborted { get; set; }

/// <summary>Gets or sets the number of tests that were not runnable.</summary>
[XmlAttribute("notRunnable")]
public int NotRunnable { get; set; }

/// <summary>Gets or sets the number of tests that were not executed.</summary>
[XmlAttribute("notExecuted")]
public int NotExecuted { get; set; }

/// <summary>Gets or sets the number of tests that were disconnected.</summary>
[XmlAttribute("disconnected")]
public int Disconnected { get; set; }

/// <summary>Gets or sets the number of tests with a warning outcome.</summary>
[XmlAttribute("warning")]
public int Warning { get; set; }

/// <summary>Gets or sets the number of completed tests.</summary>
[XmlAttribute("completed")]
public int Completed { get; set; }

/// <summary>Gets or sets the number of tests currently in progress.</summary>
[XmlAttribute("inProgress")]
public int InProgress { get; set; }

/// <summary>Gets or sets the number of tests that are pending.</summary>
[XmlAttribute("pending")]
public int Pending { get; set; }
}
17 changes: 17 additions & 0 deletions TrxLib/Execution.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Xml.Serialization;

namespace TrxLib;

/// <summary>
/// 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.
/// </summary>
public class Execution
{
/// <summary>
/// Gets or sets the execution identifier. Links this test definition to the
/// corresponding UnitTestResult via its executionId attribute.
/// </summary>
[XmlAttribute("id")]
public string? Id { get; set; }
}
28 changes: 28 additions & 0 deletions TrxLib/ResultSummary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Xml.Serialization;

namespace TrxLib;

/// <summary>
/// Represents the ResultSummary element of a TRX file.
/// Contains the overall run outcome, authoritative vstest-computed counters, and run-level output.
/// </summary>
public class ResultSummary
{
/// <summary>
/// Gets or sets the overall outcome of the test run (e.g. "Passed", "Failed", "Completed").
/// </summary>
[XmlAttribute("outcome")]
public string? Outcome { get; set; }

/// <summary>
/// Gets or sets the test result counters for the run.
/// </summary>
[XmlElement("Counters")]
public Counters? Counters { get; set; }

/// <summary>
/// Gets or sets the run-level output (e.g. run-level stdout written by the test host).
/// </summary>
[XmlElement("Output")]
public Output? Output { get; set; }
}
14 changes: 14 additions & 0 deletions TrxLib/TestEntries.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Xml.Serialization;

namespace TrxLib;

/// <summary>
/// Represents the TestEntries element of a TRX file.
/// Contains the execution index linking test IDs to execution IDs and test list categories.
/// </summary>
public class TestEntries
{
/// <summary>Gets or sets the individual test entry records.</summary>
[XmlElement("TestEntry")]
public List<TestEntry>? Items { get; set; }
}
22 changes: 22 additions & 0 deletions TrxLib/TestEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Xml.Serialization;

namespace TrxLib;

/// <summary>
/// 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).
/// </summary>
public class TestEntry
{
/// <summary>Gets or sets the test definition identifier.</summary>
[XmlAttribute("testId")]
public string? TestId { get; set; }

/// <summary>Gets or sets the execution identifier.</summary>
[XmlAttribute("executionId")]
public string? ExecutionId { get; set; }

/// <summary>Gets or sets the test list identifier.</summary>
[XmlAttribute("testListId")]
public string? TestListId { get; set; }
}
18 changes: 18 additions & 0 deletions TrxLib/TestList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Xml.Serialization;

namespace TrxLib;

/// <summary>
/// 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".
/// </summary>
public class TestList
{
/// <summary>Gets or sets the display name of the test list.</summary>
[XmlAttribute("name")]
public string? Name { get; set; }

/// <summary>Gets or sets the unique identifier of the test list.</summary>
[XmlAttribute("id")]
public string? Id { get; set; }
}
14 changes: 14 additions & 0 deletions TrxLib/TestLists.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Xml.Serialization;

namespace TrxLib;

/// <summary>
/// Represents the TestLists element of a TRX file.
/// Contains the list categories used to group test results.
/// </summary>
public class TestLists
{
/// <summary>Gets or sets the individual test list entries.</summary>
[XmlElement("TestList")]
public List<TestList>? Items { get; set; }
}
Loading
Loading