Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
118 changes: 105 additions & 13 deletions .github/workflows/build_python_connect.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,19 +173,10 @@ jobs:
sleep 1
done

- 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
# NOTE (SPARK-59032): the behavioral e2e tests used to run here, sharing this
# server with the parity gate. They now run in their own job (connect-e2e,
# below) so the parity run starts against a server nothing else has touched.
# No test is skipped.

- name: Parity - official connect suite through OUR Rust client
id: parity
Expand Down Expand Up @@ -224,6 +215,107 @@ jobs:
/tmp/connect-server.log
if-no-files-found: warn

# ---------------------------------------------------------------------------
# Behavioral e2e tests (Rust drop-in against a live server), in their OWN job
# with a separate Connect server so they never share server state with the
# parity gate. The parity gate then starts against a server nothing else has
# touched, which avoids the SPARK-59032 arrow-UDF hang (a UDF that crashes a
# Python worker on the shared server wedged worker reuse for later tests).
# ---------------------------------------------------------------------------
connect-e2e:
name: Rust behavioral e2e
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4

- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"

- 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-e2e-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-e2e-

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

- name: Install Python dependencies (server-side UDF workers)
run: |
python -m pip install --upgrade pip
python -m pip install pandas pyarrow numpy

- name: Cache Spark ${{ env.SPARK_VERSION }} distribution
uses: actions/cache@v4
with:
path: ~/spark-dist
key: spark-dist-${{ env.SPARK_VERSION }}-hadoop3

- name: Download Apache Spark ${{ env.SPARK_VERSION }} (Connect server)
id: spark-download
run: |
PKG="spark-${SPARK_VERSION}-bin-hadoop3"
if [ ! -d "${HOME}/spark-dist/${PKG}" ]; then
URL="https://archive.apache.org/dist/spark/spark-${SPARK_VERSION}/${PKG}.tgz"
mkdir -p "${HOME}/spark-dist"
echo "Downloading ${URL}"
wget -q -O "${HOME}/spark-dist/${PKG}.tgz" "${URL}"
tar -xzf "${HOME}/spark-dist/${PKG}.tgz" -C "${HOME}/spark-dist"
rm -f "${HOME}/spark-dist/${PKG}.tgz"
else
echo "Using cached Spark distribution."
fi
echo "SPARK_HOME=${HOME}/spark-dist/${PKG}" >> "$GITHUB_OUTPUT"

- name: Start Spark Connect server
env:
PYSPARK_PYTHON: python
PYSPARK_DRIVER_PYTHON: python
run: |
SPARK_HOME="${{ steps.spark-download.outputs.SPARK_HOME }}"
"${SPARK_HOME}/sbin/start-connect-server.sh" \
--packages "org.apache.spark:spark-connect_2.13:${SPARK_VERSION}" \
--driver-memory 3g \
--conf spark.sql.session.localRelationSizeLimit=1073741824 \
> /tmp/connect-server.log 2>&1 &
for i in $(seq 1 90); do
if (echo > /dev/tcp/localhost/15002) 2>/dev/null; then
echo "Connect server is up (after ${i}s)."; break
fi
if [ "$i" -eq 90 ]; then echo "Server failed to start:"; cat /tmp/connect-server.log; exit 1; fi
sleep 1
done

- name: Rust behavioral e2e tests (against the live server)
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

# ---------------------------------------------------------------------------
# E2E performance benchmark: our client vs reference, same 4.2.0 server.
# ---------------------------------------------------------------------------
Expand Down
1 change: 0 additions & 1 deletion crates/spark-connect/src/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ impl Clone for RuntimeConf {

#[cfg(test)]
mod tests {
use super::*;
use crate::session::SparkSession;

fn session() -> SparkSession {
Expand Down
2 changes: 1 addition & 1 deletion crates/spark-connect/src/dataframe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2431,7 +2431,7 @@ mod cache_tests {
// The LocalRowIterator and collect() use the same underlying ExecutePlan.
// The difference is purely in client-side consumption: streaming vs buffering.
// We verify that to_local_iterator() creates the same iterator type.
let iter: LocalRowIterator;
let _iter: LocalRowIterator;
// This test just verifies the type exists and is constructible.
// A true integration test would create an actual stream.
}
Expand Down
1 change: 0 additions & 1 deletion crates/spark-connect/src/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ impl WhenNotMatchedBySource {

#[cfg(test)]
mod tests {
use super::*;
use crate::session::SparkSession;

fn session() -> SparkSession {
Expand Down
7 changes: 2 additions & 5 deletions crates/spark-connect/src/ml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use uuid::Uuid;

use crate::dataframe::DataFrame;
use crate::plan::LogicalPlan;
use crate::session::SparkSession;

/// ML parameter handling: stores name -> literal value mappings.
///
Expand Down Expand Up @@ -418,8 +417,6 @@ impl Estimator for StandardScaler {
OperatorType::Model,
),
params: self.params.clone(),
input_col: self.input_col.clone(),
output_col: self.output_col.clone(),
};
Ok(Box::new(model))
}
Expand All @@ -430,8 +427,6 @@ impl Estimator for StandardScaler {
pub struct StandardScalerModel {
operator: MlOperator,
params: Params,
input_col: String,
output_col: String,
}

impl Transformer for StandardScalerModel {
Expand Down Expand Up @@ -1408,6 +1403,8 @@ impl Model for CrossValidatorModel {
#[cfg(test)]
mod tests {
use super::*;
// Used only by the tests below (the non-test build does not reference it).
use crate::session::SparkSession;

#[test]
fn test_params_creation() {
Expand Down
2 changes: 1 addition & 1 deletion crates/spark-connect/src/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ mod tests {

#[allow(clippy::approx_constant)]
let d = Value::Double(3.14);
#[allow(clippy::approx_constant)]

assert_eq!(d.as_f64(), Some(3.14));

let s = Value::String("test".to_string());
Expand Down
1 change: 0 additions & 1 deletion crates/spark-connect/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,6 @@ impl Default for SparkSessionBuilder {
/// Convert Arrow RecordBatch data to IPC bytes.
/// Used by createDataFrame to package local data.
fn rows_to_arrow_ipc(rows: &[Row], schema: &DataType) -> Result<Vec<u8>> {
use arrow::array::*;
use arrow::datatypes::Schema as ArrowSchema;
use arrow::ipc::writer::StreamWriter;
use arrow::record_batch::RecordBatch;
Expand Down
10 changes: 3 additions & 7 deletions crates/spark-connect/tests/catalog_golden.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use std::io::{BufRead, BufReader};
use base64::{engine::general_purpose::STANDARD, Engine};
use prost::Message;

use spark_connect::catalog::Catalog;
use spark_connect_proto as proto;

/// Recursively clear fields that vary run-to-run and are not client-authored:
Expand All @@ -36,13 +35,10 @@ fn normalize_expression(e: &mut proto::Expression) {
if let Some(expr_type) = &mut e.expr_type {
// Recursively normalize nested expressions as needed
use proto::expression::ExprType;
match expr_type {
ExprType::Alias(alias) => {
if let Some(expr) = &mut alias.expr {
normalize_expression(expr);
}
if let ExprType::Alias(alias) = expr_type {
if let Some(expr) = &mut alias.expr {
normalize_expression(expr);
}
_ => {}
}
}
}
Expand Down
1 change: 0 additions & 1 deletion crates/spark-connect/tests/e2e_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
//! Or simply skip with: `cargo test --test e2e_integration` (will skip all tests)

use spark_connect::column::{col, lit};
use spark_connect::functions;
use spark_connect::row::Value;
use spark_connect::session::SparkSession;
use spark_connect::types::DataType;
Expand Down
2 changes: 1 addition & 1 deletion crates/spark-connect/tests/ml_extra_golden.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ fn test_vector_assembler_operator_name() {

#[test]
fn test_vector_assembler_proto() {
let mut assembler = VectorAssembler::new()
let assembler = VectorAssembler::new()
.set_input_cols(vec!["col1", "col2"])
.set_output_col("vector");

Expand Down
5 changes: 1 addition & 4 deletions crates/spark-connect/tests/ml_golden.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ fn normalize_expression(e: &mut proto::Expression) {
use proto::expression::ExprType as T;
e.common = None;
if let Some(t) = e.expr_type.as_mut() {
match t {
T::Literal(_) => {}
_ => {}
}
if let T::Literal(_) = t {}
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/spark-connect/tests/plans_golden.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn normalize_relation(r: &mut proto::Relation) {
if let Some(rel_type) = &mut r.rel_type {
use proto::relation::RelType;
match rel_type {
RelType::Range(range) => {}
RelType::Range(_range) => {}
RelType::Sql(_) => {}
RelType::Project(proj) => {
if let Some(input) = &mut proj.input {
Expand Down
7 changes: 2 additions & 5 deletions crates/spark-connect/tests/readwriter_golden.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@ fn normalize_relation(r: &mut proto::Relation) {
r.common = None;
if let Some(_rel_type) = &mut r.rel_type {
use proto::relation::RelType;
match _rel_type {
RelType::Read(_read) => {
// No recursive normalization needed for Read
}
_ => {}
if let RelType::Read(_read) = _rel_type {
// No recursive normalization needed for Read
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions crates/spark-connect/tests/test_conf_tvf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
#[cfg(test)]
mod tests {
use spark_connect::plan::LogicalPlan;
use spark_connect::table_arg::TableArg;
use spark_connect::tvf::TableValuedFunction;

#[test]
fn test_unresolved_table_valued_function_plan() {
Expand Down
6 changes: 3 additions & 3 deletions crates/spark-connect/tests/window_golden.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ use std::io::{BufRead, BufReader};

use base64::{engine::general_purpose::STANDARD, Engine};
use prost::Message;
use spark_connect::column::{col, lit};
use spark_connect::column::col;
use spark_connect::expression::{Expression, LiteralExpression};
use spark_connect::functions;
use spark_connect::window::{FrameBound, FrameType, Window, WindowSpec};
use spark_connect::window::{FrameBound, WindowSpec};
use spark_connect_proto as proto;

/// An integer literal Column (pyspark infers 32-bit `integer` for small ints).
Expand Down Expand Up @@ -261,7 +261,7 @@ fn test_window_golden() {
let mut matched = 0;

for (name, expected) in &goldens {
let built = build(name).expect(&format!("missing build case for {}", name));
let built = build(name).unwrap_or_else(|| panic!("missing build case for {}", name));
let mut actual = built.to_proto();
normalize(&mut actual);

Expand Down
Loading