104 lines
3.6 KiB
Python
104 lines
3.6 KiB
Python
"""
|
|
Tests for ResultCollector — ordered reassembly, out-of-order buffering, duplicates.
|
|
|
|
Demonstrates: TDD (Interview Topic 8) — testing algorithms (heapq reassembly).
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from core.chunker.collector import ResultCollector
|
|
from core.chunker.exceptions import ReassemblyError
|
|
|
|
|
|
class TestResultCollector:
|
|
def test_in_order_emission(self, make_result):
|
|
"""Results arriving in order are emitted immediately."""
|
|
collector = ResultCollector(total_chunks=3)
|
|
|
|
emitted = collector.add(make_result(0))
|
|
assert len(emitted) == 1
|
|
assert emitted[0].sequence == 0
|
|
|
|
emitted = collector.add(make_result(1))
|
|
assert len(emitted) == 1
|
|
|
|
emitted = collector.add(make_result(2))
|
|
assert len(emitted) == 1
|
|
|
|
assert collector.is_complete
|
|
|
|
def test_out_of_order_buffering(self, make_result):
|
|
"""Out-of-order results are buffered until gaps fill."""
|
|
collector = ResultCollector(total_chunks=3)
|
|
|
|
# Arrive: 2, 0, 1
|
|
emitted = collector.add(make_result(2))
|
|
assert len(emitted) == 0
|
|
assert collector.buffered_count == 1
|
|
|
|
emitted = collector.add(make_result(0))
|
|
assert len(emitted) == 1 # Only 0 emitted, 1 still missing
|
|
|
|
emitted = collector.add(make_result(1))
|
|
assert len(emitted) == 2 # 1 and 2 now emittable
|
|
assert collector.is_complete
|
|
|
|
def test_reverse_order(self, make_result):
|
|
"""All results arrive in reverse — only last add emits everything."""
|
|
collector = ResultCollector(total_chunks=4)
|
|
|
|
for seq in [3, 2, 1]:
|
|
emitted = collector.add(make_result(seq))
|
|
assert len(emitted) == 0
|
|
|
|
emitted = collector.add(make_result(0))
|
|
assert len(emitted) == 4
|
|
assert collector.is_complete
|
|
|
|
def test_duplicate_raises(self, make_result):
|
|
"""Duplicate sequence number raises ReassemblyError."""
|
|
collector = ResultCollector(total_chunks=3)
|
|
collector.add(make_result(0))
|
|
|
|
with pytest.raises(ReassemblyError, match="Duplicate"):
|
|
collector.add(make_result(0))
|
|
|
|
def test_emitted_count(self, make_result):
|
|
"""emitted_count tracks correctly."""
|
|
collector = ResultCollector(total_chunks=3)
|
|
assert collector.emitted_count == 0
|
|
|
|
collector.add(make_result(0))
|
|
assert collector.emitted_count == 1
|
|
|
|
collector.add(make_result(2)) # buffered
|
|
assert collector.emitted_count == 1
|
|
|
|
collector.add(make_result(1)) # releases 1 and 2
|
|
assert collector.emitted_count == 3
|
|
|
|
def test_get_ordered_results(self, make_result):
|
|
"""get_ordered_results returns all emitted results in order."""
|
|
collector = ResultCollector(total_chunks=3)
|
|
collector.add(make_result(2))
|
|
collector.add(make_result(0))
|
|
collector.add(make_result(1))
|
|
|
|
ordered = collector.get_ordered_results()
|
|
assert [r.sequence for r in ordered] == [0, 1, 2]
|
|
|
|
def test_avg_processing_time(self, make_result):
|
|
"""Average processing time from sliding window."""
|
|
collector = ResultCollector(total_chunks=2)
|
|
collector.add(make_result(0, processing_time=0.1))
|
|
collector.add(make_result(1, processing_time=0.3))
|
|
|
|
assert abs(collector.avg_processing_time - 0.2) < 0.001
|
|
|
|
def test_not_complete_when_partial(self, make_result):
|
|
"""is_complete is False until all chunks emitted."""
|
|
collector = ResultCollector(total_chunks=3)
|
|
collector.add(make_result(0))
|
|
collector.add(make_result(1))
|
|
assert not collector.is_complete
|