Skip to content

Bump the nuget group across 1 directory with 4 updates #1877

Bump the nuget group across 1 directory with 4 updates

Bump the nuget group across 1 directory with 4 updates #1877

name: Build, test and package
on:
workflow_dispatch:
push:
branches: [ main ]
paths-ignore:
- "**.md"
pull_request:
branches: [ main ]
schedule:
- cron: '0 0 * * 0' # Once a week: "At 00:00 on Sunday."
defaults:
run:
shell: pwsh
jobs:
main:
name: Build, test and package
permissions:
contents: read
strategy:
fail-fast: false # don't fail if one of the matrix jobs fails. Example: try to run the windows matrix even if the ubuntu matrix fails.
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
env:
SLN_FILEPATH: ${{github.workspace}}/DotNet.Sdk.Extensions.sln
EXTENSIONS_BIN_FOLDER : ${{ github.workspace }}/src/DotNet.Sdk.Extensions/bin/Release
TESTING_EXTENSIONS_BIN_FOLDER : ${{ github.workspace }}/src/DotNet.Sdk.Extensions.Testing/bin/Release
TEST_RESULTS_ARTIFACT_NAME: test-results-${{ matrix.os }}
CODE_COVERAGE_ARTIFACT_NAME: code-coverage-report-${{ matrix.os }}
NUGET_ARTIFACT_NAME : nuget-packages-and-symbols
steps:
- name: Dump github context for debug purposes
env:
GITHUB_CONTEXT: ${{ toJSON(github) }}
run: $env:GITHUB_CONTEXT
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
7.0.x
global-json-file: global.json
- name: Cache/Restore NuGets
uses: actions/cache@v4
with:
path:
~/.nuget/packages
key: ${{ runner.os }}-nuget
restore-keys: |
${{ runner.os }}-nuget-
- name: Install reportgenerator dotnet tool
run: dotnet tool install --global dotnet-reportgenerator-globaltool
- name: Restore dependencies
run: dotnet restore ${{ env.SLN_FILEPATH }} -warnaserror
- name: Build
run: dotnet build ${{ env.SLN_FILEPATH }} -c Release -warnaserror --no-restore --no-incremental
- name: Test and code coverage
id: dotnet-test
env:
# default is 90 seconds. Increasing this because sometimes running on Windows I get:
# "vstest.console process failed to connect to testhost process after 90 seconds. This may occur due to machine slowness,
# please set environment variable VSTEST_CONNECTION_TIMEOUT to increase timeout."
# Don't know why this occurs though
VSTEST_CONNECTION_TIMEOUT: 270
run: |
$os = $PSVersionTable.OS
$testResultsDir = $(Join-Path -Path (Get-Location) -ChildPath "tests/${{ matrix.os }}/test-results")
$testCoverageDir = $(Join-Path -Path (Get-Location) -ChildPath "tests/${{ matrix.os }}/coverage-results/")
$testCoverageFile = $(Join-Path -Path $testCoverageDir -ChildPath "coverage.net8.0.opencover.xml")
Write-Output "test-results-dir=$testResultsDir" >> $env:GITHUB_OUTPUT
Write-Output "test-coverage-dir=$testCoverageDir" >> $env:GITHUB_OUTPUT
Write-Output "test-coverage-file=$testCoverageFile" >> $env:GITHUB_OUTPUT
Write-Output "::group::Test output directories."
Write-Output "test-results-dir is set to $testResultsDir"
Write-Output "test-coverage-dir is set to $testCoverageDir"
Write-Output "test-coverage-file is set to $testCoverageFile"
Write-Output "::endgroup::"
$downloadArtifactMessage = "You can inspect the test results by downloading the workflow artifact named: ${{ env.TEST_RESULTS_ARTIFACT_NAME }}."
$frameworkMonikers = @('net6.0','net7.0','net8.0')
foreach($frameworkMoniker in $frameworkMonikers)
{
Write-Output "::group::Running dotnet test for target framework $frameworkMoniker."
$testCoverageMergeFile = $(Join-Path -Path $testCoverageDir -ChildPath "coverage.$frameworkMoniker.json")
dotnet test ${{ env.SLN_FILEPATH }} `
-c Release `
--no-build `
--framework $frameworkMoniker `
--logger "trx;LogFilePrefix=framework" `
--logger GitHubActions `
--logger "liquid.custom;Template=${{github.workspace}}/tests/liquid-test-logger-template.md;runnerOS=${{ matrix.os }};os=$os;LogFilePrefix=framework" `
--results-directory "$testResultsDir" `
/p:CollectCoverage=true `
/p:CoverletOutput="$testCoverageDir" `
/p:MergeWith="$testCoverageMergeFile" `
/p:CoverletOutputFormat="json%2copencover" `
-m:1 `
-- RunConfiguration.TreatNoTestsAsError=true # this must be the last parameter and there must be a space between the -- and the RunConfiguration.TreatNoTestsAsError. See https://github.com/microsoft/vstest/pull/2610#issuecomment-942113882
Write-Output "::endgroup::"
if($LASTEXITCODE -ne 0)
{
Write-Output "::error title=Tests (${{ matrix.os }})::Tests failed on ${{ matrix.os }}. $downloadArtifactMessage"
Exit 1
}
}
Write-Output "::notice title=Tests (${{ matrix.os }})::Tests passed on ${{ matrix.os }}. $downloadArtifactMessage"
- name: Set run even if tests fail conditions
id: even-if-tests-fail
if: always()
run: |
# Some of the steps below provide feedback on the test run and I want to run them even if
# some of the previous steps failed. For that I need:
# - the 'always()' condition: without it the step only runs if the job is successful, it's like the 'if' condition on any step always has a hidden '&& success()' clause.
# - the '(steps.<step-id>.conclusion == 'success' || steps.<step-id>.conclusion == 'failure')' condition: to run the steps only if the <step-id> step has ran, regardless
# if it failed or not. It won't run if the <step-id> step has been skipped or cancelled.
#
# As such, the output from this step is meant to be used on the 'if' property of steps as follows:
# if: steps.even-if-tests-fail.outputs.condition == 'true' && always()
$condition = '${{ (steps.dotnet-test.conclusion == 'success' || steps.dotnet-test.conclusion == 'failure') }}'
Write-Output "condition=$condition" >> $env:GITHUB_OUTPUT
Write-Output "condition is set to $condition"
- name: Upload opencover test coverage file to artifacts
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v4
with:
name: opencover-test-coverage-file
path: ${{ steps.dotnet-test.outputs.test-coverage-file }}
- name: Generate code coverage report
id: code-coverage-report-generator
if: steps.even-if-tests-fail.outputs.condition == 'true' && always()
run: |
$testCoverageReportDir = $(Join-Path -Path ${{ steps.dotnet-test.outputs.test-coverage-dir }} -ChildPath "report")
Write-Output "test-coverage-report-dir=$testCoverageReportDir" >> $env:GITHUB_OUTPUT
reportgenerator `
"-reports:${{ steps.dotnet-test.outputs.test-coverage-file }}" `
"-targetdir:$testCoverageReportDir" `
-reportTypes:htmlInline
- name: Upload code coverage report to artifacts
if: steps.even-if-tests-fail.outputs.condition == 'true' && always()
uses: actions/upload-artifact@v4
with:
name: ${{ env.CODE_COVERAGE_ARTIFACT_NAME }}
path: ${{ steps.code-coverage-report-generator.outputs.test-coverage-report-dir }}
- name: Rename test results files
if: steps.even-if-tests-fail.outputs.condition == 'true' && always()
run: |
# I have another workflow that will get all test result markdown files and add them as comments to a Pull Request.
# If I don't rename the test results file there is an edge case that will cause the test results comments on the Pull Request to be incorrect.
#
# The edge case is when this job is re-run. When the job is re-run, it will upload the test results artifact multiple times.
# The artifacts work as a share so if the files have the same name they get overwritten, if not they get added to the existing artifact.
# By default, all the test results files will be unique due to a timestamp being part of their name so when re-running the job I will get the test results
# from ALL runs in the artifact with name env.TEST_RESULTS_ARTIFACT_NAME.
# Then when the workflow to add the test results to the Pull Request runs it will get ALL the markdown files in the env.TEST_RESULTS_ARTIFACT_NAME and
# add them as a Pull Request. The end result being I will have more test results showing on the PR than intended. Only the test results from the last run should be displayed
# on the Pull Request.
#
# By running this step, I make sure the test result filenames are deterministic so that on job reruns I will only get the latest test results on the the artifact with name env.TEST_RESULTS_ARTIFACT_NAME.
# Example:
# As of writing this I have 2 test projects and after running tests I will have one markdown file and one trx file per test project and framework. Let's consider the files for .net7.0:
# - one md and trx file for DotNet.Sdk.Extensions.Tests.csproj and another md and trx file for DotNet.Sdk.Extensions.Testing.Tests.csproj. Which for this example let's assume would be named:
# - framework_net7.0_20230226152259.md
# - framework_net7.0_20230226152312.md
# - framework_net7.0_20230226152259.trx
# - framework_net7.0_20230226152312.trx
#
# The filenames contain only the framework and a timestamp. Unfortunately the assembly name is not part of the filename so it's not possible to know which test project the file belongs without viewing its content.
# After renaming we would have the following filenames:
# - framework_net7.0_0.md
# - framework_net7.0_1.md
# - framework_net7.0_0.trx
# - framework_net7.0_1.trx
#
# This way when re-running the job the test result filenames are deterministic and will always override existing files in the artifact with name env.TEST_RESULTS_ARTIFACT_NAME.
# This does mean that the artifact with name env.TEST_RESULTS_ARTIFACT_NAME will always only contain the test results from the latest run.
#
$testResultsDir = '${{ steps.dotnet-test.outputs.test-results-dir }}'
# rename test result files for all frameworks
$frameworkFilters = @('framework_net6.0*','framework_net7.0*','framework_net8.0*')
foreach($frameworkFilter in $frameworkFilters)
{
# for each framework group the files by extension. There will be .md and .trx file groups
$frameworkFiles = Get-ChildItem -Path $testResultsDir -Recurse -Filter $frameworkFilter | Group-Object -Property Extension
foreach($extensionFilesGroup in $frameworkFiles)
{
# rename all the files in each group by adding a deterministic suffix. Since the count of the files in each group does not change
# between workflow runs we can use that as a deterministic suffix.
for ($i = 0; $i -lt $extensionFilesGroup.Count; $i++)
{
$file = $extensionFilesGroup.Group[$i]
$extension = $file.Extension
$frameworkPrefix = $frameworkFilter -replace '\*', ''
$newFilename = "$frameworkPrefix`_$i$extension"
Write-Output "Renaming $file to $newFilename"
Rename-Item -Path $file -NewName "$newFilename"
}
}
}
- name: Upload test results to artifacts
if: steps.even-if-tests-fail.outputs.condition == 'true' && always()
uses: actions/upload-artifact@v4
with:
name: ${{ env.TEST_RESULTS_ARTIFACT_NAME }}
path: ${{ steps.dotnet-test.outputs.test-results-dir }}
- name: Package DotNet.Sdk.Extensions
if: matrix.os == 'ubuntu-latest' # this job is on a matrix run but I only want to build the NuGet once
run: dotnet pack src/DotNet.Sdk.Extensions/DotNet.Sdk.Extensions.csproj -c Release --no-build
- name: Package DotNet.Sdk.Extensions.Testing
if: matrix.os == 'ubuntu-latest' # this job is on a matrix run but I only want to build the NuGet once
run: dotnet pack src/DotNet.Sdk.Extensions.Testing/DotNet.Sdk.Extensions.Testing.csproj -c Release --no-build
- name: Upload NuGets and symbols to artifacts
id: upload-nuget-artifacts
uses: actions/upload-artifact@v4
if: matrix.os == 'ubuntu-latest' # this job is on a matrix run but I only want to upload NuGets as artifacts once
with:
name: ${{ env.NUGET_ARTIFACT_NAME }}
path: |
${{ env.EXTENSIONS_BIN_FOLDER }}/*.nupkg
${{ env.EXTENSIONS_BIN_FOLDER }}/*.snupkg
${{ env.TESTING_EXTENSIONS_BIN_FOLDER }}/*.nupkg
${{ env.TESTING_EXTENSIONS_BIN_FOLDER }}/*.snupkg
- name: Log artifacts info
if: matrix.os == 'ubuntu-latest' # this job is on a matrix run but I only want to log this messages to the build summary once
run: |
Write-Output "::notice title=Code coverage report::You can download the code coverage report from the workflow artifact named: ${{ env.CODE_COVERAGE_ARTIFACT_NAME }}."
Write-Output "::notice title=NuGets::You can download the NuGet packages and symbols from the workflow artifact named: ${{ env.NUGET_ARTIFACT_NAME }}."