Skip to content

refactor(conftest): lift orch fixture into cvs/tests/conftest.py + fix report_plugins JSON-escape regression [AIMVT-172]#162

Merged
atnair-amd merged 3 commits into
mainfrom
atnair/conftest-port
May 12, 2026
Merged

refactor(conftest): lift orch fixture into cvs/tests/conftest.py + fix report_plugins JSON-escape regression [AIMVT-172]#162
atnair-amd merged 3 commits into
mainfrom
atnair/conftest-port

Conversation

@atnair-amd
Copy link
Copy Markdown
Contributor

Jira: AIMVT-172

Summary

  • Lift the canonical orch pytest fixture into a new cvs/tests/conftest.py. Pytest walks the conftest hierarchy upward, so suites under cvs/tests/ pick it up automatically with zero per-suiteboilerplate.
  • Delete the module-local orch fixture and its now-unused OrchestratorConfig /OrchestratorFactory imports from cvs/tests/health/rvs_cvs.py; the conftest one takesover.
  • Replace a string-form re.sub replacement in cvs/lib/report_plugins.py with a function-form lambda so JSON \n / \t escapes inside data-jsonblob are preserved verbatim. Without this, the rvs_cvs HTML report in container mode renders with an empty results table.

Why

The orch fixture is the single seam between a pytest suite and the orchestrator factory introduced by #135. As transferbench_cvs, agfhc_cvs, and friends migrate off phdl / Pssh (AIMVT-168 through AIMVT-171), every one of them would otherwise copy-paste the same ~15-line fixture body + the same two imports + the same container setup/teardown wiring. Lifting it once keeps that surface single-sourced; subsequent suite migrations become a mechanical phdlorch rename. The lifted fixture is identical to the original except it gains an explicit CLI-arg guard:

@pytest.fixture(scope="module")
def orch(pytestconfig):
    cluster_file = pytestconfig.getoption("cluster_file")
    config_file = pytestconfig.getoption("config_file")
    if not cluster_file or not config_file:
        pytest.fail("orch fixture requires --cluster_file and --config_file")
    cfg = OrchestratorConfig.from_configs(cluster_file, config_file)
    orch = OrchestratorFactory.create_orchestrator(log, cfg)
    if cfg.orchestrator == "container":
        if not orch.setup_containers():
            pytest.fail(
                f"Failed to launch container : "
                f"{orch.get_container_name(orch.container_config,
orch.container_config['image'])}"
            )
        if not orch.setup_sshd():
            pytest.fail("Failed to setup sshd in container")
    yield orch
    if cfg.orchestrator == "container":
        orch.teardown_containers()

The previous local fixture raised a NoneType deep inside OrchestratorConfig.from_configs when invoked without --cluster_file /--config_file; the explicit pytest.fail(...) turns that into an actionable message at the fixture boundary.

The report_plugins change addresses a latent regression in _update_environment_config_links: the function mutates pytest-html's embedded data-jsonblob dict to inject clickable cluster/config-file links, then re-serializes via re.sub(pattern, replacement_string, html_content). Per the re docs, string-form replacements have backslash escapes processed, which collapses the JSON-encoded \n / \t 2-char escapes into raw 0x0A / 0x09 bytes inside JSON string values, breaking JSON.parse in the browser. Function-form (lambda) replacements skip that processing.

What is NOT changed

  • No new CLI options. --cluster_file / --config_file are still registered in cvs/conftest.py:31-53; the new cvs/tests/conftest.py inherits them via pytest's conftest walk.
  • No other suite under cvs/tests/ is touched. transferbench_cvs / agfhc_cvs /others are queued under AIMVT-168 through AIMVT-171.
  • No L1 doc changes (docs/how-to/run-with-containers.rst, docs/reference/configuration-files/cluster-file.rst, cvs/input/cluster_file/README.md).
  • No addfinalizer, no eager get_container_name, no detailed=True / hosts= /exit-code-check additions on orch.exec(...).
  • cluster_file / config_file fixtures in rvs_cvs.py:24-31 left alone —cluster_dict / config_dict still consume them.

Verification

  • python -m pytest cvs/core/orchestrators/unittests/ cvs/core/runtimes/unittests/ -q — green.
  • rg -n '\bdef orch\b|OrchestratorConfig|OrchestratorFactory' cvs/tests/health/rvs_cvs.py — 0 hits.
  • cvs run rvs_cvs level-4 on an 8x MI300X node, both orchestrator: baremetal and orchestrator: container —both 2 passed, 6 skipped, HTML reports render cleanly. Run zips (auto-bundles) are attached to AIMVT-172.

Commits

  1. 053fe60 test: lift orch fixture into cvs/tests/conftest.py
  2. 2e7bcb4 test(rvs_cvs): consume conftest-lifted orch fixture
  3. 79cab56 fix(report_plugins): use function-form re.sub replacement to preserve JSON escapes

atnair-amd added 3 commits May 8, 2026 19:13
No-op for current consumers (rvs_cvs.py still has its own local
fixture which pytest resolves first). Pairs with the follow-up that
removes the local fixture so the conftest one wins.
Deletes the module-local orch fixture and its now-unused
OrchestratorConfig / OrchestratorFactory imports. The conftest
fixture from cvs/tests/conftest.py is auto-injected unchanged.
… JSON escapes

`_update_environment_config_links` mutated pytest-html's embedded
`data-jsonblob` dict (to add clickable cluster/config file links), then
re-serialized it with `re.sub(pattern, f'data-jsonblob="{encoded_json}"',
html_content)`. Python's `re.sub` processes backslash escapes in STRING
replacements (per re docs: "If repl is a string, any backslash escapes
in it are processed. That is, `\n` is converted to a single newline
character..."). The replacement here contains the JSON-encoded log
content from `json.dumps`, where multi-line strings appear as 2-char
`\n` / `\t` escape sequences. `re.sub` collapsed those into raw 0x0A /
0x09 bytes inside JSON string values, violating RFC 8259 §7 and
breaking `JSON.parse` in the browser. Net visible effect: the
pytest-html results table renders with only its header row -- no test
rows -- because the JS renderer aborts before injecting them.

Function-form replacements are NOT processed for backslash escapes
(per re docs: "If repl is a function ... it is called for every
non-overlapping occurrence of pattern ... and returns the replacement
string"). Switching to a 1-arg lambda preserves the encoded JSON
verbatim.

Verified by /tmp/verify_report_plugins_fix.py (standalone unit test):
buggy path produces blob with 4 raw 0x0A + 3 raw 0x09 -> JSONDecodeError
at offset 463 ("Invalid control character at"); fixed path produces
blob with 0 control chars, json.loads succeeds, log content and env
links both preserved. RESULT = PASS.

This is the regression that emptied the results table in rvs_cvs
container-orchestrator HTML reports; baremetal escaped the bug because
its empty `_config_files` triggered the early-exit at line 301, so the
buggy `re.sub` never ran on baremetal reports.

No siblings of this bug pattern exist elsewhere in the CVS python tree
(audited via 4 total `re.sub` call sites; this was the only HIGH-risk
one).
Comment thread cvs/tests/conftest.py
@atnair-amd atnair-amd merged commit 02ff2fe into main May 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants