Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
71f28ee
Initial plan
Copilot Oct 1, 2025
70ebec1
Update package versions and central props for xUnit3 migration
Copilot Oct 1, 2025
1440e96
Remove UnitTestType and IsTestProject properties, update xunit.runner…
Copilot Oct 1, 2025
26d6415
Update xunit.runner.json files to xUnit3 schema (backward compatible)
Copilot Oct 1, 2025
58c6581
Add test infrastructure documentation to TESTGUIDE.md
Copilot Oct 1, 2025
e9d7dc0
Add nuget.org source, update to xUnit3 packages, begin API migration
Copilot Oct 1, 2025
5b2b852
Document xUnit3 API migration requirements and create detailed guide
Copilot Oct 1, 2025
63b7ee9
Investigate xUnit3 API availability - DataAttribute confirmed present
Copilot Oct 1, 2025
9b8347e
Add CaptureTrace attribute and remove console capturing logic
Copilot Oct 2, 2025
e856ac3
Add explicit xunit.v3 DLL references, investigate DataAttribute issue
Copilot Oct 2, 2025
dd02ee4
Document DataAttribute accessibility blocker with F# compiler
Copilot Oct 2, 2025
23420bf
Disable DataAttribute classes, document remaining xUnit3 API changes
Copilot Oct 2, 2025
96c5901
Disable XUNIT_EXTRAS and custom framework - FSharp.Test.Utilities bui…
Copilot Oct 2, 2025
c874dfa
Update migration status - FSharp.Test.Utilities complete, document ne…
Copilot Oct 2, 2025
c39be56
Remove FsCheck from central Directory.Build.props
Copilot Oct 2, 2025
8937cd2
Revert NuGet.config to original state - remove nuget.org source
Copilot Oct 2, 2025
c13ae9b
Update all test projects to OutputType=Exe for xUnit3 requirement
Copilot Oct 2, 2025
b59f212
Merge branch 'main' into copilot/fix-70fb3ceb-020a-4b9d-8060-269d2c18…
T-Gro Oct 2, 2025
ee9dd1f
Document F# compiler DataAttribute resolution blocker - 7 workaround …
Copilot Oct 2, 2025
72a6449
Fix DataAttribute resolution - use Xunit.v3.IDataAttribute interface
Copilot Oct 3, 2025
7fb445f
Fix package versions and AllowMultiple attribute - test projects buil…
Copilot Oct 3, 2025
a4539c1
Merge branch 'main' into copilot/fix-70fb3ceb-020a-4b9d-8060-269d2c18…
T-Gro Oct 3, 2025
1c26447
Fix FsCheck version and update test logger for xUnit3 - build succeeds
Copilot Oct 3, 2025
166dc20
Final update: xUnit3 migration complete with 95/101 tests passing
Copilot Oct 3, 2025
5695886
Fix console output capture for FSI tests - remove CaptureTrace, auto-…
Copilot Oct 3, 2025
4d2ca0c
Update vsintegration test projects: remove UnitTestType, add OutputTy…
Copilot Oct 3, 2025
7c61fac
Fix package reference: change XUnit to xunit.v3 in VisualFSharp.Salsa…
Copilot Oct 6, 2025
b3f4979
Merge branch 'main' into copilot/fix-70fb3ceb-020a-4b9d-8060-269d2c18…
T-Gro Oct 6, 2025
3c1f0f4
Fix xUnit3 build issues: OutputType, ValueTask net472 compatibility, …
Copilot Oct 6, 2025
9170eeb
Add Program.cs entry point for FSharp.Editor.IntegrationTests xUnit3 …
Copilot Oct 6, 2025
990782f
Addressing PR comments
Copilot Oct 6, 2025
9a93fff
Fix build errors: remove duplicate Program.cs, correct F# entry point…
Copilot Oct 6, 2025
ef2070b
Update xUnit3 migration docs to reflect verified complete status with…
Copilot Nov 28, 2025
d00e9b2
Merge branch 'main' into copilot/fix-70fb3ceb-020a-4b9d-8060-269d2c18…
abonie Nov 28, 2025
b5b5dcb
Fix CI failures: TestConsole.ProvideInput install() and remove Progra…
Copilot Nov 30, 2025
ff5091f
Remove accidentally committed preprocessed.xml temp file
Copilot Nov 30, 2025
e284fe2
Fix EndToEndBuildTests: add explicit xUnit3 package versions
Copilot Nov 30, 2025
a150210
Fix EndToEndBuildTests: add xUnit3 version properties to Directory.Bu…
Copilot Dec 1, 2025
9f4a4f6
Merge branch 'main' into copilot/fix-70fb3ceb-020a-4b9d-8060-269d2c18…
abonie Dec 2, 2025
09f8e28
Add timeout to flaky MailboxProcessor race condition tests to prevent…
Copilot Dec 2, 2025
72df39c
Fix MailboxProcessor test crashes: restore TestConsole initialization…
Copilot Dec 2, 2025
42c2e3c
Fix XunitSetup.fs build error: remove TestFramework.log calls that re…
Copilot Dec 2, 2025
5cfcffb
Merge branch 'main' into copilot/fix-70fb3ceb-020a-4b9d-8060-269d2c18…
abonie Dec 3, 2025
eb56318
Change backgroundTask to async
abonie Dec 4, 2025
54a7b8a
Merge branch 'main' into copilot/fix-70fb3ceb-020a-4b9d-8060-269d2c18…
abonie Dec 5, 2025
a3128fc
Restore xunit test logger with LogFilePath in Build.ps1
Copilot Dec 5, 2025
dd15e32
Use .trx for test results
abonie Dec 8, 2025
e375ce7
Escape semicolon in bash script
abonie Dec 9, 2025
6781675
Fix LogFilePath->LogFileName
abonie Dec 9, 2025
9712627
Fix logger argument formatting in build script
abonie Dec 9, 2025
b0c9a3e
Temporary remove trx logger to check CI
abonie Dec 9, 2025
61f4b07
Remove fake failing CI test case
abonie Dec 9, 2025
049f29c
Revert "Temporary remove trx logger to check CI"
abonie Dec 9, 2025
81c5aea
Build arg list with an array
abonie Dec 9, 2025
a2f47ce
Update md files
abonie Dec 10, 2025
b121aac
Bring back IsTestProject
abonie Dec 10, 2025
a379847
Fix test results path
abonie Dec 10, 2025
795ba85
Finish migration to MTP
abonie Dec 12, 2025
1322a1b
Remove VSTest references
abonie Dec 16, 2025
8efdb8f
Remove duplicate entry
abonie Dec 16, 2025
7b6dc6b
Change to xunit query syntax
abonie Dec 17, 2025
3264ba0
Migrate tests except FSharp.Editor.IntegrationTests
abonie Dec 18, 2025
771d9bf
Remove xunit.runner from FSharp.Editor
abonie Dec 19, 2025
314bd15
Remove xunit.runner from vsintegration
abonie Dec 19, 2025
62a775a
Make component tests 64-bit
abonie Jan 8, 2026
6b11947
Make test projects 64-bit
abonie Jan 9, 2026
7654d68
Prevent completion tests from hanging
abonie Jan 9, 2026
d144f1a
Remove batching from CI
abonie Jan 12, 2026
52fccf9
Merge branch 'main' into copilot/fix-70fb3ceb-020a-4b9d-8060-269d2c18…
abonie Jan 12, 2026
8702d75
Fix forcing 64bit
abonie Jan 12, 2026
5f8cc88
Fix forcing 64bit v2
abonie Jan 13, 2026
9724f93
Ignore warning
abonie Jan 13, 2026
6664f43
Ensure legacy project tests are not parallel
abonie Jan 15, 2026
b5df76d
Remove test utilities from eng/build.sh
abonie Jan 19, 2026
1a2eac2
Fix legacy project tests
abonie Jan 19, 2026
87ba15f
Disable parallel execution of fsi tests
abonie Jan 19, 2026
eced836
Add a SKILL.md for getting build results from AzDO
abonie Jan 20, 2026
97baf60
Fix Get-BuildErrors script
abonie Jan 20, 2026
6d747cd
Remove VSTest references
abonie Jan 21, 2026
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
62 changes: 62 additions & 0 deletions .github/skills/pr-build-status/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
name: pr-build-status
description: "Retrieve Azure DevOps build information for GitHub Pull Requests, including build IDs, stage status, and failed jobs."
metadata:
author: dotnet-maui
version: "1.0"
compatibility: Requires GitHub CLI (gh) authenticated with access to dotnet/fsharp repository.
---

