Files
security-game/tools/tests/test_scene_concepts.py
T
zwitschi e4e1566e55
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Skipped
Organize tools and move Python tests into tools/tests
2026-11-03 19:45:00 +01:00

305 lines
14 KiB
Python

import json
import re
import tempfile
import unittest
from unittest import mock
from pathlib import Path
from PIL import Image
import scene_concepts as sc
class CatalogueTests(unittest.TestCase):
def test_groups_and_prompts(self):
groups = {g for g, _ in sc.CATALOGUE.values()}
self.assertEqual(groups, {"scene", "backdrop", "texture"})
for name in sc.CATALOGUE:
prompt = sc.compose_prompt(name)
self.assertGreater(len(prompt), 80, name)
for day in ("skyline_day", "skyline_dusk", "skyline_night"):
self.assertEqual(sc.CATALOGUE[day][0], "backdrop")
def test_shots_point_at_scene_concepts(self):
for shot, concept in sc.SHOTS.items():
self.assertEqual(sc.CATALOGUE[concept][0], "scene", shot)
def test_paths_stay_in_their_folders(self):
self.assertTrue(sc.raw_path("hollywood_boulevard_entrance_day").is_relative_to(sc.CONCEPT_DIR))
self.assertTrue(sc.raw_path("hollywood_boulevard_entrance_day").is_relative_to(
sc.CONCEPT_DIR / "hollywood_boulevard"))
self.assertTrue(sc.raw_path("joke_shop_entrance_day").is_relative_to(
sc.CONCEPT_DIR / "joke_shop"))
self.assertTrue(sc.raw_path("skyline_day").is_relative_to(sc.RAW_DIR))
self.assertTrue(sc.raw_path("asphalt").is_relative_to(sc.RAW_DIR))
class ReferenceGateTests(unittest.TestCase):
ROOT = Path(sc.__file__).resolve().parent.parent
def test_refs_photo_listed_in_sidecar_passes_with_provenance(self):
result = sc._reference_source(
self.ROOT / "docs/screens/refs/hollywood/"
"el-capitan-facade-day-wikimedia-2009-10-27.jpg")
self.assertTrue(result["checked"])
self.assertEqual(result["licence"], "CC BY 2.0")
self.assertIn("commons.wikimedia.org", result["page"])
def test_refs_photo_missing_from_sidecar_is_refused(self):
with tempfile.TemporaryDirectory(dir=self.ROOT / "docs/screens/refs") as tmp:
path = Path(tmp) / "unlisted.jpg"
path.write_bytes(b"x")
with self.assertRaises(SystemExit):
sc._reference_source(path)
def test_outside_photo_requires_the_explicit_override(self):
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "photo.jpg"
path.write_bytes(b"x")
with self.assertRaises(SystemExit):
sc._reference_source(path)
result = sc._reference_source(path, allow_unchecked=True)
self.assertFalse(result["checked"])
self.assertEqual(result["licence"], "unchecked")
def test_missing_file_is_refused(self):
with self.assertRaises(SystemExit):
sc._reference_source(self.ROOT / "docs/screens/refs/nope.jpg")
def test_generate_one_writes_reference_provenance(self):
reference = {"path": self.ROOT / "docs/screens/refs/hollywood/"
"el-capitan-facade-day-wikimedia-2009-10-27.jpg",
"licence": "CC BY 2.0", "page": "https://commons/x",
"checked": True}
class FakeImage:
def convert(self, mode):
return Image.new("RGB", (1600, 900))
with tempfile.TemporaryDirectory() as tmp:
out = Path(tmp) / "hollywood_boulevard_entrance_day.png"
with mock.patch.object(sc.generate_textures, "generate_image",
return_value=FakeImage()) as mocked, \
mock.patch.object(sc, "next_free_path", return_value=out), \
mock.patch.object(sc, "_rel", side_effect=str):
sc.generate_one("hollywood_boulevard_entrance_day", "m", 1536,
reference=reference)
call = mocked.call_args
self.assertIn("keep the building", call.args[0].lower())
self.assertEqual(call.kwargs["reference_path"], reference["path"])
provenance = json.loads(out.with_suffix(".refgen.json").read_text())
self.assertEqual(provenance["reference"]["licence"], "CC BY 2.0")
self.assertTrue(provenance["reference"]["licence_checked"])
class NoOverwriteTests(unittest.TestCase):
def test_next_free_path_never_returns_an_existing_file(self):
with tempfile.TemporaryDirectory() as tmp:
base = Path(tmp) / "a.png"
self.assertEqual(sc.next_free_path(base), base)
base.write_bytes(b"x")
second = sc.next_free_path(base)
self.assertEqual(second.name, "a-v2.png")
second.write_bytes(b"x")
self.assertEqual(sc.next_free_path(base).name, "a-v3.png")
class ProcessingTests(unittest.TestCase):
def _skyline(self, opaque=False):
image = Image.new("RGBA", (300, 200), (120, 170, 230, 255 if opaque else 0))
for x in range(300):
top = 80 + (x * 7) % 50
for y in range(top, 200):
image.putpixel((x, y), (40 + x % 30, 40, 50, 255))
return image
def test_key_sky_makes_an_opaque_sky_transparent(self):
keyed = sc.key_sky(self._skyline(opaque=True))
self.assertEqual(keyed.getpixel((5, 5))[3], 0)
self.assertEqual(keyed.getpixel((5, 199))[3], 255)
def test_band_keeps_tower_tops(self):
band = sc._band(self._skyline())
self.assertAlmostEqual(band.size[0] / band.size[1], 2.0, delta=0.6)
alpha = band.getchannel("A")
self.assertEqual(alpha.getpixel((0, band.size[1] - 1)), 255)
def test_processed_backdrop_audits_clean(self):
with tempfile.TemporaryDirectory() as tmp:
src = Path(tmp) / "raw.png"
self._skyline().save(src)
out = sc.process_backdrop(src, Path(tmp) / "skyline.png")
result = sc.audit_backdrop(out)
self.assertEqual(result["errors"], [])
def test_texture_processing_writes_tile_and_normal(self):
with tempfile.TemporaryDirectory() as tmp:
src = Path(tmp) / "raw.png"
Image.effect_noise((128, 128), 40).convert("RGB").save(src)
out = sc.process_texture(src, Path(tmp) / "tile.png", size=64)
self.assertEqual(Image.open(out).size, (64, 64))
normal = Image.open(out.with_name("tile_normal.png"))
self.assertEqual(normal.size, (64, 64))
r, g, b = normal.getpixel((10, 10))
self.assertGreater(b, 128) # normals point out of the surface
def _striped(period=64, size=256):
image = Image.new("RGB", (size, size), (200, 190, 170))
for x in range(size):
if (x // (period // 2)) % 2 == 0:
for y in range(size):
image.putpixel((x, y), (150, 40, 45))
return image
class StripeTests(unittest.TestCase):
def test_period_found_and_tile_wraps(self):
tile = _striped()
self.assertEqual(sc.stripe_period(tile), 64)
aligned = sc.align_stripes(tile)
self.assertEqual(aligned.size, tile.size)
# The wrap step along x matches a normal neighbouring-column step.
profile = sc._column_profile(aligned)
wrap = sc._step(profile[-1], profile[0])
hardest = max(sc._step(profile[x], profile[x + 1]) for x in range(aligned.size[0] - 1))
self.assertLessEqual(wrap, hardest * 1.25 + 0.5)
def test_flat_texture_has_no_period(self):
self.assertEqual(sc.stripe_period(Image.new("RGB", (128, 128), (90, 90, 90))), 0)
flat = Image.new("RGB", (64, 64), (128, 128, 128))
self.assertEqual(sc.align_stripes(flat).size, flat.size)
def test_striped_audit_accepts_a_boundary_on_the_edge(self):
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "awning_canvas.png"
image = _striped(period=64, size=sc.TILE)
image.save(path)
result = sc.audit_striped_tile(path)
self.assertEqual(result["errors"], [])
class PromoteTests(unittest.TestCase):
def test_promote_copies_and_never_overwrites(self):
with tempfile.TemporaryDirectory() as tmp:
picks = Path(tmp) / "picks"
global_dir = sc.PICKS_DIR
sc.PICKS_DIR = picks
try:
sc.READY_DIR = Path(tmp)
Image.new("RGB", (16, 16), (10, 20, 30)).save(Path(tmp) / "src.png")
Image.new("RGB", (16, 16), (128, 128, 255)).save(Path(tmp) / "src_normal.png")
target = sc.promote("pick", "src")
self.assertTrue(target.is_file())
self.assertTrue((picks / "pick_normal.png").is_file())
Image.new("RGB", (16, 16), (0, 0, 0)).save(Path(tmp) / "src2.png")
again = sc.promote("pick", "src2")
self.assertEqual(again, target)
self.assertNotEqual(Image.open(target).getpixel((0, 0)), (0, 0, 0))
finally:
sc.PICKS_DIR = global_dir
sc.READY_DIR = Path(tmp).parent # restored by the loader below
def test_picks_exist_in_catalogue(self):
for name, source in sc.PICKS.items():
self.assertTrue(source.startswith(name), f"{name} <- {source}")
class RealismTests(unittest.TestCase):
def test_metrics_and_gap(self):
with tempfile.TemporaryDirectory() as tmp:
flat = Path(tmp) / "flat.png"
busy = Path(tmp) / "busy.png"
Image.new("RGB", (320, 180), (128, 128, 128)).save(flat)
Image.effect_noise((320, 180), 60).convert("RGB").save(busy)
m_flat, m_busy = sc.image_metrics(flat), sc.image_metrics(busy)
self.assertLess(m_flat["edge_density"], m_busy["edge_density"])
self.assertEqual(sc.metric_gap(m_busy, m_busy), 0.0)
def test_parse_realism_response_clamps_and_limits(self):
payload = {"choices": [{"message": {"content":
'{"scores": {"overall": 14, "lighting": "3"}, "fixes": ["a","b","c","d","e","f"]}'}}]}
verdict = sc.parse_realism_response(payload)
self.assertEqual(verdict["scores"], {"overall": 10.0, "lighting": 3.0})
self.assertEqual(len(verdict["fixes"]), 5)
self.assertEqual(sc.parse_realism_response({})["scores"], {})
def test_summary_averages_per_set(self):
report = {"sets": {"a": {
"x": {"metric_gap": 0.2, "judge": {"scores": {"overall": 2.0}}},
"y": {"metric_gap": 0.4, "judge": {"scores": {"overall": 4.0}}}}}}
summary = sc.summarize(report)
self.assertEqual(summary["a"]["overall"], 3.0)
self.assertAlmostEqual(summary["a"]["metric_gap"], 0.3)
self.assertIn("Mean scores", sc.render_markdown(
{"run": "r", "judge_model": None, "sets": report["sets"] | {}} | {
"sets": {"a": {k: dict(v, target="t.png", metrics={
"edge_density": 1, "fine_detail": 1, "contrast": 1})
for k, v in report["sets"]["a"].items()}}}))
def test_request_carries_both_images(self):
with tempfile.TemporaryDirectory() as tmp:
a = Path(tmp) / "a.png"
Image.new("RGB", (8, 8)).save(a)
body = sc.build_realism_request("m", a, a)
parts = body["messages"][0]["content"]
self.assertEqual([p["type"] for p in parts], ["text", "image_url", "image_url"])
if __name__ == "__main__":
unittest.main()
class AnchorTests(unittest.TestCase):
ROOT = Path(sc.__file__).resolve().parent.parent
def test_treatment_prompt_carries_context_suffix_and_clause(self):
prompt = sc._anchor_treatment_prompt("A 1920s masonic facade.", "night")
self.assertTrue(prompt.startswith("A 1920s masonic facade."))
self.assertIn("keep the building", prompt.lower())
self.assertIn("Restage the scene:", prompt)
self.assertIn("practical lights", prompt)
def test_unknown_treatment_names_are_rejected(self):
with self.assertRaises(SystemExit):
sc._anchor_treatment_prompt("c", "underwater")
def test_name_derivation_strips_the_capture_date(self):
reference = {"path": self.ROOT / "x" / "some-facade-2009-10-27",
"licence": "CC0", "page": "", "checked": True}
# exercised through cmd_anchor's stem logic via a tiny local copy
import re as _re
stem = _re.sub(r"-[0-9]{4}-[0-9]{2}-[0-9]{2}$", "",
"el-capitan-facade-day-wikimedia-2009-10-27")
self.assertEqual(stem, "el-capitan-facade-day-wikimedia")
def test_cmd_anchor_generates_each_treatment_with_provenance(self):
reference = {"path": self.ROOT / "docs/screens/refs/hollywood/"
"el-capitan-facade-day-wikimedia-2009-10-27.jpg",
"licence": "CC BY 2.0", "page": "https://commons/x",
"checked": True}
class FakeImage:
def convert(self, mode):
return Image.new("RGB", (1600, 900))
with tempfile.TemporaryDirectory() as tmp:
out_dir = Path(tmp) / "frames"
with mock.patch.object(sc, "_reference_source", return_value=reference), \
mock.patch.object(sc.generate_textures, "generate_image",
return_value=FakeImage()) as mocked, \
mock.patch.object(sc, "next_free_path",
side_effect=lambda p: p), \
mock.patch.object(sc, "_rel", side_effect=str):
args = mock.Mock(reference="r", reference_unchecked=False,
context="A facade.", treatments="day,night",
out_dir=str(out_dir), model="m",
request_size=1536)
args.name = "" # `name` is reserved on Mock; set it explicitly
self.assertEqual(sc.cmd_anchor(args), 0)
stems = [c.args[0] for c in mocked.call_args_list]
self.assertEqual(len(stems), 2)
for treatment, call in zip(("day", "night"), mocked.call_args_list):
self.assertIn("Restage the scene:", call.args[0])
self.assertEqual(call.kwargs["reference_path"],
reference["path"])
self.assertTrue((out_dir /
f"el_capitan_facade_day_wikimedia_{treatment}.png")
.exists())