Repository navigation
CI #3746
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| run-name: "CI${{ github.event.inputs.client-libs-test-image-tag != '' && format(' using image: {0}', github.event.inputs.client-libs-test-image-tag) || '' }}" | |
| on: | |
| pull_request: | |
| push: | |
| branches: [ 'main', 'v3' ] | |
| paths: | |
| - '**' | |
| - '!/docs/*' # Don't run workflow when files are only in the /docs directory | |
| workflow_dispatch: | |
| inputs: | |
| client-libs-test-image-tag: | |
| description: 'Tag of the client libs test image to use' | |
| required: false | |
| default: '' | |
| windows-topology: | |
| description: 'Servers to start on the Windows job: full (everything) or basic (primary/replica/secure/failover only)' | |
| required: false | |
| default: 'full' | |
| type: choice | |
| options: | |
| - basic | |
| - full | |
| windows-redis-version: | |
| description: 'Pin the apt redis version on the Windows job, e.g. 6:8.0.2-1rl1~jammy1 (blank = whatever is GA)' | |
| required: false | |
| default: '' | |
| jobs: | |
| main: | |
| name: StackExchange.Redis (Ubuntu) | |
| runs-on: ubuntu-latest | |
| env: | |
| DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: "1" # Enable color output, even though the console output is redirected in Actions | |
| TERM: xterm # Enable color output in GitHub Actions | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Run tests | |
| uses: ./.github/actions/run-tests | |
| with: | |
| client-libs-test-image-tag: "${{ github.event.inputs.client-libs-test-image-tag }}" | |
| windows: | |
| name: StackExchange.Redis (Windows Server 2025) | |
| if: github.event.inputs.client-libs-test-image-tag == '' | |
| # Note: must not be windows-2022; setup-wsl runs `wsl --update` only on that image | |
| # (ImageOS == win22), and that download intermittently 403s from Microsoft, failing the job. | |
| runs-on: windows-2025 | |
| env: | |
| NUGET_CERT_REVOCATION_MODE: offline # Disabling signing because of massive perf hit, see https://github.com/NuGet/Home/issues/11548 | |
| DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: "1" # Note this doesn't work yet for Windows - see https://github.com/dotnet/runtime/issues/68340 | |
| TERM: xterm | |
| DOCKER_BUILDKIT: 1 | |
| # This runner routinely fails the library's 5s defaults for reasons that are nothing to do | |
| # with the code under test (fractional vCPU, nested virtualisation, thread-pool ramp). Raise | |
| # the floor for connections that do not ask for a specific timeout; tests that deliberately | |
| # pick a short timeout to exercise timeout behaviour are unaffected. | |
| REDIS_TESTS_MIN_TIMEOUT_MS: "20000" | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 # Fetch the full history | |
| # Surfaced early, and before restore/build, so the version to release is visible even if | |
| # something later in this (long) job fails. Cutting a release means creating a GitHub Release | |
| # tagged with whatever this prints; release.yml then verifies the tag agrees with nbgv. | |
| - name: Report computed version | |
| run: | | |
| dotnet tool install --global nbgv | |
| $version = nbgv get-version --project src/StackExchange.Redis --variable NuGetPackageVersion | |
| "### Computed package version: ``$version``" >> $env:GITHUB_STEP_SUMMARY | |
| Write-Output "Computed package version: $version" | |
| # Restore is ~80s of the build step on a cold runner, and the package set changes rarely. | |
| # | |
| # The key globs the whole workspace for **/*.csproj, so it must only ever be computed against a | |
| # still tree. Two things disturb one: the sixteen redis processes writing through DrvFs once the | |
| # servers are up - rdb files into RedisConfigs/Temp, nodes-*.conf into RedisConfigs/Cluster, and | |
| # BGSAVE's temp-<pid>.rdb appearing and being renamed under the walk - and, later, every bin/ and | |
| # obj/ the build produces. Enumerating a tree that is changing underneath you is how you get an | |
| # intermittent "hashFiles(...) failed. Fail to hash files under directory ...". | |
| # | |
| # Hashing here, straight after checkout, keeps the restore safe; but actions/cache re-evaluates | |
| # `key` in its post step to decide what to save, and that runs at the end of the job when the | |
| # churn is at its worst. So resolve it *once*, into an output, and hand the cache action a plain | |
| # string it can reuse at both ends. | |
| - name: Compute NuGet cache key | |
| id: nuget-cache-key | |
| run: | | |
| "value=${{ runner.os }}-nuget-${{ hashFiles('Directory.Packages.props', 'global.json', '**/*.csproj') }}" >> $env:GITHUB_OUTPUT | |
| - name: Cache NuGet packages | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ steps.nuget-cache-key.outputs.value }} | |
| restore-keys: ${{ runner.os }}-nuget- | |
| - uses: Vampire/setup-wsl@v7 # v7 is the first version running on Node 24 | |
| with: | |
| distribution: Ubuntu-22.04 | |
| - name: Install Redis | |
| shell: wsl-bash {0} | |
| working-directory: ./tests/RedisConfigs | |
| run: | | |
| apt-get update | |
| apt-get install curl gpg lsb-release libgomp1 jq -y | |
| curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg | |
| chmod 644 /usr/share/keyrings/redis-archive-keyring.gpg | |
| echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/redis.list | |
| apt-get update | |
| # Don't let dpkg start the packaged redis-server service: we start our own | |
| # instances below, and the packaged unit would otherwise race them for | |
| # 127.0.0.1:6379 on any run where WSL came up with systemd. | |
| # This also sidesteps an install hang we hit with the 8.10.0 package, whose | |
| # redis.conf lost "supervised auto" - redis then never sd_notify()s, so the | |
| # Type=notify unit never leaves "activating" and the postinst's | |
| # "systemctl start" blocks. That one's an upstream packaging slip being | |
| # fixed there, but the guard above is worth keeping either way. | |
| printf '#!/bin/sh\nexit 101\n' > /usr/sbin/policy-rc.d | |
| chmod +x /usr/sbin/policy-rc.d | |
| # A blank pin means "whatever is GA today", which is not reproducible: a run from last | |
| # month and a run from today can test different servers. Supply windows-redis-version | |
| # via workflow_dispatch to pin it; `apt-cache madison redis-server` lists the candidates. | |
| PIN='${{ github.event.inputs.windows-redis-version }}' | |
| if [ -n "$PIN" ]; then | |
| case "$PIN" in | |
| *[!A-Za-z0-9.:+~_-]*) echo "refusing suspicious version pin: $PIN" >&2; exit 1 ;; | |
| esac | |
| echo "pinning redis to $PIN" | |
| apt-get install -y "redis-server=$PIN" "redis-tools=$PIN" \ | |
| || { echo "pin not available; candidates:" >&2; apt-cache madison redis-server >&2; exit 1; } | |
| else | |
| apt-get install -y redis | |
| fi | |
| redis-server --version | |
| mkdir redis | |
| - name: Run redis-server | |
| shell: wsl-bash {0} | |
| working-directory: ./tests/RedisConfigs/redis | |
| run: | | |
| # Defaults to the whole topology: this job is the only coverage of .NET Framework and of | |
| # Windows' own socket/TLS stacks, so we would rather keep it complete. `basic` starts only | |
| # primary/replica/secure/failover, which is useful when bisecting whether cluster and | |
| # sentinel background load is implicated in a failure - measured across three runs it was | |
| # not, so it is opt-in rather than the default. Tests needing absent servers skip promptly | |
| # either way (see Skip.IfNoCluster / Skip.IfNoSentinel). | |
| TOPOLOGY='${{ github.event.inputs.windows-topology || 'full' }}' | |
| echo "topology: $TOPOLOGY" | |
| redis-server ../Basic/primary-6379.conf & | |
| redis-server ../Basic/replica-6380.conf & | |
| redis-server ../Basic/secure-6381.conf & | |
| redis-server ../Failover/primary-6382.conf & | |
| redis-server ../Failover/replica-6383.conf & | |
| PORTS="6379 6380 6381 6382 6383" | |
| if [ "$TOPOLOGY" = "full" ]; then | |
| for p in 7000 7001 7002 7003 7004 7005; do | |
| redis-server "../Cluster/cluster-$p.conf" --dir ../Cluster & | |
| done | |
| redis-server ../Sentinel/redis-7010.conf & | |
| redis-server ../Sentinel/redis-7011.conf & | |
| for p in 26379 26380 26381; do | |
| redis-server "../Sentinel/sentinel-$p.conf" --sentinel & | |
| done | |
| PORTS="$PORTS 7000 7001 7002 7003 7004 7005 7010 7011 26379 26380 26381" | |
| fi | |
| # Poll rather than `sleep 5`: on a fast machine this returns almost immediately, and on a | |
| # slow one it actually waits long enough. A fixed sleep gets both cases wrong. | |
| # | |
| # NB: this shell runs with -euo pipefail, so a bare `out=$(redis-cli ...)` aborts the whole | |
| # step the instant a server is not yet accepting connections - i.e. precisely the state we | |
| # are here to wait out. Every probe therefore tolerates failure explicitly. | |
| missing="" | |
| for p in $PORTS; do | |
| ready="" | |
| for _ in $(seq 1 30); do | |
| out=$(redis-cli -p "$p" PING 2>&1 || true) | |
| # secure-6381 answers NOAUTH without credentials, which still proves it is listening | |
| case "$out" in | |
| PONG|*NOAUTH*) ready="yes"; break ;; | |
| esac | |
| sleep 1 | |
| done | |
| if [ -n "$ready" ]; then | |
| echo "port $p ready: $(redis-cli -p "$p" INFO SERVER 2>/dev/null | grep redis_version || echo 'version unavailable')" | |
| else | |
| echo "port $p DID NOT COME UP" | |
| missing="$missing $p" | |
| fi | |
| done | |
| # Deliberately fatal, and deliberately not continue-on-error: running ~5000 tests against | |
| # servers that never started produces thousands of confusing connection failures ten | |
| # minutes later, instead of one clear message here. | |
| if [ -n "$missing" ]; then | |
| echo "::error::no server on:$missing" | |
| exit 1 | |
| fi | |
| - name: .NET Build | |
| run: dotnet build Build.csproj -c Release /p:CI=true | |
| # The analyzer and the props that configures it reach consumers only through the package, and a | |
| # packaging regression is silent: no diagnostics, ever, for anybody, with a build that still succeeds. | |
| # The real pack below runs only on pushes to main/v3, so check it here where PRs will see it too. | |
| - name: Verify package contents | |
| run: | | |
| $out = "${env:GITHUB_WORKSPACE}\.packcheck" | |
| dotnet pack src/StackExchange.Redis/StackExchange.Redis.csproj --no-build -c Release /p:PackageOutputPath=$out /p:CI=true | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| $nupkg = Get-ChildItem "$out\StackExchange.Redis.*.nupkg" | Select-Object -First 1 | |
| if (-not $nupkg) { Write-Error "no package was produced"; exit 1 } | |
| Add-Type -AssemblyName System.IO.Compression.FileSystem | |
| $zip = [System.IO.Compression.ZipFile]::OpenRead($nupkg.FullName) | |
| try { $names = @($zip.Entries | ForEach-Object { $_.FullName }) } finally { $zip.Dispose() } | |
| $missing = @() | |
| foreach ($required in @("analyzers/dotnet/cs/StackExchange.Redis.Build.dll", "build/StackExchange.Redis.props")) { | |
| if ($names -contains $required) { Write-Host "ok: $required" } else { $missing += $required } | |
| } | |
| if ($missing.Count) { | |
| Write-Error "$($nupkg.Name) is missing: $($missing -join ', ')" | |
| Write-Host "package contained:"; $names | Sort-Object | ForEach-Object { Write-Host " $_" } | |
| exit 1 | |
| } | |
| - name: StackExchange.Redis.Tests | |
| run: | | |
| $exitCode = 0 | |
| foreach ($tfm in @("net10.0", "net481")) { | |
| dotnet test tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj --no-build -c Release -f $tfm --logger trx --logger GitHubActions --results-directory ./test-results/ /p:CI=true | |
| if ($LASTEXITCODE -ne 0) { | |
| $exitCode = $LASTEXITCODE | |
| } | |
| } | |
| exit $exitCode | |
| - uses: dorny/test-reporter@v3 | |
| continue-on-error: true | |
| if: success() || failure() | |
| with: | |
| name: Tests Results - Windows Server 2025 | |
| path: 'test-results/*.trx' | |
| reporter: dotnet-trx | |
| # list only failures: listing every test rendered ~3MB into $GITHUB_STEP_SUMMARY, which has a | |
| # 1MB limit, so the summary was dropped entirely and failures had to be read from annotations | |
| list-suites: failed | |
| list-tests: failed | |
| - name: Upload test results | |
| uses: actions/upload-artifact@v4 | |
| if: success() || failure() | |
| with: | |
| # the trx carries per-test output, which the failure-only report above deliberately omits; that is | |
| # the difference between diagnosing a CI-only failure and guessing at it | |
| name: test-results-windows | |
| path: test-results/*.trx | |
| retention-days: 14 |