# PR Build Status Skill

Retrieve Azure DevOps build information for GitHub Pull Requests.

## Tools Required

This skill uses `bash` together with `pwsh` (PowerShell 7+) to run the PowerShell scripts. No file editing or other tools are required.

## When to Use

- User asks about CI/CD status for a PR
- User asks about failed checks or builds
- User asks "what's failing on PR #XXXXX"
- User wants to see test results

## Scripts

All scripts are in `.github/skills/pr-build-status/scripts/`

### 1. Get Build IDs for a PR
```bash
pwsh .github/skills/pr-build-status/scripts/Get-PrBuildIds.ps1 -PrNumber <PR_NUMBER>
```

### 2. Get Build Status
```bash
pwsh .github/skills/pr-build-status/scripts/Get-BuildInfo.ps1 -BuildId <BUILD_ID>
# For failed jobs only:
pwsh .github/skills/pr-build-status/scripts/Get-BuildInfo.ps1 -BuildId <BUILD_ID> -FailedOnly
```

### 3. Get Build Errors and Test Failures
```bash
# Get all errors (build errors + test failures)
pwsh .github/skills/pr-build-status/scripts/Get-BuildErrors.ps1 -BuildId <BUILD_ID>

# Get only build/compilation errors
pwsh .github/skills/pr-build-status/scripts/Get-BuildErrors.ps1 -BuildId <BUILD_ID> -ErrorsOnly

# Get only test failures
pwsh .github/skills/pr-build-status/scripts/Get-BuildErrors.ps1 -BuildId <BUILD_ID> -TestsOnly
```

## Workflow

1. Get build IDs: `scripts/Get-PrBuildIds.ps1 -PrNumber XXXXX`
2. For each build, get status: `scripts/Get-BuildInfo.ps1 -BuildId YYYYY`
3. For failed builds, get error details: `scripts/Get-BuildErrors.ps1 -BuildId YYYYY`

## Prerequisites

- `gh` (GitHub CLI) - authenticated
- `pwsh` (PowerShell 7+)
210 changes: 210 additions & 0 deletions .github/skills/pr-build-status/scripts/Get-BuildErrors.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
<#
.SYNOPSIS
Retrieves build errors and test failures from an Azure DevOps build.

.DESCRIPTION
Queries the Azure DevOps build timeline to find failed jobs and tasks,
then extracts build errors (MSBuild errors, compilation failures) and
test failures with their details.

.PARAMETER BuildId
The Azure DevOps build ID.

.PARAMETER Org
The Azure DevOps organization. Defaults to 'dnceng-public'.

.PARAMETER Project
The Azure DevOps project. Defaults to 'public'.

.PARAMETER TestsOnly
If specified, only returns test results (no build errors).

.PARAMETER ErrorsOnly
If specified, only returns build errors (no test results).

.PARAMETER JobFilter
Optional filter to match job/task names (supports wildcards).

.EXAMPLE
./Get-BuildErrors.ps1 -BuildId 1240456

.EXAMPLE
./Get-BuildErrors.ps1 -BuildId 1240456 -ErrorsOnly

.EXAMPLE
./Get-BuildErrors.ps1 -BuildId 1240456 -TestsOnly -JobFilter "*SafeArea*"

.OUTPUTS
Objects with Type (BuildError/TestFailure), Source, Message, and Details properties.
#>

[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[string]$BuildId,

[Parameter(Mandatory = $false)]
[string]$Org = "dnceng-public",

[Parameter(Mandatory = $false)]
[string]$Project = "public",

[Parameter(Mandatory = $false)]
[switch]$TestsOnly,

[Parameter(Mandatory = $false)]
[switch]$ErrorsOnly,

[Parameter(Mandatory = $false)]
[string]$JobFilter
)

$ErrorActionPreference = "Stop"

