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: