tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

test_redistribution.py (4505B)


      1 """Fast source/notice ownership, integrity and archive-safety controls."""
      2 import hashlib
      3 import io
      4 import json
      5 from pathlib import Path
      6 import tarfile
      7 import tempfile
      8 import unittest
      9 
     10 from redistribution import license_name, notices, owner
     11 from verify_redistribution import relative, verify, verify_archive
     12 
     13 
     14 class RedistributionTests(unittest.TestCase):
     15     def test_notice_names(self):
     16         for name in ('LICENSES', 'COPYING.RUNTIME', 'COPYING3', 'COPYING.LESSERv3', 'LICENSE.LGPL2.1'):
     17             self.assertTrue(license_name(name), name)
     18         for name in ('License.hs', 'licenses.json', 'license-check.sh', 'other'):
     19             self.assertFalse(license_name(name), name)
     20 
     21     def test_owners(self):
     22         with tempfile.TemporaryDirectory() as directory:
     23             root = Path(directory)
     24             (root/'input.a').write_bytes(b'archive')
     25             row = dict(name='library', roots=[str(root)])
     26             self.assertEqual(owner(root/'input.a', [row]), row)
     27             for rows in ([], [row, row]):
     28                 with self.assertRaises(ValueError):
     29                     owner(root/'input.a', rows)
     30 
     31     def test_embedded_notice(self):
     32         with tempfile.TemporaryDirectory() as directory:
     33             archive = Path(directory)/'source.tar.gz'
     34             block = b'/* Copyright Example. Redistribution permitted. */\n'
     35             with tarfile.open(archive, 'w:gz') as out:
     36                 for name, body in [('pkg/LICENSES', b'Complete license'),
     37                                    ('pkg/cbits/hash.c', block+b'int value;')]:
     38                     item = tarfile.TarInfo(name)
     39                     item.size = len(body)
     40                     out.addfile(item, io.BytesIO(body))
     41             self.assertEqual(dict(notices(archive, 'example')), {
     42                 'pkg/LICENSES': b'Complete license', 'pkg/cbits/hash.c': block})
     43 
     44     def test_archive_pairing(self):
     45         with tempfile.TemporaryDirectory() as directory:
     46             archive = Path(directory)/'release.tar.gz'
     47             with tarfile.open(archive, 'w:gz') as out:
     48                 item = tarfile.TarInfo('release/file')
     49                 item.size = 4
     50                 out.addfile(item, io.BytesIO(b'data'))
     51             expected = {'file': hashlib.sha256(b'data').hexdigest()}
     52             verify_archive(archive, 'release', expected, {})
     53             for prefix, hashes in [('wrong-prefix', expected), ('release', {}),
     54                                    ('release', {'file': 'changed'})]:
     55                 with self.assertRaises(ValueError):
     56                     verify_archive(archive, prefix, hashes, {})
     57 
     58     def test_verifier_controls(self):
     59         with tempfile.TemporaryDirectory() as directory:
     60             root = Path(directory)
     61             (root/'sources/example').mkdir(parents=True)
     62             (root/'notices').mkdir()
     63             (root/'sources/example/upstream').write_bytes(b'original source')
     64             (root/'notices/LICENSE').write_bytes(b'original license')
     65             checksum = hashlib.sha256(b'original license').hexdigest()
     66             (root/'components.json').write_text(json.dumps(dict(components=[dict(
     67                 name='example', upstream_file='upstream',
     68                 notices=[dict(file='notices/LICENSE', sha256=checksum)])])))
     69             (root/'source-link').symlink_to('sources/example/upstream')
     70             (root/'SYMLINKS.json').write_text(json.dumps({'source-link': 'sources/example/upstream'}))
     71             hashes = {str(p.relative_to(root)): hashlib.sha256(p.read_bytes()).hexdigest()
     72                       for p in root.rglob('*') if p.is_file() and not p.is_symlink() and p.name != 'SYMLINKS.json'}
     73             (root/'SHA256.json').write_text(json.dumps(hashes))
     74             self.assertEqual(verify(root)['components'], 1)
     75             (root/'notices/LICENSE').write_bytes(b'altered license')
     76             with self.assertRaises(ValueError):
     77                 verify(root)
     78             (root/'notices/LICENSE').write_bytes(b'original license')
     79             (root/'extra').write_text('unexpected')
     80             with self.assertRaises(ValueError):
     81                 verify(root)
     82             (root/'extra').unlink()
     83             (root/'source-link').unlink()
     84             (root/'source-link').symlink_to('elsewhere')
     85             with self.assertRaises(ValueError):
     86                 verify(root)
     87             for path in ('../outside', '/outside'):
     88                 with self.assertRaises(ValueError):
     89                     relative(root, path)
     90 
     91 
     92 if __name__ == '__main__':
     93     unittest.main()