| # Copyright 2021 The Pigweed Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| # use this file except in compliance with the License. You may obtain a copy of |
| # the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations under |
| # the License. |
| """Unit tests for pw_software_update/update_bundle.py.""" |
| |
| from pathlib import Path |
| import tempfile |
| import unittest |
| |
| from pw_software_update import update_bundle |
| from pw_software_update.tuf_pb2 import SignedRootMetadata, TargetsMetadata |
| |
| |
| class TargetsFromDirectoryTest(unittest.TestCase): |
| """Test turning a directory into TUF targets.""" |
| def test_excludes(self): |
| """Checks that excludes are excluded.""" |
| with tempfile.TemporaryDirectory() as tempdir_name: |
| temp_root = Path(tempdir_name) |
| foo_path = temp_root / 'foo.bin' |
| bar_path = temp_root / 'bar.bin' |
| baz_path = temp_root / 'baz.bin' |
| qux_path = temp_root / 'qux.exe' |
| for path in (foo_path, bar_path, baz_path, qux_path): |
| path.touch() |
| |
| targets = update_bundle.targets_from_directory( |
| temp_root, exclude=(Path('foo.bin'), Path('baz.bin'))) |
| |
| self.assertNotIn('foo.bin', targets) |
| self.assertEqual(bar_path, targets['bar.bin']) |
| self.assertNotIn('baz.bin', targets) |
| self.assertEqual(qux_path, targets['qux.exe']) |
| |
| def test_excludes_and_remapping(self): |
| """Checks that remapping works, even in combination with excludes.""" |
| with tempfile.TemporaryDirectory() as tempdir_name: |
| temp_root = Path(tempdir_name) |
| foo_path = temp_root / 'foo.bin' |
| bar_path = temp_root / 'bar.bin' |
| baz_path = temp_root / 'baz.bin' |
| qux_path = temp_root / 'qux.exe' |
| remap_paths = { |
| Path('foo.bin'): 'main', |
| Path('bar.bin'): 'backup', |
| Path('baz.bin'): 'tertiary', |
| } |
| for path in (foo_path, bar_path, baz_path, qux_path): |
| path.touch() |
| |
| targets = update_bundle.targets_from_directory( |
| temp_root, |
| exclude=(Path('qux.exe'), ), |
| remap_paths=remap_paths) |
| |
| self.assertEqual(foo_path, targets['main']) |
| self.assertEqual(bar_path, targets['backup']) |
| self.assertEqual(baz_path, targets['tertiary']) |
| self.assertNotIn('qux.exe', targets) |
| |
| def test_incomplete_remapping_logs(self): |
| """Checks that incomplete remappings log warnings.""" |
| with tempfile.TemporaryDirectory() as tempdir_name: |
| temp_root = Path(tempdir_name) |
| foo_path = temp_root / 'foo.bin' |
| bar_path = temp_root / 'bar.bin' |
| foo_path.touch() |
| bar_path.touch() |
| remap_paths = {Path('foo.bin'): 'main'} |
| |
| with self.assertLogs(level='WARNING') as log: |
| update_bundle.targets_from_directory( |
| temp_root, |
| exclude=(Path('qux.exe'), ), |
| remap_paths=remap_paths) |
| |
| self.assertIn('Some remaps defined, but not "bar.bin"', |
| log.output[0]) |
| |
| def test_remap_of_missing_file(self): |
| """Checks that remapping a missing file raises an error.""" |
| with tempfile.TemporaryDirectory() as tempdir_name: |
| temp_root = Path(tempdir_name) |
| foo_path = temp_root / 'foo.bin' |
| foo_path.touch() |
| remap_paths = { |
| Path('foo.bin'): 'main', |
| Path('bar.bin'): 'backup', |
| } |
| |
| with self.assertRaises(FileNotFoundError): |
| update_bundle.targets_from_directory(temp_root, |
| remap_paths=remap_paths) |
| |
| |
| class GenUnsignedUpdateBundleTest(unittest.TestCase): |
| """Test the generation of unsigned update bundles.""" |
| def test_bundle_generation(self): |
| """Tests basic creation of an UpdateBundle.""" |
| with tempfile.TemporaryDirectory() as tempdir_name: |
| temp_root = Path(tempdir_name) |
| foo_path = temp_root / 'foo.bin' |
| bar_path = temp_root / 'bar.bin' |
| baz_path = temp_root / 'baz.bin' |
| qux_path = temp_root / 'subdir' / 'qux.exe' |
| foo_bytes = b'\xf0\x0b\xa4' |
| bar_bytes = b'\x0b\xa4\x99' |
| baz_bytes = b'\xba\x59\x06' |
| qux_bytes = b'\x8a\xf3\x12' |
| foo_path.write_bytes(foo_bytes) |
| bar_path.write_bytes(bar_bytes) |
| baz_path.write_bytes(baz_bytes) |
| (temp_root / 'subdir').mkdir() |
| qux_path.write_bytes(qux_bytes) |
| targets = { |
| foo_path: 'foo', |
| bar_path: 'bar', |
| baz_path: 'baz', |
| qux_path: 'qux', |
| } |
| serialized_root_metadata_bytes = b'\x12\x34\x56\x78' |
| |
| bundle = update_bundle.gen_unsigned_update_bundle( |
| targets, |
| targets_metadata_version=42, |
| root_metadata=SignedRootMetadata( |
| serialized_root_metadata=serialized_root_metadata_bytes)) |
| |
| self.assertEqual(foo_bytes, bundle.target_payloads['foo']) |
| self.assertEqual(bar_bytes, bundle.target_payloads['bar']) |
| self.assertEqual(baz_bytes, bundle.target_payloads['baz']) |
| self.assertEqual(qux_bytes, bundle.target_payloads['qux']) |
| targets_metadata = TargetsMetadata.FromString( |
| bundle.targets_metadata['targets'].serialized_targets_metadata) |
| self.assertEqual(targets_metadata.common_metadata.version, 42) |
| self.assertEqual(serialized_root_metadata_bytes, |
| bundle.root_metadata.serialized_root_metadata) |
| |
| def test_persist_to_disk(self): |
| """Tests persisting the TUF repo to disk for debugging""" |
| with tempfile.TemporaryDirectory() as tempdir_name: |
| temp_root = Path(tempdir_name) |
| foo_path = temp_root / 'foo.bin' |
| bar_path = temp_root / 'bar.bin' |
| baz_path = temp_root / 'baz.bin' |
| qux_path = temp_root / 'subdir' / 'qux.exe' |
| foo_bytes = b'\xf0\x0b\xa4' |
| bar_bytes = b'\x0b\xa4\x99' |
| baz_bytes = b'\xba\x59\x06' |
| qux_bytes = b'\x8a\xf3\x12' |
| foo_path.write_bytes(foo_bytes) |
| bar_path.write_bytes(bar_bytes) |
| baz_path.write_bytes(baz_bytes) |
| (temp_root / 'subdir').mkdir() |
| qux_path.write_bytes(qux_bytes) |
| targets = { |
| foo_path: 'foo', |
| bar_path: 'bar', |
| baz_path: 'baz', |
| qux_path: 'subdir/qux', |
| } |
| persist_path = temp_root / 'persisted' |
| |
| update_bundle.gen_unsigned_update_bundle(targets, |
| persist=persist_path) |
| |
| self.assertEqual(foo_bytes, (persist_path / 'foo').read_bytes()) |
| self.assertEqual(bar_bytes, (persist_path / 'bar').read_bytes()) |
| self.assertEqual(baz_bytes, (persist_path / 'baz').read_bytes()) |
| self.assertEqual(qux_bytes, |
| (persist_path / 'subdir' / 'qux').read_bytes()) |
| |
| |
| class ParseTargetArgTest(unittest.TestCase): |
| """Test the parsing of target argument strings.""" |
| def test_valid_arg(self): |
| """Checks that valid remap strings are parsed correctly.""" |
| file_path, target_name = update_bundle.parse_target_arg( |
| 'foo.bin > main') |
| |
| self.assertEqual(Path('foo.bin'), file_path) |
| self.assertEqual('main', target_name) |
| |
| def test_invalid_arg_raises(self): |
| """Checks that invalid remap string raise an error.""" |
| with self.assertRaises(ValueError): |
| update_bundle.parse_target_arg('foo.bin main') |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |