feat(dirty-content-fingerprint): xceadmin shows fingerprint in --version
Adds DIRTY_FINGERPRINT = "" to _build_info.py's committed fallback stub
and cli.py's separate ImportError fallback class. New
_compose_version_display() applies the strip-and-append rule via a
genuine anchored removesuffix("-dirty"), mirroring the C/Go
implementations (tasks 3.1/4.4/4.5).
Updates one pre-existing test whose hard-coded literal-GIT_COMMIT
assertion no longer holds once a fingerprint is present, to compute its
expected value via the production function instead -- independently
assessed by review as a legitimate strengthening, not a weakened check.
4 new dedicated unit tests independently guard the transform itself with
hard-coded literal assertions, including an anchored-suffix-not-blind-
substring regression case.
Verified independently by review (including a second, differently-
constructed mutation test) across both install paths via a full real
/xce-build xceadmin cycle: editable install shows "unknown, unknown" with
no import error; wheel built from the dirty tree shows <hash>-<fingerprint>,
never <hash>-dirty-<fingerprint>. 267 passed/3 skipped.
This completes all xceadmin work (tasks 5.1-5.2) for this spec.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
eeb7a9b4db
commit
c79ccf0fae
4 changed files with 142 additions and 23 deletions
|
|
@ -1,14 +1,20 @@
|
|||
"""Committed fallback build-info stub (build-time-versioning task 5.2).
|
||||
"""Committed fallback build-info stub (build-time-versioning task 5.2;
|
||||
extended by dirty-content-fingerprint task 5.2 with DIRTY_FINGERPRINT).
|
||||
|
||||
Both fields are always "unknown" here. This file ships checked into git so
|
||||
that a non-meson `pip install -e .` (the documented fast-iteration path,
|
||||
`build-deployment.md`) still imports `xceadmin._build_info` cleanly without
|
||||
ever having run the meson build -- Requirement 1.4's "record as unknown
|
||||
rather than fail" applies here too.
|
||||
GIT_COMMIT/BUILD_TIME are always "unknown" here; DIRTY_FINGERPRINT is always
|
||||
"" (empty, not "unknown" -- matching that field's convention everywhere else
|
||||
in the dirty-content-fingerprint feature: empty means "no fingerprint
|
||||
available", distinct from the other two fields' "unknown" sentinel for "not
|
||||
injected at all"). This file ships checked into git so that a non-meson `pip
|
||||
install -e .` (the documented fast-iteration path, `build-deployment.md`)
|
||||
still imports `xceadmin._build_info` cleanly without ever having run the
|
||||
meson build -- Requirement 1.4's "record as unknown rather than fail"
|
||||
applies here too.
|
||||
|
||||
When built via the meson custom_target (`src/xceadmin/meson-build.sh`, task
|
||||
5.1), this exact filename is regenerated with real `GIT_COMMIT`/`BUILD_TIME`
|
||||
values -- but only in meson's own out-of-tree MIRRORED copy of this package
|
||||
5.1), this exact filename is regenerated with real `GIT_COMMIT`/`BUILD_TIME`/
|
||||
`DIRTY_FINGERPRINT` values -- but only in meson's own out-of-tree MIRRORED
|
||||
copy of this package
|
||||
(`$BUILD_DIR/repo-mirror/src/xceadmin/src/xceadmin/_build_info.py`). This
|
||||
checked-in copy, under the live source tree, is never overwritten.
|
||||
"""
|
||||
|
|
@ -17,3 +23,4 @@ from __future__ import annotations
|
|||
|
||||
GIT_COMMIT = "unknown"
|
||||
BUILD_TIME = "unknown"
|
||||
DIRTY_FINGERPRINT = ""
|
||||
|
|
|
|||
|
|
@ -53,16 +53,38 @@ except ImportError: # pragma: no cover - defensive only: a committed
|
|||
class _build_info: # type: ignore[no-redef]
|
||||
GIT_COMMIT = "unknown"
|
||||
BUILD_TIME = "unknown"
|
||||
DIRTY_FINGERPRINT = ""
|
||||
|
||||
|
||||
def _compose_version_display(git_commit: str, dirty_fingerprint: str) -> str:
|
||||
"""Applies the dirty-content-fingerprint feature's strip-and-append rule
|
||||
to ``git_commit``: if ``dirty_fingerprint`` is non-empty, a trailing
|
||||
``-dirty`` suffix on ``git_commit`` is stripped (an anchored suffix
|
||||
check via ``str.removesuffix``, not a blind substring replace) and
|
||||
``-<dirty_fingerprint>`` is appended in its place; otherwise
|
||||
``git_commit`` is returned exactly as-is (already correctly showing
|
||||
``-dirty`` or not, per the original build-time-versioning feature).
|
||||
Mirrors ``xce_compose_version_ex()`` in ``src/lib/utils.c`` (task 3.1)
|
||||
and ``composeVersionDisplay()`` in ``apiserver/cmd/main.go`` /
|
||||
``xcecli/cmd/main.go`` (tasks 4.4/4.5) -- the equivalent implementations
|
||||
already approved for the other version-displaying components
|
||||
(Requirements 1.1, 1.2, 1.3, 1.4, 2.1, 4.1).
|
||||
"""
|
||||
if not dirty_fingerprint:
|
||||
return git_commit
|
||||
return git_commit.removesuffix("-dirty") + "-" + dirty_fingerprint
|
||||
|
||||
|
||||
def _compose_version() -> str:
|
||||
"""Composes the string `xceadmin --version` prints: pyproject.toml's
|
||||
static semantic version -- read via ``importlib.metadata`` (the standard
|
||||
way to read an *installed* package's own version, rather than
|
||||
hand-parsing pyproject.toml) -- plus the two build-info fields, which are
|
||||
hand-parsing pyproject.toml) -- plus the build-info fields, which are
|
||||
either task 5.1's meson-generated real values or task 5.2's committed
|
||||
"unknown" fallback, whichever this install currently carries
|
||||
(Requirements 1.1, 1.2, 2.1).
|
||||
"unknown"/"" fallback, whichever this install currently carries
|
||||
(Requirements 1.1, 1.2, 2.1). ``GIT_COMMIT`` has
|
||||
``_compose_version_display``'s strip-and-append rule applied against
|
||||
``DIRTY_FINGERPRINT`` before being embedded here.
|
||||
"""
|
||||
try:
|
||||
pkg_version = _package_version("xceadmin")
|
||||
|
|
@ -71,7 +93,8 @@ def _compose_version() -> str:
|
|||
# entry point already running as `xceadmin`; fails soft to
|
||||
# "unknown" rather than raising, matching Requirement 1.4.
|
||||
pkg_version = "unknown"
|
||||
return f"{pkg_version} ({_build_info.GIT_COMMIT}, {_build_info.BUILD_TIME})"
|
||||
commit_display = _compose_version_display(_build_info.GIT_COMMIT, _build_info.DIRTY_FINGERPRINT)
|
||||
return f"{pkg_version} ({commit_display}, {_build_info.BUILD_TIME})"
|
||||
|
||||
|
||||
_DEFAULT_OUTPUT = "cluster.bin"
|
||||
|
|
|
|||
|
|
@ -93,18 +93,31 @@ def test_cli_version_flag_exits_zero_and_shows_package_version() -> None:
|
|||
|
||||
|
||||
def test_cli_version_flag_includes_build_info_fields() -> None:
|
||||
"""The printed --version string must include BOTH _build_info fields
|
||||
(whichever variant -- "unknown" fallback or meson-generated real values
|
||||
-- is currently installed), not just the bare package version, so
|
||||
--version and the eventual startup log line (out of this task's scope)
|
||||
can never disagree (Requirement 2.2)."""
|
||||
"""The printed --version string must include BUILD_TIME verbatim, plus
|
||||
the commit display -- whichever _build_info variant ("unknown"/""
|
||||
fallback, or meson-generated real values) is currently installed -- not
|
||||
just the bare package version (Requirement 2.2). dirty-content-
|
||||
fingerprint task 5.2 note: when DIRTY_FINGERPRINT is empty, the commit
|
||||
display is GIT_COMMIT verbatim (unchanged from the original
|
||||
build-time-versioning feature's behavior, asserted directly below by
|
||||
``test_compose_version_omits_suffix_when_build_info_fingerprint_empty``);
|
||||
when non-empty (e.g. a wheel built from this dirty tree),
|
||||
``_compose_version_display()``'s strip-and-append rule means the
|
||||
composed display -- not the raw GIT_COMMIT string, which may still carry
|
||||
an unstripped ``-dirty`` suffix -- is what appears in --version output.
|
||||
This test therefore computes the same expected display the production
|
||||
code does, rather than asserting GIT_COMMIT literally, so it holds for
|
||||
both fallback and real-value installs."""
|
||||
from xceadmin import _build_info
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["--version"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert _build_info.GIT_COMMIT in result.output
|
||||
expected_commit_display = cli_module._compose_version_display(
|
||||
_build_info.GIT_COMMIT, _build_info.DIRTY_FINGERPRINT
|
||||
)
|
||||
assert expected_commit_display in result.output
|
||||
assert _build_info.BUILD_TIME in result.output
|
||||
|
||||
|
||||
|
|
@ -121,6 +134,82 @@ def test_build_info_module_is_importable_with_string_fields() -> None:
|
|||
assert isinstance(_build_info.BUILD_TIME, str) and _build_info.BUILD_TIME
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# dirty-content-fingerprint task 5.2: DIRTY_FINGERPRINT is a third
|
||||
# _build_info field (empty-string fallback, not "unknown" -- see
|
||||
# _build_info.py's docstring) and cli.py's _compose_version_display()
|
||||
# applies the same strip-and-append rule already approved for the C daemons
|
||||
# (xce_compose_version_ex(), src/lib/utils.c task 3.1) and the Go binaries
|
||||
# (composeVersionDisplay(), apiserver/cmd/main.go and xcecli/cmd/main.go
|
||||
# tasks 4.4/4.5). Tested directly as a pure function (mirroring this file's
|
||||
# own _format_timestamp_ns precedent) since _compose_version() is evaluated
|
||||
# once at module-import time to build click.version_option()'s default, so
|
||||
# monkeypatching _build_info after import would not be observed by a
|
||||
# `runner.invoke(cli, ["--version"])` call.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_compose_version_display_strips_dirty_suffix_and_appends_fingerprint() -> None:
|
||||
"""dirty_fingerprint non-empty + git_commit ending in '-dirty' -> the
|
||||
suffix is stripped and the fingerprint appended in its place
|
||||
(<hash>-<fingerprint>, not <hash>-dirty-<fingerprint>) -- Requirements
|
||||
1.1, 1.2, 1.3."""
|
||||
assert cli_module._compose_version_display("abc1234-dirty", "f00dcafe") == "abc1234-f00dcafe"
|
||||
|
||||
|
||||
def test_compose_version_display_empty_fingerprint_clean_commit_passthrough() -> None:
|
||||
"""dirty_fingerprint empty + a clean (no -dirty suffix) git_commit ->
|
||||
git_commit returned exactly as-is -- Requirement 2.1."""
|
||||
assert cli_module._compose_version_display("abc1234", "") == "abc1234"
|
||||
|
||||
|
||||
def test_compose_version_display_empty_fingerprint_dirty_commit_unchanged() -> None:
|
||||
"""dirty_fingerprint empty + git_commit already ending in '-dirty'
|
||||
(fingerprint computation failed, tree still dirty) -> git_commit passed
|
||||
through completely unchanged, '-dirty' left in place -- this is
|
||||
Requirement 5.2's fallback, satisfied by simply not touching commit_id
|
||||
when dirty_fingerprint is empty."""
|
||||
assert cli_module._compose_version_display("abc1234-dirty", "") == "abc1234-dirty"
|
||||
|
||||
|
||||
def test_compose_version_display_anchored_suffix_not_blind_substring() -> None:
|
||||
"""A regression case matching the pattern already established in the Go
|
||||
tests (tasks 4.4/4.5): '-dirty' appearing elsewhere in the commit
|
||||
string (not as the trailing suffix) must NOT be stripped -- only a
|
||||
literal trailing '-dirty' is anchored-matched and removed."""
|
||||
assert (
|
||||
cli_module._compose_version_display("abc-dirty1234", "f00dcafe") == "abc-dirty1234-f00dcafe"
|
||||
)
|
||||
|
||||
|
||||
def test_compose_version_includes_fingerprint_when_build_info_has_one(monkeypatch) -> None:
|
||||
"""_compose_version() (the function that actually feeds
|
||||
click.version_option()) applies _compose_version_display() against the
|
||||
live _build_info module's GIT_COMMIT/DIRTY_FINGERPRINT fields --
|
||||
Requirement 4.1."""
|
||||
monkeypatch.setattr(cli_module._build_info, "GIT_COMMIT", "abc1234-dirty")
|
||||
monkeypatch.setattr(cli_module._build_info, "DIRTY_FINGERPRINT", "f00dcafe")
|
||||
|
||||
version_str = cli_module._compose_version()
|
||||
|
||||
assert "abc1234-f00dcafe" in version_str
|
||||
assert "abc1234-dirty-f00dcafe" not in version_str
|
||||
assert "-dirty" not in version_str
|
||||
|
||||
|
||||
def test_compose_version_omits_suffix_when_build_info_fingerprint_empty(monkeypatch) -> None:
|
||||
"""The committed fallback stub's DIRTY_FINGERPRINT = "" case: --version
|
||||
shows GIT_COMMIT exactly as _build_info carries it, no fingerprint
|
||||
suffix appended -- e.g. the editable-install path's GIT_COMMIT =
|
||||
"unknown" stays "unknown" verbatim (Requirement 2.1, 5.1)."""
|
||||
monkeypatch.setattr(cli_module._build_info, "GIT_COMMIT", "unknown")
|
||||
monkeypatch.setattr(cli_module._build_info, "DIRTY_FINGERPRINT", "")
|
||||
|
||||
version_str = cli_module._compose_version()
|
||||
|
||||
assert "(unknown," in version_str
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# config build
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue