diff --git a/.github/actions/test/linux-packaging/action.yml b/.github/actions/test/linux-packaging/action.yml new file mode 100644 index 00000000000..61d23742056 --- /dev/null +++ b/.github/actions/test/linux-packaging/action.yml @@ -0,0 +1,95 @@ +name: linux_packaging +description: 'Test very basic Linux packaging' + +# This isn't working yet +# It fails with + +# ERROR: While executing gem ... (Gem::FilePermissionError) +# You don't have write permissions for the /var/lib/gems/2.7.0 directory. +# WARNING: Installation of gem dotenv 2.8.1 failed! Must resolve manually. + +runs: + using: composite + steps: + - name: Capture Environment + if: success() || failure() + run: 'Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose' + shell: pwsh + - name: Download Build Artifacts + uses: actions/download-artifact@v4 + with: + path: "${{ github.workspace }}" + - name: Capture Artifacts Directory + continue-on-error: true + run: Get-ChildItem "${{ github.workspace }}/build/*" -Recurse + shell: pwsh + + - name: Bootstrap + run: |- + Import-Module ./build.psm1 + Start-PSBootstrap -Package + shell: pwsh + - name: Capture Artifacts Directory + continue-on-error: true + run: Import-Module ./build.psm1 + shell: pwsh + - name: Extract Files + uses: actions/github-script@v7.0.0 + env: + DESTINATION_FOLDER: "${{ github.workspace }}/bins" + ARCHIVE_FILE_PATTERNS: "${{ github.workspace }}/build/build.zip" + with: + script: |- + const fs = require('fs').promises + const path = require('path') + const target = path.resolve(process.env.DESTINATION_FOLDER) + const patterns = process.env.ARCHIVE_FILE_PATTERNS + const globber = await glob.create(patterns) + await io.mkdirP(path.dirname(target)) + for await (const file of globber.globGenerator()) { + if ((await fs.lstat(file)).isDirectory()) continue + await exec.exec(`7z x ${file} -o${target} -aoa`) + } + - name: Fix permissions + continue-on-error: true + run: |- + find "${{ github.workspace }}/bins" -type d -exec chmod +rwx {} \; + find "${{ github.workspace }}/bins" -type f -exec chmod +rw {} \; + shell: bash + - name: Capture Extracted Build ZIP + continue-on-error: true + run: Get-ChildItem "${{ github.workspace }}/bins/*" -Recurse -ErrorAction SilentlyContinue + shell: pwsh + - name: Packaging Tests + if: success() + run: |- + Import-Module ./tools/ci.psm1 + Restore-PSOptions -PSOptionsPath '${{ github.workspace }}/build/psoptions.json' + $options = (Get-PSOptions) + $rootPath = '${{ github.workspace }}/bins' + $originalRootPath = Split-Path -path $options.Output + $path = Join-Path -path $rootPath -ChildPath (split-path -leaf -path $originalRootPath) + $pwshPath = Join-Path -path $path -ChildPath 'pwsh' + chmod a+x $pwshPath + $options.Output = $pwshPath + Set-PSOptions $options + Invoke-CIFinish + shell: pwsh + - name: Upload packages + run: |- + Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}/*.deb" -Recurse | ForEach-Object { + $packagePath = $_.FullName + Write-Host "Uploading $packagePath" + Write-Host "##vso[artifact.upload containerfolder=deb;artifactname=deb]$packagePath" + } + Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}/*.rpm" -Recurse | ForEach-Object { + $packagePath = $_.FullName + Write-Host "Uploading $packagePath" + Write-Host "##vso[artifact.upload containerfolder=rpm;artifactname=rpm]$packagePath" + } + Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}/*.tar.gz" -Recurse | ForEach-Object { + $packagePath = $_.FullName + Write-Host "Uploading $packagePath" + Write-Host "##vso[artifact.upload containerfolder=rpm;artifactname=rpm]$packagePath" + } + shell: pwsh diff --git a/.github/actions/test/nix/action.yml b/.github/actions/test/nix/action.yml new file mode 100644 index 00000000000..97575b6b54d --- /dev/null +++ b/.github/actions/test/nix/action.yml @@ -0,0 +1,91 @@ +name: nix_test +description: 'Test PowerShell on non-Windows platforms' + +inputs: + purpose: + required: false + default: '' + type: string + tagSet: + required: false + default: CI + type: string + ctrfFolder: + required: false + default: ctrf + type: string + +runs: + using: composite + steps: + - name: Capture Environment + if: success() || failure() + run: 'Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose' + shell: pwsh + - name: Download Build Artifacts + uses: actions/download-artifact@v4 + with: + path: "${{ github.workspace }}" + - name: Capture Artifacts Directory + continue-on-error: true + run: Get-ChildItem "${{ github.workspace }}/build/*" -Recurse + shell: pwsh + + - name: Bootstrap + shell: pwsh + run: |- + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser + + - name: Extract Files + uses: actions/github-script@v7.0.0 + env: + DESTINATION_FOLDER: "${{ github.workspace }}/bins" + ARCHIVE_FILE_PATTERNS: "${{ github.workspace }}/build/build.zip" + with: + script: |- + const fs = require('fs').promises + const path = require('path') + const target = path.resolve(process.env.DESTINATION_FOLDER) + const patterns = process.env.ARCHIVE_FILE_PATTERNS + const globber = await glob.create(patterns) + await io.mkdirP(path.dirname(target)) + for await (const file of globber.globGenerator()) { + if ((await fs.lstat(file)).isDirectory()) continue + await exec.exec(`7z x ${file} -o${target} -aoa`) + } + + - name: Fix permissions + continue-on-error: true + run: |- + find "${{ github.workspace }}/bins" -type d -exec chmod +rwx {} \; + find "${{ github.workspace }}/bins" -type f -exec chmod +rw {} \; + shell: bash + + - name: Capture Extracted Build ZIP + continue-on-error: true + run: Get-ChildItem "${{ github.workspace }}/bins/*" -Recurse -ErrorAction SilentlyContinue + shell: pwsh + + - name: Test + if: success() + run: |- + Import-Module ./tools/ci.psm1 + Restore-PSOptions -PSOptionsPath '${{ github.workspace }}/build/psoptions.json' + $options = (Get-PSOptions) + $rootPath = '${{ github.workspace }}/bins' + $originalRootPath = Split-Path -path $options.Output + $path = Join-Path -path $rootPath -ChildPath (split-path -leaf -path $originalRootPath) + $pwshPath = Join-Path -path $path -ChildPath 'pwsh' + chmod a+x $pwshPath + $options.Output = $pwshPath + Set-PSOptions $options + Invoke-CITest -Purpose '${{ inputs.purpose }}' -TagSet '${{ inputs.tagSet }}' -TitlePrefix '${{ inputs.buildName }}' -OutputFormat JUnitXml + shell: pwsh + + - name: Convert, Publish, and Upload Pester Test Results + uses: "./.github/actions/test/process-pester-results" + with: + name: "${{ inputs.purpose }}-${{ inputs.tagSet }}" + testResultsFolder: "${{ runner.workspace }}/testResults" + ctrfFolder: "${{ inputs.ctrfFolder }}" diff --git a/.github/actions/test/process-pester-results/action.yml b/.github/actions/test/process-pester-results/action.yml new file mode 100644 index 00000000000..758bbdfc353 --- /dev/null +++ b/.github/actions/test/process-pester-results/action.yml @@ -0,0 +1,64 @@ +name: process-pester-test-results +description: 'Process Pester test results' + +inputs: + name: + required: true + default: '' + type: string + testResultsFolder: + required: false + default: "${{ runner.workspace }}/testResults" + type: string + ctrfFolder: + required: false + default: ctrf + type: string + +runs: + using: composite + steps: + - name: Convert JUnit to CTRF + run: |- + Get-ChildItem -Path "${{ inputs.testResultsFolder }}/*.xml" -Recurse | ForEach-Object { + npx --yes junit-to-ctrf $_.FullName --output ./${{ inputs.ctrfFolder }}/$($_.BaseName).json --tool Pester + } + shell: pwsh + + # this task only takes / as directory separators + - name: Publish Test Report + uses: ctrf-io/github-test-reporter@v1 + with: + report-path: './${{ inputs.ctrfFolder }}/*.json' + exit-on-fail: true + summary-report: true + test-report: false + test-list-report: false + failed-report: false + fail-rate-report: false + flaky-report: false + flaky-rate-report: false + failed-folded-report: true + previous-results-report: false + ai-report: true + skipped-report: false + suite-folded-report: false + suite-list-report: false + pull-request-report: false + commit-report: false + custom-report: false + if: always() + + - name: Upload testResults artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: junit-pester-${{ inputs.name }} + path: ${{ runner.workspace }}/testResults + + - name: Upload ctrf artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: ctrf-pester-${{ inputs.name }} + path: ${{ inputs.ctrfFolder }} diff --git a/.github/actions/test/windows/action.yml b/.github/actions/test/windows/action.yml index 6cb5cbc1d74..c8e1c86024a 100644 --- a/.github/actions/test/windows/action.yml +++ b/.github/actions/test/windows/action.yml @@ -60,48 +60,9 @@ runs: Invoke-CITest -Purpose '${{ inputs.purpose }}' -TagSet '${{ inputs.tagSet }}' -OutputFormat JUnitXml shell: pwsh - - name: Convert JUnit to CTRF - run: |- - Get-ChildItem -Path "${{ runner.workspace }}/testResults/*.xml" -Recurse | ForEach-Object { - npx --yes junit-to-ctrf $_.FullName --output .\${{ inputs.ctrfFolder }}\$($_.BaseName).json --tool Pester --env 'Windows ${{ inputs.purpose }} ${{ inputs.tagSet }}' - } - shell: powershell - - # this task only takes / as directory separators - - name: Publish Test Report - uses: ctrf-io/github-test-reporter@v1 - with: - report-path: './${{ inputs.ctrfFolder }}/*.json' - exit-on-fail: true - summary-report: true - test-report: false - test-list-report: false - failed-report: false - fail-rate-report: false - flaky-report: false - flaky-rate-report: false - failed-folded-report: true - previous-results-report: false - ai-report: true - skipped-report: false - suite-folded-report: false - suite-list-report: false - pull-request-report: false - commit-report: false - custom-report: false - - if: always() - - - name: Upload testResults artifact - if: always() - uses: actions/upload-artifact@v4 - with: - name: junit-pester-${{ inputs.purpose }}-${{ inputs.tagSet }} - path: ${{ runner.workspace }}\testResults - - - name: Upload ctrf artifact - if: always() - uses: actions/upload-artifact@v4 + - name: Convert, Publish, and Upload Pester Test Results + uses: "./.github/actions/test/process-pester-results" with: - name: ctrf-pester-${{ inputs.purpose }}-${{ inputs.tagSet }} - path: ${{ inputs.ctrfFolder }} + name: "${{ inputs.purpose }}-${{ inputs.tagSet }}" + testResultsFolder: ${{ runner.workspace }}\testResults + ctrfFolder: "${{ inputs.ctrfFolder }}" diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml new file mode 100644 index 00000000000..0709bee66c2 --- /dev/null +++ b/.github/workflows/linux-ci.yml @@ -0,0 +1,141 @@ +name: Linux-CI + +run-name: "${{ github.ref_name }} - ${{ github.run_number }}" + +on: + workflow_dispatch: + + push: + branches: + - master + - release* + - feature* + paths: + - "**" + - "!.github/ISSUE_TEMPLATE/**" + - "!.dependabot/config.yml" + - "!.pipelines/**" + - "!test/perf/**" + pull_request: + branches: + - master + - release* + - feature* + paths: + - ".github/actions/**" + - ".github/workflows/linux-ci.yml" + - "**.props" + - build.psm1 + - src/** + - test/** + - tools/buildCommon/** + - tools/ci.psm1 + - tools/WindowsCI.psm1 + - "!test/common/markdown/**" + - "!test/perf/**" +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + FORCE_FEATURE: 'False' + FORCE_PACKAGE: 'False' + NUGET_KEY: none + POWERSHELL_TELEMETRY_OPTOUT: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + system_debug: 'false' +jobs: + ci_build: + name: Build PowerShell + runs-on: ubuntu-20.04 + steps: + - name: checkout + uses: actions/checkout@v4.1.0 + with: + fetch-depth: 1000 + - name: Build + uses: "./.github/actions/build/ci" + linux_test_unelevated_ci: + name: Linux Unelevated CI + needs: ci_build + runs-on: ubuntu-20.04 + steps: + - name: checkout + uses: actions/checkout@v4.1.0 + with: + fetch-depth: 1000 + - name: Linux Unelevated CI + uses: "./.github/actions/test/nix" + with: + purpose: UnelevatedPesterTests + tagSet: CI + linux_test_elevated_ci: + name: Linux Elevated CI + needs: ci_build + runs-on: ubuntu-20.04 + steps: + - name: checkout + uses: actions/checkout@v4.1.0 + with: + fetch-depth: 1000 + - name: Linux Elevated CI + uses: "./.github/actions/test/nix" + with: + purpose: ElevatedPesterTests + tagSet: CI + linux_test_unelevated_others: + name: Linux Unelevated Others + needs: ci_build + runs-on: ubuntu-20.04 + steps: + - name: checkout + uses: actions/checkout@v4.1.0 + with: + fetch-depth: 1000 + - name: Linux Unelevated Others + uses: "./.github/actions/test/nix" + with: + purpose: UnelevatedPesterTests + tagSet: Others + linux_test_elevated_others: + name: Linux Elevated Others + needs: ci_build + runs-on: ubuntu-20.04 + steps: + - name: checkout + uses: actions/checkout@v4.1.0 + with: + fetch-depth: 1000 + - name: Linux Elevated Others + uses: "./.github/actions/test/nix" + with: + purpose: ElevatedPesterTests + tagSet: Others + verify_xunit: + name: Verify xUnit test results + needs: ci_build + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4.1.0 + with: + fetch-depth: 1000 + - name: Verify xUnit test results + uses: "./.github/actions/test/verify_xunit" + + # TODO: Enable this when we have a Linux packaging workflow + + # ERROR: While executing gem ... (Gem::FilePermissionError) + # You don't have write permissions for the /var/lib/gems/2.7.0 directory. + # WARNING: Installation of gem dotenv 2.8.1 failed! Must resolve manually. + + # linux_packaging: + # name: Attempt Linux Packaging + # needs: ci_build + # runs-on: ubuntu-20.04 + # steps: + # - name: checkout + # uses: actions/checkout@v4.1.0 + # with: + # fetch-depth: 1000 + # - name: Verify xUnit test results + # uses: "./.github/actions/test/linux-packaging" diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index e82d881686e..718cd6ccf2b 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -7,30 +7,29 @@ on: - release* - feature* paths: - - "*" + - "**" - "!.vsts-ci/misc-analysis.yml" - - "!.github/ISSUE_TEMPLATE/*" - - "!.github/workflows/*" + - "!.github/ISSUE_TEMPLATE/**" - "!.dependabot/config.yml" - - "!test/perf/*" - - "!.pipelines/*" + - "!test/perf/**" + - "!.pipelines/**" pull_request: branches: - master - release* - feature* paths: - - ".vsts-ci/templates/*" - - ".vsts-ci/windows.yml" - - "*.props" + - ".github/actions/**" + - ".github/workflows/windows-ci.yml" + - "**.props" - build.psm1 - - src/* - - test/* - - tools/buildCommon/* + - src/** + - test/** + - tools/buildCommon/** - tools/ci.psm1 - tools/WindowsCI.psm1 - - "!test/common/markdown/*" - - "!test/perf/*" + - "!test/common/markdown/**" + - "!test/perf/**" permissions: contents: read diff --git a/tools/ci.psm1 b/tools/ci.psm1 index 7dda90f14f3..f09d159b4c8 100644 --- a/tools/ci.psm1 +++ b/tools/ci.psm1 @@ -259,7 +259,7 @@ function Invoke-CITest if($IsLinux -or $IsMacOS) { - return Invoke-LinuxTestsCore -Purpose $Purpose -ExcludeTag $ExcludeTag -TagSet $TagSet -TitlePrefix $TitlePrefix + return Invoke-LinuxTestsCore -Purpose $Purpose -ExcludeTag $ExcludeTag -TagSet $TagSet -TitlePrefix $TitlePrefix -OutputFormat $OutputFormat } # CoreCLR @@ -384,7 +384,8 @@ function Invoke-CITest } Write-Verbose -Verbose "Starting Pester with output format $($arguments.OutputFormat)" - Start-PSPester @arguments -Title $title + # We just built the test tools, we don't need to rebuild them + Start-PSPester @arguments -Title $title -SkipTestToolBuild # Fail the build, if tests failed Test-PSPesterResults -TestResultsFile $expFeatureTestResultFile @@ -702,7 +703,8 @@ function Invoke-LinuxTestsCore [string] $Purpose = 'All', [string[]] $ExcludeTag = @('Slow', 'Feature', 'Scenario'), [string] $TagSet = 'CI', - [string] $TitlePrefix + [string] $TitlePrefix, + [string] $OutputFormat = "NUnitXml" ) $output = Split-Path -Parent (Get-PSOutput -Options (Get-PSOptions)) @@ -715,12 +717,13 @@ function Invoke-LinuxTestsCore $sudoResultsWithExpFeatures = $null $noSudoPesterParam = @{ - 'BinDir' = $output - 'PassThru' = $true - 'Terse' = $true - 'Tag' = @() - 'ExcludeTag' = $testExcludeTag - 'OutputFile' = $testResultsNoSudo + 'BinDir' = $output + 'PassThru' = $true + 'Terse' = $true + 'Tag' = @() + 'ExcludeTag' = $testExcludeTag + 'OutputFile' = $testResultsNoSudo + 'OutputFormat' = $OutputFormat } # Get the experimental feature names and the tests associated with them @@ -758,7 +761,7 @@ function Invoke-LinuxTestsCore if ($TitlePrefix) { $title = "$TitlePrefix - $title" } - $passThruResult = Start-PSPester @noSudoPesterParam -Title $title + $passThruResult = Start-PSPester @noSudoPesterParam -Title $title -SkipTestToolBuild $noSudoResultsWithExpFeatures += $passThruResult } @@ -773,6 +776,7 @@ function Invoke-LinuxTestsCore $sudoPesterParam['ExcludeTag'] = $ExcludeTag $sudoPesterParam['Sudo'] = $true $sudoPesterParam['OutputFile'] = $testResultsSudo + $sudoPesterParam['OutputFormat'] = $OutputFormat $title = "Pester Sudo - $TagSet" if ($TitlePrefix) { @@ -805,7 +809,9 @@ function Invoke-LinuxTestsCore if ($TitlePrefix) { $title = "$TitlePrefix - $title" } - $passThruResult = Start-PSPester @sudoPesterParam -Title $title + + # We just built the test tools for the main test run, we don't need to rebuild them + $passThruResult = Start-PSPester @sudoPesterParam -Title $title -SkipTestToolBuild $sudoResultsWithExpFeatures += $passThruResult }