Skip to content

Commit 724b755

Browse files
committed
now installing venv as part of sbt test
1 parent 7e1593f commit 724b755

3 files changed

Lines changed: 119 additions & 20 deletions

File tree

spark-plugin/build.sbt

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import xerial.sbt.Sonatype._
22
import sbtassembly.AssemblyPlugin.autoImport._
3+
import scala.sys.process._
34

45
lazy val versionNum: String = "0.8.8"
6+
lazy val spark3Version: String = "3.5.1"
7+
lazy val pythonVenvDir: String = System.getProperty("java.io.tmpdir") + "/dataflint-pyspark-venv"
8+
lazy val pythonExec: String = "python3.11"
9+
val createPythonVenv = taskKey[Unit]("Create Python venv and install pyspark test dependencies")
510
lazy val scala212 = "2.12.20"
611
lazy val scala213 = "2.13.16"
712
lazy val supportedScalaVersions = List(scala212, scala213)
@@ -35,8 +40,8 @@ lazy val plugin = (project in file("plugin"))
3540
} else {
3641
versionNum + "-SNAPSHOT"
3742
}),
38-
libraryDependencies += "org.apache.spark" %% "spark-core" % "3.5.1" % "provided",
39-
libraryDependencies += "org.apache.spark" %% "spark-sql" % "3.5.1" % "provided",
43+
libraryDependencies += "org.apache.spark" %% "spark-core" % spark3Version % "provided",
44+
libraryDependencies += "org.apache.spark" %% "spark-sql" % spark3Version % "provided",
4045
libraryDependencies += "com.amazonaws" % "aws-java-sdk-s3" % "1.12.470" % "provided",
4146
libraryDependencies += "org.apache.iceberg" %% "iceberg-spark-runtime-3.5" % "1.5.0" % "provided",
4247
libraryDependencies += "io.delta" %% "delta-spark" % "3.2.0" % "provided",
@@ -61,12 +66,12 @@ lazy val pluginspark3 = (project in file("pluginspark3"))
6166
} else {
6267
versionNum + "-SNAPSHOT"
6368
}),
64-
libraryDependencies += "org.apache.spark" %% "spark-core" % "3.5.1" % "provided",
65-
libraryDependencies += "org.apache.spark" %% "spark-sql" % "3.5.1" % "provided",
69+
libraryDependencies += "org.apache.spark" %% "spark-core" % spark3Version % "provided",
70+
libraryDependencies += "org.apache.spark" %% "spark-sql" % spark3Version % "provided",
6671
libraryDependencies += "com.amazonaws" % "aws-java-sdk-s3" % "1.12.470" % "provided",
6772
libraryDependencies += "org.apache.iceberg" %% "iceberg-spark-runtime-3.5" % "1.5.0" % "provided",
6873
libraryDependencies += "io.delta" %% "delta-spark" % "3.2.0" % "provided",
69-
74+
7075
// Assembly configuration to create fat JAR with common code
7176
assembly / assemblyJarName := s"${name.value}_${scalaBinaryVersion.value}-${version.value}.jar",
7277
// Exclude Scala library from assembly - Spark provides its own Scala runtime
@@ -96,8 +101,8 @@ lazy val pluginspark3 = (project in file("pluginspark3"))
96101
Compile / unmanagedResourceDirectories += (plugin / Compile / resourceDirectory).value,
97102
libraryDependencies += "org.scalatest" %% "scalatest-funsuite" % "3.2.17" % Test,
98103
libraryDependencies += "org.scalatest" %% "scalatest-shouldmatchers" % "3.2.17" % Test,
99-
libraryDependencies += "org.apache.spark" %% "spark-core" % "3.5.1" % Test,
100-
libraryDependencies += "org.apache.spark" %% "spark-sql" % "3.5.1" % Test,
104+
libraryDependencies += "org.apache.spark" %% "spark-core" % spark3Version % Test,
105+
libraryDependencies += "org.apache.spark" %% "spark-sql" % spark3Version % Test,
101106

