Fret Range & Cleanup
ci / checks (push) Failing after 3h14m51s

This commit is contained in:
khodowany
2026-09-19 22:29:54 -05:00
parent 85cab9d6f6
commit c07586e06a
274 changed files with 362 additions and 164309 deletions
+39 -52
View File
@@ -4,78 +4,65 @@ import tempfile
import unittest
from generator.practice.generate import generate_practice_scores, key_file_name
from generator.score import slugify, validate_generated_tree
from generator.arrange.generate import generate_performance_scores
from generator.score import validate_generated_tree
from generator.summary import validate_practice_variants
from generator.transposition import TARGET_KEYS
from tests.test_musicxml import xml_note, xml_score
class CatalogueTests(unittest.TestCase):
def test_every_practice_source_has_its_own_complete_output(self):
source = Path('data/practice')
sources = sorted(source.rglob('*.xml'))
self.assertTrue(sources)
expected = set()
for path in sources:
relative = path.relative_to(source)
self.assertEqual(len(relative.parts), 3, str(path))
self.assertIn(relative.parts[0], ('chord', 'melody'), str(path))
for key in TARGET_KEYS:
expected.add(relative.parent / slugify(path.stem) / key_file_name(key))
self.assertEqual(len(expected), len(sources) * len(TARGET_KEYS))
def test_every_numbered_source_has_its_own_complete_output(self):
source = Path('data')
sources = sorted(source.glob('*.xml'))
self.assertEqual([p.name for p in sources], [f'{i:03}.xml' for i in range(1, 367)])
expected = {Path(path.stem) / key_file_name(key) for path in sources for key in TARGET_KEYS}
self.assertEqual(len(expected), 4392)
with tempfile.TemporaryDirectory() as temporary:
output = Path(temporary)
self.assertEqual(generate_practice_scores(source, output), len(expected))
self.assertEqual({p.relative_to(output) for p in output.rglob('*.json')}, expected)
validate_practice_variants(output)
validate_generated_tree(output)
def test_numbered_paths_and_duplicate_titles_remain_distinct(self):
names = ('chord/001_lesson/001-cycles.xml', 'chord/001_lesson/010-cycles.xml', 'chord/002_lesson/001-cycles.xml', 'melody/001_lesson/127-cycles.xml')
def test_numbers_and_duplicate_titles_remain_distinct_and_songs_are_excluded(self):
with tempfile.TemporaryDirectory() as temporary:
source, output = Path(temporary) / 'source', Path(temporary) / 'output'
for name in names:
path = source / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(xml_score(xml_note()), encoding='utf-8')
self.assertEqual(generate_practice_scores(source, output), len(names) * len(TARGET_KEYS))
for name in names:
directory = output / Path(name).with_suffix('')
(source / 'songs').mkdir(parents=True)
for name in ('001.xml', '010.xml', '366.xml', 'songs/song.xml'):
(source / name).write_text(xml_score(xml_note()), encoding='utf-8')
self.assertEqual(generate_practice_scores(source, output), 36)
self.assertEqual(sorted(p.name for p in output.iterdir()), ['001', '010', '366'])
for directory in output.iterdir():
self.assertEqual({p.name for p in directory.glob('*.json')}, {key_file_name(key) for key in TARGET_KEYS})
self.assertEqual(json.loads((directory / 'c.json').read_text())['metadata']['title'], 'Fixture')
def test_invalid_source_locations_fail_before_writing(self):
for name in ('chords/001_lesson/001-exercise.xml', 'chord/001-exercise.xml', 'melody/001_lesson/extra/001-exercise.xml'):
def test_invalid_names_and_legacy_nesting_preserve_output(self):
for name in ('000.xml', '1.xml', '001-copy.xml', '001_copy.xml', 'chord/001.xml', 'practice/melody/lesson/001.xml'):
with self.subTest(name=name), tempfile.TemporaryDirectory() as temporary:
source, output = Path(temporary) / 'source', Path(temporary) / 'output'
for relative in ('chord/001_lesson/001-valid.xml', name):
for relative in ('002.xml', name):
path = source / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(xml_score(xml_note()), encoding='utf-8')
with self.assertRaisesRegex(ValueError, 'practice source must be stored'):
output.mkdir()
sentinel = output / 'existing'
sentinel.write_text('unchanged')
with self.assertRaisesRegex(ValueError, 'practice sources'):
generate_practice_scores(source, output)
self.assertFalse(output.exists())
def test_normalized_filename_collisions_fail_before_writing(self):
self.assertEqual(list(output.iterdir()), [sentinel])
self.assertEqual(sentinel.read_text(), 'unchanged')
def test_missing_and_empty_variant_directories_fail(self):
with tempfile.TemporaryDirectory() as temporary:
source, output = Path(temporary) / 'source', Path(temporary) / 'output'
lesson = source / 'chord' / '001_lesson'
lesson.mkdir(parents=True)
for name in ('001-Cycles.xml', '001_cycles.xml'):
(lesson / name).write_text(xml_score(xml_note()), encoding='utf-8')
with self.assertRaisesRegex(ValueError, 'output path collides'):
generate_practice_scores(source, output)
self.assertFalse(output.exists())
def test_runtime_id_collisions_preserve_existing_output(self):
root = Path(temporary)
(root / '001').mkdir()
with self.assertRaisesRegex(ValueError, 'practice generator drift'):
validate_practice_variants(root)
def test_songs_generate_only_authored_key_parts(self):
with tempfile.TemporaryDirectory() as temporary:
source, output = Path(temporary) / 'source', Path(temporary) / 'output'
names = ('chord/one-two/three.xml', 'chord/one/two-three.xml')
for name in names:
path = source / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(xml_score(xml_note()), encoding='utf-8')
output.mkdir()
sentinel = output / 'existing.json'
sentinel.write_text('unchanged', encoding='utf-8')
with self.assertRaisesRegex(ValueError, 'practice exercise ID collision') as error:
generate_practice_scores(source, output)
for name in names:
self.assertIn(name, str(error.exception))
self.assertEqual(list(output.iterdir()), [sentinel])
self.assertEqual(sentinel.read_text(), 'unchanged')
source, output = Path(temporary) / 'songs', Path(temporary) / 'output'
source.mkdir()
(source / 'song.xml').write_text(xml_score(xml_note('E', alter=-1), '<key><fifths>-3</fifths></key>'))
self.assertEqual(generate_performance_scores(source, output), 3)
self.assertEqual({p.name for p in output.iterdir()}, {'song-acoustic.json', 'song-electric.json', 'song-vocal.json'})
for path in output.iterdir():
metadata = json.loads(path.read_text())['metadata']
self.assertEqual(metadata['key_signature'], 'Eb')
self.assertNotIn('variant_tonic', metadata)