Files
engineering/tests/test_bootstrap.py

96 lines
4.1 KiB
Python

"""Exercise release pinning, drift detection, replacement and recovery without network."""
import importlib.util
import json
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
HOME = Path(__file__).resolve().parents[1]
spec = importlib.util.spec_from_file_location('bootstrap', HOME / 'bootstrap.py')
bootstrap = importlib.util.module_from_spec(spec)
spec.loader.exec_module(bootstrap)
class BootstrapTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary.cleanup)
self.root = Path(self.temporary.name)
self.source = self.root / 'engineering'
self.source.mkdir()
self.git('init', '-q')
self.git('config', 'user.email', 'test@example.invalid')
self.git('config', 'user.name', 'Engineering test')
(self.source / 'resources').mkdir()
(self.source / 'resources/tool').write_text('version one\n')
self.commit = self.release('v1.0.0')
self.project = self.root / 'consumer'
self.project.mkdir()
self.lock = {'repository': str(self.source), 'tag': 'v1.0.0', 'commit': self.commit}
self.save_lock()
def git(self, *args):
return subprocess.check_output(['git', '-C', str(self.source), *args], text=True, stderr=subprocess.DEVNULL).strip()
def release(self, tag):
self.git('add', '.')
self.git('commit', '-qm', tag)
self.git('tag', tag)
return self.git('rev-parse', 'HEAD')
def save_lock(self):
(self.project / 'engineering.lock.json').write_text(json.dumps(self.lock))
def test_absent_generated_and_repeat_are_deterministic(self):
bootstrap.materialize(self.project)
first = bootstrap.files(self.project / 'generated')
bootstrap.materialize(self.project)
self.assertEqual(first, bootstrap.files(self.project / 'generated'))
bootstrap.materialize(self.project, True)
def test_drift_includes_modified_added_and_missing_files(self):
bootstrap.materialize(self.project)
target = self.project / 'generated/tool'
for operation in ('modify', 'remove', 'add'):
with self.subTest(operation=operation):
bootstrap.materialize(self.project)
if operation == 'modify': target.write_text('hand edit')
elif operation == 'remove': target.unlink()
else: (target.parent / 'extra').write_text('untracked')
before = bootstrap.files(target.parent)
with self.assertRaisesRegex(ValueError, 'differ'):
bootstrap.materialize(self.project, True)
self.assertEqual(before, bootstrap.files(target.parent))
def test_update_removes_obsolete_files_and_preserves_project_files(self):
bootstrap.materialize(self.project)
(self.project / 'pom.xml').write_text('owned by project')
(self.source / 'resources/tool').unlink()
(self.source / 'resources/new').write_text('new release')
self.lock.update(tag='v1.1.0', commit=self.release('v1.1.0'))
self.save_lock()
bootstrap.materialize(self.project)
self.assertFalse((self.project / 'generated/tool').exists())
self.assertEqual('owned by project', (self.project / 'pom.xml').read_text())
def test_moved_tag_fails_before_replacing_existing_content(self):
bootstrap.materialize(self.project)
before = bootstrap.files(self.project / 'generated')
(self.source / 'resources/tool').write_text('different commit')
self.release('v1.1.0')
self.git('tag', '-f', 'v1.0.0')
with self.assertRaisesRegex(ValueError, 'tag moved'):
bootstrap.materialize(self.project)
self.assertEqual(before, bootstrap.files(self.project / 'generated'))
def test_symlink_output_is_rejected(self):
(self.project / 'generated').symlink_to(self.source / 'resources', target_is_directory=True)
with self.assertRaisesRegex(ValueError, 'symlink'):
bootstrap.materialize(self.project)
if __name__ == '__main__':
unittest.main()