96 lines
4.1 KiB
Python
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()
|