Skip to content

test: increase unit test coverage for core/ modules - #115

Open
SHA888 wants to merge 4 commits into
mainfrom
test/core-unit-test-coverage
Open

test: increase unit test coverage for core/ modules#115
SHA888 wants to merge 4 commits into
mainfrom
test/core-unit-test-coverage

Conversation

@SHA888

@SHA888 SHA888 commented Jul 31, 2026

Copy link
Copy Markdown
Owner

What

Add 65 new tests across 4 test files covering controller.py, evaluator.py, selection.py, and version_database.py — the modules called out in TODO.md P2 "Increase unit test coverage for core/ modules".

New test files

  • test_controller.py (12 tests): initialize, run_generation orchestration, select_candidates sorting/truncation, get_state, cli_interface dispatch
  • test_version_database_extended.py (26 tests): experiment tracking (add/get by experiment), get_variant_metadata, get_best_variants with filtering/limits, get_variant_statistics (empty, same-score, per-experiment), export/import (JSON string, file, duplicates, lineage preservation, experiment tracking), edge cases
  • test_selection_extended.py (14 tests): custom fitness_key, empty population (raises IndexError), single individual, elitism > population size, elitism=0, num_selected=0 and >population, custom strategies, negative/missing scores
  • test_evaluator_extended.py (13 tests): empty results, missing/partial metrics, unknown strategy fallback, all-metrics-perfect/zero, feedback content (flags low coverage/quality, clean when perfect), custom default_weights, strategy overwrite, multiple results ordering

Verification

  • ruff format --check .
  • ruff check evoseal/ tests/
  • pytest tests/unit/evoseal/ -v → 81 passed ✅ (65 new + 16 existing)

Closes TODO.md item: "Increase unit test coverage for core/ modules"

Summary by CodeRabbit

  • Tests
    • Expanded unit test coverage for controller workflows, evaluation strategies, candidate selection, and version database operations.
    • Added coverage for edge cases including empty data, missing metrics, boundary scores, duplicate records, filtering, invalid commands, and custom strategies.
    • Verified experiment tracking, metadata, statistics, lineage preservation, and JSON/file import and export.
    • Increased tracked test coverage completion from 64 to 65 overall.

Add 65 new tests across 4 test files covering controller.py, evaluator.py,
selection.py, and version_database.py — the modules called out in TODO.md
P2 'Increase unit test coverage for core/ modules'.

New test files:
- test_controller.py (12 tests): initialize, run_generation, select_candidates,
  get_state, cli_interface
- test_version_database_extended.py (26 tests): experiment tracking,
  get_variant_metadata, get_best_variants, get_variant_statistics,
  export/import, edge cases
- test_selection_extended.py (14 tests): custom fitness_key, empty population,
  elitism edge cases, custom strategies, negative/missing scores
- test_evaluator_extended.py (13 tests): empty results, missing metrics,
  unknown strategy fallback, score bounds, feedback content

Closes TODO.md item: 'Increase unit test coverage for core/ modules'
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@SHA888, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 39 seconds

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4d86c55a-8de0-4328-b2b7-469928e74a14

📥 Commits

Reviewing files that changed from the base of the PR and between 7c069f0 and c61a096.

📒 Files selected for processing (4)
  • tests/unit/evoseal/test_controller.py
  • tests/unit/evoseal/test_evaluator_extended.py
  • tests/unit/evoseal/test_selection_extended.py
  • tests/unit/evoseal/test_version_database_extended.py
📝 Walkthrough

Walkthrough

The pull request adds unit tests for four EVOSEAL core modules and updates TODO tracking totals to record the increased test coverage.

Changes

Unit test coverage

Layer / File(s) Summary
Core behavior tests
tests/unit/evoseal/test_controller.py, tests/unit/evoseal/test_evaluator_extended.py, tests/unit/evoseal/test_selection_extended.py, TODO.md
Tests cover controller execution and CLI behavior, evaluator scoring and strategies, selection edge cases, and updated coverage totals.
Version database tests
tests/unit/evoseal/test_version_database_extended.py
Tests cover database tracking, queries, statistics, persistence, imports, exports, duplicate handling, lineage, and missing records.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: increased unit test coverage for core modules.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 test/core-unit-test-coverage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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

🧹 Nitpick comments (3)
tests/unit/evoseal/test_controller.py (1)

60-66: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Strengthen the assertion for the default-score case.

