Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ bdpl/
│ ├── test_ig_stream.py # IG stream parser tests (ICS fixture)
│ ├── test_ordering.py # Episode ordering unit tests
│ ├── test_disc1_scan.py # disc1 integration tests
│ ├── test_disc2_scan.py # disc2 chapter-splitting tests
│ ├── test_disc14_scan.py # disc14 chapter-splitting tests
│ ├── test_disc3_scan.py # disc3 integration tests
│ ├── test_disc4_scan.py # disc4 single-main-title + archive tests
│ ├── test_disc5_scan.py # disc5 visible/hidden specials tests
Expand Down Expand Up @@ -122,7 +122,7 @@ bdpl archive /path/to/BDMV --out ./DigitalArchive
pytest tests/ -v
```

Tests use bundled fixture data from `tests/fixtures/disc1/` and `tests/fixtures/disc2/` by default. Set `BDPL_TEST_BDMV` to override with a real BDMV directory.
Tests use bundled fixture data from `tests/fixtures/disc1/` and `tests/fixtures/disc14/` by default. Set `BDPL_TEST_BDMV` to override with a real BDMV directory.

```bash
# Run all tests (unit tests always run; integration tests need a BDMV)
Expand Down
2 changes: 1 addition & 1 deletion PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ Files implemented:
- `tests/` — 131 tests (reader, mpls, clpi, index, movieobject, ig_stream, ordering, scan, disc-specific integration, matrix, fixture integrity, visibility heuristics, visible-only export, CLI, digital archive)
- `tests/builders.py` — Shared test-data builders for model objects
- `tests/fixtures/disc1/` — Bundled MPLS/CLPI/index/MovieObject from multi-episode disc
- `tests/fixtures/disc2/` — Bundled metadata + ICS fixture from single-m2ts chapter-split disc
- `tests/fixtures/disc14/` — Bundled metadata + ICS fixture from single-m2ts chapter-split disc
- `tests/fixtures/disc3/` — Bundled metadata fixture with 4 inferred episodes
- `tests/fixtures/disc4/` — Bundled metadata fixture for single 44:03 main title + digital archive menu gallery
- `tests/fixtures/disc5/` — Bundled metadata fixture for visible/hidden specials behavior
Expand Down
12 changes: 6 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ def disc1_path() -> Path:


@pytest.fixture(scope="session")
def disc2_path() -> Path:
"""Return path to bundled disc2 fixture."""
return _fixture_path("disc2")
def disc14_path() -> Path:
"""Return path to bundled disc14 fixture."""
return _fixture_path("disc14")


@pytest.fixture(scope="session")
Expand Down Expand Up @@ -95,9 +95,9 @@ def disc1_analysis(disc1_path):


@pytest.fixture(scope="session")
def disc2_analysis(disc2_path):
"""Run and cache full analysis for bundled disc2 fixture."""
return _analyze_fixture(disc2_path)
def disc14_analysis(disc14_path):
"""Run and cache full analysis for bundled disc14 fixture."""
return _analyze_fixture(disc14_path)


@pytest.fixture(scope="session")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
xmlns:di="urn:BDA:bdmv;discinfo">
<di:discinfo>
<di:title>
<di:name>TEST DISC 2</di:name>
<di:name>TEST DISC 14</di:name>
</di:title>
</di:discinfo>
</disclib>
File renamed without changes.
File renamed without changes.
28 changes: 14 additions & 14 deletions tests/test_disc2_scan.py → tests/test_disc14_scan.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Tests for chapter-based episode splitting (disc2 fixture)."""
"""Tests for chapter-based episode splitting (disc14 fixture)."""

import pytest

Expand All @@ -8,34 +8,34 @@


class TestChapterSplitting:
def test_finds_four_episodes(self, disc2_analysis: DiscAnalysis) -> None:
da = disc2_analysis
def test_finds_four_episodes(self, disc14_analysis: DiscAnalysis) -> None:
da = disc14_analysis
assert len(da.episodes) == 4

def test_episode_durations_reasonable(self, disc2_analysis: DiscAnalysis) -> None:
da = disc2_analysis
def test_episode_durations_reasonable(self, disc14_analysis: DiscAnalysis) -> None:
da = disc14_analysis
for ep in da.episodes:
dur_min = ep.duration_ms / 60000
assert 15 < dur_min < 35, f"Ep {ep.episode} duration {dur_min:.1f}min out of range"

def test_episodes_are_ordered(self, disc2_analysis: DiscAnalysis) -> None:
da = disc2_analysis
def test_episodes_are_ordered(self, disc14_analysis: DiscAnalysis) -> None:
da = disc14_analysis
nums = [ep.episode for ep in da.episodes]
assert nums == [1, 2, 3, 4]

def test_episode_segments_dont_overlap(self, disc2_analysis: DiscAnalysis) -> None:
da = disc2_analysis
def test_episode_segments_dont_overlap(self, disc14_analysis: DiscAnalysis) -> None:
da = disc14_analysis
for i in range(len(da.episodes) - 1):
seg_a = da.episodes[i].segments[0]
seg_b = da.episodes[i + 1].segments[0]
assert seg_a.out_ms <= seg_b.in_ms, (
f"Ep {i + 1} end {seg_a.out_ms} overlaps Ep {i + 2} start {seg_b.in_ms}"
)

def test_no_special_features(self, disc2_analysis: DiscAnalysis) -> None:
"""Disc2 is a chapter-split disc with no extras."""
assert len(disc2_analysis.special_features) == 0
def test_no_special_features(self, disc14_analysis: DiscAnalysis) -> None:
"""Disc14 is a chapter-split disc with no extras."""
assert len(disc14_analysis.special_features) == 0

def test_disc_title(self, disc2_analysis: DiscAnalysis) -> None:
def test_disc_title(self, disc14_analysis: DiscAnalysis) -> None:
"""Disc title should be extracted from META/DL/bdmt_eng.xml."""
assert disc2_analysis.disc_title == "TEST DISC 2"
assert disc14_analysis.disc_title == "TEST DISC 14"
12 changes: 6 additions & 6 deletions tests/test_disc_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
("analysis_fixture", "expected_episode_count", "expected_episode_playlists"),
[
("disc1_analysis", 3, ["00002.mpls", "00002.mpls", "00002.mpls"]),
("disc2_analysis", 4, ["00002.mpls", "00002.mpls", "00002.mpls", "00002.mpls"]),
("disc14_analysis", 4, ["00002.mpls", "00002.mpls", "00002.mpls", "00002.mpls"]),
("disc3_analysis", 4, ["00002.mpls", "00002.mpls", "00002.mpls", "00002.mpls"]),
("disc4_analysis", 1, ["00002.mpls"]),
("disc5_analysis", 1, ["00001.mpls"]),
Expand Down Expand Up @@ -44,7 +44,7 @@ def test_disc_episode_expectation_matrix(
("analysis_fixture", "expected_total", "expected_visible"),
[ # (analysis_fixture, expected_total, expected_visible)
("disc1_analysis", 9, 9), # 7 title-hint + 2 chapter-split
("disc2_analysis", 0, 0), # chapter-split disc with no extras
("disc14_analysis", 0, 0), # chapter-split disc with no extras
("disc3_analysis", 0, 0), # chapter-split disc with no extras
("disc5_analysis", 14, 11), # 14 IG-derived, 11 visible content buttons
("disc6_analysis", 3, 3), # 3 title-hint specials
Expand Down Expand Up @@ -77,7 +77,7 @@ def test_disc_special_visibility_expectation_matrix(
"analysis_fixture",
[
"disc1_analysis",
"disc2_analysis",
"disc14_analysis",
"disc3_analysis",
"disc4_analysis",
"disc5_analysis",
Expand Down Expand Up @@ -115,7 +115,7 @@ def test_disc_episode_segment_boundaries_matrix(
"analysis_fixture",
[
"disc1_analysis",
"disc2_analysis",
"disc14_analysis",
"disc3_analysis",
"disc4_analysis",
"disc5_analysis",
Expand Down Expand Up @@ -160,7 +160,7 @@ def test_disc_special_boundary_semantics_matrix(
("analysis_fixture", "expected_chapter_split_specials"),
[ # (analysis_fixture, expected_chapter_split_specials)
("disc1_analysis", 2), # PlayPL_PM marks → 2 chapter-split entries
("disc2_analysis", 0),
("disc14_analysis", 0),
("disc3_analysis", 0),
("disc4_analysis", 0),
("disc5_analysis", 0),
Expand Down Expand Up @@ -191,7 +191,7 @@ def test_disc_special_chapter_split_expectation_matrix(
@pytest.mark.parametrize(
("analysis_fixture", "expected_title"),
[
("disc2_analysis", "TEST DISC 2"),
("disc14_analysis", "TEST DISC 14"),
("disc6_analysis", "TEST DISC 6"),
("disc7_analysis", "TEST DISC 7 VOL 2"),
("disc8_analysis", "TEST DISC 8"),
Expand Down
12 changes: 6 additions & 6 deletions tests/test_ig_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
parse_ics,
)

_FIXTURE_DIR: Path = Path(__file__).parent / "fixtures" / "disc2"
_FIXTURE_DIR: Path = Path(__file__).parent / "fixtures" / "disc14"
_ICS_FILE: Path = _FIXTURE_DIR / "ics_menu.bin"


@pytest.fixture()
def ics() -> InteractiveComposition:
"""Parse the disc2 ICS fixture."""
"""Parse the disc14 ICS fixture."""
data: bytes = _ICS_FILE.read_bytes()
return parse_ics(data)

Expand All @@ -35,7 +35,7 @@ def test_ics_dimensions(ics: InteractiveComposition) -> None:


def test_ics_page_count(ics: InteractiveComposition) -> None:
"""Disc2 menu has 4 pages."""
"""Disc14 menu has 4 pages."""
assert len(ics.pages) == 4


Expand Down Expand Up @@ -64,14 +64,14 @@ def test_extract_hints_returns_actions(ics: InteractiveComposition) -> None:


def test_hints_contain_register_sets(ics: InteractiveComposition) -> None:
"""Disc2 episode buttons should SET GPR registers."""
"""Disc14 episode buttons should SET GPR registers."""
hints: list[IGMenuHint] = extract_menu_hints(ics)
reg_hints: list[IGMenuHint] = [h for h in hints if h.register_sets]
assert len(reg_hints) > 0, "Expected some register-setting buttons"


def test_hints_episode_register_pattern(ics: InteractiveComposition) -> None:
"""Disc2 episode selection buttons SET reg6 to episode indices 0-5."""
"""Disc14 episode selection buttons SET reg6 to episode indices 0-5."""
hints: list[IGMenuHint] = extract_menu_hints(ics)
reg6_values: list[int] = sorted(set(h.register_sets[6] for h in hints if 6 in h.register_sets))
# reg6 values 0-5 map to episode/segment selection
Expand All @@ -80,7 +80,7 @@ def test_hints_episode_register_pattern(ics: InteractiveComposition) -> None:


def test_episode_chapter_pattern(ics: InteractiveComposition) -> None:
"""Disc2 has buttons that SET reg2 to chapter-mark multiples of 5.
"""Disc14 has buttons that SET reg2 to chapter-mark multiples of 5.

This confirms the 5-chapters-per-episode pattern (marks 0, 5, 10, 15).
"""
Expand Down