Repository navigation
Conversation
… fallbacks - Expand the unit/local test matrix in .github/workflows/tests.yaml and the integration test matrix in .github/workflows/integration-tests.yaml to run on ubuntu-latest, macos-latest, and windows-latest with bash as the default shell, cross-platform uv cache paths, and ARM64/X64 JAVA_HOME fallback. - Replace GNU-only 'date -d' in /gcbrun authorization with portable jq fromdateiso8601 comparison, move integration workflow concurrency to the workflow level so matrix jobs do not cancel each other, and include matrix.os in /gcbrun commit status contexts. - Mitigate 3x concurrent Spark VM demand across OS runners by defaulting to multi-region fallback (us-central1,us-east1,us-west1), rotating the starting region per OS in CI, extending 503 ServiceUnavailable region fallback to isolated session lifecycle tests, and terminating the 3-VM shared session as soon as no remaining test items need it.
There was a problem hiding this comment.
Code Review
This pull request improves integration test reliability and resource utilization by introducing platform-based region rotation for CI jobs to distribute load, and dynamically terminating shared Spark sessions early when no subsequent tests require them. It also updates session lifecycle tests to support region fallbacks. The reviewer feedback correctly points out that importing helper functions directly from conftest.py is a pytest anti-pattern, and suggests refactoring _get_session_controller_client into a registered pytest fixture to be cleanly injected into the test functions.
…-platform test paths
…ller_client fixture
…Windows IDE testdata
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces Windows compatibility improvements, including support for Windows InnoSetup VS Code layouts, converting Windows drive-letter paths to file:// URIs for PySpark artifacts, and rotating fallback regions by platform in CI to distribute load. Feedback suggests using a context manager with os.scandir in ide.py to prevent unclosed directory handles on Windows, and adding a defensive check in session.py to handle cases where the active session UUID is None before writing package configurations.
| try: | ||
| entries = sorted(os.scandir(exe_dir), key=lambda e: e.name) | ||
| except OSError: | ||
| entries = [] | ||
| for entry in entries: | ||
| if _is_hex_commit_prefix(entry.name): | ||
| path = os.path.join(entry.path, "resources", "app", "product.json") | ||
| product = _read_product_json(path) | ||
| if product is not None: | ||
| commit = _str_or_none(product.get("commit")) | ||
| if commit and commit.lower().startswith(entry.name.lower()): | ||
| return path, product, False |
There was a problem hiding this comment.
Using os.scandir without a context manager can lead to unclosed directory handles, which is particularly problematic on Windows where file/directory locking is strict. Additionally, we can filter for directories and hex commit prefixes during the scan to avoid sorting non-matching entries, improving both safety and performance.
| try: | |
| entries = sorted(os.scandir(exe_dir), key=lambda e: e.name) | |
| except OSError: | |
| entries = [] | |
| for entry in entries: | |
| if _is_hex_commit_prefix(entry.name): | |
| path = os.path.join(entry.path, "resources", "app", "product.json") | |
| product = _read_product_json(path) | |
| if product is not None: | |
| commit = _str_or_none(product.get("commit")) | |
| if commit and commit.lower().startswith(entry.name.lower()): | |
| return path, product, False | |
| try: | |
| with os.scandir(exe_dir) as it: | |
| entries = sorted( | |
| [e for e in it if e.is_dir() and _is_hex_commit_prefix(e.name)], | |
| key=lambda e: e.name, | |
| ) | |
| except OSError: | |
| entries = [] | |
| for entry in entries: | |
| path = os.path.join(entry.path, "resources", "app", "product.json") | |
| product = _read_product_json(path) | |
| if product is not None: | |
| commit = _str_or_none(product.get("commit")) | |
| if commit and commit.lower().startswith(entry.name.lower()): | |
| return path, product, False |
| artifacts = PyPiArtifacts(set(artifact)) | ||
| logger.debug("Making addArtifact call to install packages") | ||
| self.addArtifact( | ||
| artifacts.write_packages_config(self._active_s8s_session_uuid), | ||
| _normalize_artifact_path( | ||
| artifacts.write_packages_config( | ||
| self._active_s8s_session_uuid | ||
| ) | ||
| ), | ||
| file=True, | ||
| ) |
There was a problem hiding this comment.
If self._active_s8s_session_uuid is None (e.g., if the session is not active or has been stopped), calling write_packages_config will result in a TypeError inside os.path.join. Adding a defensive check here prevents a cryptic crash and provides a clear error message.
if not self._active_s8s_session_uuid:
raise ManagedSparkConnectException(
"Cannot add PyPI artifacts: no active Managed Spark session."
)
artifacts = PyPiArtifacts(set(artifact))
logger.debug("Making addArtifact call to install packages")
self.addArtifact(
_normalize_artifact_path(
artifacts.write_packages_config(
self._active_s8s_session_uuid
)
),
file=True,
)
Note
Stacked on #207 (
dborowitz-ci-and-repo-modernization).Summary
Fixes broken Windows support for local artifact uploads,
%dpip/ PyPI package installation, and VS Code IDE / Data Agent Kit (DAK) detection, and expands GitHub Actions unit, local Spark, and integration test CI across Linux, macOS, and Windows with multi-region stockout fallbacks.1. Windows Bug Fixes (
pypi_artifacts.py,session.py,ide.py)PyPiArtifacts.write_packages_config(google/cloud/managed_spark_connect/pypi_artifacts.py):self.__str__()(<google.cloud.managed_spark_connect.pypi_artifacts.PyPiArtifacts object at ...>) withstr(uuid.uuid4())so temporary config filenames do not contain<or>, which are reserved characters on Windows (OSError: [Errno 22] Invalid argument).addArtifactalias (google/cloud/managed_spark_connect/session.py):_normalize_artifact_pathto convert local Windows drive-letter paths (C:\..., preserving any#aliasarchive fragments) intofile:///C:/...URIs inManagedSparkSession.addArtifacts—both for user-supplied artifact paths and for the generatedpypi=Trueconfig file—so PySpark'sArtifactManager._parse_artifactsdoes not parse the drive letter as an unsupported URL scheme"c".ManagedSparkSession.addArtifact = addArtifactsso singularsession.addArtifact(...)calls (such as from%dpip install) go throughManagedSparkSession.addArtifactsrather than falling through to the unpatched baseSparkSession.addArtifact.<commit[:10]>layout in IDE detection (google/cloud/managed_spark_connect/ide.py):_find_product_jsonto check<exe_dir>\<commit[:10]>\resources\app\product.json(used by Windows VS Code 1.96+ InnoSetup installers whereCode.exesits at the install root andresources\app\product.jsonsits under the 10-hex-character commit prefix directory) so process-tree IDE and DAK detection works on Windows.p4_vscode_windows.jsonandp4_antigravity_ide_windows.json, including the Windows.venv\Scripts\python.exelauncher redirector hop in the process chain) and skips POSIX-only/procand POSIX path replay tests on Windows (tests/unit/test_ide.py).2. Cross-Platform Unit, Local Spark, & Integration Test CI (
.github/workflows/tests.yaml,.github/workflows/integration-tests.yaml)tests.yamlandintegration-tests.yamlstrategy.matrixacrossos: ["ubuntu-latest", "macos-latest", "windows-latest"]withdefaults.run.shell: bash.uvcache paths andJAVA_HOME: ${{ env.JAVA_HOME_17_X64 || env.JAVA_HOME_17_ARM64 }}so ARM64macos-latestrunners locate Java 17 fortests/local/.concurrencyinintegration-tests.yamlto the workflow level so all 3 OS matrix legs run in parallel without cancelling each other, and replaces GNU-onlydate -din/gcbrunauthorization with portablejqfromdateiso8601comparisons.3. Multi-Region Stockout Fallbacks (
tests/integration/conftest.py,tests/integration/test_session.py)_candidate_regionsin CI across("us-central1", "us-east1", "us-west1")whentest_subnet == "default"and rotates the starting region per OS (linux→us-central1,darwin→us-east1,win32→us-west1) so concurrent OS runners spread their initial 3-VM cluster load across regions while falling back on503 ServiceUnavailable.connect_sessionearly viaisolated_session_stateonce no remaining tests need it, and uses aget_session_controller_clientfixture for isolated session lifecycle tests.Verification
ubuntu-latest,macos-latest, andwindows-latest.googlecloudtools.datacloud), verifying IDE/DAK detection (enabled and disabled states), creating aManagedSparkSession, clicking the rendereddataproc-resource:session link to open the session directly in VS Code, installing packages via%dpip install, and querying via%%sparksql.