Files
datastore/ci/tests/test_upgrade_runner_download_boundary.py
Alexey MilovidovandClaude Opus 4.8 6183af4648 Address review: fail the upgrade runner at the download boundary
`download_release_packages` now exits nonzero when a required
previous-release package is missing or fails to download, but the only
shell caller, `tests/docker_scripts/upgrade_runner.sh`, ran it inside an
`&& ... || ...` list, so `set -e` did not stop on the nonzero status. The
follow-up guard only checked `clickhouse-common-static_*.deb` and
`clickhouse-server_*.deb`, so a missing `clickhouse-client_..._amd64.deb`
or `clickhouse-common-static-dbg_..._amd64.deb` could still slip through
to `install_packages` and die there with an opaque `dpkg` glob error -
exactly the failure mode this PR set out to make clear and attributable.

Handle the download exit code explicitly: on failure, record the `FAIL`
row, write a `failure` status to `check_status.tsv`, and `exit 1` before
`install_packages` can run; on success, record the `OK` row and continue.

Add `ci/tests/test_upgrade_runner_download_boundary.py`, which extracts
the real download block from the runner (between stable anchor comments)
and runs it under bash with a stub `download_release_packages`, proving a
failed download stops before `install_packages` is reached and a
successful one proceeds. Extracting the live block keeps the test from
drifting from the script it guards.

Addresses review feedback:
https://github.com/ClickHouse/ClickHouse/pull/107916#discussion_r3444526378

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-19 21:24:09 +00:00

112 lines
3.8 KiB
Python

"""
Shell-level contract test for the previous-release download boundary in
`tests/docker_scripts/upgrade_runner.sh`.
`download_release_packages` (see `tests/ci/download_release_packages.py`) exits
nonzero when a required previous-release package is missing or fails to download.
The runner must stop at that boundary instead of recording success and reaching
`install_packages`, which would otherwise die later with an opaque `dpkg` glob
error.
The Python unit tests in `test_download_release_packages.py` prove the exit code;
this test proves the shell caller honours it. It extracts the real download block
from the runner (between stable anchor comments) and runs it under bash with a
stub `download_release_packages`, so the test cannot drift from the script it
guards.
See https://github.com/ClickHouse/ClickHouse/pull/107916
"""
import os
import subprocess
_RUNNER = os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"..",
"..",
"tests",
"docker_scripts",
"upgrade_runner.sh",
)
)
_BEGIN = (
"# --- download previous release packages: "
"fail closed on a missing required one ---"
)
_END = "# --- end download previous release packages ---"
# Mirrors the `OK`/`FAIL` status columns defined in `stress_tests.lib`. A raw
# string keeps the backslashes literal so the bash harness sees exactly what the
# library defines.
_PREAMBLE = r"""set -ex
OK="\tOK\t\\N\t"
FAIL="\tFAIL\t\\N\t"
previous_release_tag=v1.2.3.4-stable
"""
def _extract_download_block():
with open(_RUNNER, encoding="utf-8") as f:
text = f.read()
begin = text.index(_BEGIN)
end = text.index(_END, begin)
# Keep the BEGIN anchor, drop the END marker line.
return text[begin:end]
def _run_block(tmp_path, download_exit_code):
block = _extract_download_block()
# The runner writes to the absolute path /test_output; redirect it into the
# test's temporary directory.
test_output = tmp_path / "test_output"
test_output.mkdir()
block = block.replace("/test_output", str(test_output))
# Stub `download_release_packages` (a PATH command in the real job, symlinked
# to the Python script) so the block's only moving part is its own control
# flow. It drains stdin (the piped release tag) and exits with the given code.
bin_dir = tmp_path / "bin"
bin_dir.mkdir()
stub = bin_dir / "download_release_packages"
stub.write_text(f"#!/bin/bash\ncat >/dev/null\nexit {download_exit_code}\n")
stub.chmod(0o755)
# `REACHED_INSTALL` stands in for the `install_packages` call that follows the
# block in the real runner: it must be reached on success and skipped on
# failure.
script = _PREAMBLE + block + "\necho REACHED_INSTALL\n"
env = dict(os.environ, PATH=f"{bin_dir}:{os.environ['PATH']}")
proc = subprocess.run(
["bash", "-c", script],
capture_output=True,
text=True,
env=env,
cwd=str(tmp_path),
check=False,
)
return proc, test_output
def test_runner_stops_when_download_fails(tmp_path):
proc, test_output = _run_block(tmp_path, download_exit_code=1)
# The runner must exit before reaching `install_packages`.
assert proc.returncode != 0
assert "REACHED_INSTALL" not in proc.stdout
assert (test_output / "check_status.tsv").read_text().startswith("failure")
assert "FAIL" in (test_output / "test_results.tsv").read_text()
def test_runner_continues_when_download_succeeds(tmp_path):
proc, test_output = _run_block(tmp_path, download_exit_code=0)
# A successful download records the OK row and proceeds to `install_packages`.
assert proc.returncode == 0, proc.stderr
assert "REACHED_INSTALL" in proc.stdout
assert not (test_output / "check_status.tsv").exists()
assert "OK" in (test_output / "test_results.tsv").read_text()