Skip to content

Commit 3414a2f

Browse files
FastLeeasnare
andauthored
Fix three flaky integration tests (#4803)
## Summary Three previously-failing integration tests, plus a CI fix for a coverage-config issue that was masking the runner's exit status. ### Test fixes - **`test_build_notebook_dependency_graphs_installs_pytest_from_index_url`** (and its installed-twice sibling) — generate the notebook fixture at test time using whichever pip mirror is configured locally. The helper shells out to `python -m pip config list` (not `pip config get`, because `get` only inspects `user`/`global`/`site` scopes and ignores the `env` scope that `PIP_CONFIG_FILE` loads as) and parses the `global.index-url='...'` line. Falls back to public PyPI when no mirror is configured. The same test now exercises the JFrog mirror in CI and the dev proxy locally without any environment knowledge baked into the test code. Depends on #4804 keeping the JFrog URL credential-free. - **`test_installation_when_dashboard_id_is_invalid`** — skipped; it exercises a deprecated dashboard API, matching the existing skip on line 185 of the same file. - **`test_create_account_level_groups_nested_groups`** — scoped the final assertion to the 4 groups the test actually creates, instead of asserting a global "no mismatches anywhere" log line that gets polluted by stale UCX groups left in the shared workspace by earlier runs. ### CI: pin `COVERAGE_RCFILE` The integration job was failing with `failed: trigger: run: unknown: exit status 3` even when zero tests failed. Root cause: the `databrickslabs/sandbox/acceptance` wrapper invokes pytest in multiple per-directory sessions, and coverage.py walks up from CWD looking for config. At least one CWD wasn't surfacing the project's `pyproject.toml`, so coverage fell back to defaults (`branch=false`) and wrote line data while sibling sessions wrote arc data. The final session's `cov.combine()` then raised `Can't combine arc data with line data`. Setting `COVERAGE_RCFILE: ${{ github.workspace }}/pyproject.toml` forces every session to read the same `[tool.coverage.run]` config regardless of CWD. ## Test plan - [x] `make fmt` (black, ruff, mypy, pylint 10.00/10) - [x] `make test` (2011 passed, coverage 89.83%) - [x] `labs test-one` for each of the three originally-failing integration tests - [x] CI integration job green (76 ✅ / 0 ❌ / 11 ⏭️, all 11 pytest sessions exit 0) --------- Co-authored-by: Andrew Snare <andrew.snare@databricks.com>
1 parent 45a9a37 commit 3414a2f

9 files changed

Lines changed: 73 additions & 31 deletions

File tree

.build-constraints.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
hatchling==1.29.0 \
22
--hash=sha256:50af9343281f34785fab12da82e445ed987a6efb34fd8c2fc0f6e6630dbcc1b0 \
33
--hash=sha256:793c31816d952cee405b83488ce001c719f325d9cda69f1fc4cd750527640ea6
4-
packaging==26.0 \
5-
--hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \
6-
--hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529
4+
packaging==26.1 \
5+
--hash=sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f \
6+
--hash=sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de
77
# via hatchling
8-
pathspec==1.0.4 \
9-
--hash=sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645 \
10-
--hash=sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723
8+
pathspec==1.1.0 \
9+
--hash=sha256:574b128f7456bd899045ccd142dd446af7e6cfd0072d63ad73fbc55fbb4aaa42 \
10+
--hash=sha256:f5d7c555da02fd8dde3e4a2354b6aba817a89112fa8f333f7917a2a4834dd080
1111
# via hatchling
1212
pluggy==1.6.0 \
1313
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \

.github/workflows/acceptance.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,7 @@ jobs:
5858
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5959
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
6060
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
61+
# Pin coverage.py to the project's [tool.coverage.run] config (branch = true,
62+
# parallel = true) regardless of which CWD the acceptance wrapper invokes pytest
63+
# from, so every session writes the same data shape and combine() succeeds.
64+
COVERAGE_RCFILE: ${{ github.workspace }}/pyproject.toml

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ pytest = [
101101
]
102102
test = [
103103
{ include-group = "pytest" },
104-
"pip",
104+
"pip~=26.0",
105105
"python-lsp-server>=1.9.0",
106106
]
107107

tests/integration/account/test_account.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def test_create_account_level_groups(
7474

7575

7676
def test_create_account_level_groups_nested_groups(
77-
make_group, make_user, acc, ws, make_random, clean_account_level_groups, watchdog_purge_suffix, runtime_ctx, caplog
77+
make_group, make_user, acc, ws, make_random, clean_account_level_groups, watchdog_purge_suffix, runtime_ctx
7878
):
7979
suffix = f"{make_random(4).lower()}-{watchdog_purge_suffix}"
8080
# Test groups:
@@ -109,6 +109,7 @@ def test_create_account_level_groups_nested_groups(
109109
assert group
110110
assert len(group.members) == len(ws_group.members)
111111

112-
runtime_ctx.group_manager.validate_group_membership()
113-
114-
assert 'There are no groups with different membership between account and workspace.' in caplog.text
112+
mismatches = runtime_ctx.group_manager.validate_group_membership()
113+
created_names = {g.display_name for g in ws_groups}
114+
mismatched_created = {m["wf_group_name"] for m in mismatches} & created_names
115+
assert not mismatched_created, f"Created groups have mismatched membership: {mismatched_created}"

tests/integration/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import json
2-
import functools
31
import collections
4-
import os
2+
import functools
3+
import json
54
import logging
5+
import os
66
import shutil
77
import subprocess
88
from collections.abc import Callable, Generator

tests/integration/install/test_installation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ def test_installation_when_dashboard_is_trashed(ws, installation_ctx):
214214
assert True, "Installation succeeded when dashboard was trashed"
215215

216216

217+
@pytest.mark.skip(reason="Legacy dashboard creation is no longer supported by Databricks.")
217218
@pytest.mark.parametrize("dashboard_id", ["01ef4d7b294112968fa07ffae17dd55f", "invalid-dashboard-id", ""])
218219
def test_installation_when_dashboard_id_is_invalid(ws, installation_ctx, dashboard_id):
219220
"""A dashboard reference might be invalid (after manual changes), the upgrade should handle this."""

tests/integration/source_code/test_libraries.py

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
import logging
77
import re
8+
import subprocess
9+
import sys
10+
import textwrap
811
from pathlib import Path
912

1013
import pytest
@@ -13,6 +16,42 @@
1316
from tests.unit.conftest import MockPathLookup
1417

1518

19+
def _resolved_index_url() -> str:
20+
"""Return whichever pip mirror is configured locally, or public PyPI as a fallback.
21+
22+
Note: this only checks the configuration files, it ignores the PIP_INDEX_URL environment variable.
23+
"""
24+
# Cannot use 'config get': that will not consult the file if PIP_CONFIG_FILE is set.
25+
cmd = [sys.executable, "-m", "pip", "config", "list"]
26+
result = subprocess.run(cmd, capture_output=True, text=True, check=False, timeout=10)
27+
if result.returncode == 0:
28+
for line in result.stdout.splitlines():
29+
if line.startswith("global.index-url="):
30+
return line.split("=", 1)[1].strip().strip("'\"")
31+
return "https://pypi.python.org/simple"
32+
33+
34+
def _write_pytest_with_index_url_notebook(directory: Path) -> str:
35+
notebook_name = "pip_install_pytest_with_index_url"
36+
notebook = directory / f"{notebook_name}.py"
37+
notebook.write_text(
38+
textwrap.dedent(
39+
f"""\
40+
# Databricks notebook source
41+
42+
# COMMAND ----------
43+
44+
# MAGIC %pip install pytest --index-url {_resolved_index_url()}
45+
46+
# COMMAND ----------
47+
48+
import pytest
49+
"""
50+
)
51+
)
52+
return notebook_name
53+
54+
1655
@pytest.mark.parametrize(
1756
"notebook",
1857
(
@@ -31,11 +70,10 @@ def test_build_notebook_dependency_graphs_installs_wheel_with_pip_cell_in_notebo
3170
assert maybe.graph.all_relative_names() == {f"{notebook}.py", "thingy/__init__.py"}
3271

3372

34-
def test_build_notebook_dependency_graphs_installs_pytest_from_index_url(simple_ctx):
35-
ctx = simple_ctx.replace(path_lookup=MockPathLookup())
36-
maybe = ctx.dependency_resolver.build_notebook_dependency_graph(
37-
Path("pip_install_pytest_with_index_url"), CurrentSessionState()
38-
)
73+
def test_build_notebook_dependency_graphs_installs_pytest_from_index_url(simple_ctx, tmp_path):
74+
notebook_name = _write_pytest_with_index_url_notebook(tmp_path)
75+
ctx = simple_ctx.replace(path_lookup=MockPathLookup(cwd=tmp_path))
76+
maybe = ctx.dependency_resolver.build_notebook_dependency_graph(Path(notebook_name), CurrentSessionState())
3977
assert not maybe.problems
4078

4179

@@ -79,11 +117,18 @@ def test_build_notebook_dependency_graphs_when_installing_pytest_twice(caplog, s
79117
(
80118
"pip_install_demo_wheel",
81119
"pip_install_multiple_packages",
82-
"pip_install_pytest_with_index_url",
83120
),
84121
)
85122
def test_build_notebook_dependency_graphs_when_installing_notebooks_twice(caplog, simple_ctx, notebook) -> None:
86123
ctx = simple_ctx.replace(path_lookup=MockPathLookup())
87124
for _ in range(2):
88125
maybe = ctx.dependency_resolver.build_notebook_dependency_graph(Path(notebook), CurrentSessionState())
89126
assert not maybe.problems
127+
128+
129+
def test_build_notebook_dependency_graphs_when_installing_pytest_from_index_url_twice(simple_ctx, tmp_path) -> None:
130+
notebook_name = _write_pytest_with_index_url_notebook(tmp_path)
131+
ctx = simple_ctx.replace(path_lookup=MockPathLookup(cwd=tmp_path))
132+
for _ in range(2):
133+
maybe = ctx.dependency_resolver.build_notebook_dependency_graph(Path(notebook_name), CurrentSessionState())
134+
assert not maybe.problems

tests/unit/source_code/samples/pip_install_pytest_with_index_url.py

Lines changed: 0 additions & 9 deletions
This file was deleted.

uv.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)