Skip to content

Merge pull request #299 from ChrisJollyAU/net11-rc1 #147

Merge pull request #299 from ChrisJollyAU/net11-rc1

Merge pull request #299 from ChrisJollyAU/net11-rc1 #147

Workflow file for this run

name: Push
on:
push:
branches:
- '**'
paths-ignore:
- '**.md'
release:
types:
- published
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_NOLOGO: true
PIPELINE_WORKSPACE: true
DOTNET_CI: true
checkoutFetchDepth: 1
buildConfiguration: 'Debug'
skipTests: false
deterministicTests: true
uploadTestResults: true
jobs:
Preconditions:
runs-on: ubuntu-latest
outputs:
lastCommitIsAutoCommit: ${{ steps.GetHeadCommitInfo.outputs.lastCommitIsAutoCommit }}
lastCommitCreatedBeforeSeconds: ${{ steps.GetHeadCommitInfo.outputs.lastCommitCreatedBeforeSeconds }}
steps:
- name: 'General Information'
shell: pwsh
run: |
echo 'EventName: ${{ github.event_name }}'
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: ${{ env.checkoutFetchDepth }}
- name: 'Get Head Commit Info'
id: GetHeadCommitInfo
shell: pwsh
run: |
git log -${{ env.checkoutFetchDepth }} --pretty=%B
$headCommitMessage = git log -1 --skip "$(${{ env.checkoutFetchDepth }} - 1)" --pretty=%B
echo "headCommitMessage: = $headCommitMessage"
$headCommitAuthorName = git log -1 --skip "$(${{ env.checkoutFetchDepth }} - 1)" --pretty=%an
echo "headCommitAuthorName: = $headCommitAuthorName"
$headCommitAuthorEmail = git log -1 --skip "$(${{ env.checkoutFetchDepth }} - 1)" --pretty=%ae
echo "headCommitAuthorEmail: = $headCommitAuthorEmail"
$headCommitDateTime = Get-Date (git log -1 --skip "$(${{ env.checkoutFetchDepth }} - 1)" --pretty=%ci)
echo "headCommitDateTime: = $headCommitDateTime"
$lastCommitIsAutoCommit = $headCommitAuthorEmail -eq 'github-actions@github.com' -and $headCommitMessage -eq '[GitHub Actions] Update green tests.'
echo "lastCommitIsAutoCommit=$lastCommitIsAutoCommit" >> $env:GITHUB_OUTPUT
echo "lastCommitIsAutoCommit: = $lastCommitIsAutoCommit"
$lastCommitCreatedBeforeSeconds = [int]((Get-Date) - $headCommitDateTime).TotalSeconds
echo "lastCommitCreatedBeforeSeconds=$lastCommitCreatedBeforeSeconds" >> $env:GITHUB_OUTPUT
echo "lastCommitCreatedBeforeSeconds: = $lastCommitCreatedBeforeSeconds"
Changes:
runs-on: ubuntu-latest
outputs:
jet: ${{ steps.Decide.outputs.jet }}
libred: ${{ steps.Decide.outputs.libred }}
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: 'Filter Changed Paths'
id: Filter
uses: dorny/paths-filter@v4
with:
# Diff only THIS push's commits. Without a base, paths-filter defaults a non-default-branch push to
# the merge-base with the default branch (master), which on a long-lived branch like `libred` means
# every push is compared against the whole ~869-file divergence — so every filter always matched. The
# push's own delta (before..after) is what "did this push touch Jet/LibRed" actually means. On the
# first push to a branch `before` is all-zeros; paths-filter treats that as "everything changed".
base: ${{ github.event.before }}
filters: |
# Anything here rebuilds the world, so it belongs to both sides.
shared: &shared
- '.github/workflows/**'
- 'Directory.Build.props'
- 'Version.props'
- 'global.json'
- 'nuget.config'
- 'NuGet.config'
- 'Key.snk'
- 'EFCore.Jet.sln'
- 'src/Directory.Build.props'
- 'test/Directory.Build.props'
- 'test/Shared/**'
jet:
- *shared
- 'src/Shared/**'
- 'src/EFCore.Jet/**'
- 'src/EFCore.Jet.Common/**'
- 'src/EFCore.Jet.Data/**'
- 'src/EFCore.Jet.Odbc/**'
- 'src/EFCore.Jet.OleDb/**'
- 'test/EFCore.Jet.Data.Tests/**'
- 'test/EFCore.Jet.FunctionalTests/**'
- 'test/EFCore.Jet.IntegrationTests/**'
- 'test/EFCore.Jet.Tests/**'
- 'test/JetProviderExceptionTests/**'
libred:
- *shared
- 'src/LibRed/**'
- 'test/LibRed.Ado.Tests/**'
- 'test/LibRed.Core.Tests/**'
- 'test/LibRed.Core.AccessTests/**'
- 'test/LibRed.EFCore.Tests/**'
- 'test/LibRed.Engine.Tests/**'
- 'test/LibRed.Engine.AccessTests/**'
- 'test/EFCore.LibRed.FunctionalTests/**'
- 'test/EFCore.LibRed.Extended.FunctionalTests/**'
- 'src/EFCore.Jet.Common/**'
# Northwind.accdb lives here and every LibRed suite links to it as its fixture.
- 'test/JetProviderExceptionTests/**'
- name: 'Decide What To Run'
id: Decide
shell: pwsh
run: |
# A published release (or any event that isn't a push/PR) has no meaningful diff to filter
# against, so it runs everything.
$runEverything = '${{ github.event_name }}' -notin @('push', 'pull_request')
$jet = $runEverything -or ('${{ steps.Filter.outputs.jet }}' -eq 'true')
$libred = $runEverything -or ('${{ steps.Filter.outputs.libred }}' -eq 'true')
echo "jet=$($jet.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT
echo "libred=$($libred.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT
echo "runEverything: $runEverything"
echo "jet: $jet"
echo "libred: $libred"
BuildAndTest:
needs:
- Preconditions
- Changes
if: (needs.Preconditions.outputs.lastCommitIsAutoCommit != 'true' || needs.Preconditions.outputs.lastCommitCreatedBeforeSeconds > 300) && needs.Changes.outputs.jet == 'true'
strategy:
fail-fast: false
matrix:
aceVersion:
- 2010
- 2016
aceArchitecture:
- x64
- x86
dataAccessProviderType:
- ODBC
- OLE DB
os:
- windows-latest
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v5
# Defender scans every .accdb these suites write; see the action for the measurement.
- name: Exclude test files from Defender
uses: ./.github/actions/exclude-defender
- name: Set additional variables
shell: pwsh
run: |
$os = '${{ matrix.os }}'.Split('-')[0]
echo "os=$os" >> $env:GITHUB_ENV
$dotnetInstallDirectory = '.\.dotnet_${{ matrix.aceArchitecture }}'
echo "dotnetInstallDirectory=$dotnetInstallDirectory" >> $env:GITHUB_ENV
$dotnetExecutable = Join-Path $dotnetInstallDirectory 'dotnet.exe'
echo "dotnetExecutable=$dotnetExecutable" >> $env:GITHUB_ENV
$aceUrls = @{
'2010' = @{
'x64' = 'https://cirrusredorg.github.io/EntityFrameworkCore.Jet/AccessDatabaseEngine_2010_x64.exe'
'x86' = 'https://cirrusredorg.github.io/EntityFrameworkCore.Jet/AccessDatabaseEngine_2010_x86.exe'
'silent' = '/passive /quiet /norestart REBOOT=ReallySuppress'
}
'2016' = @{
'x64' = 'https://cirrusredorg.github.io/EntityFrameworkCore.Jet/AccessDatabaseEngine_2016_x64.exe'
'x86' = 'https://cirrusredorg.github.io/EntityFrameworkCore.Jet/AccessDatabaseEngine_2016_x86.exe'
'silent' = '/passive /quiet /norestart REBOOT=ReallySuppress'
}
}
$aceUrl = $aceUrls['${{ matrix.aceVersion }}']['${{ matrix.aceArchitecture }}']
echo "aceUrl=$aceUrl" >> $env:GITHUB_ENV
$aceSilentInstallArgument = $aceUrls['${{ matrix.aceVersion }}']['silent']
echo "aceSilentInstallArgument=$aceSilentInstallArgument" >> $env:GITHUB_ENV
$defaultConnection = '${{ matrix.dataAccessProviderType }}' -eq 'ODBC' ? 'DBQ=Jet.accdb' : 'Data Source=Jet.accdb;Persist Security Info=False;'
echo "defaultConnection=$defaultConnection" >> $env:GITHUB_ENV
$matrixId = '${{ matrix.aceVersion }}-${{ matrix.aceArchitecture }}-' + '${{ matrix.dataAccessProviderType }}'.Replace(' ', '') + '${{ matrix.os }}'
echo "matrixId=$matrixId" >> $env:GITHUB_ENV
- name: Output Variables
shell: pwsh
run: |
echo "os: ${{ env.os }}"
echo "buildConfiguration: ${{ env.buildConfiguration }}"
echo "aceVersion: ${{ matrix.aceVersion }}"
echo "aceArchitecture: ${{ matrix.aceArchitecture }}"
echo "aceUrl: ${{ env.aceUrl }}"
echo "aceSilentInstallArgument: ${{ env.aceSilentInstallArgument }}"
echo "dataAccessProviderType: ${{ matrix.dataAccessProviderType }}"
echo "defaultConnection: ${{ env.defaultConnection }}"
echo "matrixId: ${{ env.matrixId }}"
echo "skipTests: ${{ env.skipTests }}"
echo "dotnetInstallDirectory: ${{ env.dotnetInstallDirectory }}"
echo "dotnetExecutable: ${{ env.dotnetExecutable }}"
echo "github.event_name: ${{ github.event_name }}"
echo "github.repository: ${{ github.repository }}"
- name: .NET Information Before SDK Install
shell: pwsh
run: try { & '${{ env.dotnetExecutable }}' --info } catch { echo 'No ${{ matrix.aceArchitecture }} .NET SDK installed.' }
- name: Install .NET SDK
shell: pwsh
run: |
function Retry-Command {
[CmdletBinding()]
Param(
[Parameter(Position=0, Mandatory=$true)]
[ScriptBlock]$ScriptBlock,
[Parameter(Position=1, Mandatory=$false)]
[int]$Maximum = 5,
[Parameter(Mandatory=$false)]
[switch]$ExponentialBackoff
)
$attempt = 0
do {
if ($attempt -gt 0 -and $ExponentialBackoff) {
Start-Sleep -Seconds ([Math]::Pow(2, $attempt) - 1)
}
$attempt++
try {
$ScriptBlock.Invoke()
return
} catch {
Write-Error $_.Exception.InnerException.Message -ErrorAction Continue
}
} while ($attempt -lt $Maximum)
throw 'Max retries exceeded.'
}
Retry-Command {
&([ScriptBlock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -JSonFile global.json -Architecture '${{ matrix.aceArchitecture }}' -InstallDir '${{ env.dotnetInstallDirectory }}' -Verbose
} -Maximum 10 -ExponentialBackoff
# x86 hosts the tests in a 2GB user address space, and ACE is the one native component in that
# process. On ACE 2010 the same driver and tests get through all 23,509 of shard 3 on x64 and die
# partway on x86, which is the shape of address-space pressure - so lift the ceiling to 4GB by
# setting IMAGE_FILE_LARGE_ADDRESS_AWARE (COFF Characteristics 0x0020). Patched directly rather
# than via editbin so no vcvars environment is needed; signatures are invalidated, which does not
# matter on an ephemeral runner. (ACE 2016 dies early on BOTH architectures, so this cannot help
# there - its failure is the driver, not address space.)
#
# This runs BEFORE the build: MSBuild keeps worker nodes alive after a build and they hold
# dotnet.exe open, so patching it afterwards fails with "being used by another process".
- name: 'Make the x86 SDK large-address-aware'
if: matrix.aceArchitecture == 'x86'
shell: pwsh
run: |
function Set-LargeAddressAware {
param([string]$Path)
$bytes = [System.IO.File]::ReadAllBytes($Path)
$peOffset = [BitConverter]::ToInt32($bytes, 0x3C)
if ([BitConverter]::ToUInt32($bytes, $peOffset) -ne 0x00004550) { return 'not-a-pe' }
$machine = [BitConverter]::ToUInt16($bytes, $peOffset + 4)
if ($machine -ne 0x014C) { return "skipped-$('0x{0:X4}' -f $machine)" }
$charOffset = $peOffset + 22
$characteristics = [BitConverter]::ToUInt16($bytes, $charOffset)
if (($characteristics -band 0x0020) -ne 0) { return 'already' }
[BitConverter]::GetBytes([UInt16]($characteristics -bor 0x0020)).CopyTo($bytes, $charOffset)
[System.IO.File]::WriteAllBytes($Path, $bytes)
$verify = [System.IO.File]::ReadAllBytes($Path)
if (([BitConverter]::ToUInt16($verify, $charOffset) -band 0x0020) -eq 0) { return 'FAILED' }
return 'patched'
}
$target = '${{ env.dotnetExecutable }}'
if (Test-Path $target) {
echo ("{0,-9} {1}" -f (Set-LargeAddressAware -Path $target), $target)
} else {
echo "missing: $target"
}
- name: .NET Information After SDK Install
shell: pwsh
run: try { & '${{ env.dotnetExecutable }}' --info } catch { echo 'No ${{ matrix.aceArchitecture }} .NET SDK installed.' }
- name: ACE Information Before ACE Install
shell: pwsh
run: |
'DAO:'
Get-ChildItem 'HKLM:\SOFTWARE\Classes\DAO.DBEngine*' | Select-Object
'OLE DB:'
foreach ($provider in [System.Data.OleDb.OleDbEnumerator]::GetRootEnumerator())
{
$v = New-Object PSObject
for ($i = 0; $i -lt $provider.FieldCount; $i++)
{
Add-Member -in $v NoteProperty $provider.GetName($i) $provider.GetValue($i)
}
$v
}
- name: Install Access Database Engine
shell: pwsh
run: |
$setupFileName = 'AccessDatabaseEngine_${{ matrix.aceVersion }}_${{ matrix.aceArchitecture }}.exe'
Invoke-WebRequest '${{ env.aceUrl }}' -OutFile $setupFileName
& ".\$setupFileName" ${{ env.aceSilentInstallArgument }} | Out-Default
- name: ACE Information After ACE Install
shell: pwsh
run: |
'DAO:'
Get-ChildItem 'HKLM:\SOFTWARE\Classes\DAO.DBEngine*' | Select-Object
'OLE DB:'
foreach ($provider in [System.Data.OleDb.OleDbEnumerator]::GetRootEnumerator())
{
$v = New-Object PSObject
for ($i = 0; $i -lt $provider.FieldCount; $i++)
{
Add-Member -in $v NoteProperty $provider.GetName($i) $provider.GetValue($i)
}
$v
}
- name: Build Solution
shell: pwsh
run: |
& '${{ env.dotnetExecutable }}' build --configuration '${{ env.buildConfiguration }}'
# The apphosts the build emits are the other candidate for the process that ends up holding ACE,
# depending on whether VSTest launches the output-directory apphost or 'dotnet exec testhost.dll'.
# Shut the build server down first so nothing is still holding these open.
- name: 'Make the x86 test hosts large-address-aware'
if: matrix.aceArchitecture == 'x86'
shell: pwsh
run: |
function Set-LargeAddressAware {
param([string]$Path)
$bytes = [System.IO.File]::ReadAllBytes($Path)
$peOffset = [BitConverter]::ToInt32($bytes, 0x3C)
if ([BitConverter]::ToUInt32($bytes, $peOffset) -ne 0x00004550) { return 'not-a-pe' }
$machine = [BitConverter]::ToUInt16($bytes, $peOffset + 4)
if ($machine -ne 0x014C) { return "skipped-$('0x{0:X4}' -f $machine)" }
$charOffset = $peOffset + 22
$characteristics = [BitConverter]::ToUInt16($bytes, $charOffset)
if (($characteristics -band 0x0020) -ne 0) { return 'already' }
[BitConverter]::GetBytes([UInt16]($characteristics -bor 0x0020)).CopyTo($bytes, $charOffset)
[System.IO.File]::WriteAllBytes($Path, $bytes)
$verify = [System.IO.File]::ReadAllBytes($Path)
if (([BitConverter]::ToUInt16($verify, $charOffset) -band 0x0020) -eq 0) { return 'FAILED' }
return 'patched'
}
& '${{ env.dotnetExecutable }}' build-server shutdown | Out-Null
$results = @{}
foreach ($target in (Get-ChildItem -Path '. est' -Recurse -Filter '*.exe' -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match '\bin\' } | ForEach-Object { $_.FullName })) {
$state = Set-LargeAddressAware -Path $target
$results[$state] = 1 + ($results[$state] ?? 0)
if ($state -in 'patched', 'already', 'FAILED') {
echo ("{0,-9} {1}" -f $state, (Resolve-Path $target -Relative))
}
}
echo ''
echo (($results.GetEnumerator() | Sort-Object Name | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join ' ')
if ($results['FAILED']) { echo 'Could not set the flag on one or more binaries.'; exit 1 }
- name: 'Run Tests: EFCore.Jet.Data.Tests'
if: always() && env.skipTests != 'true'
shell: pwsh
run: |
$env:EFCoreJet_DefaultConnection = '${{ env.defaultConnection }}'
# No --verbosity detailed: it dumps every test's captured output (EF logs the compiled shaper
# expression tree per query under the Query category), which ran to ~62,000 lines in 43s per shard
# and got truncated by GitHub anyway. Nothing consumes it - the green-tests extraction parses the
# trx, and crash detection looks for Sequence_* blame dumps - so it cost console I/O for a log too
# big to read. On these sequential suites those writes sit directly on the critical path.
& '${{ env.dotnetExecutable }}' test .\test\EFCore.Jet.Data.Tests --configuration '${{ env.buildConfiguration }}' -p:FixedTestOrder=${{ env.deterministicTests }} --logger trx --blame-hang-timeout 3m
# EFCore.Jet.Tests is not run: the project compiles to an assembly with no tests in it. Its only two
# test files have been <Compile Remove>d since 45bae420 (the initial EF 9 update, July 2024), so the
# step spent two years reporting green over an empty run. Much of what is in those files appears to
# be covered by EFCore.Jet.FunctionalTests now; restore the step if they come back into the build.
- name: 'Run Tests: EFCore.Jet.FunctionalTests (Shard 1 - Query Core)'
if: always() && env.skipTests != 'true'
shell: pwsh
run: |
$shardDir = '.\test\EFCore.Jet.FunctionalTests\TestResults\shard1'
for ($i = 0; $i -lt 3; $i++) {
if (Test-Path $shardDir -PathType Container) {
Get-ChildItem $shardDir | Remove-Item -Recurse -Force
}
$env:EFCoreJet_DefaultConnection = '${{ env.defaultConnection }}'
& '${{ env.dotnetExecutable }}' test .\test\EFCore.Jet.FunctionalTests --configuration '${{ env.buildConfiguration }}' -p:FixedTestOrder=${{ env.deterministicTests }} --logger trx --blame-hang-timeout 3m --results-directory $shardDir --filter "FullyQualifiedName~.FunctionalTests.Query.&FullyQualifiedName!~Northwind&FullyQualifiedName!~GearsOfWar"
#
# Check for test runner crashes:
#
$currentTestRunTrx = Get-ChildItem $shardDir -Filter '*.trx' | Sort-Object LastWriteTime | Select-Object -Last 1
if ($null -eq $currentTestRunTrx) {
echo 'Test runner log file is missing.'
exit 3
}
$currentTestRunDir = Join-Path $shardDir $currentTestRunTrx.BaseName
if (Test-Path $currentTestRunDir) {
if ($null -ne (Get-ChildItem $currentTestRunDir -Filter 'Sequence_*' -Recurse)) {
# Split string because searching the log for that phrase should only show actual crashes and not this line.
echo ('Test runner cras' + 'hed.')
continue
}
}
echo 'Test runner ran until the end.'
break
}
$establishedGreenTestsFilePath = ".\test\EFCore.Jet.FunctionalTests\GreenTests\ace_${{ matrix.aceVersion }}_$('${{ matrix.dataAccessProviderType }}'.Replace(' ', '').ToLowerInvariant())_${{ matrix.aceArchitecture }}.txt"
$failIfKeepsCrashing = Test-Path $establishedGreenTestsFilePath
if ($i -ge 3 -and $failIfKeepsCrashing) {
echo 'Test runner keeps crashing.'
exit 2
}
exit 0
- name: 'Run Tests: EFCore.Jet.FunctionalTests (Shard 2 - Query Associations and Translations)'
if: always() && env.skipTests != 'true'
shell: pwsh
run: |
$shardDir = '.\test\EFCore.Jet.FunctionalTests\TestResults\shard2'
for ($i = 0; $i -lt 3; $i++) {
if (Test-Path $shardDir -PathType Container) {
Get-ChildItem $shardDir | Remove-Item -Recurse -Force
}
$env:EFCoreJet_DefaultConnection = '${{ env.defaultConnection }}'
& '${{ env.dotnetExecutable }}' test .\test\EFCore.Jet.FunctionalTests --configuration '${{ env.buildConfiguration }}' -p:FixedTestOrder=${{ env.deterministicTests }} --logger trx --blame-hang-timeout 3m --results-directory $shardDir --filter "FullyQualifiedName~.FunctionalTests.Query.Northwind|FullyQualifiedName~GearsOfWar"
#
# Check for test runner crashes:
#
$currentTestRunTrx = Get-ChildItem $shardDir -Filter '*.trx' | Sort-Object LastWriteTime | Select-Object -Last 1
if ($null -eq $currentTestRunTrx) {
echo 'Test runner log file is missing.'
exit 3
}
$currentTestRunDir = Join-Path $shardDir $currentTestRunTrx.BaseName
if (Test-Path $currentTestRunDir) {
if ($null -ne (Get-ChildItem $currentTestRunDir -Filter 'Sequence_*' -Recurse)) {
# Split string because searching the log for that phrase should only show actual crashes and not this line.
echo ('Test runner cras' + 'hed.')
continue
}
}
echo 'Test runner ran until the end.'
break
}
$establishedGreenTestsFilePath = ".\test\EFCore.Jet.FunctionalTests\GreenTests\ace_${{ matrix.aceVersion }}_$('${{ matrix.dataAccessProviderType }}'.Replace(' ', '').ToLowerInvariant())_${{ matrix.aceArchitecture }}.txt"
$failIfKeepsCrashing = Test-Path $establishedGreenTestsFilePath
if ($i -ge 3 -and $failIfKeepsCrashing) {
echo 'Test runner keeps crashing.'
exit 2
}
exit 0
- name: 'Run Tests: EFCore.Jet.FunctionalTests (Shard 3 - Non-Query, excluding CompiledModel)'
if: always() && env.skipTests != 'true'
shell: pwsh
run: |
$shardDir = '.\test\EFCore.Jet.FunctionalTests\TestResults\shard3'
for ($i = 0; $i -lt 3; $i++) {
if (Test-Path $shardDir -PathType Container) {
Get-ChildItem $shardDir | Remove-Item -Recurse -Force
}
$env:EFCoreJet_DefaultConnection = '${{ env.defaultConnection }}'
& '${{ env.dotnetExecutable }}' test .\test\EFCore.Jet.FunctionalTests --configuration '${{ env.buildConfiguration }}' -p:FixedTestOrder=${{ env.deterministicTests }} --logger trx --blame-hang-timeout 3m --results-directory $shardDir --filter "FullyQualifiedName!~.FunctionalTests.Query.&FullyQualifiedName!~CompiledModel"
#
# Check for test runner crashes:
#
$currentTestRunTrx = Get-ChildItem $shardDir -Filter '*.trx' | Sort-Object LastWriteTime | Select-Object -Last 1
if ($null -eq $currentTestRunTrx) {
echo 'Test runner log file is missing.'
exit 3
}
$currentTestRunDir = Join-Path $shardDir $currentTestRunTrx.BaseName
if (Test-Path $currentTestRunDir) {
if ($null -ne (Get-ChildItem $currentTestRunDir -Filter 'Sequence_*' -Recurse)) {
# Split string because searching the log for that phrase should only show actual crashes and not this line.
echo ('Test runner cras' + 'hed.')
continue
}
}
echo 'Test runner ran until the end.'
break
}
$establishedGreenTestsFilePath = ".\test\EFCore.Jet.FunctionalTests\GreenTests\ace_${{ matrix.aceVersion }}_$('${{ matrix.dataAccessProviderType }}'.Replace(' ', '').ToLowerInvariant())_${{ matrix.aceArchitecture }}.txt"
$failIfKeepsCrashing = Test-Path $establishedGreenTestsFilePath
if ($i -ge 3 -and $failIfKeepsCrashing) {
echo 'Test runner keeps crashing.'
exit 2
}
exit 0
- name: 'Run Tests: EFCore.Jet.FunctionalTests (Shard 4 - CompiledModel)'
if: always() && env.skipTests != 'true'
shell: pwsh
run: |
$shardDir = '.\test\EFCore.Jet.FunctionalTests\TestResults\shard4'
for ($i = 0; $i -lt 3; $i++) {
if (Test-Path $shardDir -PathType Container) {
Get-ChildItem $shardDir | Remove-Item -Recurse -Force
}
$env:EFCoreJet_DefaultConnection = '${{ env.defaultConnection }}'
& '${{ env.dotnetExecutable }}' test .\test\EFCore.Jet.FunctionalTests --configuration '${{ env.buildConfiguration }}' -p:FixedTestOrder=${{ env.deterministicTests }} --logger trx --blame-hang-timeout 3m --results-directory $shardDir --filter "FullyQualifiedName~CompiledModel"
#
# Check for test runner crashes:
#
$currentTestRunTrx = Get-ChildItem $shardDir -Filter '*.trx' | Sort-Object LastWriteTime | Select-Object -Last 1
if ($null -eq $currentTestRunTrx) {
echo 'Test runner log file is missing.'
exit 3
}
$currentTestRunDir = Join-Path $shardDir $currentTestRunTrx.BaseName
if (Test-Path $currentTestRunDir) {
if ($null -ne (Get-ChildItem $currentTestRunDir -Filter 'Sequence_*' -Recurse)) {
# Split string because searching the log for that phrase should only show actual crashes and not this line.
echo ('Test runner cras' + 'hed.')
continue
}
}
echo 'Test runner ran until the end.'
break
}
$establishedGreenTestsFilePath = ".\test\EFCore.Jet.FunctionalTests\GreenTests\ace_${{ matrix.aceVersion }}_$('${{ matrix.dataAccessProviderType }}'.Replace(' ', '').ToLowerInvariant())_${{ matrix.aceArchitecture }}.txt"
$failIfKeepsCrashing = Test-Path $establishedGreenTestsFilePath
if ($i -ge 3 -and $failIfKeepsCrashing) {
echo 'Test runner keeps crashing.'
exit 2
}
exit 0
- name: 'Rename Test Results'
if: always() && env.skipTests != 'true'
shell: pwsh
run: |
Get-ChildItem -Filter '*.trx' -Recurse | Sort-Object LastWriteTime | ForEach { Rename-Item $_.FullName "ace_${{ matrix.aceVersion }}_$('${{ matrix.dataAccessProviderType }}'.Replace(' ', '').ToLowerInvariant())_${{ matrix.aceArchitecture }}_$($_.Name)" -Verbose }
- name: 'Upload Test Results'
if: always() && env.skipTests != 'true' && env.uploadTestResults == 'true'
uses: actions/upload-artifact@v6
with:
name: test-results_${{ env.matrixId }}
path: |
test\EFCore.Jet.Data.Tests\TestResults\*.trx
test\EFCore.Jet.FunctionalTests\TestResults\**\*.trx
- name: 'Check Tests: EFCore.Jet.FunctionalTests'
if: env.skipTests != 'true'
shell: pwsh
run: |
# Collect and merge test results from all shards.
$testResultsDir = '.\test\EFCore.Jet.FunctionalTests\TestResults'
$allTrxFiles = @(Get-ChildItem $testResultsDir -Filter '*.trx' -Recurse)
if ($allTrxFiles.Count -eq 0) {
echo 'No test result files found.'
exit 3
}
$allNodes = @()
foreach ($trxFile in $allTrxFiles) {
$allNodes += (Select-Xml -Path $trxFile.FullName -XPath "//ns:UnitTestResult" -Namespace @{"ns"="http://microsoft.com/schemas/VisualStudio/TeamTest/2010"}).Node
}
$allTestsFilePath = Join-Path $testResultsDir 'combined_All.txt'
$allNodes | Sort-Object -Property testName -CaseSensitive | ForEach-Object { "$($_.outcome -eq 'Passed' ? 'P' : $_.outcome -eq 'NotExecuted' ? 'N' : $_.outcome -eq 'Failed' ? 'F' : 'U') $($_.testName)" } | Set-Content $allTestsFilePath
$greenTestsFilePath = Join-Path $testResultsDir 'combined_Passed.txt'
Get-Content $allTestsFilePath | Where-Object { $_.StartsWith('P ') } | ForEach-Object { $_.Substring(2) } | Set-Content $greenTestsFilePath
# Compare test file against previously committed file.
$establishedGreenTestsFilePath = ".\test\EFCore.Jet.FunctionalTests\GreenTests\ace_${{ matrix.aceVersion }}_$('${{ matrix.dataAccessProviderType }}'.Replace(' ', '').ToLowerInvariant())_${{ matrix.aceArchitecture }}.txt"
if (Test-Path $establishedGreenTestsFilePath) {
$diffResult = Compare-Object (Get-Content $establishedGreenTestsFilePath) (Get-Content $greenTestsFilePath)
$notGreenAnymore = $diffResult | Where-Object { $_.SideIndicator -eq '<=' } | Select-Object -ExpandProperty InputObject
if ($null -ne $notGreenAnymore) {
echo "`nThe following $(@($notGreenAnymore).Length) tests passed in previous runs, but didn't pass in this run:`n"
$notGreenAnymore
exit 1
}
echo 'All tests that passed in previous runs still passed in this run.'
$newlyGreenTests = $diffResult | Where-Object { $_.SideIndicator -eq '=>' } | Select-Object -ExpandProperty InputObject
if ($newlyGreenTests.Length -gt 0) {
Copy-Item $greenTestsFilePath $establishedGreenTestsFilePath -Force -Verbose
echo "`nThe following new tests passed that did not pass before:`n"
$newlyGreenTests
$commitGreenTestsFile = $establishedGreenTestsFilePath
echo "commitGreenTestsFile=$commitGreenTestsFile" >> $env:GITHUB_ENV
}
}
echo 'Check succeeded.'
- name: 'Upload Green Tests'
if: ${{ env.commitGreenTestsFile != '' }}
uses: actions/upload-artifact@v6
with:
name: green-tests_${{ env.matrixId }}
path: ${{ env.commitGreenTestsFile }}
LibRed:
needs:
- Preconditions
- Changes
if: (needs.Preconditions.outputs.lastCommitIsAutoCommit != 'true' || needs.Preconditions.outputs.lastCommitCreatedBeforeSeconds > 300) && needs.Changes.outputs.libred == 'true'
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-latest
# ARM64 legs. LibRed is fully managed with no ACE dependency, so these are the ones that
# actually prove the cross-platform claim on a non-x64 architecture. Excludes LibRedAccess,
# which cross-checks against the real Access engine and so needs Windows x64 with ACE.
- ubuntu-24.04-arm
- windows-11-arm
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v5
# Defender scans every .accdb these suites write; see the action for the measurement.
- name: Exclude test files from Defender
uses: ./.github/actions/exclude-defender
- name: Install .NET SDK
uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
- name: .NET Information
run: dotnet --info
- name: 'Run Tests: LibRed.Engine.Tests'
if: env.skipTests != 'true'
run: dotnet test ./test/LibRed.Engine.Tests --configuration ${{ env.buildConfiguration }} -p:FixedTestOrder=${{ env.deterministicTests }} --blame-hang-timeout 3m
# The file-format half of LibRed.Core.Tests. It reads and writes .accdb files with LibRed alone — the
# cross-checks against the real Access engine live in LibRed.Core.AccessTests — so it belongs on this
# matrix rather than the Windows one, and reading the on-disk format on Linux/macOS/ARM64 is the part
# of the cross-platform claim that was previously only asserted for the engine.
- name: 'Run Tests: LibRed.Core.Tests'
if: always() && env.skipTests != 'true'
run: dotnet test ./test/LibRed.Core.Tests --configuration ${{ env.buildConfiguration }} -p:FixedTestOrder=${{ env.deterministicTests }} --blame-hang-timeout 3m
# The rest of the LibRed suites cross-check LibRed's output against the real engine over OLE DB,
# so unlike the job above they do need Windows with ACE installed.
LibRedAccess:
needs:
- Preconditions
- Changes
if: (needs.Preconditions.outputs.lastCommitIsAutoCommit != 'true' || needs.Preconditions.outputs.lastCommitCreatedBeforeSeconds > 300) && needs.Changes.outputs.libred == 'true'
strategy:
fail-fast: false
matrix:
aceVersion:
- 2016
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v5
# Defender scans every .accdb these suites write; see the action for the measurement.
- name: Exclude test files from Defender
uses: ./.github/actions/exclude-defender
- name: Install .NET SDK
uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
- name: Install Access Database Engine
shell: pwsh
run: |
$setupFileName = 'AccessDatabaseEngine_${{ matrix.aceVersion }}_x64.exe'
Invoke-WebRequest "https://cirrusredorg.github.io/EntityFrameworkCore.Jet/$setupFileName" -OutFile $setupFileName
& ".\$setupFileName" /passive /quiet /norestart REBOOT=ReallySuppress | Out-Default
- name: 'ACE Information'
shell: pwsh
run: |
foreach ($provider in [System.Data.OleDb.OleDbEnumerator]::GetRootEnumerator())
{
$v = New-Object PSObject
for ($i = 0; $i -lt $provider.FieldCount; $i++)
{
Add-Member -in $v NoteProperty $provider.GetName($i) $provider.GetValue($i)
}
$v
}
# The file-format tests that cross-check against ACE. Their ACE-free half runs on the five-platform
# LibRed job above.
- name: 'Run Tests: LibRed.Core.AccessTests'
if: env.skipTests != 'true'
shell: pwsh
run: dotnet test .\test\LibRed.Core.AccessTests --configuration '${{ env.buildConfiguration }}' -p:FixedTestOrder=${{ env.deterministicTests }} --blame-hang-timeout 5m
# The engine tests that cross-check against ACE. They belong here rather than in the cross-platform
# LibRed job above, which runs on five platforms precisely to prove LibRed needs no ACE at all.
- name: 'Run Tests: LibRed.Engine.AccessTests'
if: always() && env.skipTests != 'true'
shell: pwsh
run: dotnet test .\test\LibRed.Engine.AccessTests --configuration '${{ env.buildConfiguration }}' -p:FixedTestOrder=${{ env.deterministicTests }} --blame-hang-timeout 5m
- name: 'Run Tests: LibRed.Ado.Tests'
if: always() && env.skipTests != 'true'
shell: pwsh
run: dotnet test .\test\LibRed.Ado.Tests --configuration '${{ env.buildConfiguration }}' -p:FixedTestOrder=${{ env.deterministicTests }} --blame-hang-timeout 5m
- name: 'Run Tests: LibRed.EFCore.Tests'
if: always() && env.skipTests != 'true'
shell: pwsh
run: dotnet test .\test\LibRed.EFCore.Tests --configuration '${{ env.buildConfiguration }}' -p:FixedTestOrder=${{ env.deterministicTests }} --blame-hang-timeout 5m
LibRedFunctional:
needs:
- Preconditions
- Changes
# Non-blocking for now: both suites still fail in the low hundreds out of ~38,000 run, so the job would
# be red on every push and quickly learn to be ignored. It still runs on every push and the log is there
# to read. Drop this line once the remaining failures are fixed or skipped.
continue-on-error: true
if: (needs.Preconditions.outputs.lastCommitIsAutoCommit != 'true' || needs.Preconditions.outputs.lastCommitCreatedBeforeSeconds > 300) && needs.Changes.outputs.libred == 'true'
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-latest
# ARM64 legs. LibRed is fully managed with no ACE dependency, so these are the ones that
# actually prove the cross-platform claim on a non-x64 architecture. Excludes LibRedAccess,
# which cross-checks against the real Access engine and so needs Windows x64 with ACE.
- ubuntu-24.04-arm
- windows-11-arm
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v5
# Defender scans every .accdb these suites write; see the action for the measurement.
- name: Exclude test files from Defender
uses: ./.github/actions/exclude-defender
- name: Install .NET SDK
uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
# No Access engine on ANY leg now. Windows used to install ACE as a control, on the reasoning that a
# test failing only off Windows would be a real cross-platform gap - but all three legs have since
# agreed exactly (35,578 passed / 179 failed / 2,016 skipped), so nothing here needs ACE and the
# control has served its purpose. LibRedAccess still installs it, which is where the cross-checks
# against the real engine belong.
- name: 'Run Tests: EFCore.LibRed.FunctionalTests'
if: env.skipTests != 'true'
run: dotnet test test/EFCore.LibRed.FunctionalTests --configuration ${{ env.buildConfiguration }} -p:FixedTestOrder=${{ env.deterministicTests }} --blame-hang-timeout 10m
# The same specification suite against extended mode, where the generator emits standard SQL instead of
# Jet dialect workarounds. It has its own baselines, so it is a separate project rather than a matrix leg.
# always(), so a failure in the compat suite above does not skip this one.
- name: 'Run Tests: EFCore.LibRed.Extended.FunctionalTests'
if: always() && env.skipTests != 'true'
run: dotnet test test/EFCore.LibRed.Extended.FunctionalTests --configuration ${{ env.buildConfiguration }} -p:FixedTestOrder=${{ env.deterministicTests }} --blame-hang-timeout 10m
MergeArtifacts:
needs: BuildAndTest
if: always()
outputs:
testResultsAvailable: ${{ steps.MergeTestResults.conclusion == 'success' }}
greenTestsAvailable: ${{ steps.MergeGreenTests.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- name: 'Check Test Results Artifacts'
id: CheckTestResultsArtifacts
uses: actions/github-script@v8
with:
script: |
var allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: '${{ github.run_id }}',
});
var artifacts = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name.startsWith("test-results_");
});
if (artifacts.length > 0) {
core.setOutput('artifactsAvailable', 'true');
console.log('Test results artifacts found.');
}
- name: 'Merge Test Results'
id: MergeTestResults
if: steps.CheckTestResultsArtifacts.outputs.artifactsAvailable == 'true'
uses: actions/upload-artifact/merge@v6
with:
name: test-results
pattern: test-results_*
delete-merged: true
- name: 'Check Green Tests Artifacts'
id: CheckGreenTestsArtifacts
uses: actions/github-script@v8
with:
script: |
var allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: '${{ github.run_id }}',
});
var artifacts = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name.startsWith("green-tests_");
});
if (artifacts.length > 0) {
core.setOutput('artifactsAvailable', 'true');
console.log('Green Tests Artifacts found.');
}
- name: 'Merge Green Tests'
id: MergeGreenTests
if: steps.CheckGreenTestsArtifacts.outputs.artifactsAvailable == 'true'
uses: actions/upload-artifact/merge@v6
with:
name: green-tests
pattern: green-tests_*
delete-merged: true
NuGet:
if: (github.event_name == 'push' || github.event_name == 'release') && github.repository == 'CirrusRedOrg/EntityFrameworkCore.Jet'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup .NET SDK
uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
- name: .NET Information
shell: pwsh
run: |
dotnet --info
- name: NuGet Pack
shell: pwsh
run: |
$officialBuild = '${{ github.ref }}' -match '(?<=^refs/tags/v)\d+\.\d+\.\d+.*$'
$officialVersion = $Matches.0
$wipBuild = '${{ github.ref }}' -match '^refs/heads/.*-wip$'
$ciBuildOnly = $wipBuild -or ('${{ github.ref }}' -match '^refs/heads/(?:master|.*-servicing)$')
$continuousIntegrationTimestamp = Get-Date -Format yyyyMMddHHmmss
$buildSha = '${{ github.sha }}'.SubString(0, 7);
$pack = $officialBuild -or $ciBuildOnly
$pushToAzureArtifacts = $pack
$pushToMygetOrg = $pack
$pushToNugetOrg = $pack -and $officialBuild
echo "pushToAzureArtifacts: $pushToAzureArtifacts"
echo "pushToMygetOrg: $pushToMygetOrg"
echo "pushToNugetOrg: $pushToNugetOrg"
echo "officialBuild: $officialBuild"
echo "officialVersion: $officialVersion"
echo "wipBuild: $wipBuild"
echo "ciBuildOnly: $ciBuildOnly"
echo "continuousIntegrationTimestamp: $continuousIntegrationTimestamp"
echo "buildSha: $buildSha"
echo "pack: $pack"
if ($pack)
{
$projectFiles = Get-ChildItem src/*/*.csproj -Recurse | % { $_.FullName }
$combinations = @('default', @('Release')), @('withPdbs', @('Release', 'Debug')) #, @('embeddedPdbs', @('Release', 'Debug'))
foreach ($combination in $combinations)
{
$type = $combination[0]
$configurations = $combination[1]
foreach ($configuration in $configurations)
{
$arguments = 'pack', '-c', $configuration, '-o', "nupkgs/$configuration/$type", '-p:ContinuousIntegrationBuild=true'
if ($officialBuild)
{
$arguments += "-p:OfficialVersion=$officialVersion"
}
if ($ciBuildOnly)
{
$arguments += "-p:ContinuousIntegrationTimestamp=$continuousIntegrationTimestamp"
$arguments += "-p:BuildSha=$buildSha"
}
switch ($type)
{
'withPdbs' { $arguments += '-p:PackPdb=true', '-p:IncludeSymbols=false' }
'embeddedPdbs' { $arguments += '-p:DebugType=embedded', '-p:IncludeSymbols=false' }
}
foreach ($projectFile in $projectFiles)
{
echo "Type: $type, Configuration: $configuration, Project: $projectFile"
echo "Pack command: dotnet $(($arguments + $projectFile) -join ' ')"
& dotnet ($arguments + $projectFile)
}
}
}
}
echo "pushToAzureArtifacts=$pushToAzureArtifacts" >> $env:GITHUB_ENV
echo "pushToMygetOrg=$pushToMygetOrg" >> $env:GITHUB_ENV
echo "pushToNugetOrg=$pushToNugetOrg" >> $env:GITHUB_ENV
- name: Upload Artifacts
uses: actions/upload-artifact@v6
with:
name: nupkgs
path: nupkgs
- name: "NuGet Push - myget.org - Debug"
if: ${{ env.pushToMygetOrg == 'true' }}
working-directory: nupkgs
shell: pwsh
run: dotnet nuget push './Debug/withPdbs/**/*.nupkg' --api-key '${{ secrets.MYGETORG_CIRRUSRED_ALLPACKAGES_DEBUG_PUSHNEW }}' --source 'https://www.myget.org/F/cirrusred-debug/api/v3/index.json' --skip-duplicate
- name: "NuGet Push - myget.org - Release"
if: ${{ env.pushToMygetOrg == 'true' }}
working-directory: nupkgs
shell: pwsh
run: dotnet nuget push './Release/default/**/*.nupkg' --api-key '${{ secrets.MYGETORG_CIRRUSRED_ALLPACKAGES_PUSHNEW }}' --source 'https://www.myget.org/F/cirrusred/api/v3/index.json' --skip-duplicate
- name: "NuGet Push - nuget.org - Release"
if: ${{ env.pushToNugetOrg == 'true' }}
working-directory: nupkgs
shell: pwsh
run: dotnet nuget push './Release/default/**/*.nupkg' --api-key '${{ secrets.NUGETORG_EFCOREJET_ALLPACKAGES_PUSHNEW }}' --source 'https://api.nuget.org/v3/index.json' --skip-duplicate