Repository navigation
Conversation
… extras - Add tests/integration/conftest.py to provide a session-scoped default multi-node ManagedSparkSession for non-destructive tests (SQL, UDFs, %%sparksql, %dpip) and an isolated_session_state fixture for lifecycle tests. - Configure isolated control-plane and session lifecycle tests with spark.master=local (single-node mode: 1 driver VM, 0 executor node pools) and 4-core executor overrides instead of 16-core executors. - Remove the unused image_version=[None, '3.0'] fixture that previously doubled the integration suite. - Set default safety TTLs (MANAGED_SPARK_CONNECT_TTL_SECONDS=1800, MANAGED_SPARK_CONNECT_IDLE_TTL_SECONDS=600) in conftest.py and fold test_stop_named_session_with_terminate_true + finally cleanup into test_session_reuse_with_custom_id so named sessions are never leaked. - Consolidate termination and deletion lifecycle tests into test_serverless_session_termination_and_deletion_lifecycle, assert SessionTemplate default runtime version via get_session_template, and remove pure local CLI/regex tests from tests/integration/ that are already covered in tests/unit/. - Together, these reduce remote Dataproc Serverless clusters per run from 42 to 5 (1 multi-node + 4 single-node) and total GCE VMs from ~130 to 7 (-94.6%). - Move test_create_local_spark_session (DATAPROC_WORKLOAD_TYPE=batch) to tests/local/test_sparksql_local.py so it runs on every PR in the spark lane, and isolate local Spark Connect environment/proxy state. - Update integration-tests.yaml to install .[client,interactive] and tests.yaml to verify unit tests both without [interactive] and with [interactive].
There was a problem hiding this comment.
Code Review
This pull request refactors the integration tests by consolidating common fixtures into tests/integration/conftest.py, reducing duplication across test files, and adding robust lifecycle and SQL-related tests. It also improves local Spark session tests and unit tests for environments without interactive dependencies. The review feedback highlights two important improvements: replacing os.environ.clear() with a safer environment restoration approach to prevent unsetting critical system variables, and using getattr when accessing SparkSession._instantiatedSession to avoid potential AttributeError across different PySpark versions.
| os.environ.clear() | ||
| os.environ.update(original_environment) |
There was a problem hiding this comment.
Clearing os.environ completely with os.environ.clear() can be dangerous in concurrent test environments or when other threads/libraries are running. It temporarily unsets critical system environment variables (like PATH, HOME, TMPDIR, etc.), which can cause unexpected failures in concurrent processes or subprocesses. A safer approach is to only delete the keys that were added during the test and restore the original values for modified keys.
| os.environ.clear() | |
| os.environ.update(original_environment) | |
| for key in list(os.environ.keys()): | |
| if key not in original_environment: | |
| del os.environ[key] | |
| os.environ.update(original_environment) |
| if SparkSession._instantiatedSession is not None: | ||
| SparkSession._instantiatedSession.stop() |
There was a problem hiding this comment.
Directly accessing SparkSession._instantiatedSession can raise an AttributeError if the attribute is not present on the SparkSession class (for example, in different PySpark versions or if the class is mocked/patched in certain test environments). Using getattr is a safer and more robust way to retrieve and stop the instantiated session.
| if SparkSession._instantiatedSession is not None: | |
| SparkSession._instantiatedSession.stop() | |
| instantiated = getattr(SparkSession, "_instantiatedSession", None) | |
| if instantiated is not None: | |
| instantiated.stop() |
|
TIL CI only runs on PRs from main repo branches, not forks (and this even applied to the other PRs I submitted this week, yikes, but good thing it ran on the 2.0.0 PR). I will reopen. |
Summary
Refactors the integration test suite and CI workflows to substantially reduce Dataproc Serverless session churn, VM footprint, and vulnerability to regional GCE stockouts (
503 Failed to create main node pool), while expanding CI coverage across packaging extras ([client],[spark], and with/without[interactive]).ml-pipeline-session-*× 2 + terminated-recreate × 2)finally+ 10m idle TTL)Key Changes
P0: Shared Session Fixture, Dead Parameterization Removal, and Leak Prevention
tests/integration/conftest.py): Provides a session-scoped default multi-nodeManagedSparkSession(_shared_connect_session/connect_session) shared across all non-destructive tests (test_create_spark_session_with_default_notebook_behavior,test_reuse_s8s_spark_session,test_sql_functions,test_sql_udf,test_sparksql_magic_with_managed_spark_session,test_dpip_install_success,test_dpip_install_failure), and anisolated_session_statefixture for tests that manage their own session lifecycle.image_version=[None, "3.0"]fixture: Previously parameterized every test intest_session.pyandtest_magics.pywithout ever passingimage_versionto the session builder, doubling the entire integration suite.MANAGED_SPARK_CONNECT_TTL_SECONDS=1800(30m) andMANAGED_SPARK_CONNECT_IDLE_TTL_SECONDS=600(10m, the minimum allowed byServerlessValidator) by default inconftest.py, and foldstest_stop_named_session_with_terminate_true+finally:cleanup intotest_session_reuse_with_custom_idso named sessions are never leaked for 24 hours.P1: Single-Node Mode (
spark.master=local) & Test Consolidationspark.master=localon isolated control-plane/lifecycle tests: Provisions a single 4-vCPU driver VM and 0 executor node pools fortest_create_spark_session_with_session_template_and_user_provided_session_config,test_session_reuse_with_custom_id, andtest_serverless_session_termination_and_deletion_lifecycle, while keeping the sharedconnect_sessionmulti-node to test out-of-the-box defaults, remote UDFs, and%dpipartifact distribution..config("spark.executor.cores", "7") ... .config("spark.executor.cores", "16")to"8""4"withspark.master=local, and verifiesSessionTemplatedefault runtime version ("3.0") viaget_session_templaterather than spinning up a separate cluster.test_stop_managed_session_with_terminate_false,test_get_or_create_spark_session_with_terminated_serverless_session,test_stop_spark_session_with_terminated_serverless_session, andtest_stop_spark_session_with_deleted_serverless_sessionintotest_serverless_session_termination_and_deletion_lifecycle(2 single-node sessions instead of 4 multi-node sessions).tests/integration/: Removes pure local CLI/regex validation tests that are already covered intests/unit/.P2: Realistic CI Packaging Lanes
.[client,interactive]inintegration-tests.yaml: Tests against the lightweightpyspark-client+pyspark-connectruntime recommended in the README, ensures%%sparksqlis not skipped in integration CI, and fixes the pip cache key to hashsetup.pyandrequirements-test.txt.[interactive]intests.yaml: Installs.[${{ matrix.spark-extra }}]first to runTestWithoutInteractiveExtra(expanded to testManagedSparkSession.builder.getOrCreate(),get_tqdm_bar(),_repr_html_(), andstop()whenIPython/sqlparse/traitlets/colabsqlviz/ipywidgetsare absent), then installs.[${{ matrix.spark-extra }},interactive]for the full unit and local suites.test_create_local_spark_sessionintests/local/test_sparksql_local.py: Moves theDATAPROC_WORKLOAD_TYPE=batchlocal classic Spark test intotests/local/so it runs on every PR in thesparklane under Java 17/21, and isolates local Spark Connect proxy/environment state.