Skip to content

Improve GitHub issue blockers: fail loud on bad issue refs, harden state handling - #3584

Merged
mkoura merged 13 commits into
masterfrom
blockers_fixes
Aug 3, 2026
Merged

Improve GitHub issue blockers: fail loud on bad issue refs, harden state handling#3584
mkoura merged 13 commits into
masterfrom
blockers_fixes

Conversation

@mkoura

@mkoura mkoura commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes several silent-failure modes in blockers.py / gh_issue.py and adds unit test
coverage for both modules.

Bug fixes

  • Nonexistent issue no longer silently xfails forever. A typo in an issue number or
    repo name used to be treated as an open issue, so the test was xfailed indefinitely
    with no signal. It now raises ValueError pointing at the bad reference.
  • Undetermined issue state is now logged. When the state cannot be retrieved (API
    failure, rate limiting, missing GitHub instance), the issue is still conservatively
    treated as blocked, but a warning is logged instead of pretending the issue is known
    to be open.
  • Transient API failures are no longer cached for the whole session. Previously a
    single rate-limit burst or network blip marked the issue as failed for the entire
    pytest run, silencing all subsequent logging. Retrieval is now retried on the next
    call and the failure is logged on every attempt.
  • Invalid fixed_in version fails at issue definition time (import of issues.py,
    i.e. test collection) with the issue reference in the error message, instead of
    surfacing as a confusing InvalidVersion inside the first test that runs the check.
  • finish_test() with an empty issue list raises ValueError instead of xfailing
    the test with an empty reason, and the check runs before any GitHub API call.

Documentation

  • Corrected the fixed_in docstring: it claimed the value is ignored on unknown
    projects, but it is in fact compared against the cardano-node version, which is what
    the existing issue definitions rely on (e.g. consensus_*, ledger_*).
  • Added missing Args/Returns/Raises docstring sections and fixed stale comments.

Refactoring

  • Named state sentinels (STATE_CLOSED, STATE_UNKNOWN, STATE_FAILURE) in
    gh_issue replace bare strings shared across the two modules.
  • get_state() always returns str; the None case (no GitHub instance) is folded
    into STATE_FAILURE, which the only caller handled identically anyway.
  • Removed the now-unused GHIssue.is_closed().

Tests

  • New framework_tests/test_blockers.py: eager fixed_in validation, the blocked
    check for all issue states, version comparison, the no-token path, repo-to-version
    dispatch, and both finish_test variants (GitHub API fully mocked).
  • New framework_tests/test_gh_issue.py: get_state caching semantics, including
    that transient failures are not cached.

Notes for reviewers

  • force_blocked=True still skips the issue existence check on purpose: that path
    deliberately avoids any API dependence.
  • Possibly stale data noticed while reviewing (not touched in this PR): api_829
    (fixed_in="10.5.0.0") and api_1261 (fixed_in="11.0.2") in tests/issues.py
    look like cardano-api release numbers, but the value is compared against the
    cardano-node version. Worth double-checking which product version was meant.

mkoura added 12 commits August 3, 2026 17:26
The docstring claimed fixed_in is ignored on unknown projects, but
the code compares it against the cardano-node version. Existing
issue definitions rely on this behavior, so document it instead.
A typo in an issue number resulted in state "unknown", which was
treated as an open issue, so the test was silently xfailed forever.
Raise ValueError instead so the wrong issue reference surfaces
immediately. Transient failures ("get_state_failure", missing
GitHub instance) still conservatively assume the issue is blocked.
Calling finish_test with an empty iterable silently xfailed the
test with an empty reason, masking a bug in the caller. Raise
ValueError instead.
An invalid fixed_in version string raised InvalidVersion only when
the blocked check ran inside a test, looking like a test failure.
Parse it eagerly in __init__ so the error points at the issue
definition. Also avoids reparsing on every check.
- Name the state sentinels (STATE_UNKNOWN, STATE_FAILURE) so callers
  don't have to match bare strings.
- Don't cache transient retrieval failures. A single rate-limit burst
  or network blip no longer marks the issue as failed for the whole
  pytest run, and the failure is logged on every attempt.
- Drop is_closed(), its last caller now works with get_state()
  directly.
- Document get_state() return values.
- Log a warning when the issue state could not be determined (API
  failure, rate limiting, missing GitHub instance) instead of
  silently xfailing the test as if the issue was known to be open.
- Use named state sentinels from gh_issue instead of bare strings.
- Turn _fixed_in_version into a property so the blocked check stays
  correct when the fixed_in attribute is changed after init (the
  copy() + mutate pattern used with the message attribute). Keep
  eager validation in __init__ and include the issue reference in
  the error message.
- Broaden the nonexistent-issue wording: UnknownObjectException is
  also raised for inaccessible or renamed repos.
- Add missing Args/Returns/Raises docstring sections.
Cover eager fixed_in validation, the blocked check for all issue
states (open, closed, unknown, undetermined), fixed_in comparison,
the no-token path, and both finish_test variants. GitHub API access
is mocked via GHIssue.get_state.
An unavailable GitHub instance and a failed state retrieval are
handled identically by the only caller, so collapse the None signal
into STATE_FAILURE. Also add STATE_CLOSED so no caller needs a bare
state string.
- Drop the _fixed_in_version property, parse fixed_in inline at its
  single use site. Validation stays in __init__ and post-init changes
  to fixed_in keep being respected.
- Keep the __init__ attribute assignments contiguous by validating
  before assigning.
- Build the repeated repo#issue string once per check.
- Use gh_issue.STATE_CLOSED instead of a bare string and drop the
  None handling, get_state now always returns str.
- Check for empty issues before any GitHub API call and take
  tp.Collection instead of tp.Iterable.
- Document that both finish_test variants never return.
- Adjust tests: shared helpers for state patching, dispatch test for
  the repo to version mapping, lint fixes.
Cover the caching semantics of get_state: real states and unknown
issues are cached, transient retrieval failures are not, and a
missing GitHub instance reports STATE_FAILURE.
- Use plain comments instead of the Sphinx #: marker, which is not
  used anywhere else in the codebase.
- Clarify get_state Returns wording.
- Use types.SimpleNamespace and an iterator in the fake GitHub
  helper instead of a dynamic class and list.pop bookkeeping.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the GitHub-issue-based “blocker” mechanism used by the test framework by making failures explicit (bad issue refs, undetermined states, invalid fixed_in) and by adding unit tests that fully mock GitHub interactions.

Changes:

  • Introduces explicit issue state sentinels in gh_issue and updates get_state() caching semantics (don’t cache transient failures).
  • Makes blockers.GH fail loud on nonexistent issues, warn-and-assume-blocked on undetermined state / missing token, and eagerly validates fixed_in.
  • Adds unit test coverage for gh_issue and blockers (including caching behavior, dispatch, and finish_test paths).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
framework_tests/test_gh_issue.py Adds unit tests for GHIssue.get_state caching and failure semantics (GitHub API mocked).
framework_tests/test_blockers.py Adds unit tests for blocker decision logic, eager fixed_in validation, dispatch, and finish_test.
cardano_node_tests/utils/gh_issue.py Adds state constants and refines get_state() to return a string sentinel on failure and avoid caching transient errors.
cardano_node_tests/utils/blockers.py Makes blocker checks stricter (bad refs raise, undetermined state warns) and validates fixed_in at definition time.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread framework_tests/test_gh_issue.py
Comment thread framework_tests/test_gh_issue.py Outdated
Comment thread framework_tests/test_blockers.py Outdated
Fixtures in framework_tests consistently annotate the return type,
only tests omit the None return type.
@mkoura
mkoura merged commit a8b6fd2 into master Aug 3, 2026
3 checks passed
@mkoura
mkoura deleted the blockers_fixes branch August 3, 2026 16:39
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