@@ -0,0 +1,64 @@
|
||||
"""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})
|
||||
Reference in New Issue
Block a user