diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 6adb251a5..1f8cbc314 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -5,6 +5,7 @@ on: branches: [ main ] paths-ignore: - '**/*.md' + - 'docs/**' workflow_dispatch: inputs: categories: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d4e6b42a9..a81e8d7ea 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -5,6 +5,7 @@ on: branches: [ main ] paths-ignore: - '**/*.md' + - 'docs/**' jobs: analyze: diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index a363807b7..3b7bb91da 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -1,5 +1,8 @@ name: Examples +# The shared build inputs are the same as in tests.yml and tests-tcp.yml. Directory.Packages.props also sets +# the versions of the packages that only the examples use (Polly, the OpenTelemetry console exporter, and +# some Microsoft.Extensions packages), so a version change runs this workflow. on: push: branches: [main] @@ -8,7 +11,14 @@ on: - "ClickHouse.Driver/**" - "ClickHouse.Driver.Common/**" - "ClickHouse.Driver.Tcp/**" + - "Directory.Packages.props" + - "Directory.Build.*" + - "global.json" + - "[Nn]u[Gg]et.[Cc]onfig" + - ".editorconfig" + - "sgKey.snk" - ".github/workflows/examples.yml" + - "!**/*.md" pull_request: branches: [main] paths: @@ -16,7 +26,14 @@ on: - "ClickHouse.Driver/**" - "ClickHouse.Driver.Common/**" - "ClickHouse.Driver.Tcp/**" + - "Directory.Packages.props" + - "Directory.Build.*" + - "global.json" + - "[Nn]u[Gg]et.[Cc]onfig" + - ".editorconfig" + - "sgKey.snk" - ".github/workflows/examples.yml" + - "!**/*.md" workflow_dispatch: inputs: ref: diff --git a/.github/workflows/reusable-tcp.yml b/.github/workflows/reusable-tcp.yml index a65a7c4b1..637eb2433 100644 --- a/.github/workflows/reusable-tcp.yml +++ b/.github/workflows/reusable-tcp.yml @@ -11,9 +11,16 @@ on: default: latest required: false type: string + coverage: + default: false + required: false + type: boolean ref: required: false type: string + secrets: + CODECOV_TOKEN: + required: false permissions: contents: read @@ -52,10 +59,30 @@ jobs: # No filter: the Cloud category marks the tests tests-cloud-tcp.yml also runs against a real Cloud # service, not tests that need one. They run here too, against the container. # - # The framework is passed through the environment rather than interpolated into the run + # The inputs are passed through the environment rather than interpolated into the run # script, so a caller-supplied input cannot inject shell. - name: Test - run: dotnet test ClickHouse.Driver.Tcp.Tests/ClickHouse.Driver.Tcp.Tests.csproj --framework "$FRAMEWORK" --configuration Release --verbosity normal --logger GitHubActions /clp:ErrorsOnly + run: | + coverage_args=() + if [ "$COVERAGE" = "true" ]; then + coverage_args=(/p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:SkipAutoProps=true) + fi + dotnet test ClickHouse.Driver.Tcp.Tests/ClickHouse.Driver.Tcp.Tests.csproj --framework "$FRAMEWORK" --configuration Release --verbosity normal --logger GitHubActions /clp:ErrorsOnly "${coverage_args[@]}" env: CLICKHOUSE_VERSION: ${{ inputs.clickhouse-version }} FRAMEWORK: ${{ inputs.framework }} + COVERAGE: ${{ inputs.coverage }} + + # The tcp flag keeps this report apart from the HTTP report (flag http). codecov.yml carries each + # flag forward to the commits that do not run its suite. Codecov records the upload for the commit + # that started the workflow. With an explicit ref, that is not the commit under test, so the job + # does not upload. + - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7 + if: inputs.coverage && !inputs.ref + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true + disable_search: true + verbose: true + flags: tcp + files: ./ClickHouse.Driver.Tcp.Tests/coverage.${{ inputs.framework }}.opencover.xml diff --git a/.github/workflows/reusable.yml b/.github/workflows/reusable.yml index 89bffc6fb..97366429f 100644 --- a/.github/workflows/reusable.yml +++ b/.github/workflows/reusable.yml @@ -58,11 +58,14 @@ jobs: CLICKHOUSE_VERSION: ${{ inputs.clickhouse-version }} FRAMEWORK: ${{ inputs.framework }} + # Codecov records the upload for the commit that started the workflow. With an explicit ref, that is + # not the commit under test, so the job does not upload. - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7 - if: inputs.coverage + if: inputs.coverage && !inputs.ref with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true disable_search: true verbose: true + flags: http files: ./ClickHouse.Driver.Tests/coverage.${{ inputs.framework }}.opencover.xml diff --git a/.github/workflows/tests-tcp.yml b/.github/workflows/tests-tcp.yml index 27664f984..2ed2660d7 100644 --- a/.github/workflows/tests-tcp.yml +++ b/.github/workflows/tests-tcp.yml @@ -1,8 +1,10 @@ name: TCP Tests -# Runs the ClickHouse.Driver.Tcp test suite, gated on changes to the TCP projects only. -# The TCP project is standalone (no reference to ClickHouse.Driver), so its build/test closure is just -# the two TCP projects plus the shared package versions and the CI wiring itself. +# Runs the ClickHouse.Driver.Tcp test suite. The TCP projects do not reference ClickHouse.Driver, so an +# HTTP-only change does not run this suite. They do reference ClickHouse.Driver.Common, so the path filters +# select it, together with the shared build inputs, the workflows of this suite, and codecov.yml (the +# Codecov settings for its coverage report). tests.yml and examples.yml list the same shared build inputs; +# keep the three lists the same. on: push: # Only the long-lived integration branches run on push (validating what landed). The per-epic tcp/** @@ -12,20 +14,36 @@ on: paths: - "ClickHouse.Driver.Tcp/**" - "ClickHouse.Driver.Tcp.Tests/**" + - "ClickHouse.Driver.Common/**" - "Directory.Packages.props" + - "Directory.Build.*" + - "global.json" + - "[Nn]u[Gg]et.[Cc]onfig" + - ".editorconfig" + - "sgKey.snk" + - "codecov.yml" - ".github/workflows/tests-tcp.yml" - ".github/workflows/reusable-tcp.yml" - ".github/workflows/tests-cloud-tcp.yml" + - "!**/*.md" pull_request: # Matches the PR's base branch, so a PR stacked onto another tcp/** epic branch triggers too. branches: [main, tcp-client, "tcp/**"] paths: - "ClickHouse.Driver.Tcp/**" - "ClickHouse.Driver.Tcp.Tests/**" + - "ClickHouse.Driver.Common/**" - "Directory.Packages.props" + - "Directory.Build.*" + - "global.json" + - "[Nn]u[Gg]et.[Cc]onfig" + - ".editorconfig" + - "sgKey.snk" + - "codecov.yml" - ".github/workflows/tests-tcp.yml" - ".github/workflows/reusable-tcp.yml" - ".github/workflows/tests-cloud-tcp.yml" + - "!**/*.md" workflow_dispatch: inputs: ref: @@ -37,6 +55,15 @@ permissions: contents: read jobs: + coverage-tcp: + name: Code Coverage (TCP) + uses: $/.github/workflows/reusable-tcp.yml + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + coverage: true + ref: ${{ inputs.ref }} + regress-clickhouse-tcp: name: ClickHouse regression (TCP) uses: $/.github/workflows/reusable-tcp.yml @@ -65,6 +92,42 @@ jobs: matrix: framework: ["net10.0", "net9.0", "net8.0"] # TCP library targets net8.0+ only + build-http-driver: + name: HTTP driver build (TCP) + # ClickHouse.Driver references ClickHouse.Driver.Tcp for net8.0 and later and compiles against its public + # types, so a TCP change can break the HTTP driver build, possibly on some target frameworks only. + # tests.yml does not run for a TCP-only change, so this job builds ClickHouse.Driver for every target + # framework. + runs-on: ubuntu-22.04 + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref || github.ref }} + # No step here talks to git again, so the job token does not need to stay in .git/config. + persist-credentials: false + + - uses: actions/cache@v6 + name: Cache NuGet + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} + restore-keys: | + ${{ runner.os }}-nuget- + + # The .NET 10 SDK builds the older target frameworks too: their reference packs come from NuGet. + - name: Setup .NET Core + uses: actions/setup-dotnet@v6 + with: + dotnet-version: | + 10.x + + # GeneratePackageOnBuild=false: compile only, without the NuGet package. + - name: Build + run: dotnet build ClickHouse.Driver/ClickHouse.Driver.csproj --configuration Release -p:GeneratePackageOnBuild=false + cloud-tcp: name: Cloud (TCP) # On a pull request only when its branch is in this repository, which needs write access to push. Dependabot diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 42adbe2f3..230846400 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,12 +1,58 @@ name: Tests +# Runs the HTTP driver suite. The path filters select the HTTP driver and its test projects, the shared +# ClickHouse.Driver.Common project, the shared build inputs, the workflows of this suite, and codecov.yml +# (the Codecov settings for its coverage report). The shared build inputs are the files that MSBuild, the +# SDK and NuGet read from the repository root (some do not exist yet), the analyzer settings and the +# strong-name key. tests-tcp.yml and examples.yml list the same shared build inputs; keep the three lists +# the same. +# +# ClickHouse.Driver references ClickHouse.Driver.Tcp (net8.0 and later) to pack the TCP assembly into the +# NuGet package, so it compiles against the TCP public types. No HTTP code or test uses them, so a TCP-only +# change does not run this suite. tests-tcp.yml builds ClickHouse.Driver for every target framework instead. on: push: branches: [main] - paths-ignore: - - "**/*.md" + paths: + - "ClickHouse.Driver/**" + - "ClickHouse.Driver.Tests/**" + - "ClickHouse.Driver.IntegrationTests/**" + - "ClickHouse.Driver.Common/**" + - "Directory.Packages.props" + - "Directory.Build.*" + - "global.json" + - "[Nn]u[Gg]et.[Cc]onfig" + - ".editorconfig" + - "sgKey.snk" + - "codecov.yml" + - ".github/workflows/tests.yml" + - ".github/workflows/reusable.yml" + - ".github/workflows/tests-integration.yml" + - ".github/workflows/tests-cloud.yml" + - ".github/workflows/tests-windows.yml" + - ".github/workflows/tests-macos.yml" + - "!**/*.md" pull_request: branches: [main] + paths: + - "ClickHouse.Driver/**" + - "ClickHouse.Driver.Tests/**" + - "ClickHouse.Driver.IntegrationTests/**" + - "ClickHouse.Driver.Common/**" + - "Directory.Packages.props" + - "Directory.Build.*" + - "global.json" + - "[Nn]u[Gg]et.[Cc]onfig" + - ".editorconfig" + - "sgKey.snk" + - "codecov.yml" + - ".github/workflows/tests.yml" + - ".github/workflows/reusable.yml" + - ".github/workflows/tests-integration.yml" + - ".github/workflows/tests-cloud.yml" + - ".github/workflows/tests-windows.yml" + - ".github/workflows/tests-macos.yml" + - "!**/*.md" workflow_dispatch: inputs: ref: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 405fa1e6f..b7ff7bb84 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -93,7 +93,7 @@ dotnet test ClickHouse.Driver.IntegrationTests/ClickHouse.Driver.IntegrationTest ### Running tests with code coverage -The project uses Coverlet for code coverage: +The project uses Coverlet for code coverage. The HTTP and TCP drivers have separate test projects: ```bash dotnet test ClickHouse.Driver.Tests/ClickHouse.Driver.Tests.csproj \ @@ -102,12 +102,23 @@ dotnet test ClickHouse.Driver.Tests/ClickHouse.Driver.Tests.csproj \ /p:CollectCoverage=true \ /p:CoverletOutputFormat=opencover \ /p:SkipAutoProps=true + +dotnet test ClickHouse.Driver.Tcp.Tests/ClickHouse.Driver.Tcp.Tests.csproj \ + --framework net9.0 \ + -c Release \ + /p:CollectCoverage=true \ + /p:CoverletOutputFormat=opencover \ + /p:SkipAutoProps=true ``` -Coverage reports are generated in the test project directory: +The TCP tests start a ClickHouse container with Testcontainers unless `CLICKHOUSE_TCP_CONNECTION` sets a +server connection string. + +Coverage reports are generated in each test project directory: ``` ClickHouse.Driver.Tests/coverage.net9.0.opencover.xml +ClickHouse.Driver.Tcp.Tests/coverage.net9.0.opencover.xml ``` ### Running benchmarks @@ -189,7 +200,9 @@ See `changelog.d/README.md` for the categories and naming convention. ### Expected CI behavior - **All tests must pass** on all target frameworks and ClickHouse versions -- **Code coverage** is reported to Codecov (must not decrease significantly) +- **Code coverage** is reported to Codecov. The HTTP and TCP suites upload separate reports (flags `http` + and `tcp`), and Codecov shows their combined total. The `codecov/patch` check has a 75% target for the + lines that a pull request changes. - **Benchmarks** run but don't fail the build (used for performance regression detection) - **CodeQL** must not find security issues - **Public API analyzer** will fail if API surface changes aren't documented diff --git a/ClickHouse.Driver.Tcp.Tests/ClickHouse.Driver.Tcp.Tests.csproj b/ClickHouse.Driver.Tcp.Tests/ClickHouse.Driver.Tcp.Tests.csproj index 512048c8d..7b3f1bbcd 100644 --- a/ClickHouse.Driver.Tcp.Tests/ClickHouse.Driver.Tcp.Tests.csproj +++ b/ClickHouse.Driver.Tcp.Tests/ClickHouse.Driver.Tcp.Tests.csproj @@ -8,6 +8,8 @@ true CS8002;CHTCP0001 + + **/Vendor/**/*.cs diff --git a/codecov.yml b/codecov.yml index b04d567b4..007ac9233 100644 --- a/codecov.yml +++ b/codecov.yml @@ -9,6 +9,13 @@ coverage: threshold: 1% target: 75% +# The HTTP suite uploads with the flag http and the TCP suite with the flag tcp. Their path filters let a +# commit run only one suite. Carryforward then copies the other flag's report from the nearest earlier +# commit that has one, so the total of each commit covers both drivers. +flag_management: + default_rules: + carryforward: true + # Vendored third-party source (e.g. the K4os LZ4/xxHash codec) is a verbatim, # re-derivable copy we don't own; its coverage is not meaningful for this repo. ignore: