blob: e5aed1c96d5374e0cb515d1db11c98f30c63a733 [file]
#!/usr/bin/env bash
# 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.
# =============================================================================
# run-scala-test.sh - Run ScalaTest like IntelliJ IDEA
# =============================================================================
#
# This script simulates IntelliJ IDEA's ScalaTest execution to allow running
# individual test methods, which is not possible with standard Maven commands
# due to the conflict between -Dsuites and -am parameters.
#
# Usage:
# ./dev/run-scala-test.sh [options] -P<profiles> -pl <module> -s <suite> [-s <suite2> ...] [-t "test name"]
#
# Examples:
# # Run entire suite
# ./dev/run-scala-test.sh \
# -Pjava-17,spark-4.0,scala-2.13,backends-velox,hadoop-3.3,spark-ut \
# -pl gluten-ut/spark40 \
# -s org.apache.spark.sql.GlutenDeprecatedDatasetAggregatorSuite
#
# # Run single test method
# ./dev/run-scala-test.sh \
# -Pjava-17,spark-4.0,scala-2.13,backends-velox,hadoop-3.3,spark-ut \
# -pl gluten-ut/spark40 \
# -s org.apache.spark.sql.GlutenDeprecatedDatasetAggregatorSuite \
# -t "typed aggregation: class input with reordering"
#
# # Run multiple suites in one JVM
# ./dev/run-scala-test.sh \
# -Pjava-17,spark-4.0,scala-2.13,backends-velox,hadoop-3.3,spark-ut,delta \
# -pl gluten-ut/spark40 \
# -s org.apache.spark.sql.GlutenTPCDSV1_4_PlanStabilitySuite \
# -s org.apache.spark.sql.GlutenTPCHPlanStabilitySuite
#
# # With Maven Daemon (mvnd) for faster builds
# ./dev/run-scala-test.sh --mvnd \
# -Pjava-17,spark-4.0,scala-2.13,backends-velox,hadoop-3.3,spark-ut \
# -pl gluten-ut/spark40 \
# -s org.apache.spark.sql.GlutenDeprecatedDatasetAggregatorSuite
#
# # Clean build (required when switching Maven profiles)
# ./dev/run-scala-test.sh --mvnd --clean \
# -Pjava-17,spark-4.1,scala-2.13,backends-velox,hadoop-3.3,spark-ut \
# -pl gluten-ut/spark41 \
# -s org.apache.spark.sql.GlutenTPCDSV1_4_PlanStabilitySuite
#
# # Export classpath only (no test execution)
# ./dev/run-scala-test.sh --export-only \
# -Pjava-17,spark-4.0,scala-2.13,backends-velox,hadoop-3.3,spark-ut \
# -pl gluten-ut/spark40 \
# -s org.apache.spark.sql.GlutenDeprecatedDatasetAggregatorSuite
#
# =============================================================================
set -e
# Get script directory and project root
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GLUTEN_HOME="$(cd "${SCRIPT_DIR}/.." && pwd)"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
# =============================================================================
# Timing helpers
# =============================================================================
timer_now() {
date +%s%N
}
# Returns elapsed time in seconds (with milliseconds)
timer_elapsed() {
local start=$1 end=$2
echo "scale=1; ($end - $start) / 1000000000" | bc
}
format_duration() {
local secs=$1
local mins=$(echo "$secs / 60" | bc)
local remaining=$(echo "scale=1; $secs - $mins * 60" | bc)
if [[ "$mins" -gt 0 ]]; then
printf "%dm %.1fs" "$mins" "$remaining"
else
printf "%.1fs" "$remaining"
fi
}
log_timing() {
[[ "$ENABLE_PROFILER" != "true" ]] && return
local label=$1 elapsed=$2
local formatted=$(format_duration "$elapsed")
echo -e "${CYAN}[TIME]${NC} ${label}: ${MAGENTA}${formatted}${NC}"
}
# Print timing summary table. Args: include_step4 (true/false)
print_timing_summary() {
[[ "$ENABLE_PROFILER" != "true" ]] && return
local include_step4=${1:-false}
local mvn_total=$(echo "$TIMING_STEP1 + $TIMING_STEP2" | bc)
local overall=$(echo "$mvn_total + $TIMING_STEP3" | bc)
local cached_tag=""
[[ "$CACHE_HIT" == "true" ]] && cached_tag=" (cached)"
echo ""
echo -e "${CYAN}==========================================${NC}"
echo -e "${CYAN} ⏱ Timing Summary${NC}"
echo -e "${CYAN}==========================================${NC}"
printf " %-40s %s\n" "Step 1 - Compile + Classpath (mvn):" "$(format_duration $TIMING_STEP1)${cached_tag}"
printf " %-40s %s\n" "Step 2 - Evaluate JVM args (mvn):" "$(format_duration $TIMING_STEP2)${cached_tag}"
printf " %-40s %s\n" "Step 3 - Resolve classpath:" "$(format_duration $TIMING_STEP3)"
if [[ "$include_step4" == "true" ]]; then
printf " %-40s %s\n" "Step 4 - ScalaTest execution:" "$(format_duration $TIMING_STEP4)"
overall=$(echo "$overall + $TIMING_STEP4" | bc)
fi
echo " ------------------------------------------"
printf " %-40s %s\n" "Maven total:" "$(format_duration $mvn_total)"
if [[ "$include_step4" == "true" ]]; then
printf " %-40s %s\n" "Test execution:" "$(format_duration $TIMING_STEP4)"
fi
printf " %-40s %s\n" "Overall:" "$(format_duration $overall)"
echo -e "${CYAN}==========================================${NC}"
}
# =============================================================================
# Module Mapping: artifactId -> directory path
# =============================================================================
# Format: "artifactId:directory:type"
# type: "scala" for target/scala-X.XX/classes, "java" for target/classes
declare -A MODULE_MAP=(
# Core modules
["gluten-core"]="gluten-core:scala"
["gluten-substrait"]="gluten-substrait:scala"
["gluten-ui"]="gluten-ui:scala"
["gluten-arrow"]="gluten-arrow:scala"
# Backend modules
["backends-velox"]="backends-velox:scala"
["backends-clickhouse"]="backends-clickhouse:scala"
# RAS modules
["gluten-ras-common"]="gluten-ras/common:scala"
["gluten-ras-planner"]="gluten-ras/planner:scala"
# Shims modules (Java only, no scala subdirectory)
["spark-sql-columnar-shims-common"]="shims/common:java"
["spark-sql-columnar-shims-spark32"]="shims/spark32:java"
["spark-sql-columnar-shims-spark33"]="shims/spark33:java"
["spark-sql-columnar-shims-spark34"]="shims/spark34:java"
["spark-sql-columnar-shims-spark35"]="shims/spark35:java"
["spark-sql-columnar-shims-spark40"]="shims/spark40:java"
["spark-sql-columnar-shims-spark41"]="shims/spark41:java"
# Unit test modules
["gluten-ut-common"]="gluten-ut/common:scala"
["gluten-ut-test"]="gluten-ut/test:scala"
["gluten-ut-spark32"]="gluten-ut/spark32:scala"
["gluten-ut-spark33"]="gluten-ut/spark33:scala"
["gluten-ut-spark34"]="gluten-ut/spark34:scala"
["gluten-ut-spark35"]="gluten-ut/spark35:scala"
["gluten-ut-spark40"]="gluten-ut/spark40:scala"
["gluten-ut-spark41"]="gluten-ut/spark41:scala"
# Data lake modules
["gluten-delta"]="gluten-delta:scala"
["gluten-iceberg"]="gluten-iceberg:scala"
["gluten-hudi"]="gluten-hudi:scala"
["gluten-paimon"]="gluten-paimon:scala"
# Shuffle modules
["gluten-celeborn"]="gluten-celeborn:scala"
["gluten-uniffle"]="gluten-uniffle:scala"
# Other modules
["gluten-kafka"]="gluten-kafka:scala"
)
# =============================================================================
# Functions
# =============================================================================
print_usage() {
cat << EOF
Usage: $0 [options] -P<profiles> -pl <module> -s <suite> [-s <suite2> ...] [-t "test name"]
Required:
-P<profiles> Maven profiles (e.g., -Pjava-17,spark-4.0,scala-2.13,backends-velox)
-pl <module> Target module (e.g., gluten-ut/spark40)
-s <suite> Full suite class name (can specify multiple -s for multiple suites)
Optional:
-t "test name" Specific test method name to run
--mvnd Use Maven Daemon (mvnd) instead of ./build/mvn
--clean Run 'mvn clean' before compiling (use when switching profiles)
--force Force Maven rebuild, bypass build cache
--profile Enable Maven profiler (reports in .profiler/)
--export-only Export classpath and exit (no test execution)
--jvm-arg <arg> Pass extra JVM argument to test process (repeatable)
--help Show this help message
Examples:
# Run entire suite
$0 -Pjava-17,spark-4.0,scala-2.13,backends-velox,hadoop-3.3,spark-ut \\
-pl gluten-ut/spark40 \\
-s org.apache.spark.sql.GlutenDeprecatedDatasetAggregatorSuite
# Run single test method
$0 -Pjava-17,spark-4.0,scala-2.13,backends-velox,hadoop-3.3,spark-ut \\
-pl gluten-ut/spark40 \\
-s org.apache.spark.sql.GlutenDeprecatedDatasetAggregatorSuite \\
-t "typed aggregation: class input with reordering"
# Run multiple suites in one JVM
$0 -Pjava-17,spark-4.0,scala-2.13,backends-velox,hadoop-3.3,spark-ut,delta \\
-pl gluten-ut/spark40 \\
-s org.apache.spark.sql.GlutenTPCDSV1_4_PlanStabilitySuite \\
-s org.apache.spark.sql.GlutenTPCHPlanStabilitySuite
EOF
}
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${BLUE}[STEP]${NC} $1"
}
# Detect Scala version from profiles
detect_scala_version() {
local profiles="$1"
if [[ "$profiles" == *"scala-2.13"* ]]; then
echo "2.13"
else
echo "2.12"
fi
}
# Get target classes directory for a module
get_target_dir() {
local artifact_id="$1"
local scala_version="$2"
local class_type="$3"
local mapping="${MODULE_MAP[$artifact_id]}"
if [[ -z "$mapping" ]]; then
return 1
fi
local dir_path="${mapping%%:*}"
local module_type="${mapping##*:}"
local target_path="${GLUTEN_HOME}/${dir_path}/target"
if [[ "$module_type" == "scala" ]]; then
echo "${target_path}/scala-${scala_version}/${class_type}"
else
echo "${target_path}/${class_type}"
fi
}
# Replace gluten jar paths with target/classes directories
replace_gluten_paths() {
local classpath="$1"
local scala_version="$2"
local result=""
local added_paths=""
IFS=':' read -ra CP_ENTRIES <<< "$classpath"
for entry in "${CP_ENTRIES[@]}"; do
if [[ "$entry" != *"/org/apache/gluten/"* && "$entry" != *"/gluten-"*"/target/"* ]]; then
result="${result}:${entry}"
continue
fi
# Local target directories are already in the desired form from reactor builds
if [[ -d "$entry" && "$entry" == *"/target/"* ]]; then
result="${result}:${entry}"
continue
fi
local filename=$(basename "$entry")
local artifact_id=""
for known_artifact in "${!MODULE_MAP[@]}"; do
if [[ "$filename" == "${known_artifact}-"* ]]; then
artifact_id="$known_artifact"
break
fi
done
if [[ -z "$artifact_id" ]]; then
log_error "Unknown gluten module in classpath: $entry" >&2
log_error "Please add it to MODULE_MAP in this script." >&2
exit 1
fi
local class_type="classes"
[[ "$filename" == *"-tests.jar" ]] && class_type="test-classes"
local target_dir=$(get_target_dir "$artifact_id" "$scala_version" "$class_type")
if [[ -d "$target_dir" && "$added_paths" != *"$target_dir"* ]]; then
result="${result}:${target_dir}"
added_paths="${added_paths}:${target_dir}"
fi
done
echo "${result#:}"
}
# =============================================================================
# Parse Arguments
# =============================================================================
PROFILES=""
MODULE=""
SUITES=()
TEST_METHOD=""
EXTRA_MVN_ARGS=""
ENABLE_PROFILER=false
EXPORT_ONLY=false
ENABLE_CLEAN=false
FORCE_BUILD=false
USE_MVND=false
EXTRA_JVM_ARGS=()
while [[ $# -gt 0 ]]; do
case $1 in
-P*)
PROFILES="${1#-P}"
shift
;;
-pl)
MODULE="$2"
shift 2
;;
-s)
SUITES+=("$2")
shift 2
;;
-t)
TEST_METHOD="$2"
shift 2
;;
--profile)
ENABLE_PROFILER=true
shift
;;
--clean)
ENABLE_CLEAN=true
shift
;;
--force)
FORCE_BUILD=true
shift
;;
--mvnd)
USE_MVND=true
shift
;;
--export-only)
EXPORT_ONLY=true
shift
;;
--jvm-arg)
EXTRA_JVM_ARGS+=("$2")
shift 2
;;
--help)
print_usage
exit 0
;;
*)
# Collect other arguments for Maven
EXTRA_MVN_ARGS="${EXTRA_MVN_ARGS} $1"
shift
;;
esac
done
# Validate required arguments
if [[ -z "$PROFILES" ]]; then
log_error "Missing required argument: -P<profiles>"
print_usage
exit 1
fi
if [[ -z "$MODULE" ]]; then
log_error "Missing required argument: -pl <module>"
print_usage
exit 1
fi
if [[ ${#SUITES[@]} -eq 0 ]]; then
log_error "Missing required argument: -s <suite>"
print_usage
exit 1
fi
# Detect Scala version
SCALA_VERSION=$(detect_scala_version "$PROFILES")
log_info "Detected Scala version: ${SCALA_VERSION}"
# =============================================================================
# Build Cache - Skip Maven when source files haven't changed
# =============================================================================
#
# After a successful Maven build, a sentinel file is touched. On subsequent
# runs, if no .scala/.java/pom.xml files are newer than the sentinel, Maven
# is skipped entirely and cached classpath/JVM args are reused. This saves
# ~52s of Zinc analysis loading overhead per run.
#
# Use --force to bypass the cache, or --clean which implicitly bypasses it.
# =============================================================================
CACHE_DIR="${GLUTEN_HOME}/.run-scala-test-cache"
mkdir -p "$CACHE_DIR"
CACHE_KEY=$(echo "${PROFILES}__${MODULE}" | md5sum | cut -d' ' -f1)
BUILD_SENTINEL="${CACHE_DIR}/sentinel_${CACHE_KEY}"
CLASSPATH_CACHE="${CACHE_DIR}/classpath_${CACHE_KEY}.txt"
JVM_ARGS_CACHE="${CACHE_DIR}/jvm_args_${CACHE_KEY}.txt"
can_skip_maven() {
[[ "$ENABLE_CLEAN" == "true" ]] && return 1
[[ "$FORCE_BUILD" == "true" ]] && return 1
[[ ! -f "$BUILD_SENTINEL" || ! -f "$CLASSPATH_CACHE" || ! -f "$JVM_ARGS_CACHE" ]] && return 1
# Check if any source file or pom.xml changed since last successful build
local changed
changed=$(find "${GLUTEN_HOME}" \
\( -path "*/target" -o -path "*/.git" -o -path "*/.run-scala-test-cache" \
-o -path "*/.profiler" -o -path "*/.mvn" \) -prune -o \
-newer "$BUILD_SENTINEL" \( \
-name "pom.xml" -o \
\( -path "*/src/*" \( -name "*.scala" -o -name "*.java" \) \) \
\) -print 2>/dev/null | head -1)
[[ -z "$changed" ]]
}
save_build_cache() {
echo "$RAW_CLASSPATH" > "$CLASSPATH_CACHE"
echo "$JVM_ARGS" > "$JVM_ARGS_CACHE"
touch "$BUILD_SENTINEL"
log_info "Build cache saved"
}
if can_skip_maven; then
log_step "Steps 1-2: Using cached build (no source changes detected)"
RAW_CLASSPATH=$(cat "$CLASSPATH_CACHE")
JVM_ARGS=$(cat "$JVM_ARGS_CACHE")
TIMING_STEP1=0
TIMING_STEP2=0
CACHE_HIT=true
log_info "Cached classpath (${#RAW_CLASSPATH} chars)"
log_info "Cached JVM args: ${JVM_ARGS:0:100}..."
log_info "Maven compilation skipped — use --force to rebuild"
else
# =============================================================================
# Step 0: Ensure maven-profiler extension is installed (optional)
# =============================================================================
MVN_EXTENSIONS_FILE="${GLUTEN_HOME}/.mvn/extensions.xml"
if [[ ! -f "${MVN_EXTENSIONS_FILE}" ]]; then
log_info "Creating .mvn/extensions.xml for maven-profiler..."
mkdir -p "${GLUTEN_HOME}/.mvn"
cat > "${MVN_EXTENSIONS_FILE}" << 'EXTENSIONS_EOF'
<?xml version="1.0" encoding="UTF-8"?>
<extensions>
<extension>
<groupId>fr.jcgay.maven</groupId>
<artifactId>maven-profiler</artifactId>
<version>3.3</version>
</extension>
</extensions>
EXTENSIONS_EOF
fi
# =============================================================================
# Step 1: Compile and get classpath
# =============================================================================
log_step "Step 1: Compiling and getting classpath..."
TIMER_STEP1_START=$(timer_now)
CLASSPATH_FILE="/tmp/gluten-test-classpath-$$.txt"
cd "${GLUTEN_HOME}"
log_info "Running: ./build/mvn test-compile dependency:build-classpath -pl ${MODULE} -am -P${PROFILES} ..."
# Enable Maven profiler if requested
if [[ "$ENABLE_PROFILER" == "true" ]]; then
EXTRA_MVN_ARGS="${EXTRA_MVN_ARGS} -Dprofile -DprofileFormat=JSON"
log_info "Profiler enabled - timing summary will be printed; JSON reports in .profiler/"
fi
# Build Maven goals
MVN_GOALS="test-compile dependency:build-classpath"
if [[ "$ENABLE_CLEAN" == "true" ]]; then
MVN_GOALS="clean ${MVN_GOALS}"
log_info "Clean build requested"
fi
# Select Maven command: --mvnd uses Maven Daemon (persistent JVM that
# keeps Zinc's JIT-optimized code and classloader caches across builds).
if [[ "$USE_MVND" == "true" ]]; then
MVN_CMD="./build/mvnd"
log_info "Using Maven Daemon (mvnd) for faster builds"
else
MVN_CMD="./build/mvn"
fi
${MVN_CMD} ${MVN_GOALS} \
-T 1C -q \
-pl "${MODULE}" -am \
-P"${PROFILES},fast-build" \
-DincludeScope=test \
-Dmdep.outputFile="${CLASSPATH_FILE}" \
${EXTRA_MVN_ARGS}
if [[ ! -f "${CLASSPATH_FILE}" ]]; then
log_error "Failed to generate classpath file"
exit 1
fi
RAW_CLASSPATH=$(cat "${CLASSPATH_FILE}")
log_info "Got raw classpath (${#RAW_CLASSPATH} chars)"
TIMER_STEP1_END=$(timer_now)
TIMING_STEP1=$(timer_elapsed $TIMER_STEP1_START $TIMER_STEP1_END)
log_timing "Step 1 - Compile + Classpath (mvn)" "$TIMING_STEP1"
# =============================================================================
# Step 2: Get JVM arguments
# =============================================================================
log_step "Step 2: Getting JVM arguments from pom.xml..."
TIMER_STEP2_START=$(timer_now)
# Always use build/mvn for help:evaluate (mvnd returns empty output for this goal)
JVM_ARGS_RAW=$(./build/mvn help:evaluate \
-Dexpression=extraJavaTestArgs \
-q -DforceStdout \
-P"${PROFILES}" 2>/dev/null || echo "")
# Clean up JVM args: remove comments, trim whitespace, convert to single line
JVM_ARGS=$(echo "$JVM_ARGS_RAW" | \
grep -v '<!--' | \
grep -v '^\s*$' | \
tr '\n' ' ' | \
tr -s ' ')
log_info "JVM args: ${JVM_ARGS:0:100}..."
TIMER_STEP2_END=$(timer_now)
TIMING_STEP2=$(timer_elapsed $TIMER_STEP2_START $TIMER_STEP2_END)
log_timing "Step 2 - Evaluate JVM args (mvn)" "$TIMING_STEP2"
# Save to cache for next run
save_build_cache
fi # end of can_skip_maven else branch
# =============================================================================
# Step 3: Replace gluten paths with target/classes
# =============================================================================
log_step "Step 3: Replacing gluten jar paths with target/classes..."
TIMER_STEP3_START=$(timer_now)
RESOLVED_CLASSPATH=$(replace_gluten_paths "$RAW_CLASSPATH" "$SCALA_VERSION")
# Also add the target module's test-classes at the beginning (highest priority)
MODULE_TEST_CLASSES="${GLUTEN_HOME}/${MODULE}/target/scala-${SCALA_VERSION}/test-classes"
MODULE_CLASSES="${GLUTEN_HOME}/${MODULE}/target/scala-${SCALA_VERSION}/classes"
if [[ -d "$MODULE_TEST_CLASSES" ]]; then
RESOLVED_CLASSPATH="${MODULE_TEST_CLASSES}:${RESOLVED_CLASSPATH}"
fi
if [[ -d "$MODULE_CLASSES" ]]; then
RESOLVED_CLASSPATH="${MODULE_CLASSES}:${RESOLVED_CLASSPATH}"
fi
log_info "Resolved classpath ready"
# Sanity check: no non-.m2 jar files should remain in the classpath
IFS=':' read -ra FINAL_ENTRIES <<< "$RESOLVED_CLASSPATH"
for entry in "${FINAL_ENTRIES[@]}"; do
if [[ "$entry" == *.jar && "$entry" != *"/.m2/"* ]]; then
log_error "Non-.m2 jar found in resolved classpath: $entry"
log_error "All gluten jars should be replaced with target/classes or target/test-classes directories."
exit 1
fi
done
TIMER_STEP3_END=$(timer_now)
TIMING_STEP3=$(timer_elapsed $TIMER_STEP3_START $TIMER_STEP3_END)
log_timing "Step 3 - Resolve classpath" "$TIMING_STEP3"
# =============================================================================
# Step 3.5: Create pathing JAR
# =============================================================================
# A "pathing JAR" is a thin JAR whose MANIFEST.MF Class-Path header lists all
# the real classpath entries as file: URIs. This avoids exceeding OS command-line
# length limits. Works on Java 8+ (unlike @argfile which requires Java 9+).
# Also includes META-INF/classpath.txt for human-readable review.
# =============================================================================
PATHING_JAR="/tmp/gluten-test-pathing-$$.jar"
rm -f "${PATHING_JAR}"
PATHING_MANIFEST_DIR="/tmp/gluten-test-manifest-$$"
mkdir -p "${PATHING_MANIFEST_DIR}/META-INF"
# Convert colon-separated classpath to space-separated file: URIs for manifest.
# Also write a human-readable classpath listing (one entry per line, same order).
CP_URIS=""
IFS=':' read -ra _CP_ITEMS <<< "${RESOLVED_CLASSPATH}"
: > "${PATHING_MANIFEST_DIR}/META-INF/classpath.txt"
for _item in "${_CP_ITEMS[@]}"; do
[[ -z "$_item" ]] && continue
echo "$_item" >> "${PATHING_MANIFEST_DIR}/META-INF/classpath.txt"
# Ensure directories end with / (required by Class-Path spec for directories)
[[ -d "$_item" && "$_item" != */ ]] && _item="${_item}/"
CP_URIS="${CP_URIS} file://${_item}"
done
CP_URIS="${CP_URIS# }"
# Write manifest with proper 72-byte line wrapping.
# MANIFEST.MF spec: max 72 bytes per line; continuation lines start with
# a single space character.
{
echo "Manifest-Version: 1.0"
_mf_header="Class-Path: ${CP_URIS}"
echo "${_mf_header:0:72}"
_mf_rest="${_mf_header:72}"
while [[ -n "$_mf_rest" ]]; do
echo " ${_mf_rest:0:71}"
_mf_rest="${_mf_rest:71}"
done
echo ""
} > "${PATHING_MANIFEST_DIR}/META-INF/MANIFEST.MF"
# Build the pathing JAR (manifest + human-readable classpath listing)
(cd "${PATHING_MANIFEST_DIR}" && jar cfm "${PATHING_JAR}" META-INF/MANIFEST.MF META-INF/classpath.txt)
rm -rf "${PATHING_MANIFEST_DIR}"
log_info "Created pathing JAR: ${PATHING_JAR} ($(du -h "${PATHING_JAR}" | cut -f1))"
log_info " Review classpath: unzip -p ${PATHING_JAR} META-INF/classpath.txt"
# =============================================================================
# Build java command (shared by export-only and run modes)
# =============================================================================
JAVA_CMD="${JAVA_HOME}/bin/java"
[[ ! -x "$JAVA_CMD" ]] && JAVA_CMD="java"
SPARK_TEST_HOME_ARG=""
[[ -n "$SPARK_HOME" ]] && SPARK_TEST_HOME_ARG="-Dspark.test.home=${SPARK_HOME}"
JAVA_ARGS=(
${JVM_ARGS}
"${EXTRA_JVM_ARGS[@]}"
"-Dlog4j.configurationFile=file:${GLUTEN_HOME}/${MODULE}/src/test/resources/log4j2.properties"
${SPARK_TEST_HOME_ARG}
-cp "${PATHING_JAR}"
org.scalatest.tools.Runner
-oDF
)
for s in "${SUITES[@]}"; do
JAVA_ARGS+=(-s "$s")
done
[[ -n "$TEST_METHOD" ]] && JAVA_ARGS+=(-t "${TEST_METHOD}")
# =============================================================================
# Step 3.6: Export-only mode (if requested)
# =============================================================================
if [[ "$EXPORT_ONLY" == "true" ]]; then
echo ""
echo -e "${YELLOW}# Run the test with:${NC}"
echo "${JAVA_CMD} ${JAVA_ARGS[*]}"
echo ""
print_timing_summary false
exit 0
fi
# =============================================================================
# Step 4: Run ScalaTest
# =============================================================================
log_step "Step 4: Running ScalaTest..."
log_info "Suite(s): ${SUITES[*]}"
[[ -n "$TEST_METHOD" ]] && log_info "Test method: ${TEST_METHOD}"
echo ""
echo "=========================================="
echo "Running ScalaTest"
echo "=========================================="
echo ""
TIMER_STEP4_START=$(timer_now)
TEST_EXIT_CODE=0
${JAVA_CMD} "${JAVA_ARGS[@]}" || TEST_EXIT_CODE=$?
rm -f "${PATHING_JAR}"
TIMER_STEP4_END=$(timer_now)
TIMING_STEP4=$(timer_elapsed $TIMER_STEP4_START $TIMER_STEP4_END)
log_timing "Step 4 - ScalaTest execution" "$TIMING_STEP4"
print_timing_summary true
exit $TEST_EXIT_CODE