查看测试代码test_330_compose_horizontal_framebuffer_viewport.py
import pytest
from emulator.rendering.background_viewport import (
NAMETABLE_PIXEL_HEIGHT,
NAMETABLE_PIXEL_WIDTH,
compose_horizontal_framebuffer_viewport,
)
from emulator.rendering.framebuffer import Framebuffer, RGBColor
LEFT_COLOR: RGBColor = (10, 20, 30)
RIGHT_COLOR: RGBColor = (40, 50, 60)
def solid_nametable_framebuffer(color: RGBColor) -> Framebuffer:
"""Create one rendered nametable whose source is easy to identify."""
return Framebuffer(
width=NAMETABLE_PIXEL_WIDTH,
height=NAMETABLE_PIXEL_HEIGHT,
pixels=[color] * (NAMETABLE_PIXEL_WIDTH * NAMETABLE_PIXEL_HEIGHT),
)
def coordinate_nametable_framebuffer(source_id: int) -> Framebuffer:
"""Encode source identity and coordinates into each RGB pixel."""
return Framebuffer(
width=NAMETABLE_PIXEL_WIDTH,
height=NAMETABLE_PIXEL_HEIGHT,
pixels=[
(source_id, x, y)
for y in range(NAMETABLE_PIXEL_HEIGHT)
for x in range(NAMETABLE_PIXEL_WIDTH)
],
)
def test_viewport_at_zero_uses_the_entire_left_nametable():
"""
Objective:
With no horizontal offset, the visible screen is the left logical nametable.
"""
left = solid_nametable_framebuffer(LEFT_COLOR)
right = solid_nametable_framebuffer(RIGHT_COLOR)
result = compose_horizontal_framebuffer_viewport(left, right, viewport_x=0)
assert result.width == NAMETABLE_PIXEL_WIDTH
assert result.height == NAMETABLE_PIXEL_HEIGHT
assert result.pixels == left.pixels
assert result is not left
def test_viewport_at_256_uses_the_entire_right_nametable():
"""
Objective:
Logical X 256 is the first pixel column of the right nametable.
"""
left = solid_nametable_framebuffer(LEFT_COLOR)
right = solid_nametable_framebuffer(RIGHT_COLOR)
result = compose_horizontal_framebuffer_viewport(left, right, viewport_x=256)
assert result.pixels == right.pixels
assert result is not right
def test_viewport_at_200_crosses_the_nametable_seam_on_every_row():
"""
Objective:
Compose 56 columns from the end of the left nametable and 200 columns from the
beginning of the right nametable without changing Y rows.
"""
left = coordinate_nametable_framebuffer(source_id=1)
right = coordinate_nametable_framebuffer(source_id=2)
result = compose_horizontal_framebuffer_viewport(left, right, viewport_x=200)
expected_pixels: list[RGBColor] = []
for y in range(NAMETABLE_PIXEL_HEIGHT):
row_start = y * NAMETABLE_PIXEL_WIDTH
row_end = row_start + NAMETABLE_PIXEL_WIDTH
expected_pixels.extend(left.pixels[row_start + 200:row_end])
expected_pixels.extend(right.pixels[row_start:row_start + 200])
assert result.pixels == expected_pixels
def test_viewport_wraps_from_right_nametable_back_to_left():
"""
Objective:
A viewport beginning at logical X 300 consumes 212 right-hand columns before
wrapping to 44 columns from the left nametable.
"""
left = coordinate_nametable_framebuffer(source_id=1)
right = coordinate_nametable_framebuffer(source_id=2)
result = compose_horizontal_framebuffer_viewport(left, right, viewport_x=300)
assert result.get_pixel(0, 0) == right.get_pixel(44, 0)
assert result.get_pixel(211, 0) == right.get_pixel(255, 0)
assert result.get_pixel(212, 0) == left.get_pixel(0, 0)
assert result.get_pixel(255, 239) == left.get_pixel(43, 239)
def test_composition_does_not_mutate_either_source_framebuffer():
"""
Objective:
Keep viewport composition pure so source nametables can be reused and failures
remain local to the returned framebuffer.
"""
left = coordinate_nametable_framebuffer(source_id=1)
right = coordinate_nametable_framebuffer(source_id=2)
left_before = list(left.pixels)
right_before = list(right.pixels)
result = compose_horizontal_framebuffer_viewport(left, right, viewport_x=200)
assert left.pixels == left_before
assert right.pixels == right_before
assert result is not left
assert result is not right
def test_rejects_left_framebuffer_with_wrong_dimensions():
"""
Objective:
Reject an invalid left source at the function boundary instead of failing during
pixel indexing.
"""
left = Framebuffer(width=255, height=240)
right = solid_nametable_framebuffer(RIGHT_COLOR)
with pytest.raises(ValueError, match="Left nametable framebuffer"):
compose_horizontal_framebuffer_viewport(left, right, viewport_x=0)
def test_rejects_right_framebuffer_with_wrong_dimensions():
"""
Objective:
Apply the same explicit dimension invariant to the right source.
"""
left = solid_nametable_framebuffer(LEFT_COLOR)
right = Framebuffer(width=256, height=239)
with pytest.raises(ValueError, match="Right nametable framebuffer"):
compose_horizontal_framebuffer_viewport(left, right, viewport_x=0)
测试函数 (7)
test_viewport_at_zero_uses_the_entire_left_nametabletest_viewport_at_256_uses_the_entire_right_nametabletest_viewport_at_200_crosses_the_nametable_seam_on_every_rowtest_viewport_wraps_from_right_nametable_back_to_lefttest_composition_does_not_mutate_either_source_framebuffertest_rejects_left_framebuffer_with_wrong_dimensionstest_rejects_right_framebuffer_with_wrong_dimensions