Skip to content

✅ increase unit test coverage #182

✅ increase unit test coverage

✅ increase unit test coverage #182

Workflow file for this run

name: Test
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_call:
permissions:
contents: read
concurrency:
group: test-${{ github.ref }}
cancel-in-progress: true
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
jobs:
lint:
name: Lint & Build (Release, warn-as-error)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup .NET 8 SDK
uses: actions/setup-dotnet@v5
with:
dotnet-version: '8.0.x'
- name: Cache NuGet
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.[cC][sS]proj', '**/*.props', '**/*.targets') }}
restore-keys: nuget-${{ runner.os }}-
- name: Restore
run: dotnet restore
- name: dotnet format (verify)
run: dotnet format --verify-no-changes --verbosity minimal
- name: Build (warnings -> errors)
run: dotnet build --configuration Release -p:TreatWarningsAsErrors=true --no-restore
- name: Pack NuGet (artifact for smoke_netcore31)
run: |
dotnet pack QsNet/QsNet.csproj -c Release -p:ContinuousIntegrationBuild=true -p:IncludeSymbols=false -p:Version=0.0.0-ci -o $RUNNER_TEMP/nupkg
- name: Upload nupkg artifact
uses: actions/upload-artifact@v4
with:
name: qsnet-nupkg
path: ${{ runner.temp }}/nupkg/*.nupkg
retention-days: 1
test:
name: Test ${{ matrix.dotnet }} on ${{ matrix.os }}
needs: lint
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
dotnet: '8.0.x'
experimental: false
- os: ubuntu-latest
dotnet: '9.0.x'
experimental: false
- os: ubuntu-latest
dotnet: '10.0.x'
experimental: true
steps:
- uses: actions/checkout@v5
- name: Setup .NET SDK (stable)
if: ${{ matrix.dotnet != '10.0.x' }}
uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ matrix.dotnet }}
- name: Setup .NET SDK (10 preview)
if: ${{ matrix.dotnet == '10.0.x' }}
uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ matrix.dotnet }}
dotnet-quality: preview
- name: Cache NuGet
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ matrix.dotnet }}-${{ hashFiles('**/*.[cC][sS]proj') }}
restore-keys: |
nuget-${{ runner.os }}-${{ matrix.dotnet }}-
nuget-${{ runner.os }}-
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test + Coverage
run: dotnet test --configuration Release --no-build --verbosity normal --logger "trx;LogFileName=test-results.trx" --collect "XPlat Code Coverage"
- name: Upload TRX
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.dotnet }}
path: '**/*.trx'
overwrite: true
retention-days: 7
- name: Upload raw coverage
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.dotnet }}
path: QsNet.Tests/TestResults/**/*.cobertura.xml
if-no-files-found: error
overwrite: true
retention-days: 7
ensure_compatibility:
name: Ensure compatibility with qs (JS vs C#)
needs: lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '20'
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: '8.0.x'
- name: Install JS dependencies
run: npm install
working-directory: QsNet.Comparison/js
- name: Run C# comparison
run: dotnet run --project QsNet.Comparison -c Release > cs.out
- name: Run Node comparison
run: node QsNet.Comparison/js/qs.js > node.out
- name: Show first lines (debug)
if: ${{ always() }}
run: |
echo "=== C# (first 80 lines) ==="
head -n 80 cs.out || true
echo "=== Node (first 80 lines) ==="
head -n 80 node.out || true
- name: Diff outputs
run: |
set -eo pipefail
diff -u node.out cs.out > diff.out || (echo "::error::Differences found between Node and C# outputs. See artifact diff.out"; exit 1)
- name: Upload diff artifacts on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: comparison-diff
path: |
node.out
cs.out
diff.out
smoke:
name: Consumer smoke (net5.0; net6.0; net7.0; net8.0; net9.0)
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup .NET SDKs (5, 6, 7, 8, 9)
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
5.0.x
6.0.x
7.0.x
8.0.x
9.0.x
- name: Setup .NET 10 preview SDK (non-blocking)
continue-on-error: true
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x
dotnet-quality: preview
- name: Create consumer project (multi-target)
run: |
set -euo pipefail
mkdir -p consumer-smoke
cat > consumer-smoke/consumer-smoke.csproj <<'XML'
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net5.0;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>false</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../QsNet/QsNet.csproj" />
</ItemGroup>
</Project>
XML
cat > consumer-smoke/Program.cs <<'CS'
using System;
using System.Collections.Generic;
using QsNet;
class P
{
static int Main()
{
try
{
// Decode should not throw and should return a non-null object graph.
var decoded = Qs.Decode("foo[bar]=baz&foo[list][]=a&foo[list][]=b");
// Encode should produce a non-empty query string.
var qs = Qs.Encode(new Dictionary<string, object?> {
["foo"] = new Dictionary<string, object?> { ["bar"] = "baz" }
});
if (string.IsNullOrEmpty(qs))
throw new Exception("Encode returned empty string");
Console.WriteLine("SMOKE OK");
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
return 1;
}
}
}
CS
- name: Restore consumer
run: dotnet restore consumer-smoke/consumer-smoke.csproj
- name: Build consumer (net6.0; net7.0; net8.0; net9.0)
run: |
set -euo pipefail
for f in net6.0 net7.0 net8.0 net9.0; do
echo "Building $f"
dotnet build -c Release -f "$f" consumer-smoke/consumer-smoke.csproj --no-restore
done
- name: Build consumer (net5.0 — non-blocking)
continue-on-error: true
run: |
set -euo pipefail
dotnet build -c Release -f net5.0 consumer-smoke/consumer-smoke.csproj --no-restore
- name: Build consumer (net10.0 preview — non-blocking)
continue-on-error: true
run: |
set -euo pipefail
dotnet build -c Release -f net10.0 consumer-smoke/consumer-smoke.csproj --no-restore
- name: Run smoke (net5.0 — non-blocking)
continue-on-error: true
run: |
set -euo pipefail
dotnet run -c Release --framework net5.0 --project consumer-smoke/consumer-smoke.csproj --no-build
- name: Run smoke (net6.0; net7.0; net8.0; net9.0)
run: |
set -euo pipefail
for f in net6.0 net7.0 net8.0 net9.0; do
echo "Running smoke for $f"
dotnet run -c Release --framework "$f" --project consumer-smoke/consumer-smoke.csproj --no-build
done
- name: Run smoke (net10.0 preview — non-blocking)
continue-on-error: true
run: |
set -euo pipefail
dotnet run -c Release --framework net10.0 --project consumer-smoke/consumer-smoke.csproj --no-build
smoke_netcore31:
name: Consumer smoke (netcoreapp3.1 compile-only)
needs: lint
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/dotnet/sdk:3.1
continue-on-error: true
steps:
- uses: actions/checkout@v5
- name: Download nupkg
uses: actions/download-artifact@v5
with:
name: qsnet-nupkg
path: ./nupkg
- name: Verify local nupkg
run: |
set -eu
echo "Listing $GITHUB_WORKSPACE/nupkg";
ls -la "$GITHUB_WORKSPACE/nupkg" || { echo "nupkg directory missing"; exit 1; }
- name: Ensure git in container
run: |
set -eu
if ! command -v git >/dev/null 2>&1; then
apt-get update
apt-get install -y git ca-certificates
rm -rf /var/lib/apt/lists/*
fi
- name: Create consumer project (netcoreapp3.1)
run: |
set -eu
mkdir -p consumer-core31
cat > consumer-core31/consumer-core31.csproj <<'XML'
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>false</ImplicitUsings>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="QsNet" Version="0.0.0-ci" />
</ItemGroup>
</Project>
XML
cat > consumer-core31/Program.cs <<'CS'
using System;
using System.Collections.Generic;
using QsNet;
class P
{
static int Main()
{
// compile-only smoke; not executed in CI
var decoded = Qs.Decode("a[b]=c&x[]=1&x[]=2");
var qs = Qs.Encode(new Dictionary<string, object?> {
["a"] = new Dictionary<string, object?> { ["b"] = "c" }
});
return string.IsNullOrEmpty(qs) ? 1 : 0;
}
}
CS
- name: Restore consumer
run: dotnet restore consumer-core31/consumer-core31.csproj --source "$GITHUB_WORKSPACE/nupkg" --source https://api.nuget.org/v3/index.json
- name: Build consumer (compile only)
run: dotnet build -c Release consumer-core31/consumer-core31.csproj --no-restore
smoke_netfx:
name: Consumer smoke (.NET Framework 4.6.1 & 4.8.1 on Windows)
needs: lint
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
- name: Setup .NET SDK
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
8.0.x
- name: Create consumer project (net461 & net481)
shell: bash
run: |
set -euo pipefail
mkdir -p consumer-netfx
cat > consumer-netfx/consumer-netfx.csproj <<'XML'
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net461;net481</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>false</ImplicitUsings>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../QsNet/QsNet.csproj" />
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
</ItemGroup>
</Project>
XML
cat > consumer-netfx/Program.cs <<'CS'
using System;
using System.Collections.Generic;
using QsNet;
class P
{
static int Main()
{
try
{
var decoded = Qs.Decode("foo[bar]=baz&foo[list][]=a&foo[list][]=b");
var qs = Qs.Encode(new Dictionary<string, object?> {
["foo"] = new Dictionary<string, object?> { ["bar"] = "baz" }
});
if (string.IsNullOrEmpty(qs))
throw new Exception("Encode returned empty string");
#if NET461
Console.WriteLine("SMOKE net461 OK");
#elif NET481
Console.WriteLine("SMOKE net481 OK");
#else
Console.WriteLine("SMOKE NETFX OK");
#endif
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
return 1;
}
}
}
CS
- name: Restore consumer
run: dotnet restore consumer-netfx/consumer-netfx.csproj
- name: Build consumer
run: dotnet build -c Release consumer-netfx/consumer-netfx.csproj --no-restore
- name: Run smoke (net461)
run: dotnet run -c Release --framework net461 --project consumer-netfx/consumer-netfx.csproj --no-build
- name: Run smoke (net481)
run: dotnet run -c Release --framework net481 --project consumer-netfx/consumer-netfx.csproj --no-build
coverage:
name: Coverage (merged)
runs-on: ubuntu-latest
needs:
- test
- ensure_compatibility
- smoke
- smoke_netcore31
- smoke_netfx
steps:
- uses: actions/checkout@v5
- name: Setup .NET 8 SDK
uses: actions/setup-dotnet@v5
with:
dotnet-version: '8.0.x'
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test w/ coverage (8.0—single run)
run: dotnet test --configuration Release --no-build --collect "XPlat Code Coverage" --results-directory coverage --settings coverlet.runsettings
- name: Install ReportGenerator
run: dotnet tool install -g dotnet-reportgenerator-globaltool
- name: Generate consolidated report
run: reportgenerator -reports:"**/coverage.cobertura.xml" -targetdir:"coverage/report" -reporttypes:"Html;Cobertura;JsonSummary"
- name: Upload to Codecov
uses: codecov/codecov-action@v5
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
files: coverage/report/Cobertura.xml
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
- name: Upload coverage report artifact
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/report/
retention-days: 7