Skip to content

Commit 8f4c63e

Browse files
authored
Plumb test failures through to github (#15831)
This does two bits: 1. correctly marks our tests as failed in xUnit, so that AzDo will pick up that the tests have failed. 2. Actually intentionally mark skipped tests as skipped in xUnit. We were doing this accidentally before. 3. Add a CI step to log test failures in a way that they can show up on GitHub Probably regressed around #6992 and #4490. ### details #### Part the first We were relying on the MUX build scripts to convert our WTT test logs to xUnit format, which AzDo then ingests. That script we used relied on some WinUI-specific logic around retrying tests. They have some logic to auto-retry failed tests. They then mark a test as "skipped" if it passed less than some threshold of times. Since we were never setting that variable, we would mark a test as "skipped" if it had _0_ passes. So, all failures showed up on AzDo as "skipped". Why didn't we notice this? Well, the `Run-Tests.ps1` script will still return `1` if _any_ tests failed. So the test job would fail if there was a failure, AzDo just wouldn't know which test it was. #### part the second Updates `ConvertWttLogToXUnitLog` in `HelixTestHelpers.cs` to understand that a test can be skipped, in addition to pass/fail. Removes all the logic for dealing with retries, cause we didn't need that. #### part the third TAEF doesn't emit error messages in a way that AzDo can immediately pick up on which tests failed. This means that Github gives us this useless error message: ![image](https://github.com/microsoft/terminal/assets/18356694/3be6de00-22e1-421c-93d4-176bd2be4cab) That's the only "error" that AzDo knows about. This PR changes that by adding a build step to manually parse the xUnit results, and log the names of any tests that failed. By logging them with a prefix of `##vso[task.logissue type=error]`, then AzDo will surface that text as an error message. GitHub can then grab that text and surface it too. ### Addenda: Why aren't we using the VsTest module as noted in #4490 (comment), the vstest module is literally 6x slower than just running TAEF directly.
1 parent e5a430f commit 8f4c63e

File tree

4 files changed

+64
-42
lines changed

4 files changed

+64
-42
lines changed

build/Helix/ConvertWttLogToXUnit.ps1

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ Param(
2020
$helixResultsContainerUri = $Env:HELIX_RESULTS_CONTAINER_URI
2121
$helixResultsContainerRsas = $Env:HELIX_RESULTS_CONTAINER_RSAS
2222

23-
$rerunPassesRequiredToAvoidFailure = $env:rerunPassesRequiredToAvoidFailure
24-
2523
Add-Type -Language CSharp -ReferencedAssemblies System.Xml,System.Xml.Linq,System.Runtime.Serialization,System.Runtime.Serialization.Json (Get-Content $PSScriptRoot\HelixTestHelpers.cs -Raw)
2624

2725
$testResultParser = [HelixTestHelpers.TestResultParser]::new($TestNamePrefix, $helixResultsContainerUri, $helixResultsContainerRsas)
28-
$testResultParser.ConvertWttLogToXUnitLog($WttInputPath, $WttSingleRerunInputPath, $WttMultipleRerunInputPath, $XUnitOutputPath, $rerunPassesRequiredToAvoidFailure)
26+
$testResultParser.ConvertWttLogToXUnitLog($WttInputPath, $WttSingleRerunInputPath, $WttMultipleRerunInputPath, $XUnitOutputPath)

build/Helix/HelixTestHelpers.cs

Lines changed: 17 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,13 @@ public TestResult()
2020
public string Name { get; set; }
2121
public string SourceWttFile { get; set; }
2222
public bool Passed { get; set; }
23+
public bool Skipped { get; set; }
2324
public bool CleanupPassed { get; set; }
2425
public TimeSpan ExecutionTime { get; set; }
2526
public string Details { get; set; }
2627

2728
public List<string> Screenshots { get; private set; }
2829
public List<TestResult> RerunResults { get; private set; }
29-
30-
// Returns true if the test pass rate is sufficient to avoid being counted as a failure.
31-
public bool PassedOrUnreliable(int requiredNumberOfPasses)
32-
{
33-
if(Passed)
34-
{
35-
return true;
36-
}
37-
else
38-
{
39-
if(RerunResults.Count == 1)
40-
{
41-
return RerunResults[0].Passed;
42-
}
43-
else
44-
{
45-
return RerunResults.Where(r => r.Passed).Count() >= requiredNumberOfPasses;
46-
}
47-
}
48-
}
4930
}
5031

5132
//
@@ -221,7 +202,9 @@ public static TestPass ParseTestWttFile(string fileName, bool cleanupFailuresAre
221202
testsExecuting--;
222203

223204
// If any inner test fails, we'll still fail the outer
224-
currentResult.Passed &= element.Attribute("Result").Value == "Pass";
205+
var value = element.Attribute("Result").Value;
206+
currentResult.Passed = value == "Pass";
207+
currentResult.Skipped = value == "Skipped";
225208

226209
// Only gather execution data if this is the outer test we ran initially
227210
if (testsExecuting == 0)
@@ -498,7 +481,7 @@ public Dictionary<string, string> GetSubResultsJsonByMethodName(string wttInputP
498481
return subResultsJsonByMethod;
499482
}
500483

501-
public void ConvertWttLogToXUnitLog(string wttInputPath, string wttSingleRerunInputPath, string wttMultipleRerunInputPath, string xunitOutputPath, int requiredPassRateThreshold)
484+
public void ConvertWttLogToXUnitLog(string wttInputPath, string wttSingleRerunInputPath, string wttMultipleRerunInputPath, string xunitOutputPath)
502485
{
503486
TestPass testPass = TestPass.ParseTestWttFileWithReruns(wttInputPath, wttSingleRerunInputPath, wttMultipleRerunInputPath, cleanupFailuresAreRegressions: true, truncateTestNames: false);
504487
var results = testPass.TestResults;
@@ -510,8 +493,8 @@ public void ConvertWttLogToXUnitLog(string wttInputPath, string wttSingleRerunIn
510493
// If the test failed sufficiently often enough for it to count as a failed test (determined by a property on the
511494
// Azure DevOps job), we'll later mark it as failed during test results processing.
512495

513-
int failedCount = results.Where(r => !r.PassedOrUnreliable(requiredPassRateThreshold)).Count();
514-
int skippedCount = results.Where(r => !r.Passed && r.PassedOrUnreliable(requiredPassRateThreshold)).Count();
496+
int failedCount = results.Where(r => !r.Passed).Count();
497+
int skippedCount = results.Where(r => (!r.Passed && r.Skipped)).Count();
515498

516499
var root = new XElement("assemblies");
517500

@@ -557,12 +540,13 @@ public void ConvertWttLogToXUnitLog(string wttInputPath, string wttSingleRerunIn
557540

558541
string resultString = string.Empty;
559542

560-
if (result.Passed)
543+
if (result.Passed && !result.Skipped)
561544
{
562545
resultString = "Pass";
563546
}
564-
else if(result.PassedOrUnreliable(requiredPassRateThreshold))
547+
else if (result.Skipped)
565548
{
549+
566550
resultString = "Skip";
567551
}
568552
else
@@ -571,31 +555,25 @@ public void ConvertWttLogToXUnitLog(string wttInputPath, string wttSingleRerunIn
571555
}
572556

573557

574-
test.SetAttributeValue("result", resultString);
575-
576558
if (!result.Passed)
577559
{
578-
// If a test failed, we'll have rerun it multiple times.
579-
// We'll save the subresults to a JSON text file that we'll upload to the helix results container -
580-
// this allows it to be as long as we want, whereas the reason field in Azure DevOps has a 4000 character limit.
581-
string subResultsFileName = methodName + "_subresults.json";
582-
string subResultsFilePath = Path.Combine(Path.GetDirectoryName(wttInputPath), subResultsFileName);
583-
584-
if (result.PassedOrUnreliable(requiredPassRateThreshold))
560+
if (result.Skipped)
585561
{
586562
var reason = new XElement("reason");
587-
reason.Add(new XCData(GetUploadedFileUrl(subResultsFileName, helixResultsContainerUri, helixResultsContainerRsas)));
563+
reason.Add(new XCData("Test skipped"));
588564
test.Add(reason);
589565
}
590-
else
591-
{
566+
else {
592567
var failure = new XElement("failure");
593568
var message = new XElement("message");
594-
message.Add(new XCData(GetUploadedFileUrl(subResultsFileName, helixResultsContainerUri, helixResultsContainerRsas)));
569+
message.Add(new XCData("Test failed"));
595570
failure.Add(message);
596571
test.Add(failure);
597572
}
598573
}
574+
575+
test.SetAttributeValue("result", resultString);
576+
599577
collection.Add(test);
600578
}
601579

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
Param(
2+
[Parameter(Mandatory = $true)]
3+
[string]$XUnitOutputPath
4+
)
5+
6+
# This script is used to parse the XUnit output from the test runs and print out
7+
# the tests that failed.
8+
#
9+
# Why you might ask? Well, it sure seems like Azure DevOps doesn't like the fact
10+
# that we just call our tests in a powershell script. It can't seemingly find
11+
# the actual errors in the TAEF logs. That means when you just go to the
12+
# "Checks" page on GitHub, the Azure DevOps integration doesn't have anything
13+
# meaningful to say other than "PowerShell exited with code '1'". If we however,
14+
# just manually emit the test names formatted with "#[error]" in front of them,
15+
# well, then the integration will all work like magic.
16+
17+
# Load the test results as a XML object
18+
$testResults = [xml](Get-Content -Path $XUnitOutputPath)
19+
20+
# Our XML looks like:
21+
# <assemblies>
22+
# <assembly name="MUXControls.Test.dll" test-framework="TAEF" run-date="2023-08-14" run-time="11:38:01" total="524" passed="520" failed="4" skipped="1" time="8943" errors="0">
23+
# <collection total="524" passed="520" failed="4" skipped="1" name="Test collection" time="8943">
24+
# <test name="ControlCoreTests::TestSimpleClickSelection" type="ControlCoreTests" method="TestSimpleClickSelection" time="0.016" result="Fail">
25+
26+
# Iterate over all the assemblies and print all the tests that failed
27+
foreach ($assembly in $testResults.assemblies.assembly) {
28+
foreach ($collection in $assembly.collection) {
29+
foreach ($test in $collection.test) {
30+
if ($test.result -eq "Fail") {
31+
# This particular format is taken from the Azure DevOps documentation:
32+
# https://github.com/microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md
33+
# This will treat this line as an error message
34+
Write-Output "##vso[task.logissue type=error]$($test.name) Failed"
35+
}
36+
}
37+
}
38+
}

build/pipelines/templates-v2/job-test-project.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ jobs:
6565
arguments: -WttInputPath '${{ parameters.testLogPath }}' -WttSingleRerunInputPath 'unused.wtl' -WttMultipleRerunInputPath 'unused2.wtl' -XUnitOutputPath 'onBuildMachineResults.xml' -TestNamePrefix '$(BuildConfiguration).$(BuildPlatform)'
6666
condition: ne(variables['PGOBuildMode'], 'Instrument')
6767

68+
- task: PowerShell@2
69+
displayName: 'Manually log test failures'
70+
inputs:
71+
targetType: filePath
72+
filePath: build\Helix\OutputTestErrorsForAzureDevops.ps1
73+
arguments: -XUnitOutputPath 'onBuildMachineResults.xml'
74+
condition: ne(variables['PGOBuildMode'], 'Instrument')
75+
6876
- task: PublishTestResults@2
6977
displayName: 'Upload converted test logs'
7078
condition: ne(variables['PGOBuildMode'], 'Instrument')

0 commit comments

Comments
 (0)