102107
// Include source and resources from plugin directory for tests
103108
Test / unmanagedSourceDirectories += (plugin / Compile / sourceDirectory).value / "scala",
@@ -107,6 +112,24 @@ lazy val pluginspark3 = (project in file("pluginspark3"))
107112
// Run test suites sequentially — parallel suites share the SparkSession via getOrCreate()
108113
// and one suite stopping the session causes NPEs in concurrently-running suites
109114
Test / parallelExecution := false,
115+
createPythonVenv := {
116+
val venvDir = new java.io.File(pythonVenvDir)
117+
val log = streams.value.log
118+
if (!venvDir.exists()) {
119+
log.info(s"Creating Python venv at $pythonVenvDir ...")
120+
val rc = Process(Seq(pythonExec, "-m", "venv", pythonVenvDir)).!
121+
if (rc != 0) sys.error(s"Failed to create Python venv at $pythonVenvDir")
122+
}
123+
val pip = s"$pythonVenvDir/bin/pip"
124+
log.info(s"Installing pyspark==$spark3Version pandas pyarrow into venv...")
125+
val rc = Process(Seq(pip, "install", "--quiet", s"pyspark==$spark3Version", "pandas", "pyarrow")).!
126+
if (rc != 0) sys.error("pip install failed")
127+
},
128+
Test / compile := (Test / compile).dependsOn(createPythonVenv).value,
129+
Test / testOnly := (Test / testOnly).dependsOn(createPythonVenv).evaluated,
130+
Test / javaOptions ++= Seq(
131+
s"-Ddataflint.projectRoot=${baseDirectory.value.getParentFile.toString}",
132+
),
110133
Test / javaOptions ++= {
111134
// --add-opens is not supported on Java 8 (spec version starts with "1.")
112135
if (sys.props("java.specification.version").startsWith("1.")) Seq.empty

spark-plugin/pluginspark3/src/test/scala/org/apache/spark/dataflint/DataFlintPythonIntegrationSpec.scala

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,13 @@ import java.nio.file.Paths
1616
* for the subprocess — no manual gateway setup needed. The Python script then connects
1717
* to this JVM via launch_gateway() and accesses the session through DataFlintStaticSession.
1818
*
19-
* Requires: .venv with pyspark, pandas, pyarrow installed.
20-
* python3 -m venv .venv && .venv/bin/pip install pyspark pandas pyarrow
19+
* Python dependencies (pyspark, pandas, pyarrow) are installed automatically into
20+
* .venv at test startup if not already present.
2121
*/
2222
class DataFlintPythonIntegrationSpec extends AnyFunSuite with Matchers with BeforeAndAfterAll with DataFlintTestHelper {
2323

24-
// pluginspark3 tests run with CWD = spark-plugin/pluginspark3/, so go up one level
25-
// to reach the project root where .venv and pyspark-testing live.
26-
private val projectRoot = Paths.get("").toAbsolutePath//.getParent
27-
28-
private val venvPython: String = {
29-
val p = projectRoot.resolve(Paths.get(".venv", "bin", "python3"))
30-
require(p.toFile.exists(),
31-
s"Python venv not found at $p\n" +
32-
"Run: python3 -m venv .venv && .venv/bin/pip install pyspark pandas pyarrow")
33-
p.toString
34-
}
24+
private val projectRoot = Paths.get(sys.props.getOrElse("dataflint.projectRoot", ""))
25+
private val venvPython: String = System.getProperty("java.io.tmpdir") + "/dataflint-pyspark-venv/bin/python3"
3526

3627
private val scriptPath: String =
3728
projectRoot.resolve(
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""
2+
Integration test helper: registers one DataFlint Python exec node scenario as a temp view.
3+
4+
Usage: pass the test name as the first argument (sys.argv[1]):
5+
batch_eval — @udf → registers batch_eval_view
6+
arrow_eval — @pandas_udf scalar → registers arrow_eval_view
7+
flat_map_groups — applyInPandas → registers flat_map_groups_view
8+
flat_map_cogroups— cogroup.applyIn… → registers flat_map_cogroups_view
9+
10+
The Scala test (DataFlintPythonIntegrationSpec) calls PythonRunner.main with one of
11+
the above names, then checks the corresponding view's executedPlan.
12+
"""
13+
import sys
14+
import pyspark
15+
import pyspark.java_gateway
16+
from pyspark.sql import SparkSession
17+
from pyspark.sql.functions import udf, pandas_udf
18+
from pyspark.sql.types import LongType
19+
import pandas as pd
20+
21+
test_name = sys.argv[1] if len(sys.argv) > 1 else ""
22+
23+
gateway = pyspark.java_gateway.launch_gateway()
24+
static = gateway.jvm.org.apache.spark.dataflint.DataFlintStaticSession
25+
jsc = static.javaSparkContext()
26+
spark_jvm = static.session()
27+
28+
conf = pyspark.conf.SparkConf(True, gateway.jvm, jsc.getConf())
29+
sc = pyspark.SparkContext(gateway=gateway, jsc=jsc, conf=conf)
30+
spark = SparkSession(sc, jsparkSession=spark_jvm)
31+
32+
df = spark.createDataFrame(
33+
[(1, "a"), (2, "b"), (3, "a"), (4, "b")],
34+
["id", "cat"]
35+
)
36+
37+
38+
def register_batch_eval():
39+
@udf(returnType=LongType())
40+
def double_udf(x):
41+
return x * 2
42+
df.select(double_udf("id")).createOrReplaceTempView("batch_eval_view")
43+
44+
45+
def register_arrow_eval():
46+
@pandas_udf(LongType())
47+
def double_pandas_udf(s: pd.Series) -> pd.Series:
48+
return s * 2
49+
df.select(double_pandas_udf("id")).createOrReplaceTempView("arrow_eval_view")
50+
51+
52+
def register_flat_map_groups():
53+
def identity_group(key, pdf):
54+
return pdf
55+
df.groupby("cat").applyInPandas(
56+
identity_group, schema="id long, cat string"
57+
).createOrReplaceTempView("flat_map_groups_view")
58+
59+
60+
def register_flat_map_cogroups():
61+
df2 = spark.createDataFrame(
62+
[(1, "x"), (2, "y"), (3, "z"), (4, "w")],
63+
["id", "label"]
64+
)
65+
def cogroup_fn(left: pd.DataFrame, right: pd.DataFrame) -> pd.DataFrame:
66+
left = left.copy()
67+
left["label"] = right["label"].values[0] if len(right) > 0 else None
68+
return left[["id", "cat", "label"]]
69+
df.groupby("id").cogroup(df2.groupby("id")).applyInPandas(
70+
cogroup_fn, schema="id long, cat string, label string"
71+
).createOrReplaceTempView("flat_map_cogroups_view")
72+
73+
74+
_tests = {
75+
"batch_eval": register_batch_eval,
76+
"arrow_eval": register_arrow_eval,
77+
"flat_map_groups": register_flat_map_groups,
78+
"flat_map_cogroups": register_flat_map_cogroups,
79+
}
80+
81+
if test_name not in _tests:
82+
print(f"Unknown test '{test_name}'. Valid options: {list(_tests)}", file=sys.stderr)
83+
sys.exit(1)
84+
85+
_tests[test_name]()

0 commit comments

Comments
 (0)