Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
36 changes: 36 additions & 0 deletions plugins/spark/v3.5/regtests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,39 @@ Note: the regression tests expect Polaris to run with certain options, e.g. with
storage, default realm `POLARIS` and root credentials `root:secret`; if you run the above command,
this will be the case. If you run Polaris in a different way, make sure that Polaris is configured
appropriately.

## Running Specific Test Suites

By default, `run.sh` executes all test suites (Delta and Hudi). To run a specific suite, use the
`REGTEST_SUITE` environment variable:

```bash
# Run only Delta tests (existing spark_sql)
env POLARIS_HOST=localhost REGTEST_SUITE="spark_sql.sh:delta:spark_sql" ./plugins/spark/v3.5/regtests/run.sh

# Run only Hudi tests
env POLARIS_HOST=localhost REGTEST_SUITE="spark_hudi.sh:hudi:spark_hudi" ./plugins/spark/v3.5/regtests/run.sh
```

## Table Format Support

The regression tests support multiple table formats through the `--tableFormat` parameter in `setup.sh`:

- **Delta** (default): Uses `DeltaCatalog` for `spark_catalog`. Tests both Iceberg and Delta tables.
- **Hudi**: Uses `HoodieCatalog` for `spark_catalog`. Tests both Iceberg and Hudi tables.

Each test suite is isolated with its own Spark configuration and catalog setup. The `spark_catalog`
can only be configured to one catalog implementation at a time, which is why separate test suites
are needed for Delta and Hudi formats.

### Manual Setup

You can manually run `setup.sh` with a specific table format:

```bash
# Setup for Delta tables (default)
./plugins/spark/v3.5/regtests/setup.sh --sparkVersion 3.5.6 --scalaVersion 2.12 --polarisVersion 0.1.0 --tableFormat delta

# Setup for Hudi tables
./plugins/spark/v3.5/regtests/setup.sh --sparkVersion 3.5.6 --scalaVersion 2.12 --polarisVersion 0.1.0 --tableFormat hudi
```
132 changes: 81 additions & 51 deletions plugins/spark/v3.5/regtests/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@ SPARK_VERSION="3.5.6"

SPARK_SHELL_OPTIONS=("PACKAGE" "JAR")

# Define test suites to run
# Each suite specifies: test_file:table_format:test_shortname
declare -a TEST_SUITES=(
"spark_sql.sh:delta:spark_sql"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about let's enforce the test file name to the format like xxx_<table_format>.sh, and have a separate folder to include all test src file and reference file. Then we just need to list the folder to get all test files, and extract the table format by parsing the file name. The benefit would be easy to onboard new tests, and developer doesn't have to input a long string when running single test (just the file name)

"spark_hudi.sh:hudi:spark_hudi"
)

# Allow running specific test via environment variable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can potentially also allow running all suites for a particular format by taking table format as an argument to this script. We can probably do that in a separate PR as an improvement.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets do in another pr

echo "REGTEST_SUITE=${REGTEST_SUITE}"
if [[ -n "$REGTEST_SUITE" ]]; then
echo "Overriding TEST_SUITES to run only: ${REGTEST_SUITE}"
TEST_SUITES=("${REGTEST_SUITE}")
fi
echo "Will run test suites: ${TEST_SUITES[@]}"

for SCALA_VERSION in "${SCALA_VERSIONS[@]}"; do
echo "RUN REGRESSION TEST FOR SPARK_MAJOR_VERSION=${SPARK_MAJOR_VERSION}, SPARK_VERSION=${SPARK_VERSION}, SCALA_VERSION=${SCALA_VERSION}"
# find the project jar
Expand All @@ -89,59 +104,74 @@ for SCALA_VERSION in "${SCALA_VERSIONS[@]}"; do
fi

for SPARK_SHELL_OPTION in "${SPARK_SHELL_OPTIONS[@]}"; do
# clean up the default configuration if exists
if [ -f "${SPARK_HOME}" ]; then
SPARK_CONF="${SPARK_HOME}/conf/spark-defaults.conf"
if [ -f ${SPARK_CONF} ]; then
rm ${SPARK_CONF}
fi
fi

if [ "${SPARK_SHELL_OPTION}" == "PACKAGE" ]; then
# run the setup without jar configuration
source ${SCRIPT_DIR}/setup.sh --sparkVersion ${SPARK_VERSION} --scalaVersion ${SCALA_VERSION} --polarisVersion ${POLARIS_VERSION}
else
source ${SCRIPT_DIR}/setup.sh --sparkVersion ${SPARK_VERSION} --scalaVersion ${SCALA_VERSION} --polarisVersion ${POLARIS_VERSION} --jar ${JAR_PATH}
fi