# Get build timeline
$timelineUrl = "https://dev.azure.com/$Org/$Project/_apis/build/builds/${BuildId}/timeline?api-version=7.0"

try {
$timeline = Invoke-RestMethod -Uri $timelineUrl -Method Get -ContentType "application/json"
}
catch {
Write-Error "Failed to query Azure DevOps timeline API: $_"
exit 1
}

$allResults = @()

# --- SECTION 1: Find Build Errors from Failed Tasks ---
if (-not $TestsOnly) {
$failedTasks = $timeline.records | Where-Object {
$_.type -eq "Task" -and
$_.result -eq "failed" -and
$_.log.url -and
(-not $JobFilter -or $_.name -like $JobFilter)
}

foreach ($task in $failedTasks) {
Write-Host "Analyzing failed task: $($task.name)" -ForegroundColor Red

try {
$log = Invoke-RestMethod -Uri $task.log.url -Method Get
$lines = $log -split "`r?`n"

# Find MSBuild errors and ##[error] markers
$errorLines = $lines | Where-Object {
$_ -match ": error [A-Z]+\d*:" -or # MSBuild errors (CS1234, MT1234, etc.)
$_ -match ": Error :" -or # Xamarin.Shared.Sdk errors
$_ -match "##\[error\]" # Azure DevOps error markers
}

foreach ($errorLine in $errorLines) {
# Clean up the line
$cleanLine = $errorLine -replace "^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s*", ""
$cleanLine = $cleanLine -replace "##\[error\]", ""

# Skip generic "exited with code" errors - we want the actual error
if ($cleanLine -match "exited with code") {
continue
}

$allResults += [PSCustomObject]@{
Type = "BuildError"
Source = $task.name
Message = $cleanLine.Trim()
Details = ""
}
}
}
catch {
Write-Warning "Failed to fetch log for task $($task.name): $_"
}
}
}

# --- SECTION 2: Find Test Failures from Jobs ---
if (-not $ErrorsOnly) {
$jobs = $timeline.records | Where-Object {
$_.type -eq "Job" -and
$_.log.url -and
$_.state -eq "completed" -and
$_.result -eq "failed" -and
(-not $JobFilter -or $_.name -like $JobFilter)
}

foreach ($job in $jobs) {
Write-Host "Analyzing job for test failures: $($job.name)" -ForegroundColor Yellow

try {
$logContent = Invoke-RestMethod -Uri $job.log.url -Method Get
$lines = $logContent -split "`r?`n"

# Find test result lines: "failed <TestName> (duration)"
# Format: ESC[31mfailedESC[m TestName ESC[90m(duration)ESC[m
# Note: \x1b is the hex escape for the ESC character (0x1B)
for ($i = 0; $i -lt $lines.Count; $i++) {
# Match ANSI-colored format - the reset code ESC[m comes immediately after "failed"
if ($lines[$i] -match '^\d{4}-\d{2}-\d{2}.*\x1b\[31mfailed\x1b\[m\s+(.+?)\s+\x1b\[90m\(([^)]+)\)\x1b\[m$') {
$testName = $matches[1]
$duration = $matches[2]
$errorMessage = ""
$stackTrace = ""

# Look ahead for error message and stack trace
for ($j = $i + 1; $j -lt $lines.Count; $j++) {
$line = $lines[$j]
$cleanLine = $line -replace "^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s*", ""

if ($cleanLine -match "^\s*Error Message:") {
for ($k = $j + 1; $k -lt [Math]::Min($j + 10, $lines.Count); $k++) {
$msgLine = $lines[$k] -replace "^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s*", ""
if ($msgLine -match "^\s*Stack Trace:" -or [string]::IsNullOrWhiteSpace($msgLine)) {
break
}
$errorMessage += $msgLine.Trim() + " "
}
}

if ($cleanLine -match "^\s*Stack Trace:") {
for ($k = $j + 1; $k -lt [Math]::Min($j + 5, $lines.Count); $k++) {
$stLine = $lines[$k] -replace "^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s*", ""
if ($stLine -match "at .+ in .+:line \d+") {
$stackTrace = $stLine.Trim()
break
}
}
break
}

# Stop if we hit the next test (plain or ANSI-colored format)
if ($cleanLine -match '(?:\x1b\[\d+m)?(passed|failed|skipped)(?:\x1b\[m)?\s+\S+') {
break
}
}

$allResults += [PSCustomObject]@{
Type = "TestFailure"
Source = $job.name
Message = $testName
Details = if ($errorMessage) { "$errorMessage`n$stackTrace".Trim() } else { $stackTrace }
}
}
}
}
catch {
Write-Warning "Failed to fetch log for job $($job.name): $_"
}
}
}

