Skip to content

Log warnings on silent patch step skips#67

Merged
wpak-ai merged 5 commits into
cppalliance:developfrom
henry0816191:feat/patch-skip-warning
Jun 25, 2026
Merged

Log warnings on silent patch step skips#67
wpak-ai merged 5 commits into
cppalliance:developfrom
henry0816191:feat/patch-skip-warning

Conversation

@henry0816191

@henry0816191 henry0816191 commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add PatchStep._skip(reason) on the patch pipeline base class to emit logging.warning with the step name and skip reason.
  • Instrument all seven patch steps (container_mounts, b2_source_cache, restore_capy_timestamps, capy_copy_preservation, b2_bootstrap_skip, image_substitution, codecov_skip) on every no-op exit path.
  • Extend eight existing negative tests in test_patcher_patches.py and add test_patch_step_skip_emits_warning in test_patch_pipeline.py.

Patch steps that returned without changing workflow YAML gave no diagnostic when config was incomplete or target content was missing.

Test plan

  • pytest tests/test_patch_pipeline.py tests/test_patcher_patches.py
  • pytest -m "not integration"
  • ruff check / ruff format --check / mypy localci/core/patch_pipeline.py localci/core/patch_steps.py

Related issue

Summary by CodeRabbit

  • Bug Fixes
    • Improved the patching flow to safely and consistently skip unsupported scenarios (or when changes are already present) using actionable warning logs, without modifying workflow contents unexpectedly.
  • Tests
    • Expanded negative/skip coverage across container mounts, boost source cache, Capy timestamps/copy preservation, B2 bootstrap skip, image substitution, and Codecov.
    • Added log assertions to verify the correct step name and skip reason, plus new workflow fixtures for missing container-related fields.

@henry0816191 henry0816191 self-assigned this Jun 24, 2026
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 13b125bf-af2d-4a81-9a56-d036fd40faea

📥 Commits

Reviewing files that changed from the base of the PR and between b1d4438 and 9c341c6.

📒 Files selected for processing (1)
  • cli/tests/test_patcher_patches.py
 __________________________________________
< Every day I'm shufflin'... through code. >
 ------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
📝 Walkthrough

Walkthrough

Adds PatchStep._skip(reason) to log warning messages for skipped patch steps. All concrete patch steps now call it on early exits, and tests assert the warning output for skip cases.

Changes

Patch skip logging

Layer / File(s) Summary
PatchStep base class: logger and _skip helper
cli/localci/core/patch_pipeline.py
Adds logging import, module-level logger, and PatchStep._skip(reason) that emits a warning naming the step and reason.
Concrete apply() implementations updated to call _skip
cli/localci/core/patch_steps.py
Updates container mount, boost cache, capy timestamp, capy copy, b2 bootstrap, image substitution, and codecov steps to log skip reasons and return early, while preserving immediate returns after successful edits.
Unit test: _skip emits warning
cli/tests/test_patch_pipeline.py
Adds a test that invokes ContainerMountsStep.apply() with incomplete context and asserts the warning is captured on the patch logger.
Negative-case tests assert skip warnings
cli/tests/test_patcher_patches.py, cli/tests/fixtures/patcher/container_image_no_container.yml, cli/tests/fixtures/patcher/container_mount_no_container_block.yml
Adds warning-capture helpers, updates negative tests to assert skip messages, and adds fixtures covering missing container fields/blocks.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • wpak-ai

Poem

A rabbit hops through YAML lanes,
And logs the skips when work remains.
_skip now speaks with warning light,
So missing steps are seen just right.
🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: warning logs for silent patch-step skips.
Linked Issues check ✅ Passed The PR implements a generic skip-warning helper, updates patch steps, and adds the required warning assertions and new test.
Out of Scope Changes check ✅ Passed The added fixtures and test updates are directly tied to the skip-logging behavior and do not appear unrelated.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@henry0816191

Copy link
Copy Markdown
Collaborator Author

@coderabbitai, full review

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== patch_pipeline.py ==="
cat -n cli/localci/core/patch_pipeline.py

echo ""
echo "=== patch_steps.py ==="
cat -n cli/localci/core/patch_steps.py

echo ""
echo "=== test_patch_pipeline.py ==="
cat -n cli/tests/test_patch_pipeline.py

echo ""
echo "=== test_patcher_patches.py ==="
cat -n cli/tests/test_patcher_patches.py

Length of output: 44930



@henry0816191 Here is the full review of PR #67.


Overall Assessment

The PR cleanly achieves its goal: every silent early-return in the patch pipeline now emits a logging.warning, and the test suite is extended to verify it. The design — a single _skip helper on the base class that captures self.name automatically — is the right abstraction. A few things are worth addressing before merging.


cli/localci/core/patch_pipeline.py

_skip log format — %r on the step name

logger.warning("Patch step %r skipped: %s", self.name, reason)
# produces: Patch step 'container_mounts' skipped: …

Using %r wraps the name in single quotes, which is a bit noisy for a human-readable warning. %s would read more naturally:

logger.warning("Patch step %s skipped: %s", self.name, reason)
# produces: Patch step container_mounts skipped: …

If you keep %r, the tests still pass because "container_mounts" in "'container_mounts'" is True — but this is a subtle reliance on substring matching that could mask a future naming change.


cli/localci/core/patch_steps.py

Inconsistent _skip call-site patterns (3 different styles)

The three styles currently in use:

# Style 1 — one-liner (ContainerMountsStep L44, ImageSubstitutionStep L222)
return self._skip("job_id and container_mount_options are required")

# Style 2 — two separate lines (RestoreCapyTimestampsStep, B2BootstrapSkipStep)
self._skip("restore step already present")
return

# Style 3 — last-statement implicit return (B2SourceCacheStep, CapyCopyPreservationStep, CodecovSkipStep)
self._skip("no cp -rL boost-source boost-root line found")

All three are functionally identical (_skip returns None), but the inconsistency adds cognitive overhead. I'd recommend picking one convention. Style 2 (explicit two lines) is the clearest because it separates "emit the warning" from "exit the function" and does not suggest _skip has a meaningful return value.

Two ContainerMountsStep skip paths not covered by tests

ContainerMountsStep.apply has three skip paths:

# Condition Message Tested?
1 not ctx.job_id or not ctx.container_mount_options job_id and container_mount_options are required
2 Job header found, but no container: block below it job {job_id!r} has no container block
3 Outer loop exhausted — job header never found job {job_id!r} not found in workflow

Paths 2 and 3 are exercised by production workflows but have no unit tests. A minimal fixture (a job with job_id set but no container: section) would cover both.

Missing trailing return after _skip at the end of the outer loop in ContainerMountsStep

Line 75:

        self._skip(f"job {ctx.job_id!r} not found in workflow")
# no return here — but line 74 above has one

This is fine because it is the last statement, but it reads inconsistently alongside line 74 which has an explicit return. If you standardise on Style 2, add return here too.

ImageSubstitutionStep: asymmetric failure modes

When ctx.entry.name is not in the workflow, the step raises ValueError (line 231). When the container: field is missing from the located block, it calls _skip (line 269). This asymmetry is not new to this PR, but since you are now making skip/raise behaviour visible through warnings, a short docstring explaining the distinction would help future maintainers:

class ImageSubstitutionStep(PatchStep):
    """Replace matrix container image with the locally-built image tag.

    Raises ValueError if the matrix entry name is not found (unexpected workflow
    structure).  Skips silently if the container field is absent from the entry
    block (valid but unsupported layout).
    """

cli/tests/test_patch_pipeline.py

test_patch_step_skip_emits_warning — consider checking r.levelno

The assertion only checks message content, not log level:

assert any(
    "container_mounts" in r.message
    and "job_id and container_mount_options are required" in r.message
    for r in caplog.records
)

If _skip were accidentally changed to logger.info instead of logger.warning, this test would still pass. Adding a level check makes the test a complete specification:

assert any(
    r.levelno == logging.WARNING
    and "container_mounts" in r.message
    and "job_id and container_mount_options are required" in r.message
    for r in caplog.records
)

The same applies to _assert_skip_warning in test_patcher_patches.py.


cli/tests/test_patcher_patches.py

_assert_skip_warning does not assert log level

Same concern as above — the helper should also assert r.levelno == logging.WARNING:

def _assert_skip_warning(caplog, step_name: str, reason_fragment: str) -> None:
    assert any(
        r.levelno == logging.WARNING
        and step_name in r.message
        and reason_fragment in r.message
        for r in caplog.records
    ), (
        f"expected WARNING for {step_name!r} containing {reason_fragment!r}, "
        f"got: {[(r.levelname, r.message) for r in caplog.records]}"
    )

test_negative_leaves_workflow_unchanged_without_boost_copy_line checks multiple skip warnings in one run

This test uses boost_cache.yml and calls patcher_paths(workflow) with the full default pipeline. The caplog block will capture warnings from all steps that skip on this fixture (e.g., capy_copy_preservation, b2_bootstrap_skip, codecov_skip), not just b2_source_cache. The test correctly asserts one specific warning, so it works — but the error message on failure lists all captured records, which could be confusing. A comment explaining why multiple skip warnings are expected would help.


Summary of Actionable Items

Priority Item
Medium Standardise _skip call-site style to one pattern across all steps
Medium Add tests for ContainerMountsStep paths 2 & 3 (job found but no container; job not found)
Low Assert r.levelno == logging.WARNING in _assert_skip_warning and test_patch_step_skip_emits_warning
Low Switch %r to %s for step name in the _skip log message, or document the %r choice
Low Add a docstring to ImageSubstitutionStep explaining the raise-vs-skip distinction

The PR is in good shape — no functional bugs, the coverage improvements are real, and the _skip abstraction is a clean design. The items above are mostly polish and test completeness.

…d warnings

- Extended PatchStep.apply docstring in patch_pipeline.py to state that early exits that do not modify ctx.lines must call _skip(reason).
@henry0816191

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@henry0816191 henry0816191 requested a review from bradjin8 June 24, 2026 15:17

@bradjin8 bradjin8 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Clean implementation of #65 via PatchStep._skip(reason) (Option B). All seven patch steps instrumented; tests assert WARNING level and skip reasons. Author addressed CodeRabbit follow-ups in adc79c1b1d4438 (%s log format, WARNING level checks, ContainerMounts paths 2/3, single-step test configs, apply() docstring).

Test plan

  • CI green
  • pytest tests/test_patch_pipeline.py tests/test_patcher_patches.py — 44 passed locally

Comment on lines 301 to 302
assert "cp -a boost-root/." in content

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: test_negative_leaves_workflow_unchanged_without_boost_copy_line still uses the full default pipeline; other new negatives use single-step configs

@henry0816191 henry0816191 requested a review from wpak-ai June 25, 2026 16:19
@wpak-ai wpak-ai merged commit 1fb8bd3 into cppalliance:develop Jun 25, 2026
7 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.

Add logging.warning() on silent patch skips (3pt)

3 participants