# run the spark_sql test
loginfo "Starting test spark_sql.sh"

TEST_FILE="spark_sql.sh"
TEST_SHORTNAME="spark_sql"
TEST_TMPDIR="/tmp/polaris-spark-regtests/${TEST_SHORTNAME}_${SPARK_MAJOR_VERSION}_${SCALA_VERSION}"
TEST_STDERR="${TEST_TMPDIR}/${TEST_SHORTNAME}.stderr"
TEST_STDOUT="${TEST_TMPDIR}/${TEST_SHORTNAME}.stdout"

mkdir -p ${TEST_TMPDIR}
if (( ${VERBOSE} )); then
${SCRIPT_DIR}/${TEST_FILE} 2>${TEST_STDERR} | grep -v 'loading settings' | tee ${TEST_STDOUT}
else
${SCRIPT_DIR}/${TEST_FILE} 2>${TEST_STDERR} | grep -v 'loading settings' > ${TEST_STDOUT}
fi
loginfo "Test run concluded for ${TEST_SUITE}:${TEST_SHORTNAME}"

TEST_REF="$(realpath ${SCRIPT_DIR})/${TEST_SHORTNAME}.ref"
if cmp --silent ${TEST_STDOUT} ${TEST_REF}; then
loggreen "Test SUCCEEDED: ${TEST_SUITE}:${TEST_SHORTNAME}"
else
logred "Test FAILED: ${TEST_SUITE}:${TEST_SHORTNAME}"
echo '#!/bin/bash' > ${TEST_TMPDIR}/${TEST_SHORTNAME}.fixdiffs.sh
echo "meld ${TEST_STDOUT} ${TEST_REF}" >> ${TEST_TMPDIR}/${TEST_SHORTNAME}.fixdiffs.sh
chmod 750 ${TEST_TMPDIR}/${TEST_SHORTNAME}.fixdiffs.sh
logred "To compare and fix diffs (if 'meld' installed): ${TEST_TMPDIR}/${TEST_SHORTNAME}.fixdiffs.sh"
logred "Or manually diff: diff ${TEST_STDOUT} ${TEST_REF}"
logred "See stderr from test run for additional diagnostics: ${TEST_STDERR}"
diff ${TEST_STDOUT} ${TEST_REF}
NUM_FAILURES=$(( NUM_FAILURES + 1 ))
fi
# Loop through each test suite
for TEST_SUITE_CONFIG in "${TEST_SUITES[@]}"; do
# Parse test suite configuration (format: test_file:table_format:test_shortname)
IFS=':' read -r TEST_FILE TABLE_FORMAT TEST_SHORTNAME <<< "$TEST_SUITE_CONFIG"

# Skip this suite if REGTEST_SUITE is set and doesn't match
if [[ -n "$REGTEST_SUITE" ]] && [[ "$TEST_SUITE_CONFIG" != "$REGTEST_SUITE" ]]; then
echo "Skipping test suite ${TEST_SHORTNAME} (REGTEST_SUITE=${REGTEST_SUITE})"
continue
fi

loginfo "Setting up for test suite: ${TEST_SHORTNAME} with table format: ${TABLE_FORMAT}"

# clean up the default configuration if exists
if [ -f "${SPARK_HOME}" ]; then
SPARK_CONF="${SPARK_HOME}/conf/spark-defaults.conf"
if [ -f ${SPARK_CONF} ]; then
rm ${SPARK_CONF}
fi
fi

# Run setup with appropriate table format
if [ "${SPARK_SHELL_OPTION}" == "PACKAGE" ]; then
# run the setup without jar configuration
source ${SCRIPT_DIR}/setup.sh --sparkVersion ${SPARK_VERSION} --scalaVersion ${SCALA_VERSION} --polarisVersion ${POLARIS_VERSION} --tableFormat ${TABLE_FORMAT}
else
source ${SCRIPT_DIR}/setup.sh --sparkVersion ${SPARK_VERSION} --scalaVersion ${SCALA_VERSION} --polarisVersion ${POLARIS_VERSION} --jar ${JAR_PATH} --tableFormat ${TABLE_FORMAT}
fi

# run the test
loginfo "Starting test ${TEST_FILE}"

TEST_TMPDIR="/tmp/polaris-spark-regtests/${TEST_SHORTNAME}_${SPARK_MAJOR_VERSION}_${SCALA_VERSION}_${SPARK_SHELL_OPTION}"
TEST_STDERR="${TEST_TMPDIR}/${TEST_SHORTNAME}.stderr"
TEST_STDOUT="${TEST_TMPDIR}/${TEST_SHORTNAME}.stdout"

mkdir -p ${TEST_TMPDIR}
if (( ${VERBOSE} )); then
${SCRIPT_DIR}/${TEST_FILE} 2>${TEST_STDERR} | grep -v 'loading settings' | tee ${TEST_STDOUT}
else
${SCRIPT_DIR}/${TEST_FILE} 2>${TEST_STDERR} | grep -v 'loading settings' > ${TEST_STDOUT}
fi
loginfo "Test run concluded for ${TEST_SHORTNAME}"

# Compare output with reference
TEST_REF="$(realpath ${SCRIPT_DIR})/${TEST_SHORTNAME}.ref"
if cmp --silent ${TEST_STDOUT} ${TEST_REF}; then
loggreen "Test SUCCEEDED: ${TEST_SHORTNAME}"
else
logred "Test FAILED: ${TEST_SHORTNAME}"
echo '#!/bin/bash' > ${TEST_TMPDIR}/${TEST_SHORTNAME}.fixdiffs.sh
echo "meld ${TEST_STDOUT} ${TEST_REF}" >> ${TEST_TMPDIR}/${TEST_SHORTNAME}.fixdiffs.sh
chmod 750 ${TEST_TMPDIR}/${TEST_SHORTNAME}.fixdiffs.sh
logred "To compare and fix diffs (if 'meld' installed): ${TEST_TMPDIR}/${TEST_SHORTNAME}.fixdiffs.sh"
logred "Or manually diff: diff ${TEST_STDOUT} ${TEST_REF}"
logred "See stderr from test run for additional diagnostics: ${TEST_STDERR}"
diff ${TEST_STDOUT} ${TEST_REF}
NUM_FAILURES=$(( NUM_FAILURES + 1 ))
fi
done
done

# clean up
if [ "${SPARK_EXISTS}" = "FALSE" ]; then
rm -rf ${SPARK_HOME}
export SPARK_HOME=""
fi
clean up
Commented out for faster development/testing - uncomment for CI or final runs
if [ "${SPARK_EXISTS}" = "FALSE" ]; then
rm -rf ${SPARK_HOME}
export SPARK_HOME=""
fi
done

# clean the output dir
Expand Down
62 changes: 59 additions & 3 deletions plugins/spark/v3.5/regtests/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@
# Warning - it will set the SPARK_HOME environment variable with the spark setup
#
# The script can be called independently like following
# ./setup.sh --sparkVersion ${SPARK_VERSION} --scalaVersion ${SCALA_VERSION} --jar ${JAR_PATH}
# ./setup.sh --sparkVersion ${SPARK_VERSION} --scalaVersion ${SCALA_VERSION} --jar ${JAR_PATH} --tableFormat ${TABLE_FORMAT}
# Required Parameters:
# --sparkVersion : the spark version to setup
# --scalaVersion : the scala version of spark to setup
# --jar : path to the local Polaris Spark client jar
#
# Optional Parameters:
# --tableFormat : table format to configure (delta|hudi). Default: delta
#

set -x