This test asserts only selected[0]["score"] == 0.8. It does not verify the full descending order or that the entry missing score defaults to 0.0 at the tail. The comment on line 63 documents the expected order 0.8, 0.6, 0.0, but the assertions do not check it.

Proposed stronger assertion
     assert len(selected) == 3
     assert selected[0]["score"] == 0.8
+    assert selected[1]["score"] == 0.6
+    assert selected[2].get("score", 0.0) == 0.0
🤖 Prompt for 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.

In `@tests/unit/evoseal/test_controller.py` around lines 60 - 66, Strengthen
test_select_candidates_defaults_missing_score_to_zero by asserting the complete
selected order: scores must be 0.8, 0.6, then 0.0, confirming descending ranking
and the missing-score default at the tail.
tests/unit/evoseal/test_selection_extended.py (1)

146-151: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Verify the claimed clamping behavior.

The docstring states negative scores are clamped to 0 in roulette selection, but the assertion only checks len(selected) == 2. Add an assertion that actually confirms clamping (for example, that selection does not raise and that all individuals remain eligible), or adjust the docstring to match what the test verifies.

🤖 Prompt for 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.

In `@tests/unit/evoseal/test_selection_extended.py` around lines 146 - 151, Update
test_roulette_with_negative_scores to verify the documented negative-score
clamping behavior, not only the result length: assert selection completes
without error and confirms the expected eligible individuals can be selected.
Alternatively, revise the docstring to describe only the behavior the test
actually checks.
tests/unit/evoseal/test_version_database_extended.py (1)

19-301: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract a shared fixture for VersionDatabase() instantiation.

db = VersionDatabase() repeats in every one of the 24 test functions in this file. Replace it with a pytest fixture to reduce duplication and simplify future changes to database setup.

Suggested fix
 import pytest

 from evoseal.core.version_database import VersionDatabase

+
+@pytest.fixture
+def db():
+    return VersionDatabase()
+
 # --- Experiment tracking ---


-def test_add_variant_with_experiment_id():
-    db = VersionDatabase()
+def test_add_variant_with_experiment_id(db):
     db.add_variant("v1", "code", {}, 0.8, experiment_id="exp1")

Apply the same pattern to the remaining test functions.

🤖 Prompt for 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.

In `@tests/unit/evoseal/test_version_database_extended.py` around lines 19 - 301,
Introduce a pytest fixture that returns a VersionDatabase instance, then update
all test functions in this file to accept the fixture and remove their local
VersionDatabase() instantiations. Apply this consistently across the experiment,
metadata, statistics, import/export, and edge-case tests while preserving each
test’s existing behavior.
🤖 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/evoseal/test_controller.py`:
- Around line 1-16: Mark the new unit test modules with pytestmark =
pytest.mark.unit after their imports: tests/unit/evoseal/test_controller.py
lines 1-16, tests/unit/evoseal/test_evaluator_extended.py lines 1-13, and
tests/unit/evoseal/test_selection_extended.py lines 1-14. Use the existing
pytest import and apply the marker at module scope in each file.

In `@tests/unit/evoseal/test_version_database_extended.py`:
- Around line 1-14: Add the repository’s unit pytest marker at module scope in
test_version_database_extended.py using pytestmark, so all tests in this
extended VersionDatabase test module are classified for marker-based CI
selection; retain the existing pytest import and test behavior.
- Around line 224-278: Update VersionDatabase.import_variants to check each
incoming variant_id against existing variants before appending or registering
experiment and lineage data, skipping duplicates and excluding them from the
returned import count. Preserve existing records and only apply experiment
tracking and lineage updates for newly imported variants, so
test_import_variants_skips_duplicates verifies the contract.

---

Nitpick comments:
In `@tests/unit/evoseal/test_controller.py`:
- Around line 60-66: Strengthen
test_select_candidates_defaults_missing_score_to_zero by asserting the complete
selected order: scores must be 0.8, 0.6, then 0.0, confirming descending ranking
and the missing-score default at the tail.

In `@tests/unit/evoseal/test_selection_extended.py`:
- Around line 146-151: Update test_roulette_with_negative_scores to verify the
documented negative-score clamping behavior, not only the result length: assert
selection completes without error and confirms the expected eligible individuals
can be selected. Alternatively, revise the docstring to describe only the
behavior the test actually checks.

In `@tests/unit/evoseal/test_version_database_extended.py`:
- Around line 19-301: Introduce a pytest fixture that returns a VersionDatabase
instance, then update all test functions in this file to accept the fixture and
remove their local VersionDatabase() instantiations. Apply this consistently
across the experiment, metadata, statistics, import/export, and edge-case tests
while preserving each test’s existing behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fe01d1b6-a4dc-4786-8971-979c4a4e9d63

📥 Commits

Reviewing files that changed from the base of the PR and between 5aa70f1 and 67287d0.

📒 Files selected for processing (5)
  • TODO.md
  • tests/unit/evoseal/test_controller.py
  • tests/unit/evoseal/test_evaluator_extended.py
  • tests/unit/evoseal/test_selection_extended.py
  • tests/unit/evoseal/test_version_database_extended.py

Comment thread tests/unit/evoseal/test_controller.py
Comment thread tests/unit/evoseal/test_version_database_extended.py
Comment on lines +224 to +278
def test_import_variants_skips_duplicates():
db = VersionDatabase()
db.add_variant("v1", "original", {}, 0.5)
export_data = {
"variants": {
"v1": {
"variant_id": "v1",
"source": "imported",
"test_results": {},
"eval_score": 0.9,
"parent_ids": [],
"metadata": {},
"experiment_id": None,
}
},
"lineage": {},
}
count = db.import_variants(json.dumps(export_data))
assert count == 0
assert db.get_variant("v1")["source"] == "original"


def test_import_variants_with_experiment_tracking():
db = VersionDatabase()
export_data = {
"variants": {
"v1": {
"variant_id": "v1",
"source": "code",
"test_results": {},
"eval_score": 0.8,
"parent_ids": [],
"metadata": {},
"experiment_id": "exp1",
}
},
"lineage": {},
}
db.import_variants(json.dumps(export_data))
assert db.get_variant_experiment("v1") == "exp1"
assert "v1" in db.get_experiment_variants("exp1")


def test_import_variants_preserves_lineage():
db = VersionDatabase()
export_data = {
"variants": {
"v1": {"variant_id": "v1", "source": "A", "test_results": {}, "eval_score": 0.5},
"v2": {"variant_id": "v2", "source": "B", "test_results": {}, "eval_score": 0.8},
},
"lineage": {"v2": ["v1"]},
}
db.import_variants(json.dumps(export_data))
assert db.get_lineage("v2") == ["v1"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect import_variants implementation for duplicate handling and experiment/lineage tracking.
ast-grep run --pattern 'def import_variants($$$) {
  $$$
}' --lang python evoseal/core/version_database.py

Repository: SHA888/EVOSEAL

Length of output: 478


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files | rg '(^|/)version_database\.py$|test_version_database_extended\.py$' || true

echo "== outline version_database =="
ast-grep outline evoseal/core/version_database.py --view expanded || true

echo "== import_variants occurrences =="
rg -n "def import_variants|import_variants|experiment_id|lineage" evoseal/core/version_database.py tests/unit/evoseal/test_version_database_extended.py

Repository: SHA888/EVOSEAL

Length of output: 9507


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant version_database sections =="
python3 - <<'PY'
from pathlib import Path
p=Path("evoseal/core/version_database.py")
lines=p.read_text().splitlines()
for i,l in enumerate(lines,1):
    if "class VersionDatabase" in l:
        print(f"class at {i}")
PY

# Print line-numbered slices around likely functions/tests
for start in $(rg -n "def (import_variants|add_variant|get_variant_experiment|get_experiment_variants|get_lineage|set_lineage)|def get_variant_experiment|def get_experiment_variants|def get_lineage" evoseal/core/version_database.py tests/unit/evoseal/test_version_database_extended.py | cut -d: -f1 | sort -u); do
    echo "===== $start ====="
    sed -n "$((start>20?start-20:1)),$((start+80))p" evoseal/core/version_database.py tests/unit/evoseal/test_version_database_extended.py 2>/dev/null | cat -n
done

Repository: SHA888/EVOSEAL

Length of output: 291


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== version_database.py import_variants implementation =="
sed -n '272,310p' evoseal/core/version_database.py | cat -n

echo "== import-related tests =="
sed -n '180,280p' tests/unit/evoseal/test_version_database_extended.py | cat -n

Repository: SHA888/EVOSEAL

Length of output: 5578


No duplicate-skip behavior for import_variants.

import_variants registers experiment_id and lineage after appending imported variants, so these tests do not exercise the claimed duplicate-skip behavior and will pass with the current implementation. Update import_variants to skip already-imported variant IDs if this contract is intended.

🧰 Tools
🪛 ast-grep (0.45.0)

[info] 240-240: use jsonify instead of json.dumps for JSON output
Context: json.dumps(export_data)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 261-261: use jsonify instead of json.dumps for JSON output
Context: json.dumps(export_data)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 275-275: use jsonify instead of json.dumps for JSON output
Context: json.dumps(export_data)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🤖 Prompt for 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.

In `@tests/unit/evoseal/test_version_database_extended.py` around lines 224 - 278,
Update VersionDatabase.import_variants to check each incoming variant_id against
existing variants before appending or registering experiment and lineage data,
skipping duplicates and excluding them from the returned import count. Preserve
existing records and only apply experiment tracking and lineage updates for
newly imported variants, so test_import_variants_skips_duplicates verifies the
contract.

@SHA888

SHA888 commented Jul 31, 2026

Copy link
Copy Markdown
Owner Author

Review feedback verification

I verified each review point against the actual source code (selection.py, version_database.py). Here's the analysis:

1. Stochastic flakiness — Not a real issue

The reviewer missed that SelectionAlgorithm.tournament_selection has elitism: int = DEFAULT_ELITISM where DEFAULT_ELITISM = 1. The test doesn't pass elitism=0, so default elitism=1 applies. With inverted scores, v4 has the highest custom_score (0.5), so it's guaranteed to be selected as the elite before any tournament randomness. The assertion any(ind["id"] == "v4" for ind in selected) is deterministically true.

Empirically verified: 500/500 runs passed with zero failures.

The other three tests only assert len(selected) == N, which is always deterministic. Also, the code uses secrets.SystemRandom() (not random), so random.seed() wouldn't help anyway.

2. IndexError for empty population — Already correct

Traced the code path: with population=[] and default elitism=1, the elitism block produces an empty selected list, the tournament loop is skipped (pop is empty), then secrets.SystemRandom().choice(selected) with an empty list raises IndexError: Cannot choose from an empty sequence. Confirmed at runtime.

3. import_variants_preserves_lineage missing keys — Already correct

  • import_variants stores variant data as-is — no field validation
  • experiment_id is accessed with .get("experiment_id") → safely returns None for missing keys
  • Lineage is imported from the separate lineage dict, not from variant data
  • The test only calls db.get_lineage("v2") which reads from self.lineage

4. Assumptions "taken on faith" — All verified from source

Every assumption checks out against the actual code:

  • Default-score fallback to 0.0: .get(fitness_key, 0)
  • get_best_variants sort order (descending): sorted(..., reverse=True)
  • score_distribution shape: returns dict[str, int]

Summary

All tests are correct as written. No code changes needed. Full test suite passes (81/81), ruff clean.

…comment

- Add seeded_rng fixture that monkeypatches secrets.SystemRandom with a
  deterministic random.Random(42), applied to all stochastic selection tests.
- Fix misleading comment in test_tournament_custom_fitness_key: v4 is favored
  because it has the highest custom_score (0.5), not because eval_score equals
  0.5. Also assert selected[0] is v4 (deterministic via elitism) instead of
  the probabilistic any() check.
- Verified: IndexError for empty population is correct (secrets.SystemRandom
  .choice([]) raises IndexError); no change needed there.

Addresses review feedback on PR #115.
@SHA888

SHA888 commented Jul 31, 2026

Copy link
Copy Markdown
Owner Author

Addressed the review feedback. Here is my point-by-point analysis:

1. Unseeded RNG in stochastic tests ✅ Fixed

  • Added a seeded_rng fixture that monkeypatches secrets.SystemRandom with a deterministic random.Random(42).
  • Applied to all stochastic tests: test_tournament_custom_fitness_key, test_num_selected_larger_than_population, test_tournament_with_negative_scores, test_roulette_with_negative_scores, test_tournament_missing_eval_score.
  • 100 consecutive runs confirmed zero flakiness after the fix.

2. IndexError for empty population ✅ Already correct — no change needed

  • The reviewer flagged this as an "unverifiable assumption", but I traced the code: empty pop → elitism loop skipped → while len(selected) < num_selected and pop → False → fallback while len(selected) < num_selected:secrets.SystemRandom().choice([])raises IndexError. The tests are correct as-is.

3. Misleading comment on test_tournament_custom_fitness_key ✅ Fixed

  • Old comment: "v4 (eval_score=0.5, custom_score=0.5) should be favored" — misleadingly ties favoring to eval_score coincidence.
  • New comment: "v4 has the highest custom_score (0.5) so it is guaranteed as the elite." — correctly explains why v4 appears.
  • Also changed assertion from probabilistic any(ind["id"] == "v4" ...) to deterministic selected[0]["id"] == "v4" (v4 is guaranteed as the elite with default elitism=1).

4. Broader caveat (score formulas, defaults, exceptions taken on faith) ℹ️ Informational

  • The defaults (elitism=1, tournament_size=3) and exception behavior are verifiable in selection.py. The score formula is not in this test file. No action needed — this is context for the reviewer, not a defect.

@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: 1

🤖 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/evoseal/test_selection_extended.py`:
- Around line 120-123: Strengthen the edge-case assertions in the
oversized-selection, negative-score roulette, and missing-score tests: pass
elitism=0 where needed, then assert expected individual IDs rather than only
result length. In test_num_selected_larger_than_population, verify all selected
entries are population members and that padding repeats members; update the
roulette test to verify negative scores are clamped as documented, and the
missing-score test to verify the individual selected under the default score of
0.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d285700-4cc1-4650-b4fc-a9c513eb0e06

📥 Commits

Reviewing files that changed from the base of the PR and between 67287d0 and 7c069f0.

📒 Files selected for processing (1)
  • tests/unit/evoseal/test_selection_extended.py

Comment thread tests/unit/evoseal/test_selection_extended.py
- Add pytestmark = pytest.mark.unit to all 4 new test files
- Strengthen test_select_candidates_defaults_missing_score_to_zero with
  full top-3 order assertions (0.8, 0.6, 0.0)
- Strengthen test_num_selected_larger_than_population to verify padding
  uses population members with repeats
- Strengthen test_roulette_with_negative_scores to verify selected
  individuals come from the population
- Strengthen test_tournament_missing_eval_score to verify v1 is selected
  (higher score beats missing-score default of 0)
@SHA888

SHA888 commented Jul 31, 2026

Copy link
Copy Markdown
Owner Author

Review feedback analysis — verified against actual source

I verified each point from the review against evoseal/core/version_database.py, evoseal/core/selection.py, evoseal/core/controller.py, and evoseal/core/evaluator.py. Here's what I found:

1. test_add_variant_without_experiment_id uses db.experiment_variants == {} ❌ Disagree

The reviewer is incorrect that this is a "private/internal" attribute. experiment_variants is a plain public attribute on VersionDatabase (line 35: self.experiment_variants: dict[str, list[str]] = {}) — no underscore prefix, no @property, no encapsulation. There is no "list all experiments" method in the public API; get_experiment_variants(experiment_id) takes a specific ID and returns [] for unknown ones, which can't distinguish "no experiments exist" from "this specific ID doesn't exist." Combined with the get_variant_experiment("v1") is None assertion already on the previous line, the test's intent is fully covered. No change made.

2. seeded_rng fixture assumes secrets.SystemRandom() called fresh ⚠️ Partially valid

I verified selection.py and confirmed it calls secrets.SystemRandom() inline at every use site (lines 72-73, 81, 110, 119, 135) — not cached. The fixture is NOT a no-op for the current implementation. The fragility concern is fair (if someone refactors to cache the RNG, the patch breaks), but the current code is verified to work with it. No change needed — the fixture works correctly.

3. IndexError for empty population ✅ Verified correct

I ran both strategies against an empty population and confirmed:

  • tournament: secrets.SystemRandom().choice(selected) on empty list → IndexError: Cannot choose from an empty sequence
  • roulette: same path → same IndexError

The IndexError is the actual runtime behavior, not an assumption. No change needed.

4. test_select_candidates_defaults_missing_score_to_zero only asserts first element ✅ Agree

The comment claimed order 0.8, 0.6, 0.0 but only checked the first. Fixed — added assertions for all three elements:

assert selected[1]["score"] == 0.6
assert selected[2].get("score", 0.0) == 0.0

5. Can't verify Controller.run_generation from the diff ❌ Reviewer limitation, not a code issue

The source files are in the repo. I verified against controller.py:

  • Line 42-43: test_path = "." then self.test_runner.run_tests(test_path) — confirms run_tests(".") is correct
  • evaluator.evaluate(test_results) — confirmed
  • select_candidates(eval_results) — confirmed
  • Test assertions match the implementation exactly

The tests are correct. The reviewer just wasn't shown these files in the diff.


Additional changes applied (from CodeRabbit inline comments):

  • Added pytestmark = pytest.mark.unit to all 4 new test files (consistent with existing convention in tests/unit/)
  • Strengthened test_num_selected_larger_than_population to verify padding uses population members with repeats
  • Strengthened test_roulette_with_negative_scores to verify selected individuals come from the population
  • Strengthened test_tournament_missing_eval_score to verify v1 is selected (higher score beats missing-score default)

- Remove unused  import in test_version_database_extended.py
- Pass explicit  in test_tournament_custom_fitness_key to
  decouple from the DEFAULT_ELITISM constant
@SHA888

SHA888 commented Jul 31, 2026

Copy link
Copy Markdown
Owner Author

Review feedback addressed (c61a096)

Fixed

  1. Dead from pathlib import Path import — Removed. Tests use tmp_path fixture directly; Path was never referenced.

  2. test_tournament_custom_fitness_key implicit elitism coupling — Added explicit elitism=1 to the select() call so the test no longer depends on the DEFAULT_ELITISM constant.

Verified correct — no change needed

  1. seeded_rng monkeypatch effectiveness — Verified against source. selection.py line 10 does import secrets (module-level) then calls secrets.SystemRandom() at runtime (lines 73, 81, 110, 115, 119, 135). The monkeypatch.setattr(secrets, "SystemRandom", lambda: rng) patches the module attribute which is looked up at call time — the patch is not a no-op. Tests using seeded_rng are deterministic.

  2. test_import_variants_preserves_lineage incomplete variant dicts — Verified: import_variants does self.variants[variant_id] = variant_data directly (line 291), no required-key validation. get_lineage() reads from self.lineage, not from the variant dict. The test works for its purpose. Note: expanding the dicts to include all keys triggered a pre-existing OSError in Path(json_data).exists() for long JSON strings — that's a separate bug in import_variants, not related to this PR.

  3. Hardcoded evaluator weights (0.7/0.2/0.1) and score_distribution single-bin assertion — These match the actual source defaults: evaluator.py lines 42-44 (pass_rate: 0.7, coverage: 0.2, quality: 0.1), version_database.py lines 209-210 (single-bin when min == max). The assertions are testing real behavior, not assumed constants.

@SHA888

SHA888 commented Jul 31, 2026

Copy link
Copy Markdown
Owner Author

Review feedback — verified against source

Addressed each point from the review against the actual evoseal/core/ source files. Summary: no code changes needed — all four points are either not applicable or already handled correctly.

1. seeded_rng fixture / SystemRandom instantiation — ✅ Not an issue

selection.py creates secrets.SystemRandom() inline at each call site (lines 73, 81, 110, 115, 119, 135) — never stored at class or import level. The monkeypatch replacing secrets.SystemRandom with lambda: rng works correctly.

Additionally, test_tournament_custom_fitness_key's assert selected[0]["id"] == "v4" tests the elite (elitism=1), which is a deterministic sort — not affected by the RNG at all.

2. test_import_variants_preserves_lineage missing keys — ✅ Not a bug

import_variants accesses experiment_id via variant_data.get("experiment_id") (line 277 of version_database.py) — safe .get(), not direct bracket indexing. Missing keys produce None, not KeyError. The test works correctly with partial dicts.

I investigated adding the missing keys for consistency, but this exposed a pre-existing quirk: import_variants calls Path(json_data).exists() without try/except, and adding fields pushed the JSON string past the 256-char filename limit, causing OSError. The shorter dicts are actually fine — the .get() pattern handles them safely.

3. db.experiment_variants == {}⚠️ Minor style point, not actionable as described

The reviewer calls experiment_variants "private/internal" — it's actually a public attribute (no underscore prefix, directly documented in __init__). And there is no public API method to check "are there any experiments at all?" — get_experiment_variants(id) requires an experiment ID. The existing assert db.get_variant_experiment("v1") is None on the line above already verifies the behavior. The attribute check adds a belt-and-suspenders confirmation that the internal state is consistent. Leaving as-is.

4. Caveat (implementation detail assumptions) — ✅ All verified correct

Each assertion the reviewer flagged matches the actual source:

  • Weight formula 0.7/0.2/0.1: Evaluator.__init__ defaults to {"pass_rate": 0.7, "coverage": 0.2, "quality": 0.1} — exact match.
  • IndexError on empty population: Both tournament_selection and roulette_wheel_selection exhaust pop, then hit secrets.SystemRandom().choice(selected) where selected is still empty → IndexError. Correct.
  • Single-bucket distribution on equal min/max: _calculate_score_distribution returns {f"{min_score:.2f}": len(scores)} when min_score == max_score — one key. Correct.

No code changes. All 81 tests pass.

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.

1 participant