65 lines
3.2 KiB
Python
65 lines
3.2 KiB
Python
"""Dependency direction, cache coverage and explicit composition inputs."""
|
|
import ast
|
|
import json
|
|
import pathlib
|
|
import subprocess
|
|
import sys
|
|
import unittest
|
|
import unittest.mock
|
|
|
|
import generator.cache
|
|
import generator.composition
|
|
import generator.role_cache
|
|
import generator.source
|
|
import generator.vocabulary
|
|
import tests.solo_fixtures
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
|
|
class GeneratorBoundaries(unittest.TestCase):
|
|
def test_generator_imports_are_acyclic(self):
|
|
files = {'.'.join(path.relative_to(ROOT).with_suffix('').parts): path for path in (ROOT / 'generator').rglob('*.py')}
|
|
graph = {name: set(generator.cache.imports(path.read_bytes())) & files.keys() for name, path in files.items()}
|
|
visited, active = set(), []
|
|
def visit(name):
|
|
self.assertNotIn(name, active, ' -> '.join([*active, name]))
|
|
if name in visited:
|
|
return
|
|
active.append(name)
|
|
for dependency in graph[name]:
|
|
visit(dependency)
|
|
active.pop()
|
|
visited.add(name)
|
|
for name in graph:
|
|
visit(name)
|
|
def test_composition_import_does_not_load_rendering_or_publication(self):
|
|
code = "import generator.composition, sys, json; print(json.dumps(sorted(sys.modules)))"
|
|
result = subprocess.run([sys.executable, '-c', code], cwd=ROOT, capture_output=True, text=True, check=True)
|
|
forbidden = ('PIL', 'fontTools', 'generator.engraving', 'generator.production', 'generator.score_io')
|
|
self.assertFalse([name for name in json.loads(result.stdout) if name.startswith(forbidden)])
|
|
def test_all_previously_missing_role_dependencies_change_the_fingerprint(self):
|
|
baseline = generator.role_cache.code_key()
|
|
read = pathlib.Path.read_bytes
|
|
for name in ('solo/policy.py', 'source.py', 'accompaniment.py'):
|
|
changed = ROOT / 'generator' / name
|
|
with self.subTest(dependency=name):
|
|
def contents(path):
|
|
return read(path) + (b'\n# changed implementation\n' if path == changed else b'')
|
|
with unittest.mock.patch.object(pathlib.Path, 'read_bytes', contents):
|
|
self.assertNotEqual(generator.role_cache.code_key(), baseline)
|
|
def test_context_owns_immutable_resources_and_survives_caller_mutation(self):
|
|
resources = generator.vocabulary.load()
|
|
context = generator.vocabulary.CompositionContext.create(resources, 'electric_accompaniment')
|
|
expected = context.role()['center']
|
|
resources['roles']['roles']['electric_accompaniment']['center'] = 999
|
|
self.assertEqual(context.role()['center'], expected)
|
|
with self.assertRaises(TypeError):
|
|
context.data('roles')['roles']['electric_accompaniment']['center'] = 999
|
|
self.assertNotEqual(generator.vocabulary.load()['roles']['roles']['electric_accompaniment']['center'], 999)
|
|
def test_role_generation_does_not_consult_ambient_policy(self):
|
|
source = generator.source.SourceAnalysis.read(tests.solo_fixtures.song(count=1, chords=('CM7',)))
|
|
with unittest.mock.patch('generator.vocabulary.current', side_effect=AssertionError('implicit policy dependency')):
|
|
for role in generator.composition_types.ROLES:
|
|
with self.subTest(role=role):
|
|
generator.composition.generate_role(source, role, ROOT / 'data', {'predict': True})
|