Expand All @@ -40,6 +43,7 @@ SPARK_VERSION=3.5.6
SCALA_VERSION=2.12
POLARIS_CLIENT_JAR=""
POLARIS_VERSION=""
TABLE_FORMAT="delta"
while [[ $# -gt 0 ]]; do
case "$1" in
--sparkVersion)
Expand All @@ -62,13 +66,24 @@ while [[ $# -gt 0 ]]; do
shift # past argument
shift # past value
;;
--tableFormat)
TABLE_FORMAT="$2"
shift # past argument
shift # past value
;;
--) shift;
break
;;
esac
done

echo "SET UP FOR SPARK_VERSION=${SPARK_VERSION} SCALA_VERSION=${SCALA_VERSION} POLARIS_VERSION=${POLARIS_VERSION} POLARIS_CLIENT_JAR=${POLARIS_CLIENT_JAR}"
echo "SET UP FOR SPARK_VERSION=${SPARK_VERSION} SCALA_VERSION=${SCALA_VERSION} POLARIS_VERSION=${POLARIS_VERSION} POLARIS_CLIENT_JAR=${POLARIS_CLIENT_JAR} TABLE_FORMAT=${TABLE_FORMAT}"

# Validate table format
if [[ "$TABLE_FORMAT" != "delta" && "$TABLE_FORMAT" != "hudi" ]]; then
echo "Error: Invalid table format '${TABLE_FORMAT}'. Must be 'delta' or 'hudi'."
exit 1
fi

if [ "$SCALA_VERSION" == "2.12" ]; then
SPARK_DISTRIBUTION=spark-${SPARK_VERSION}-bin-hadoop3
Expand Down Expand Up @@ -141,14 +156,33 @@ else
if [[ -z "$POLARIS_CLIENT_JAR" ]]; then
cat << EOF >> ${SPARK_CONF}
# POLARIS Spark client test conf
EOF
if [[ "$TABLE_FORMAT" == "hudi" ]]; then
cat << EOF >> ${SPARK_CONF}
spark.jars.packages org.apache.polaris:polaris-spark-3.5_$SCALA_VERSION:$POLARIS_VERSION
# Note: Hudi package is passed via --packages on command line in spark_hudi.sh
# to ensure it's resolved before Kryo initialization
EOF
else
cat << EOF >> ${SPARK_CONF}
spark.jars.packages org.apache.polaris:polaris-spark-3.5_$SCALA_VERSION:$POLARIS_VERSION,io.delta:delta-spark_${SCALA_VERSION}:3.2.1
EOF
fi
else
cat << EOF >> ${SPARK_CONF}
# POLARIS Spark client test conf
spark.jars $POLARIS_CLIENT_JAR
EOF
if [[ "$TABLE_FORMAT" == "hudi" ]]; then
cat << EOF >> ${SPARK_CONF}
# Note: Hudi package is passed via --packages on command line in spark_hudi.sh
# to ensure it's resolved before Kryo initialization
EOF
else
cat << EOF >> ${SPARK_CONF}
spark.jars.packages io.delta:delta-spark_${SCALA_VERSION}:3.2.1
EOF
fi
fi

cat << EOF >> ${SPARK_CONF}
Expand All @@ -157,9 +191,26 @@ spark.sql.variable.substitute true

spark.driver.extraJavaOptions -Dderby.system.home=${DERBY_HOME}

EOF

if [[ "$TABLE_FORMAT" == "hudi" ]]; then
cat << EOF >> ${SPARK_CONF}
spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions,org.apache.spark.sql.hudi.HoodieSparkSessionExtension
# this configuration is needed for hudi table
spark.sql.catalog.spark_catalog=org.apache.spark.sql.hudi.catalog.HoodieCatalog
spark.serializer=org.apache.spark.serializer.KryoSerializer
spark.kryo.registrator=org.apache.spark.HoodieSparkKryoRegistrar
hoodie.metadata.enable=false
EOF
else
cat << EOF >> ${SPARK_CONF}
spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions,io.delta.sql.DeltaSparkSessionExtension
# this configuration is needed for delta table
spark.sql.catalog.spark_catalog=org.apache.spark.sql.delta.catalog.DeltaCatalog
EOF
fi

cat << EOF >> ${SPARK_CONF}
spark.sql.catalog.polaris=org.apache.polaris.spark.SparkCatalog
spark.sql.catalog.polaris.uri=http://${POLARIS_HOST:-localhost}:8181/api/catalog
# this configuration is currently only used for iceberg tables, generic tables currently
Expand All @@ -182,7 +233,12 @@ fi
echo "Launch spark-sql at ${SPARK_HOME}/bin/spark-sql"
# bootstrap dependencies so that future queries don't need to wait for the downloads.
# this is mostly useful for building the Docker image with all needed dependencies
${SPARK_HOME}/bin/spark-sql -e "SELECT 1"
if [[ "$TABLE_FORMAT" == "hudi" ]]; then
# For Hudi: Pass --packages on command line to match official Hudi docs approach

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think we need the if else here anymore

${SPARK_HOME}/bin/spark-sql --packages org.apache.hudi:hudi-spark3.5-bundle_${SCALA_VERSION}:1.1.1 -e "SELECT 1"
else
${SPARK_HOME}/bin/spark-sql -e "SELECT 1"
fi

# ensure SPARK_HOME is setup for later tests
export SPARK_HOME=$SPARK_HOME
45 changes: 45 additions & 0 deletions plugins/spark/v3.5/regtests/spark_hudi.ref
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{"defaults":{"default-base-location":"file:///tmp/spark_hudi_catalog"},"overrides":{"prefix":"spark_hudi_catalog"},"endpoints":["GET /v1/{prefix}/namespaces","GET /v1/{prefix}/namespaces/{namespace}","HEAD /v1/{prefix}/namespaces/{namespace}","POST /v1/{prefix}/namespaces","POST /v1/{prefix}/namespaces/{namespace}/properties","DELETE /v1/{prefix}/namespaces/{namespace}","GET /v1/{prefix}/namespaces/{namespace}/tables","GET /v1/{prefix}/namespaces/{namespace}/tables/{table}","HEAD /v1/{prefix}/namespaces/{namespace}/tables/{table}","POST /v1/{prefix}/namespaces/{namespace}/tables","POST /v1/{prefix}/namespaces/{namespace}/tables/{table}","DELETE /v1/{prefix}/namespaces/{namespace}/tables/{table}","POST /v1/{prefix}/tables/rename","POST /v1/{prefix}/namespaces/{namespace}/register","POST /v1/{prefix}/namespaces/{namespace}/tables/{table}/metrics","POST /v1/{prefix}/transactions/commit","GET /v1/{prefix}/namespaces/{namespace}/views","GET /v1/{prefix}/namespaces/{namespace}/views/{view}","HEAD /v1/{prefix}/namespaces/{namespace}/views/{view}","POST /v1/{prefix}/namespaces/{namespace}/views","POST /v1/{prefix}/namespaces/{namespace}/views/{view}","DELETE /v1/{prefix}/namespaces/{namespace}/views/{view}","POST /v1/{prefix}/views/rename","GET polaris/v1/{prefix}/namespaces/{namespace}/generic-tables","POST polaris/v1/{prefix}/namespaces/{namespace}/generic-tables","DELETE polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}","GET polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}","GET /polaris/v1/{prefix}/namespaces/{namespace}/policies","POST /polaris/v1/{prefix}/namespaces/{namespace}/policies","GET /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}","PUT /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}","DELETE /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}","PUT /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}/mappings","POST /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}/mappings","GET /polaris/v1/{prefix}/applicable-policies"]}
Catalog created
spark-sql (default)> use polaris;
spark-sql ()> create namespace hudi_db1;
spark-sql ()> create namespace hudi_db2;
spark-sql ()> show namespaces;
hudi_db1
hudi_db2
spark-sql ()>
> create namespace hudi_db1.schema1;
spark-sql ()> show namespaces in hudi_db1;
hudi_db1.schema1
spark-sql ()>
> create table hudi_db1.schema1.hudi_tb1 (id int, name string) using hudi location 'file:///tmp/spark_hudi_catalog/hudi_tb1';
spark-sql ()> show tables in hudi_db1;
spark-sql ()> show tables in hudi_db1.schema1;
spark-sql ()>
> use hudi_db1.schema1;
spark-sql (hudi_db1.schema1)> insert into hudi_tb1 values (1, 'alice'), (2, 'bob');
spark-sql (hudi_db1.schema1)> select * from hudi_tb1 order by id;
spark-sql (hudi_db1.schema1)>
> create table hudi_tb2 (name string, age int, country string) using hudi partitioned by (country) location 'file:///tmp/spark_hudi_catalog/hudi_tb2';
spark-sql (hudi_db1.schema1)> insert into hudi_tb2 values ('anna', 10, 'US'), ('james', 32, 'US'), ('yan', 16, 'CHINA');
spark-sql (hudi_db1.schema1)> select name, country from hudi_tb2 order by age;
spark-sql (hudi_db1.schema1)>
> show tables;
spark-sql (hudi_db1.schema1)>
> use hudi_db1;
spark-sql (hudi_db1)> create table iceberg_tb (col1 int);
spark-sql (hudi_db1)> insert into iceberg_tb values (100), (200);
spark-sql (hudi_db1)> select * from iceberg_tb order by col1;
100
200
spark-sql (hudi_db1)>
> show tables;
iceberg_tb
spark-sql (hudi_db1)> show tables in hudi_db1.schema1;
spark-sql (hudi_db1)>
> drop table hudi_db1.schema1.hudi_tb1;
spark-sql (hudi_db1)> drop table hudi_db1.schema1.hudi_tb2;
spark-sql (hudi_db1)> drop namespace hudi_db1.schema1;
spark-sql (hudi_db1)> drop table iceberg_tb;
spark-sql (hudi_db1)> drop namespace hudi_db1;
spark-sql (hudi_db1)> drop namespace hudi_db2;
spark-sql (hudi_db1)>
Loading
Loading