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
|
|
@ -77,7 +77,7 @@
|
||||||
- _Boundary: xcecli/cmd/main.go_
|
- _Boundary: xcecli/cmd/main.go_
|
||||||
- _Depends: 4.1_
|
- _Depends: 4.1_
|
||||||
|
|
||||||
- [ ] 5. Core: xceadmin fingerprint display
|
- [x] 5. Core: xceadmin fingerprint display
|
||||||
- [x] 5.1 (P) meson-build.sh generates the fingerprint field in the build-info module
|
- [x] 5.1 (P) meson-build.sh generates the fingerprint field in the build-info module
|
||||||
- `dirty_fingerprint` threaded through as an 8th positional arg (`$8`), correctly numbered and consistent between `meson.build` and `meson-build.sh`; `_build_info.py` gains `DIRTY_FINGERPRINT` with an independent empty-string fallback (not `"unknown"`); `GIT_COMMIT`/`BUILD_TIME`'s existing generation and fallback confirmed byte-for-byte untouched
|
- `dirty_fingerprint` threaded through as an 8th positional arg (`$8`), correctly numbered and consistent between `meson.build` and `meson-build.sh`; `_build_info.py` gains `DIRTY_FINGERPRINT` with an independent empty-string fallback (not `"unknown"`); `GIT_COMMIT`/`BUILD_TIME`'s existing generation and fallback confirmed byte-for-byte untouched
|
||||||
- Observable confirmed live (independently re-verified by review, full real build+test+wheel cycles across 3 scenarios): dirty tree -> real, non-empty `DIRTY_FINGERPRINT` present identically in the mirrored tree, the build/lib copy, an installed venv, and the packaged wheel, cross-checked byte-for-byte against that same build's `compile_commands.json`; explicit-empty fingerprint -> genuinely `""` while `GIT_COMMIT`/`BUILD_TIME` pass through real values; all-empty -> `GIT_COMMIT`/`BUILD_TIME` still fall back to `"unknown"` while `DIRTY_FINGERPRINT` stays `""`; 261 passed/3 skipped in all three scenarios
|
- Observable confirmed live (independently re-verified by review, full real build+test+wheel cycles across 3 scenarios): dirty tree -> real, non-empty `DIRTY_FINGERPRINT` present identically in the mirrored tree, the build/lib copy, an installed venv, and the packaged wheel, cross-checked byte-for-byte against that same build's `compile_commands.json`; explicit-empty fingerprint -> genuinely `""` while `GIT_COMMIT`/`BUILD_TIME` pass through real values; all-empty -> `GIT_COMMIT`/`BUILD_TIME` still fall back to `"unknown"` while `DIRTY_FINGERPRINT` stays `""`; 261 passed/3 skipped in all three scenarios
|
||||||
|
|
@ -85,10 +85,10 @@
|
||||||
- _Boundary: xceadmin/meson.build, xceadmin/meson-build.sh_
|
- _Boundary: xceadmin/meson.build, xceadmin/meson-build.sh_
|
||||||
- _Depends: 1_
|
- _Depends: 1_
|
||||||
|
|
||||||
- [ ] 5.2 (P) Committed fallback stub and CLI fingerprint wiring
|
- [x] 5.2 (P) Committed fallback stub and CLI fingerprint wiring
|
||||||
- The committed fallback stub (`src/xceadmin/src/xceadmin/_build_info.py`) gains `DIRTY_FINGERPRINT = ""` alongside its existing two `"unknown"` fields
|
- `_build_info.py`'s fallback stub gains `DIRTY_FINGERPRINT = ""`; `cli.py`'s new `_compose_version_display()` applies the strip-and-append rule via a genuine anchored `removesuffix("-dirty")` (independently confirmed via a second, differently-constructed mutation test); both fallback paths (`_build_info.py`'s module stub and `cli.py`'s separate `ImportError` fallback class) correctly carry the field
|
||||||
- `cli.py`'s `_compose_version()` applies the same strip-and-append rule as the other components: if `DIRTY_FINGERPRINT` is non-empty, strip a trailing `-dirty` from `GIT_COMMIT` and append `-DIRTY_FINGERPRINT`; if empty, use `GIT_COMMIT` as-is
|
- A pre-existing test's hard-coded literal-`GIT_COMMIT` assertion was updated to compute its expected value via the production `_compose_version_display()` function instead -- independently assessed by review as a legitimate strengthening (the old assertion's premise no longer holds once a fingerprint is present), not a weakened/gutted check; the 4 new dedicated unit tests independently guard the transform itself with hard-coded literal assertions
|
||||||
- Observable confirmed live (both install paths): an editable install (`pip install -e .`) shows no fingerprint suffix (empty field, `GIT_COMMIT` unaffected, no import error); a wheel built from a dirty tree shows `<hash>-<fingerprint>`, not `<hash>-dirty-<fingerprint>`
|
- Observable confirmed live (independently re-verified by review, both install paths via a full real `/xce-build xceadmin` cycle): editable install shows `0.1.0 (unknown, unknown)`, no import error; wheel built from the dirty tree shows `<hash>-<fingerprint>`, never `<hash>-dirty-<fingerprint>`; full suite 267 passed/3 skipped (261 baseline + 6 new)
|
||||||
- _Requirements: 1.1, 1.2, 1.3, 1.4, 2.1, 4.1_
|
- _Requirements: 1.1, 1.2, 1.3, 1.4, 2.1, 4.1_
|
||||||
- _Boundary: xceadmin/src/xceadmin_
|
- _Boundary: xceadmin/src/xceadmin_
|
||||||
- _Depends: 5.1_
|
- _Depends: 5.1_
|
||||||
|
|
|
||||||
|
|
@ -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
|
GIT_COMMIT/BUILD_TIME are always "unknown" here; DIRTY_FINGERPRINT is always
|
||||||
that a non-meson `pip install -e .` (the documented fast-iteration path,
|
"" (empty, not "unknown" -- matching that field's convention everywhere else
|
||||||
`build-deployment.md`) still imports `xceadmin._build_info` cleanly without
|
in the dirty-content-fingerprint feature: empty means "no fingerprint
|
||||||
ever having run the meson build -- Requirement 1.4's "record as unknown
|
available", distinct from the other two fields' "unknown" sentinel for "not
|
||||||
rather than fail" applies here too.
|
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
|
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`
|
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
|
`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
|
(`$BUILD_DIR/repo-mirror/src/xceadmin/src/xceadmin/_build_info.py`). This
|
||||||
checked-in copy, under the live source tree, is never overwritten.
|
checked-in copy, under the live source tree, is never overwritten.
|
||||||
"""
|
"""
|
||||||
|
|
@ -17,3 +23,4 @@ from __future__ import annotations
|
||||||
|
|
||||||
GIT_COMMIT = "unknown"
|
GIT_COMMIT = "unknown"
|
||||||
BUILD_TIME = "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]
|
class _build_info: # type: ignore[no-redef]
|
||||||
GIT_COMMIT = "unknown"
|
GIT_COMMIT = "unknown"
|
||||||
BUILD_TIME = "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:
|
def _compose_version() -> str:
|
||||||
"""Composes the string `xceadmin --version` prints: pyproject.toml's
|
"""Composes the string `xceadmin --version` prints: pyproject.toml's
|
||||||
static semantic version -- read via ``importlib.metadata`` (the standard
|
static semantic version -- read via ``importlib.metadata`` (the standard
|
||||||
way to read an *installed* package's own version, rather than
|
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
|
either task 5.1's meson-generated real values or task 5.2's committed
|
||||||
"unknown" fallback, whichever this install currently carries
|
"unknown"/"" fallback, whichever this install currently carries
|
||||||
(Requirements 1.1, 1.2, 2.1).
|
(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:
|
try:
|
||||||
pkg_version = _package_version("xceadmin")
|
pkg_version = _package_version("xceadmin")
|
||||||
|
|
@ -71,7 +93,8 @@ def _compose_version() -> str:
|
||||||
# entry point already running as `xceadmin`; fails soft to
|
# entry point already running as `xceadmin`; fails soft to
|
||||||
# "unknown" rather than raising, matching Requirement 1.4.
|
# "unknown" rather than raising, matching Requirement 1.4.
|
||||||
pkg_version = "unknown"
|
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"
|
_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:
|
def test_cli_version_flag_includes_build_info_fields() -> None:
|
||||||
"""The printed --version string must include BOTH _build_info fields
|
"""The printed --version string must include BUILD_TIME verbatim, plus
|
||||||
(whichever variant -- "unknown" fallback or meson-generated real values
|
the commit display -- whichever _build_info variant ("unknown"/""
|
||||||
-- is currently installed), not just the bare package version, so
|
fallback, or meson-generated real values) is currently installed -- not
|
||||||
--version and the eventual startup log line (out of this task's scope)
|
just the bare package version (Requirement 2.2). dirty-content-
|
||||||
can never disagree (Requirement 2.2)."""
|
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
|
from xceadmin import _build_info
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(cli, ["--version"])
|
result = runner.invoke(cli, ["--version"])
|
||||||
|
|
||||||
assert result.exit_code == 0, result.output
|
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
|
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
|
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
|
# config build
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue