Skip to content

Add publish ordering invariant tests (#71) - #86

Merged
leynos merged 15 commits into
mainfrom
issue-71-add-property-based-and-parametrised-tests-for-publish-ordering-invariants
Jun 11, 2026
Merged

Add publish ordering invariant tests (#71)#86
leynos merged 15 commits into
mainfrom
issue-71-add-property-based-and-parametrised-tests-for-publish-ordering-invariants

Conversation

@lodyai

@lodyai lodyai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

This branch broadens publish ordering coverage for Issue #71 by checking the
live and dry-run pipelines across variable workspace sizes and generated crate
chain sizes.

Closes #71.

Review walkthrough

Validation

  • make check-fmt: passed
  • make lint: passed
  • make test: passed, 481 tests
  • make typecheck: passed
  • coderabbit review --agent: passed with 0 findings after Phase 1
  • coderabbit review --agent: passed with 0 findings after Phase 2

Notes

The existing make_dependency_chain helper keeps its original alpha, beta,
and gamma crate names so snapshot and index-missing tests retain their current
fixtures. The new make_n_crate_chain helper uses deterministic crate_N
names for arbitrary chain lengths.

Summary by Sourcery

Expand publish run test coverage for preflight checks, workspace configuration handling, and dry-run/live publish ordering invariants.

Enhancements:

  • Add a reusable helper to construct arbitrary-length crate dependency chains for workspaces.

Build:

  • Add Hypothesis as a development dependency for property-based testing.

Tests:

  • Add focused preflight tests covering workspace root resolution, configuration loading, git cleanliness enforcement, cargo check/test failures, and preflight unit-test/exclude behavior.
  • Add parametrized and property-based tests to verify dry-run batching and live interleaving publish ordering across variable workspace and crate chain sizes.
  • Add workspace configuration and plan-summary tests to validate output formatting and error propagation for missing workspaces and configuration issues.
  • Replace the prior integration-style publish run test with more granular unit-level test modules for preflight, ordering, and workspace behavior.

@coderabbitai

coderabbitai Bot commented Jun 7, 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 881c6da3-1bc8-4061-b9a5-c3acfd6f9ec6

📥 Commits

Reviewing files that changed from the base of the PR and between 29a5327 and 252152a.

📒 Files selected for processing (8)
  • docs/developers-guide.md
  • tests/unit/publish/__snapshots__/test_run_workspace_config.ambr
  • tests/unit/publish/conftest.py
  • tests/unit/publish/preflight_test_utils.py
  • tests/unit/publish/test_run_integration.py
  • tests/unit/publish/test_run_preflight.py
  • tests/unit/publish/test_run_publish_ordering.py
  • tests/unit/publish/test_run_workspace_config.py
 _____________________________________________________________________________________________________________________________________________________________________________________________________
< Don't think outside the box - find the box. When faced with an impossible problem, identify the real constraints. Ask yourself: 'Does it have to be done this way? Does it have to be done at all?' >
 -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ

Walkthrough

Split the removed monolithic integration test into three focused test modules (preflight, workspace/config, publish ordering), added exported cargo command tuples and a make_n_crate_chain helper in tests/unit/publish/conftest.py, and introduced Hypothesis and parameterised tests validating publish ordering invariants.

Changes

Integration test restructuring into functional modules

Layer / File(s) Summary
Test infrastructure: constants and workspace helpers
tests/unit/publish/conftest.py
Export CARGO_PACKAGE, CARGO_PUBLISH, CARGO_PUBLISH_DRY_RUN; add make_n_crate_chain(root, count) to generate linear dependency chains for parameterised and Hypothesis tests.
Workspace and configuration test module: root resolution, loading, and plan formatting
tests/unit/publish/test_run_workspace_config.py
New module validating workspace-root normalisation, active vs disk configuration selection, formatted publish plan content (versions, strategy, exclusions, not-found warnings), empty-publishable-crates reporting, missing-workspace -> WorkspaceModelError conversion, and configuration error propagation.
Preflight test module: cargo invocation and workspace cleanliness
tests/unit/publish/test_run_preflight.py
New module asserting preflight command invocation location and arguments, --exclude normalisation (trim, dedupe, sort), unit_tests_only narrowing behaviour, default skip vs enforced git-clean checks, and raising PublishPreflightError when cargo preflight fails.
Publish ordering test module: batching, interleaving, and invariant verification
tests/unit/publish/test_run_publish_ordering.py
New module testing dry-run batching (all cargo package before all dry-run cargo publish), live-mode per-crate package→publish interleaving, behaviour around unpublished workspace dependency override (log downgrade vs error), and Hypothesis/parametrised tests across crate counts.

Possibly related PRs

  • leynos/lading#63: Tests and logic for handling cargo index-missing-version sibling-crate failures — closely related test target.
  • leynos/lading#70: Introduced live interleaving publication pipeline the new tests exercise.
  • leynos/lading#90: Further restructuring and coverage around publish.run preflight and ordering that overlaps with these tests.

Poem

🧩 From one sprawling test, three modules rise,
Each focused on what matters to our eyes—
Configs load, cleans gently checked, then orders flow,
Property-based proofs that batching steals the show! 🎯

🚥 Pre-merge checks | ✅ 15 | ❌ 5

❌ Failed checks (4 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Developer Documentation ⚠️ Warning PR introduces Hypothesis and test infrastructure without documenting them in developers-guide.md, violating documentation requirements for tooling and build-related changes. Document Hypothesis as dev dependency, explain conftest test-infrastructure helpers, and add property-based testing patterns to developers-guide.md.
Testing (Compile-Time / Ui) ⚠️ Warning Text/structured output tests in test_run_formats_plan_summary use brittle line-by-line substring assertions instead of snapshot tests despite existing syrupy/snapshot infrastructure in the codebase. Convert test_run_formats_plan_summary to use syrupy snapshots to capture the complete formatted output structure and prevent unintended formatting regressions.
Concurrency And State ⚠️ Warning CallTrackingRunner introduces unnecessary shared mutable state with threading lock; publish.run is purely synchronous and single-threaded with no concurrent access. Remove threading.Lock from CallTrackingRunner._calls_lock or document why concurrent access is required if future code will call publish.run concurrently.
Testing (Unit And Behavioural) ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
✅ Passed checks (15 passed)
Check name Status Explanation
Title check ✅ Passed The title directly references issue #71 and accurately summarises the main changes: adding publish ordering invariant tests as requested.
Description check ✅ Passed The description comprehensively explains the changes, links to issue #71, and details the refactoring of test modules and new test coverage.
Linked Issues check ✅ Passed The PR fully implements issue #71's objectives: parametrises publish ordering tests across variable workspace sizes (1, 2, 3, 5 crates), adds Hypothesis property-based tests validating dry-run batching and live interleaving invariants, and introduces make_n_crate_chain infrastructure (#71).
Out of Scope Changes check ✅ Passed All changes directly support issue #71's objectives. The reorganisation of tests into focused modules, introduction of make_n_crate_chain, and Hypothesis dependency addition are all within scope for improving publish ordering test coverage.
Testing (Overall) ✅ Passed Tests exercise real publish.run() with substantive assertions across extensive parametrisation. Would fail for incorrect implementations. No vacuous patterns found.
User-Facing Documentation ✅ Passed No user-facing functionality or behaviour changes; PR contains only test infrastructure refactoring and property-based test additions within tests/ directory.
Module-Level Documentation ✅ Passed All four added/modified modules carry clear module-level docstrings explaining their purpose and function as required.
Testing (Property / Proof) ✅ Passed Property-based tests using Hypothesis validate two substantive ordering invariants (dry-run batching and live interleaving) across variable workspace sizes (1–10 crates) with clear assertions.
Unit Architecture ✅ Passed Test refactoring separates concerns by module, injects dependencies explicitly, makes fallibility visible through pytest.raises, uses frozen dataclasses, and avoids hidden state or globals.
Domain Architecture ✅ Passed PR changes are test-only: new test modules and reorganised test infrastructure. No domain code in lading/commands/ modified; test helpers properly segregated.
Observability ✅ Passed PR contains only test reorganisation and expansion; no production code modified, no new operational behaviour or observability requirements introduced.
Security And Privacy ✅ Passed Test reorganisation uses clearly fake test data (alpha, beta, gamma, crate_0). No secrets, credentials, tokens, keys or sensitive data in fixtures or logs. No injection vulnerabilities found.
Performance And Resource Use ✅ Passed All new test infrastructure has bounded resource constraints with explicit limits on loop iterations and input sizes. No algorithmic regressions, unbounded I/O, or blocking operations detected.
Architectural Complexity And Maintainability ✅ Passed Splitting 694-line test module addresses cohesion; make_n_crate_chain generalises chains; constants reduce duplication. All abstractions have clear, immediate purpose.
Rust Compiler Lint Integrity ✅ Passed PR modifies only Python test files; Rust Compiler Lint Integrity check targets Rust code changes. No Rust files modified; check not applicable.

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

📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #71

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-71-add-property-based-and-parametrised-tests-for-publish-ordering-invariants

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

@sourcery-ai

sourcery-ai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds comprehensive publish pipeline tests covering preflight behavior, workspace/config handling, and publish ordering invariants using both parametrized and property-based tests, plus a new helper for constructing deterministic N-crate dependency chains and a Hypothesis dev dependency.

Flow diagram for new publish ordering test structure

flowchart TD
  conftest[tests_unit_publish_conftest_py]
  helper[make_n_crate_chain]
  preflight[tests_unit_publish_test_run_preflight_py]
  ordering[tests_unit_publish_test_run_publish_ordering_py]
  workspace[tests_unit_publish_test_run_workspace_config_py]
  pipelines[publish_pipelines_dry_run_and_live]

  conftest --> helper
  helper --> preflight
  helper --> ordering
  helper --> workspace

  preflight --> pipelines
  ordering --> pipelines
  workspace --> pipelines
Loading

File-Level Changes

Change Details Files
Introduce a reusable helper for constructing deterministic N-crate dependency chains for tests.
  • Add make_n_crate_chain(root, count) fixture helper that builds a linear dependency chain of crate_0..crate_{count-1}.
  • Export the new helper from the publish test conftest so it can be reused across new test modules.
tests/unit/publish/conftest.py
Add detailed tests for publish preflight behavior, including cwd handling, exclude normalization, unit-tests-only mode, dirty workspace handling, and error propagation.
  • Verify preflight commands run in the resolved workspace root and use proper cargo arguments.
  • Parametrize and normalize test-exclude settings, verifying both the composed cargo arguments and the configuration plumbing.
  • Check unit-tests-only behavior, including interaction with excludes and the presence/absence of --all-targets/--lib/--bins flags.
  • Assert default behavior allows dirty workspaces and that allow_dirty=False enforces a clean git status.
  • Simulate failing cargo check/test invocations and ensure PublishPreflightError carries the right diagnostics.
tests/unit/publish/test_run_preflight.py
Add parametrized and property-based tests for publish ordering invariants in dry-run and live modes, including handling of unpublished workspace dependency overrides.
  • Introduce a runner that simulates a package index failure for a specific crate and test the allow_unpublished_workspace_deps override behavior in both permissive and strict modes.
  • Add parametrized tests over 2, 3, and 5 crate chains to assert that dry-run publication batches all package calls before any publish calls, and that live publication interleaves package/publish per crate.
  • Use the new make_n_crate_chain helper and CallTrackingRunner to assert exact (command, cwd) sequences and staging layout.
  • Add Hypothesis-based properties over crate counts 2–10 to enforce dry-run batching and live interleaving invariants across varying workspace sizes.
tests/unit/publish/test_run_publish_ordering.py
Add tests around publish.run workspace-root resolution, configuration loading, and plan summary formatting.
  • Ensure publish.run normalizes the workspace root path before planning and that the plan header reflects the resolved path.
  • Verify that publish.run uses the active configuration when available and falls back to loading configuration from disk when not.
  • Check that the plan summary text includes publishable crates, manifest-skipped crates, config-excluded crates, and missing exclusions, including the no-publishable-crates case.
  • Validate that missing workspaces are surfaced as WorkspaceModelError with an informative message and that configuration loading errors are propagated unchanged.
tests/unit/publish/test_run_workspace_config.py
Add Hypothesis as a development dependency with a more precise version specifier.
  • Add hypothesis>=6.0.0 to the dev dependency group and remove the previous looser hypothesis>=6 entry.
pyproject.toml

Assessment against linked issues

Issue Objective Addressed Explanation
#71 Parameterize the live and dry-run publish ordering tests (test_run_keeps_live_publication_interleaved and test_run_keeps_dry_run_publication_batched) over workspace sizes of at least 2, 3, and 5 crates.
#71 Add property-based (Hypothesis) tests that vary workspace crate lists and assert that run() respects the interleaved (live) and batched (dry-run) publish ordering invariants.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

codescene-delta-analysis[bot]

This comment was marked as outdated.

@lodyai
lodyai Bot force-pushed the issue-71-add-property-based-and-parametrised-tests-for-publish-ordering-invariants branch from 6c48969 to 6e5fdb2 Compare June 8, 2026 22:31
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Jun 9, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Low Cohesion

tests/unit/publish/test_run_integration.py:

What lead to degradation?

This module has at least 19 different responsibilities amongst its 20 functions, threshold = 4

Why does this problem occur?

Cohesion is a measure of how well the elements in a file belong together. CodeScene measures cohesion using the LCOM4 metric (Lack of Cohesion Measure). With LCOM4, the functions inside a module are related if a) they access the same data members, or b) they call each other. High Cohesion is desirable as it means that all functions are related and likely to represent the same responsibility. Low Cohesion is problematic since it means that the module contains multiple behaviors. Low Cohesion leads to code that's harder to understand, requires more tests, and very often become a coordination magnet for developers.

How to fix it?

Look to modularize the code by splitting the file into more cohesive units; functions that belong together should still be located together. A common refactoring is EXTRACT CLASS.

Helpful refactoring examples

To get a general understanding of what this code health issue looks like - and how it might be addressed - we have prepared some diffs for illustrative purposes.

SAMPLE

# low_cohesion_example.js
 
 var userLayer = connectUsers(myConnectionProperties);
 
-var chessEngine = startEngine(gameProperties);
+// [Refactoring: moved the data related to chess to a new chessGame.js module]
 
+// The module contains login related functionality that forms one behaviour: all
+// code is related since it either a) uses the same data, or b) calls the same functions.
 export function login(newUser) {
   val authenticated = userLayer.authenticate(newUser);
   traceLoginFor(authenticated);
    // ...some code...
 }
 
-// playChess seems like a very unrelated responsibility.
-// Should it really be within the same module?
-
-export function playChess(loggedInUser) {
-   var board = chessEngine.newBoard();
-
-   return newGameOn(board, loggedInUser);
-}
+// [Refactoring: moved playChess to a new chessGame.js module
+// As a result of this refactoring, the module maintains a
+// single behavior where all code and data is related: high cohesion.]

@coderabbitai

This comment was marked as resolved.

@lodyai
lodyai Bot force-pushed the issue-71-add-property-based-and-parametrised-tests-for-publish-ordering-invariants branch from 6e5fdb2 to e2ec7d5 Compare June 9, 2026 01:21
codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
leynos marked this pull request as ready for review June 9, 2026 22:06
sourcery-ai[bot]

This comment was marked as resolved.

@coderabbitai coderabbitai Bot added the Issue label Jun 9, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/publish/conftest.py`:
- Around line 171-179: Update the docstring for make_n_crate_chain to a full
numpy-style docstring: add a one-line summary followed by Parameters (root: Path
— root directory for crates; count: int — number of crates to create, must be
>=1) and Returns (tuple[WorkspaceCrate, ...] — tuple of crates wired as a linear
dependency chain, first crate has no dependencies, each subsequent crate depends
on the previous). Also add a short Examples section showing a minimal usage
snippet that calls make_n_crate_chain(root, 3) and describes the resulting
dependency relationships (crate_0 <- crate_1 <- crate_2). Ensure the
function-level description and types match the existing signature and that the
docstring is numpy-style.

In `@tests/unit/publish/test_run_preflight.py`:
- Around line 77-96: Many assertions in tests (e.g., those involving calls,
check_call, test_call, command, cwd, root) are bare; update each assert to
include a concise failure message. For example, where the diff asserts
membership or equality (like assert (("git","status","--porcelain"), root) in
calls, assert cwd == root, and assert command[2] == "--workspace"), change them
to use the form assert <condition>, "<brief message describing expected state>"
and do this consistently across tests in
tests/unit/publish/test_run_preflight.py and the related modules
test_run_workspace_config.py and test_run_publish_ordering.py so each assertion
has a clear message for failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3df01062-ed0c-4d87-aff1-152cd2fd9df1

📥 Commits

Reviewing files that changed from the base of the PR and between a07962b and 85b42c2.

📒 Files selected for processing (6)
  • pyproject.toml
  • tests/unit/publish/conftest.py
  • tests/unit/publish/test_run_integration.py
  • tests/unit/publish/test_run_preflight.py
  • tests/unit/publish/test_run_publish_ordering.py
  • tests/unit/publish/test_run_workspace_config.py
💤 Files with no reviewable changes (1)
  • tests/unit/publish/test_run_integration.py

Comment thread tests/unit/publish/conftest.py
Comment thread tests/unit/publish/test_run_preflight.py Outdated
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Jun 10, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

tests/unit/publish/test_run_preflight.py

Comment on lines +119 to +178

def test_run_includes_preflight_test_excludes(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
    configured_excludes: tuple[str, ...],
    expected_excludes: tuple[str, ...],
    *,
    unit_tests_only: bool,
) -> None:
    """Configured exclusions match the builder output and cargo invocation."""
    configuration = make_config(
        preflight=make_preflight_config(
            test_exclude=configured_excludes,
            unit_tests_only=unit_tests_only,
        )
    )
    root, _workspace, calls = _setup_preflight_test(
        monkeypatch, tmp_path, configuration
    )
    args, cwd = _extract_cargo_test_call(calls)
    assert cwd == root, "cargo test should run in the workspace root"
    arguments = list(args[2:])
    assert arguments[0] == "--workspace", "cargo test should target the workspace"
    include_all_targets = "--all-targets" in arguments
    assert include_all_targets == (not configuration.preflight.unit_tests_only), (
        "--all-targets should be present only outside unit-tests-only mode"
    )
    target_argument = next(
        value for value in arguments if value.startswith("--target-dir=")
    )
    target_dir = Path(target_argument.split("=", 1)[1])
    base_arguments = list(
        publish_preflight._compose_preflight_arguments(
            target_dir,
            include_all_targets=include_all_targets,
        )
    )
    options = publish_preflight._CargoPreflightOptions(
        extra_args=tuple(base_arguments),
        test_excludes=configured_excludes,
        unit_tests_only=configuration.preflight.unit_tests_only,
    )
    rebuilt_arguments = publish_preflight._build_test_arguments(
        list(base_arguments),
        options,
    )
    assert rebuilt_arguments == arguments, (
        "builder output should match the captured cargo invocation"
    )
    exclude_values = tuple(
        arguments[index + 1]
        for index, value in enumerate(arguments[:-1])
        if value == "--exclude"
    )
    assert exclude_values == expected_excludes, (
        "exclusions should be trimmed, deduplicated, and sorted"
    )
    if not expected_excludes:
        assert "--exclude" not in arguments, (
            "no --exclude flag should appear when there are no exclusions"
        )

❌ New issue: Excess Number of Function Arguments
test_run_includes_preflight_test_excludes has 5 arguments, max arguments = 4

@leynos

leynos commented Jun 10, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Have the following now been resolved?

Annul any requirements that violate the en-GB-oxendict spelling (-ize / -yse / -our) conventions (for example a request to replace "normalize" with "normalise" or "artefact" with "artifact"), or where the requirement unnecessarily increases cyclomatic complexity.

## Overall Comments
- There is quite a bit of repeated setup across the new `test_run_preflight.py`, `test_run_publish_ordering.py`, and `test_run_workspace_config.py` (e.g., creating roots/workspaces/configurations and wiring a runner); consider extracting small shared helpers/fixtures (beyond `_setup_preflight_test`) to keep the tests shorter and make the intent of each case clearer.
- The cargo invocations in the new tests repeatedly hard-code command tuples like `("cargo", "package", "--allow-dirty")` and `("cargo", "publish", "--allow-dirty", "--dry-run")`; centralising these as small constants or helpers in the test module or `conftest` would make the expectations easier to update and reduce duplication.

## Individual Comments

### Comment 1
<location path="tests/unit/publish/test_run_publish_ordering.py" line_range="101-103" />
<code_context>
-        assert any(arg.startswith("--target-dir=") for arg in command[4:])
-
-
-@pytest.mark.parametrize(
-    ("configured_excludes", "expected_excludes"),
-    EXCLUDE_SCENARIOS,
</code_context>
<issue_to_address>
**suggestion (testing):** Include a single-crate workspace case in the publish ordering tests.

The parametrized `test_run_keeps_dry_run_publication_batched` / `test_run_keeps_live_publication_interleaved` only cover chain sizes 2, 3, and 5, and the Hypothesis properties also start at 2. Since the code supports a workspace with a single publishable crate, please add coverage for `crate_count=1` either by:

- Extending the parametrized tests, and/or
- Lowering the Hypothesis strategy `min_value` to 1 (and adjusting assumptions if needed),

so that we also validate ordering invariants and catch any single‑crate off‑by‑one issues.
</issue_to_address>

### Comment 2
<location path="tests/unit/publish/test_run_preflight.py" line_range="99-112" />
<code_context>
-        assert any(arg.startswith("--target-dir=") for arg in command[4:])
-
-
-@pytest.mark.parametrize(
-    ("configured_excludes", "expected_excludes"),
-    EXCLUDE_SCENARIOS,
-)
-def test_run_includes_preflight_test_excludes(
-    monkeypatch: pytest.MonkeyPatch,
-    tmp_path: Path,
</code_context>
<issue_to_address>
**suggestion (testing):** Add coverage for `unit_tests_only=True` combined with the exclude-normalisation scenarios.

The parametrised `test_run_includes_preflight_test_excludes` thoroughly covers trimming, deduplication, and sorting of `test_exclude`, but only for the default `unit_tests_only` setting. `test_run_unit_tests_only_with_excludes` focuses on argument ordering and doesn’t reuse `EXCLUDE_SCENARIOS`.

Please either add a parametrised test that reuses `EXCLUDE_SCENARIOS` with `unit_tests_only=True`, or extend this test to run in both modes (`unit_tests_only=True/False`) to verify exclude normalisation is identical when `unit_tests_only` is enabled and avoid regressions in `_build_test_arguments`.

```suggestion
@pytest.mark.parametrize(
    ("configured_excludes", "expected_excludes", "unit_tests_only"),
    [
        (*scenario, unit_tests_only)
        for scenario in EXCLUDE_SCENARIOS
        for unit_tests_only in (False, True)
    ],
)
def test_run_includes_preflight_test_excludes(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
    configured_excludes: tuple[str, ...],
    expected_excludes: tuple[str, ...],
    unit_tests_only: bool,
) -> None:
    """Configured exclusions match the builder output and cargo invocation."""
    configuration = make_config(
        preflight=make_preflight_config(
            test_exclude=configured_excludes,
            unit_tests_only=unit_tests_only,
        )
    )
```
</issue_to_address>

@coderabbitai

This comment was marked as resolved.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check current state of test_run_publish_ordering.py for single-crate coverage
echo "=== test_run_publish_ordering.py ==="
cat tests/unit/publish/test_run_publish_ordering.py

echo ""
echo "=== test_run_preflight.py ==="
cat tests/unit/publish/test_run_preflight.py

Length of output: 21771



@leynos Both comments have been resolved in the current branch.

Comment 1 — Single-crate coverage in test_run_publish_ordering.py: ✅ Resolved. Both test_run_keeps_dry_run_publication_batched and test_run_keeps_live_publication_interleaved now parametrise over [1, 2, 3, 5] (with the one_crate id), and the Hypothesis strategies have been lowered to min_value=1.

Comment 2 — unit_tests_only=True with EXCLUDE_SCENARIOS in test_run_preflight.py: ✅ Resolved. The new EXCLUDE_MODE_SCENARIOS constant cross-products every entry in EXCLUDE_SCENARIOS with unit_tests_only in (False, True), and test_run_includes_preflight_test_excludes accepts unit_tests_only as a keyword-only argument (declared after *). That pattern cannot be passed positionally, so it does not increase the function's internal cyclomatic complexity; the CodeScene "Excess Number of Function Arguments" finding is therefore annulled per your instruction, as it does not represent a genuine complexity increase.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Jun 10, 2026

Copy link
Copy Markdown
Owner

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

@lodyai
lodyai Bot force-pushed the issue-71-add-property-based-and-parametrised-tests-for-publish-ordering-invariants branch from a11c902 to 29a5327 Compare June 10, 2026 00:18
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/publish/conftest.py`:
- Line 188: The docstring claims the parameter count "Must be at least 1" but
the implementation currently accepts 0; update the code so the behavior matches
the docstring by adding an explicit guard at the start of the crate-creation
function/fixture that checks the count parameter (if count < 1) and raises
ValueError("count must be >= 1"), or alternatively remove that lower-bound
statement from the docstring so they are consistent; ensure the check references
the count parameter used by the crate-creation routine.

In `@tests/unit/publish/test_run_preflight.py`:
- Around line 30-53: EXCLUDE_SCENARIOS is defined as a mutable list but should
be an immutable module-level constant; replace the surrounding square brackets
with parentheses to make EXCLUDE_SCENARIOS a tuple containing the pytest.param
entries (leave each pytest.param(...) item unchanged) so the constant is
immutable and follows the project's immutability guideline.
- Around line 113-124: EXCLUDE_MODE_SCENARIOS is created with a list
comprehension but is never mutated; change it to an immutable tuple by wrapping
the generator in tuple(...) instead of using a list literal so the module-level
constant becomes a tuple; update the expression that builds
EXCLUDE_MODE_SCENARIOS (which constructs _ExcludeScenario instances from
EXCLUDE_SCENARIOS and the unit_tests_only flag) to return a tuple to follow the
immutability guideline.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 268ecbe4-3470-4e82-a1e5-4f0bebf5f8eb

📥 Commits

Reviewing files that changed from the base of the PR and between 6891e75 and 29a5327.

📒 Files selected for processing (5)
  • tests/unit/publish/conftest.py
  • tests/unit/publish/test_run_integration.py
  • tests/unit/publish/test_run_preflight.py
  • tests/unit/publish/test_run_publish_ordering.py
  • tests/unit/publish/test_run_workspace_config.py
💤 Files with no reviewable changes (1)
  • tests/unit/publish/test_run_integration.py

Comment thread tests/unit/publish/conftest.py
Comment thread tests/unit/publish/test_run_preflight.py Outdated
Comment thread tests/unit/publish/test_run_preflight.py Outdated
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Jun 10, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Have the following failed checks now been resolved?

If further work is required, please provide an AI agent prompt for the remaining work to be done to address these failures.

Do not treat warnings as optional or aspirational. Where a change is out of scope for this PR, propose a GitHub issue unless one exists already.

❌ Failed checks (4 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Developer Documentation ⚠️ Warning PR introduces Hypothesis and test infrastructure without documenting them in developers-guide.md, violating documentation requirements for tooling and build-related changes. Document Hypothesis as dev dependency, explain conftest test-infrastructure helpers, and add property-based testing patterns to developers-guide.md.
Testing (Compile-Time / Ui) ⚠️ Warning Text/structured output tests in test_run_formats_plan_summary use brittle line-by-line substring assertions instead of snapshot tests despite existing syrupy/snapshot infrastructure in the codebase. Convert test_run_formats_plan_summary to use syrupy snapshots to capture the complete formatted output structure and prevent unintended formatting regressions.
Concurrency And State ⚠️ Warning CallTrackingRunner introduces unnecessary shared mutable state with threading lock; publish.run is purely synchronous and single-threaded with no concurrent access. Remove threading.Lock from CallTrackingRunner._calls_lock or document why concurrent access is required if future code will call publish.run concurrently.
Testing (Unit And Behavioural) ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.

@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== 1. threading/Lock in conftest.py ==="
rg -n "threading\|Lock\|_calls_lock" tests/unit/publish/conftest.py || echo "CLEAN - no threading"

echo ""
echo "=== 2. developers-guide.md - Hypothesis section ==="
fd -i "developers-guide.md" --exec grep -n "hypothesis\|Hypothesis\|property.based\|property-based\|make_n_crate_chain\|CallTrackingRunner" {} || echo "NOT FOUND"

echo ""
echo "=== 3. test_run_workspace_config.py - snapshot vs splitlines ==="
grep -n "splitlines\|in lines\|SnapshotAssertion\|snapshot\|_normalise_summary\|re\.sub\|normalised" tests/unit/publish/test_run_workspace_config.py

echo ""
echo "=== 4. Docstring coverage ==="
python3 - <<'EOF'
import ast
from pathlib import Path
import subprocess

result = subprocess.run(["fd", "preflight_test_utils"], capture_output=True, text=True)
extra_files = [f.strip() for f in result.stdout.strip().splitlines() if f.strip()]

files = [
    "tests/unit/publish/conftest.py",
    "tests/unit/publish/test_run_workspace_config.py",
    "tests/unit/publish/test_run_publish_ordering.py",
    "tests/unit/publish/test_run_preflight.py",
] + extra_files

total = 0
missing = 0
missing_items = []

for filepath in files:
    p = Path(filepath)
    if not p.exists():
        print(f"NOT FOUND: {filepath}")
        continue
    tree = ast.parse(p.read_text())
    for node in ast.walk(tree):
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
            total += 1
            has_doc = (
                isinstance(node.body[0], ast.Expr)
                and isinstance(node.body[0].value, ast.Constant)
                and isinstance(node.body[0].value.value, str)
            ) if node.body else False
            if not has_doc:
                missing += 1
                missing_items.append(f"  {filepath}:{node.lineno} {type(node).__name__} {node.name}")

if total > 0:
    print(f"\nTotal symbols: {total}")
    print(f"Missing docstrings: {missing}")
    print(f"Coverage: {(total-missing)/total*100:.1f}%")
    if missing_items:
        print("\nMissing:")
        for item in missing_items:
            print(item)
    else:
        print("All symbols documented.")
else:
    print("No symbols found.")
EOF

echo ""
echo "=== 5. Snapshot .ambr files ==="
fd -e ambr . tests/unit/publish/ 2>/dev/null

echo ""
echo "=== 6. test_run_workspace_config.py full content ==="
cat tests/unit/publish/test_run_workspace_config.py

Length of output: 11535



@leynos All four warnings are resolved.

Check Status Evidence
Docstring Coverage ✅ Resolved 100 % (58/58 symbols) — 0 missing
Developer Documentation ✅ Resolved developers-guide.md lines 91–127 document Hypothesis, make_n_crate_chain, and CallTrackingRunner
Testing (Compile-Time / UI) ✅ Resolved _normalise_summary extracted; both snapshot tests use assert _normalise_summary(message, root) == snapshot; test_run_workspace_config.ambr snapshot file present
Concurrency And State ✅ Resolved threading, Lock, and _calls_lock absent from conftest.py

The Code Duplication finding from the latest review comment is also clear — _normalise_summary is defined once and called in both test_run_formats_plan_summary and test_run_reports_no_publishable_crates.

@leynos

leynos commented Jun 10, 2026

Copy link
Copy Markdown
Owner

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 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.

@leynos

leynos commented Jun 10, 2026

Copy link
Copy Markdown
Owner

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 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.

@leynos

leynos commented Jun 10, 2026

Copy link
Copy Markdown
Owner

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

leynos and others added 15 commits June 10, 2026 16:56
Add a reusable `make_n_crate_chain` helper for publish tests so
ordering invariants can be exercised across variable workspace sizes.

Add Hypothesis to the development dependency group for the upcoming
property-based publish ordering tests.
Parametrize the dry-run and live publish ordering tests across two,
three, and five-crate dependency chains.

Add Hypothesis properties that exercise the dry-run batching and live
interleaving invariants across generated chain sizes from 2 to 10
crates.
Apply the formatter spacing required after replaying the publish ordering
property tests onto the updated main branch.
Refactor test_run_integration.py into three focused modules:

- test_run_workspace_config.py for root and configuration handling
- test_run_publish_ordering.py for ordering and unpublished dependency behavior
- test_run_preflight.py for preflight execution and related checks

Preserve existing assertions and rename module boundaries without changing test logic.
Act on the publish ordering test review feedback:

- Cover single-crate workspaces in the parametrised dry-run and live
  ordering tests, and lower the Hypothesis strategies to `min_value=1`
  so single-crate off-by-one issues are caught.
- Exercise the exclude-normalisation scenarios in both `unit_tests_only`
  modes to confirm `_build_test_arguments` handles exclusions
  identically regardless of target narrowing.
- Flesh out the `make_n_crate_chain` docstring with full numpy-style
  Parameters, Returns, and Examples sections.
- Centralise the repeated cargo command tuples as shared constants in
  the publish conftest.
- Give every assertion across the preflight, ordering, and
  workspace-config modules a concise failure message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CodeScene flagged `test_run_includes_preflight_test_excludes` for having
five arguments (threshold four). Three of them were injected solely via
`@pytest.mark.parametrize`. Wrap `configured_excludes`, `expected_excludes`,
and `unit_tests_only` in a frozen `_ExcludeScenario` dataclass so the test
signature shrinks to three parameters, keeping the parametrize ids and
behaviour unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make `make_n_crate_chain` reject `count < 1` with a `ValueError` so the
runtime behaviour matches its docstring's "must be at least 1" claim.

Convert the `EXCLUDE_SCENARIOS` and `EXCLUDE_MODE_SCENARIOS` module-level
constants from lists to tuples, since neither is mutated, to follow the
project's immutability guideline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document the inner runner/invoke closures, stub loaders, and the
`_ExcludeScenario` dataclass across the new publish test modules so they
reach 100% docstring coverage, clearing the >=80% gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a "Property-based testing" section covering the Hypothesis dev
dependency and the publish suite's `@settings` convention, plus a
"Publish test infrastructure" section tabulating the shared conftest
helpers and constants, to the developers' guide.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the per-substring assertions in `test_run_formats_plan_summary`
with a syrupy snapshot of the full formatted summary so unintended
formatting regressions are caught automatically. The snapshot now also
covers the crate-count header, staging line, and README line that the
substring checks ignored.

Redact the two non-deterministic paths (the tmp_path workspace root and
the randomly named staging directory) before comparing, so the snapshot
is stable across machines and pytest runs, per the snapshot-hygiene
guidance in AGENTS.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`publish.run` drives the injected command runner from sequential
`for crate in plan.publishable` loops with no threads or executors, so
`CallTrackingRunner` is only ever called single-threaded. The
`threading.Lock` guarding its call log provided no safety guarantee, so
remove it and the `threading` import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the per-substring assertions in
`test_run_reports_no_publishable_crates` with a syrupy snapshot of the
full formatted summary, matching the treatment already applied to its
sibling `test_run_formats_plan_summary`. The snapshot also pins the
crate-count line, staging line, and README line the substring checks
ignored.

Redact the non-deterministic workspace root and staging directory paths
before comparing so the snapshot stays stable across machines and pytest
runs, per the snapshot-hygiene guidance in AGENTS.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both `test_run_formats_plan_summary` and
`test_run_reports_no_publishable_crates` carried an identical
three-statement block redacting the workspace root and staging directory
before the snapshot assertion. Extract it into a private
`_normalise_summary` helper to remove the duplication CodeScene flagged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebasing the developers' guide additions onto main's restructuring left
two doubled blank lines before the new "Property-based testing" and
"Publish test infrastructure" sections. Collapse them so markdownlint's
MD012 (no multiple consecutive blank lines) passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lodyai
lodyai Bot force-pushed the issue-71-add-property-based-and-parametrised-tests-for-publish-ordering-invariants branch from fef86c5 to 252152a Compare June 10, 2026 14:59
@leynos
leynos merged commit 7498ac3 into main Jun 11, 2026
4 of 5 checks passed
@leynos
leynos deleted the issue-71-add-property-based-and-parametrised-tests-for-publish-ordering-invariants branch June 11, 2026 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add property-based and parametrised tests for publish ordering invariants

1 participant