Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
20af6e1
[SPARK-58987][INFRA] Parity gate: run official suite through our clie…
HyukjinKwon Aug 25, 2026
58858ea
[SPARK-58987][INFRA] Add Rust coverage CI gate + fill retries/storage…
HyukjinKwon Aug 25, 2026
54c299b
[SPARK-58987][INFRA] Add the generated known-environmental-failures m…
HyukjinKwon Aug 25, 2026
eed74ad
[SPARK-58987][INFRA] Parity gate: retry flaky files; coverage: match …
HyukjinKwon Aug 25, 2026
d1f89c3
[SPARK-58987][INFRA] Coverage: target 90%; pin one arch for local mer…
HyukjinKwon Aug 25, 2026
9cc44c9
[SPARK-58987][INFRA] Fix off-by-one bound in the jitter retry test
HyukjinKwon Aug 25, 2026
0990f32
[SPARK-58987][INFRA] Lint: fix ruff E741/B007 and rustfmt in new code
HyukjinKwon Aug 25, 2026
035b4e3
[SPARK-58998][PYTHON] Restore drop-in API fidelity: Column/DataFrame …
HyukjinKwon Aug 25, 2026
750abe2
[SPARK-58987][INFRA] Keep coverage as tooling; defer the gating CI job
HyukjinKwon Aug 25, 2026
046314b
[SPARK-58998][PYTHON] Drop-in fidelity: DataFrame aggregation/util me…
HyukjinKwon Aug 25, 2026
6f1bb26
[SPARK-58998][PYTHON] Drop-in fidelity: spark.conf/read/catalog + df.…
HyukjinKwon Aug 25, 2026
f6d3af8
[SPARK-58998][PYTHON] Drop-in fidelity: filter/where(str), pivot, unp…
HyukjinKwon Aug 25, 2026
b3939b0
[SPARK-58998][PYTHON] Drop-in fidelity: writer.option accepts non-str…
HyukjinKwon Aug 25, 2026
d3cea30
[SPARK-58998] Fix Sort plan dropping bare (non-SortOrder) columns
HyukjinKwon Aug 25, 2026
2ab6e75
[SPARK-58998] Close the drop-in API gap; complete Arrow type mapping
HyukjinKwon Aug 25, 2026
d5624d1
[SPARK-58987][INFRA] ruff format the scripts
HyukjinKwon Aug 25, 2026
b64da1b
[SPARK-58998][PYTHON] Skip window_with_slide_and_start in the dispatc…
HyukjinKwon Aug 25, 2026
ca32e8c
Fix streaming query command results being discarded (status/isActive/…
HyukjinKwon Aug 25, 2026
762f259
Add coverage smoke tests for every function/plan/DataType builder
HyukjinKwon Aug 25, 2026
325f990
Fix 13 variadic functions silently dropping all arguments
HyukjinKwon Aug 25, 2026
bc33579
Fix parity gate: manifest deselection never matched (gate ran every s…
HyukjinKwon Aug 25, 2026
67a9c99
Make parity deselection rootdir-independent (plugin suffix match)
HyukjinKwon Aug 25, 2026
931fbcb
Address review: Sort enum constants, GIL detach, map keys, conversion…
HyukjinKwon Aug 25, 2026
60071a6
Address review: harden the parity gate (retries, drift check, auditab…
HyukjinKwon Aug 25, 2026
1364558
Address review: fix non-compiling doc snippets + add docs CI validation
HyukjinKwon Aug 25, 2026
f352fd2
Address review: rust_coverage.sh must fail if the Python suite can't run
HyukjinKwon Aug 25, 2026
964e936
Address review: assert values in e2e_wrapper, not just absence of exc…
HyukjinKwon Aug 25, 2026
9ce170e
Build the crate before the docs snippet check
HyukjinKwon Aug 25, 2026
9585141
Drop-in fidelity: options(**kwargs), StreamingQuery.explain() default…
HyukjinKwon Aug 25, 2026
4a4d5c4
Trim parity manifest to CI reality (drift check found 29 stale skips)
HyukjinKwon Aug 25, 2026
544f2c1
Add native Rust e2e coverage for tvf/catalog/session/group/streaming/…
HyukjinKwon Aug 25, 2026
cdfa2f9
Cover read/write round-trips (readwriter.rs) and Row/Value accessors
HyukjinKwon Aug 25, 2026
f194380
Cover the artifact-upload and tag APIs (important client-side paths)
HyukjinKwon Aug 25, 2026
400c747
Cover the client-side ML request-building (ml.rs 62%->79%)
HyukjinKwon Aug 25, 2026
5a894a1
Drop-in fidelity: addArtifacts(*paths) varargs; test artifact upload …
HyukjinKwon Aug 25, 2026
9654b9f
Replace proto-snapshot coverage tests with behavioral Column e2e
HyukjinKwon Aug 25, 2026
600c8e4
Fix select_expr and to_json (found by behavioral e2e), add DataFrame e2e
HyukjinKwon Aug 26, 2026
37c442c
Fix DataType::String.simpleString for the default collation (behavior…
HyukjinKwon Aug 26, 2026
9681ace
Fix parity gate holes: score on pytest returncode, match plural "errors"
HyukjinKwon Aug 26, 2026
7525aca
Address review: createDataFrame TIMESTAMP regression + 6 drop-in fide…
HyukjinKwon Aug 26, 2026
a6a287c
Extend behavioral type tests: proto/JSON round-trips + StructType hel…
HyukjinKwon Aug 26, 2026
1e6ac8d
Add behavioral functions e2e (math/string/conditional/collection/aggr…
HyukjinKwon Aug 26, 2026
9a34c8a
Cover catalog create_table/refresh/recover_partitions (client-side DD…
HyukjinKwon Aug 26, 2026
462912c
Exercise the UDF-builder client paths (group/dataframe applyInPandas/…
HyukjinKwon Aug 26, 2026
f6aacd2
Extend functions golden 440 -> 508 (byte-for-byte vs reference)
HyukjinKwon Aug 26, 2026
814cdbd
Address re-review: V2 writer option coercion, run e2e in CI, plan-ser…
HyukjinKwon Aug 26, 2026
763275e
rustfmt: apply formatting to e2e_columns + plan_arg_regression_golden
HyukjinKwon Aug 26, 2026
b3ab882
functions: match reference builder args for uuid + tuple_sketch_agg_{…
HyukjinKwon Aug 26, 2026
3559267
catalog: return catalog list ops as lazy relations (fix empty-result …
HyukjinKwon Aug 26, 2026
8e37d73
table_arg: implement TableArg::to_proto + replace empty stub test
HyukjinKwon Aug 26, 2026
fbfb379
session: support NULL values in createDataFrame typed columns (+ cove…
HyukjinKwon Aug 26, 2026
cbc9d8c
window: unit-test WindowSpec/Window builders (was ~52% covered)
HyukjinKwon Aug 26, 2026
233e6d5
row: cover Value Display + accessors + Row helpers (was ~67%)
HyukjinKwon Aug 26, 2026
c7ae47a
tvf: cover variant_explode / variant_explode_outer / python_worker_logs
HyukjinKwon Aug 26, 2026
a730b58
catalog: cover the db/pattern-qualified + metadata API variants (100%…
HyukjinKwon Aug 26, 2026
bd83612
readwriter: cover saveAsTable/insertInto/bucketBy/sortBy + writerV2 (…
HyukjinKwon Aug 26, 2026
0e72ba4
streaming: cover file-source readers + name + process_all_available
HyukjinKwon Aug 26, 2026
7dd2faf
dataframe: implement `to` (schema reconcile) + `with_metadata` (were …
HyukjinKwon Aug 26, 2026
f43e61c
ml: implement Evaluator.evaluate via MlCommand (was a stub) + cover M…
HyukjinKwon Aug 26, 2026
d3d4366
readwriter: add xml reader/writer + expose jdbc/xml in the Python dro…
HyukjinKwon Aug 26, 2026
0c5b87a
Fix 3 confirmed stubs found by the stub sweep: transpose, zipWithInde…
HyukjinKwon Aug 26, 2026
fddaf6b
column: expose name/astype/try_cast/getItem + cast accepts DataType o…
HyukjinKwon Aug 26, 2026
df01b03
functions: add the optional-arg variants (round/ceil/floor/bround sca…
HyukjinKwon Aug 26, 2026
2370bf1
drop-in parity: sample/unionByName/tail/DecimalType/Window/Builder si…
HyukjinKwon Aug 26, 2026
e7465b4
types: make the simple DataType classes instantiable with simpleStrin…
HyukjinKwon Aug 26, 2026
d8125ee
types: real DataType object model (StructType/StructField/Array/Map) …
HyukjinKwon Aug 26, 2026
618a131
dataframe drop-in: expose transpose / lateralJoin / repartitionByRange
HyukjinKwon Aug 26, 2026
29fc618
Fix CI (drop removed zip_with_index test) + add the 7 complex DataTyp…
HyukjinKwon Aug 26, 2026
cea2850
column: alias(name, metadata=...) attaches column metadata
HyukjinKwon Aug 26, 2026
77e95b1
readwriter: reader format methods accept per-call **options
HyukjinKwon Aug 26, 2026
fbaf87f
dataframe: expose df-level approxQuantile / crosstab / freqItems
HyukjinKwon Aug 26, 2026
e288a90
readwriter: full jdbc signature (column/bounds/numPartitions/properties)
HyukjinKwon Aug 26, 2026
6a51ac8
types: DecimalType exposes precision/scale attrs + simpleString/typeN…
HyukjinKwon Aug 26, 2026
08cc3d2
ml: expose pyspark.ml.connect Python bindings (the 11 core ML classes)
HyukjinKwon Aug 26, 2026
1d7ced9
dataframe: expose groupingSets/toJSON/dropDuplicatesWithinWatermark +…
HyukjinKwon Aug 26, 2026
2602f6b
Fix e2e_dataframe for the Result-returning same_semantics/semantic_hash
HyukjinKwon Aug 26, 2026
36e118a
readwriter: full explicit pyspark signatures for reader csv/json/orc/…
HyukjinKwon Aug 26, 2026
2c0ea50
dataframe: full explicit pyspark signatures for writer csv/json/parqu…
HyukjinKwon Aug 26, 2026
072aa08
Expose SparkSession.tvf, copyFromLocalToFs, Observation, and DataFram…
HyukjinKwon Aug 26, 2026
f24e7d9
ml: add MulticlassClassificationEvaluator + CrossValidator(+Model) (c…
HyukjinKwon Aug 26, 2026
7a9c6d1
drop-in: writer.partitionBy accepts nested list + createDataFrame Ato…
HyukjinKwon Aug 26, 2026
0a7e330
parity gate: retry test_parity_arrow_python_udf (transient worker-spa…
HyukjinKwon Aug 26, 2026
a36703a
ml: unit-test Multiclass/CrossValidator + every estimator's fit/trans…
HyukjinKwon Aug 26, 2026
dafe645
udf: make the drop-in udf() path actually work end-to-end
HyukjinKwon Aug 26, 2026
99cd5d2
Close all public-API parity gaps + WASM UDF SQL registration
HyukjinKwon Aug 26, 2026
eb96a5e
Add Rust + Python coverage badges and a coverage CI workflow
HyukjinKwon Aug 26, 2026
e3d54df
docs: finalize README with coverage badges and API parity statement
HyukjinKwon Aug 26, 2026
d918991
README: real repo slug for coverage badges, parity + WASM UDF sections
HyukjinKwon Aug 26, 2026
627a75b
Add comprehensive offline unit tests for core Rust crate (78 tests)
HyukjinKwon Aug 26, 2026
84c629c
Add Python drop-in offline tests, coverage scoping, and server-gated e2e
HyukjinKwon Aug 26, 2026
98a3cf1
Re-vendor cloudpickle 3.1.1 -> 3.1.2 to match Spark v4.2.0
HyukjinKwon Aug 26, 2026
11dafff
WASM UDF spark.udf().register(); fix metadata JSON re-quoting; covera…
HyukjinKwon Aug 26, 2026
47dca74
Fix clippy errors + quiet warnings; add vendored-upstream drift CI
HyukjinKwon Aug 26, 2026
798ff55
AGENTS.md: full version-upgrade parity playbook
HyukjinKwon Aug 26, 2026
daf6229
Raise core coverage past 90% (region/function); bound the parity CI job
HyukjinKwon Aug 26, 2026
ad10539
Re-vendor serializers.py + add version.py from Spark v4.2.0
HyukjinKwon Aug 26, 2026
47be40d
Parity gate: give slow worker-heavy files a larger per-file timeout
HyukjinKwon Aug 26, 2026
085d95d
Embed the WASM UDF packer in the crate; drop the pyspark_wasm_udf pac…
HyukjinKwon Aug 26, 2026
bcac60c
Embed the WASM UDF packer script + wire it up (follow-up to package r…
HyukjinKwon Aug 26, 2026
f9eb3d7
AGENTS.md: always merge PRs with the dev merge script
HyukjinKwon Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 44 additions & 24 deletions .github/workflows/build_python_connect.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ concurrency:

# Mirrors Apache Spark's build_python_connect.yml approach, adapted for the
# Rust-backed client: download the Spark 4.2.0 Connect server, start it, pull the
# official v4.2.0 connect test files, and run them BOTH with the reference pyspark
# client and with our client - the gate is "no regressions vs the reference".
# official v4.2.0 connect test files, and run them through our client - the gate is
# "every test passes except the checked-in known-environmental-failures manifest".
# A separate job enforces Rust test coverage across all crates.

on:
push:
Expand All @@ -44,11 +45,15 @@ env:

jobs:
# ---------------------------------------------------------------------------
# Reference-vs-ours parity gate against a real Spark 4.2.0 Connect server.
# Parity gate: the official connect suite through our client, against a real
# Spark 4.2.0 Connect server (see scripts/run_official_tests.py).
# ---------------------------------------------------------------------------
connect-parity:
name: Connect parity vs reference (Python ${{ matrix.python-version }})
name: Connect parity (official suite via our client, Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
# The official suite runs serially (--jobs 1) and takes ~45-60 min; bound the job so
# a genuinely hung test fails it instead of running to GitHub's 6-hour default.
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -153,7 +158,7 @@ jobs:
SPARK_HOME="${{ steps.spark-download.outputs.SPARK_HOME }}"
echo "Starting Connect server on ${SPARK_CONNECT_TESTING_REMOTE}"
# Give the driver extra heap: the parity gate runs all 84 files serially against
# this one server (~2h), and a 1g default degrades late in the run (GC pressure),
# this one server, and a 1g default degrades late in the run (GC pressure),
# which is what made the streaming listener/observation files flake.
"${SPARK_HOME}/sbin/start-connect-server.sh" \
--packages "org.apache.spark:spark-connect_2.13:${SPARK_VERSION}" \
Expand All @@ -168,40 +173,54 @@ jobs:
sleep 1
done

- name: Parity - reference client vs OUR Rust client (via transport injection)
- name: Rust behavioral e2e tests (against the live server)
# These assert results against a live server and otherwise self-gate on
# SPARK_REMOTE, so they no-op in the server-less rust.yml job. Run them here,
# where a Spark 4.2.0 Connect server is already up, so the drop-in behavior
# (functions/columns/dataframe/tvf/catalog/session/streaming/merge/ml/artifact)
# is actually verified in CI. Serial: they share the one server session.
env:
SPARK_REMOTE: sc://localhost:15002
run: |
cargo test -p apache-spark-connect \
--test e2e_functions --test e2e_columns --test e2e_dataframe \
--test e2e_coverage --test e2e_integration \
-- --test-threads=1

- name: Parity - official connect suite through OUR Rust client
id: parity
env:
PYTHONUNBUFFERED: "1" # stream per-file progress into the live log
RUST_PYSPARK_SO: ${{ github.workspace }}/python/pyspark/_pyspark.so
run: |
# pipefail so the script's exit code (nonzero on regressions) gates the job -
# otherwise `| tee` would mask it and the gate would be falsely green.
# pipefail so the script's exit code (nonzero on any unexpected failure) gates
# the job - otherwise `| tee` would mask it and the gate would be falsely green.
set -o pipefail
# "ours" runs each official connect test through the Rust transport plugin
# (genuinely exercises our client); "reference" runs stock pyspark. Fails on
# any regression (a test the reference passes but ours fails).
# Runs the full SQL connect suite, including Python/pandas UDF and arrow
# modules (the server now has worker-capable Python via PYSPARK_PYTHON).
# Run the official connect suite ONLY through the Rust transport plugin (which
# genuinely exercises our client), requiring every test to pass except the
# checked-in manifest of known *environmental* failures (scripts/
# parity_known_failures.txt) - tests the reference client also fails in this
# single-node pure-Connect environment. Now that the client is at full parity we
# no longer run the reference client on every file just to derive that baseline,
# which roughly halves this job. Regenerate the manifest with
# scripts/gen_parity_skiplist.py when the pinned Spark version bumps.
# Run serially (--jobs 1), matching Apache's own connect test runner
# (./python/run-tests --parallelism=1). The streaming-query-listener tests
# (e.g. test_parity_listener) wait on real server-pushed progress events and
# are timeout-sensitive; running files in parallel starves them under server
# contention and makes them exceed the per-file cap even though they pass when
# run alone. Serial execution removes that artifact without weakening the gate.
python scripts/rust_parity_diff.py \
# (./python/run-tests --parallelism=1): the streaming-query-listener tests wait
# on real server-pushed progress events and are timeout-sensitive, so parallel
# runs starve them under server contention. Serial removes that artifact.
python scripts/run_official_tests.py \
--spark "${{ steps.spark-source.outputs.SPARK_SOURCE }}" \
--remote "${SPARK_CONNECT_TESTING_REMOTE}" \
--path-contains "sql/tests/connect" \
--jobs 1 \
2>&1 | tee /tmp/parity_diff.log
2>&1 | tee /tmp/parity_ours.log

- name: Upload parity diff + server log
- name: Upload parity log + server log
if: always()
uses: actions/upload-artifact@v4
with:
name: parity-diff-py${{ matrix.python-version }}
name: parity-ours-py${{ matrix.python-version }}
path: |
/tmp/parity_diff.log
/tmp/parity_ours.log
/tmp/connect-server.log
if-no-files-found: warn

Expand Down Expand Up @@ -323,3 +342,4 @@ jobs:
run: cargo test -p apache-spark-connect -p apache-spark-connect-core --release
- name: No-stub audit
run: bash scripts/audit_no_stubs.sh

230 changes: 230 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#

name: Coverage

# Cancel an in-progress run when a newer commit supersedes it (per workflow + ref).
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
push:
branches: [master]
pull_request:
branches: [master]

env:
CARGO_TERM_COLOR: always

jobs:
coverage:
name: Measure and publish coverage badges
runs-on: ubuntu-latest
permissions:
contents: write # Needed to push to badges branch
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for badges branch operations

- name: Install Rust toolchain
run: |
rustup toolchain install stable --profile minimal
rustup default stable

- name: Cache cargo & target
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-coverage-

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler

- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov

- name: Generate Rust coverage report
run: |
cargo llvm-cov --no-cfg-coverage -p apache-spark-connect \
--json --output-path coverage-rust.json

- name: Parse Rust coverage and create badge JSON
# Extract line coverage % from llvm-cov JSON output and convert to shields.io
# endpoint format. Parse the JSON, extract the "line" coverage percentage,
# and generate the endpoint badge structure.
run: |
python3 << 'PYTHON_EOF'
import json

# Read the llvm-cov output
with open("coverage-rust.json", "r") as f:
data = json.load(f)

# Extract line coverage percentage (or region if line not available)
coverage_pct = None
if "data" in data and len(data["data"]) > 0:
coverage_obj = data["data"][0]
if "line_coverage_percent" in coverage_obj:
coverage_pct = coverage_obj["line_coverage_percent"]
elif "region_coverage_percent" in coverage_obj:
coverage_pct = coverage_obj["region_coverage_percent"]

if coverage_pct is None:
coverage_pct = 0

# Determine color based on coverage threshold
if coverage_pct >= 80:
color = "green"
elif coverage_pct >= 60:
color = "yellow"
else:
color = "red"

# Create shields.io endpoint JSON format
badge_json = {
"schemaVersion": 1,
"label": "rust coverage",
"message": f"{coverage_pct:.1f}%",
"color": color
}

with open("coverage-rust-badge.json", "w") as f:
json.dump(badge_json, f)

print(f"Rust coverage: {coverage_pct:.1f}% ({color})")
PYTHON_EOF

- name: Install Python and dependencies
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip

- name: Install Python build dependencies
run: |
python -m pip install --upgrade pip
python -m pip install maturin pytest coverage

- name: Build Python extension
run: |
cargo build -p pyspark-rs --release
cp target/release/lib_pyspark.so python/pyspark/_pyspark.so

- name: Measure Python coverage with offline test suite
run: |
cd python && PYTHONPATH=$(pwd) python -m coverage run --rcfile=.coveragerc -m pytest tests/test_dropin_offline.py
PYTHONPATH=$(pwd) python -m coverage json --rcfile=.coveragerc -o coverage-python.json

- name: Parse Python coverage and create badge JSON
run: |
python3 << 'PYTHON_EOF'
import json

# Read the coverage JSON output
with open("python/coverage-python.json", "r") as f:
data = json.load(f)

# Extract percent covered from totals
coverage_pct = data["totals"]["percent_covered"]

# Determine color based on coverage threshold
if coverage_pct >= 90:
color = "green"
elif coverage_pct >= 75:
color = "yellow"
else:
color = "red"

# Create shields.io endpoint JSON format
badge_json = {
"schemaVersion": 1,
"label": "python coverage",
"message": f"{coverage_pct:.1f}%",
"color": color
}

with open("coverage-python-badge.json", "w") as f:
json.dump(badge_json, f)

print(f"Python coverage: {coverage_pct:.1f}% ({color})")
PYTHON_EOF

- name: Ensure badges directory exists
run: mkdir -p badges

- name: Copy badges to output directory
run: |
cp coverage-rust-badge.json badges/coverage-rust.json
cp coverage-python-badge.json badges/coverage-python.json

- name: Check if badges branch exists
id: check-badges-branch
run: |
if git rev-parse --verify origin/badges > /dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi

- name: Create/update badges branch (orphan)
if: github.event_name == 'push' && github.repository == 'apache/spark-connect-rust' && github.ref == 'refs/heads/master'
run: |
# Commit the badges to the badges branch (orphan, contains only badge files).
# This approach uses git directly: checkout/create the orphan badges branch,
# copy in the badge files, and force-push (badges branch is always ephemeral).
git config user.name "GitHub Actions"
git config user.email "actions@github.com"

# Fetch badges branch if it exists
if git rev-parse --verify origin/badges > /dev/null 2>&1; then
git fetch origin badges:badges
git checkout badges
else
# Create orphan branch (no parent commits)
git checkout --orphan badges
git rm -rf .
fi

# Copy new badges into the branch
mkdir -p badges
cp coverage-rust-badge.json badges/coverage-rust.json
cp coverage-python-badge.json badges/coverage-python.json

# Commit and push
git add badges/
git commit -m "ci: update coverage badges" || echo "No changes to commit"
git push -f origin badges

- name: Upload coverage reports as artifacts (for PR preview)
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v4
with:
name: coverage-badges
path: |
badges/coverage-rust.json
badges/coverage-python.json
retention-days: 5
37 changes: 37 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ on:
- "docs/**"
- "mkdocs.yml"
- ".github/workflows/docs.yml"
pull_request:
paths:
- "docs/**"
- "mkdocs.yml"
- ".github/workflows/docs.yml"
- "crates/**"
workflow_dispatch:

# `mkdocs gh-deploy` builds the site and pushes it to the `gh-pages` branch,
Expand All @@ -39,6 +45,37 @@ concurrency:
cancel-in-progress: true

jobs:
# PR-time validation (no deploy): catch nav/link/config regressions with
# `mkdocs build --strict`, and compile the self-contained Rust snippets in the
# guides against the crate so a doc example can't drift out of sync with the API
# (the class of bug that shipped `lit`/`SparkSessionBuilder` examples that did not
# compile). Runs on forks and non-master refs, where the deploy job never runs.
validate:
name: Validate docs (strict build + snippet compile)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install MkDocs Material
run: pip install "mkdocs-material==9.*"
- name: mkdocs build --strict
run: mkdocs build --strict
- name: Install Rust toolchain
run: |
rustup toolchain install stable --profile minimal
rustup default stable
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler
- name: Build the crate (snippet check compiles against its rlib)
run: cargo build -p apache-spark-connect
- name: Compile the guides' Rust snippets
run: python scripts/check_doc_snippets.py

build-and-deploy:
name: Build and deploy the docs site
runs-on: ubuntu-latest
Expand Down
Loading
Loading