spriteforge/tests/test_incremental.py

85 lines
3.9 KiB
Python
Raw Permalink Normal View History

import json
from pathlib import Path
import shutil
from typer.testing import CliRunner
from spriteforge.backend_registry import register_backend_contract
from spriteforge.backends.base import Backend,BackendContext,RawAsset
from spriteforge.backends.registry import register_backend
from spriteforge.cache import input_hash
from spriteforge.cli import app
from spriteforge.manifest import AssetEntry,AssetSpec
runner=CliRunner()
def manifest_file(tmp_path):
path=tmp_path/"assets.yaml"
path.write_text("""\
a_floor:
backend: procedural
generator: floor_tile
params: {width: 24, height: 12, color: '#605040'}
b_decal:
backend: procedural
generator: decal
params: {width: 20, height: 10, color: '#904030'}
""")
return path
def build(path,output,cache,*extra):
return runner.invoke(app,["build",str(path),"--output",str(output),"--cache-dir",str(cache),*extra])
def test_second_build_hits_artifact_cache(tmp_path):
path=manifest_file(tmp_path);output=tmp_path/"out";cache=tmp_path/"cache"
first=build(path,output,cache,"--jobs","2")
assert first.exit_code==0 and "generated=2" in first.stdout
second=build(path,output,cache,"--jobs","2")
assert second.exit_code==0 and "artifact=2" in second.stdout and "generated=0" in second.stdout
def test_raw_cache_survives_artifact_eviction(tmp_path):
path=manifest_file(tmp_path);output=tmp_path/"out";cache=tmp_path/"cache"
assert build(path,output,cache).exit_code==0
shutil.rmtree(cache/"artifacts")
repeated=build(path,output,cache)
assert repeated.exit_code==0 and "raw=2" in repeated.stdout and "artifact=0" in repeated.stdout
def test_metadata_tags_do_not_invalidate_render_or_artifact(tmp_path):
path=manifest_file(tmp_path);output=tmp_path/"out";cache=tmp_path/"cache"
assert build(path,output,cache).exit_code==0
path.write_text(path.read_text().replace("a_floor:\n", "a_floor:\n tags: [environment]\n"))
repeated=build(path,output,cache)
assert repeated.exit_code==0 and "generated=0" in repeated.stdout and "artifact=2" in repeated.stdout
index=json.loads((output/"index.json").read_text())
assert next(item for item in index["assets"] if item["id"]=="a_floor")["tags"]==["environment"]
def test_one_asset_change_preserves_other_and_palette(tmp_path):
path=manifest_file(tmp_path);output=tmp_path/"out";cache=tmp_path/"cache"
assert build(path,output,cache).exit_code==0
old=json.loads((output/"index.json").read_text());old_items={x["id"]:x for x in old["assets"]};palette=old["palette"]["hash"]
path.write_text(path.read_text().replace("#605040","#405870"))
changed=build(path,output,cache,"--asset","a_floor")
assert changed.exit_code==0 and "generated=1" in changed.stdout
new=json.loads((output/"index.json").read_text());new_items={x["id"]:x for x in new["assets"]}
assert new_items["b_decal"]==old_items["b_decal"]
assert new_items["a_floor"]["hash"]!=old_items["a_floor"]["hash"]
assert new["palette"]["hash"]==palette
class DependencyBackend(Backend):
name="dependency_test";version="1"
def dependencies(self,spec,context):return (Path(spec.params["path"]),)
def generate(self,spec,context)->RawAsset:raise AssertionError("not used")
def test_dependency_bytes_invalidate_input_hash(tmp_path):
register_backend_contract("dependency_test",lambda spec:None,replace=True)
register_backend("dependency_test",DependencyBackend,replace=True)
source=tmp_path/"source.bin";source.write_bytes(b"one")
spec=AssetSpec(backend="dependency_test",params={"path":str(source)})
entry=AssetEntry("thing",spec,tmp_path/"assets.yaml",1)
before=input_hash(entry)[0];source.write_bytes(b"two");after=input_hash(entry)[0]
assert before!=after
def test_filtered_palette_rebuild_is_rejected(tmp_path):
path=manifest_file(tmp_path)
result=build(path,tmp_path/"out",tmp_path/"cache","--asset","a_floor","--rebuild-palette")
assert result.exit_code==2 and "unfiltered full build" in result.stderr