# Remove duplicate errors (same message from same source)
$uniqueResults = $allResults | Group-Object -Property Type, Source, Message | ForEach-Object {
$_.Group | Select-Object -First 1
}

# Summary
$buildErrors = ($uniqueResults | Where-Object { $_.Type -eq "BuildError" }).Count
$testFailures = ($uniqueResults | Where-Object { $_.Type -eq "TestFailure" }).Count

Write-Host "`nSummary: $buildErrors build error(s), $testFailures test failure(s)" -ForegroundColor Cyan

$uniqueResults
104 changes: 104 additions & 0 deletions .github/skills/pr-build-status/scripts/Get-BuildInfo.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<#
.SYNOPSIS
Retrieves detailed status information for an Azure DevOps build.

.DESCRIPTION
Queries the Azure DevOps build timeline API and returns comprehensive
information about the build including all stages, their status, and
any failed or canceled jobs.

.PARAMETER BuildId
The Azure DevOps build ID.

.PARAMETER Org
The Azure DevOps organization. Defaults to 'dnceng-public'.

.PARAMETER Project
The Azure DevOps project. Defaults to 'public'.

.PARAMETER FailedOnly
If specified, only returns failed or canceled stages and jobs.

.EXAMPLE
./Get-BuildInfo.ps1 -BuildId 1240455

.EXAMPLE
./Get-BuildInfo.ps1 -BuildId 1240455 -FailedOnly

.EXAMPLE
./Get-BuildInfo.ps1 -BuildId 1240455 -Org "dnceng-public" -Project "public"

.OUTPUTS
Object with BuildId, Status, Result, Stages, and FailedJobs properties.
#>

[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[string]$BuildId,

[Parameter(Mandatory = $false)]
[string]$Org = "dnceng-public",

[Parameter(Mandatory = $false)]
[string]$Project = "public",

[Parameter(Mandatory = $false)]
[switch]$FailedOnly
)

$ErrorActionPreference = "Stop"

# Get build info
$buildUrl = "https://dev.azure.com/$Org/$Project/_apis/build/builds/${BuildId}?api-version=7.0"
$timelineUrl = "https://dev.azure.com/$Org/$Project/_apis/build/builds/$BuildId/timeline?api-version=7.0"

try {
$build = Invoke-RestMethod -Uri $buildUrl -Method Get -ContentType "application/json"
$timeline = Invoke-RestMethod -Uri $timelineUrl -Method Get -ContentType "application/json"
}
catch {
Write-Error "Failed to query Azure DevOps API: $_"
exit 1
}

# Extract stages
$stages = $timeline.records | Where-Object { $_.type -eq "Stage" } | ForEach-Object {
[PSCustomObject]@{
Name = $_.name
State = $_.state
Result = $_.result
}
} | Sort-Object -Property { $_.State -eq "completed" }, { $_.State -eq "inProgress" }

# Extract failed/canceled jobs
$failedJobs = $timeline.records |
Where-Object {
($_.type -eq "Stage" -or $_.type -eq "Job") -and
($_.result -eq "failed" -or $_.result -eq "canceled")
} |
ForEach-Object {
[PSCustomObject]@{
Name = $_.name
Type = $_.type
Result = $_.result
}
} | Sort-Object -Property Type, Name

if ($FailedOnly) {
$failedJobs
}
else {
[PSCustomObject]@{
BuildId = $BuildId
BuildNumber = $build.buildNumber
Status = $build.status
Result = $build.result
Pipeline = $build.definition.name
StartTime = $build.startTime
FinishTime = $build.finishTime
Stages = $stages
FailedJobs = $failedJobs
Link = "https://dev.azure.com/$Org/$Project/_build/results?buildId=$BuildId"
}
}
Loading
Loading