| #!/usr/bin/env python3.8 |
| # Copyright 2020 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| r"""Unit test for verify_build.py. |
| |
| Need to have SCRUTINY and ZBI environmental variables set. |
| |
| To manually run this test: |
| |
| SCRUTINY=~/fuchsia/out/default/host_x64/scrutiny \ |
| ZBI=~/fuchsia/out/default/host_x64/zbi python3 \ |
| verify_build_test.py |
| """ |
| import os |
| import subprocess |
| import sys |
| import tempfile |
| import unittest |
| import unittest.mock as mock |
| |
| import verify_build |
| |
| SUBPROCESS_RUN = subprocess.run |
| |
| |
| class RunVerifyZbiKernelCmdlineTest(unittest.TestCase): |
| |
| def verify_kernel_cmdline(self, golden, actual): |
| with tempfile.TemporaryDirectory() as test_folder: |
| golden_file = os.path.join(test_folder, 'golden') |
| stamp_file = os.path.join(test_folder, 'stamp') |
| fuchsia_folder = os.path.join(test_folder, 'fuchsia') |
| test_zbi = os.path.join(test_folder, 'test.zbi') |
| cmdline_file = os.path.join(test_folder, 'cmdline') |
| scrutiny = os.environ['SCRUTINY'] |
| with open(golden_file, 'w+') as f: |
| f.write(golden) |
| with open(cmdline_file, 'wb+') as f: |
| f.write(actual) |
| |
| # Use ZBI to create a test.zbi that only contains cmdline. |
| subprocess.check_call( |
| [ |
| os.environ['ZBI'], '-o', test_zbi, '-T', 'CMDLINE', |
| cmdline_file |
| ]) |
| os.mkdir(fuchsia_folder) |
| |
| args = [ |
| '--type', 'kernel_cmdline', '--zbi-file', test_zbi, |
| '--scrutiny', scrutiny, '--golden-files', golden_file, |
| '--stamp', stamp_file |
| ] |
| # Verify the cmdline in the generated ZBI. |
| result = verify_build.main(args) |
| if result == 0: |
| # Verify stamp file is created. |
| self.assertTrue(os.path.isfile(stamp_file)) |
| return result |
| |
| def verify_bootfs_filelist(self, want_filelist, got_files): |
| with tempfile.TemporaryDirectory() as test_folder: |
| golden_file = os.path.join(test_folder, 'golden') |
| stamp_file = os.path.join(test_folder, 'stamp') |
| fuchsia_folder = os.path.join(test_folder, 'fuchsia') |
| os.mkdir(fuchsia_folder) |
| test_zbi = os.path.join(test_folder, 'test.zbi') |
| with open(golden_file, 'w+') as f: |
| f.write(want_filelist) |
| |
| fake_scrutiny = os.path.join(test_folder, 'fake_scrutiny') |
| with open(fake_scrutiny, 'w+') as f: |
| f.write('fake scrutiny') |
| |
| # Create a dummy test.zbi. We are not going to use the real scrutiny |
| # to parse it so its content doesn't matter. |
| with open(test_zbi, 'w+') as f: |
| f.write('test ZBI') |
| |
| zbi_files = {} |
| for file in got_files: |
| zbi_files[os.path.join('bootfs', file)] = 'bootfs file' |
| fake_subprocess = FakeSubprocess(zbi_files, {}) |
| with mock.patch('subprocess.run') as mock_run: |
| mock_run.side_effect = fake_subprocess.run |
| |
| args = [ |
| '--type', 'bootfs_filelist', '--zbi-file', test_zbi, |
| '--scrutiny', fake_scrutiny, '--golden-files', golden_file, |
| '--stamp', stamp_file |
| ] |
| result = verify_build.main(args) |
| if result == 0: |
| # Verify stamp file is created. |
| self.assertTrue(os.path.isfile(stamp_file)) |
| return result |
| |
| def verify_static_pkgs( |
| self, want_pkgs, zbi_files, blobfs_files, system_image_files): |
| with tempfile.TemporaryDirectory() as test_folder: |
| golden_file = os.path.join(test_folder, 'golden') |
| stamp_file = os.path.join(test_folder, 'stamp') |
| depfile = os.path.join(test_folder, 'depfile') |
| fuchsia_folder = os.path.join(test_folder, 'fuchsia') |
| os.mkdir(fuchsia_folder) |
| test_zbi = os.path.join(test_folder, 'test.zbi') |
| test_blobfs = os.path.join(test_folder, 'test.blob') |
| with open(golden_file, 'w+') as f: |
| f.write(want_pkgs) |
| |
| fake_scrutiny = os.path.join(test_folder, 'fake_scrutiny') |
| with open(fake_scrutiny, 'w+') as f: |
| f.write('fake scrutiny') |
| |
| fake_far = os.path.join(test_folder, 'fake_far') |
| with open(fake_scrutiny, 'w+') as f: |
| f.write('fake far') |
| |
| # Create a dummy test.zbi. We are not going to use the real scrutiny |
| # to parse it so its content doesn't matter. |
| with open(test_zbi, 'w+') as f: |
| f.write('test ZBI') |
| |
| blobs_folder = os.path.join(test_folder, 'blobs') |
| os.mkdir(blobs_folder) |
| blobfs_manifest = os.path.join(blobs_folder, 'blobs.manifest') |
| with open(blobfs_manifest, 'w+') as bf: |
| for blobfs_file in blobfs_files: |
| # We use the blob merkle as the blob content file name here. |
| with open(os.path.join(blobs_folder, blobfs_file), |
| 'w+') as f: |
| f.write(blobfs_files[blobfs_file]) |
| bf.write(blobfs_file + '=' + blobfs_file + '\n') |
| |
| fake_subprocess = FakeSubprocess(zbi_files, system_image_files) |
| with mock.patch('subprocess.run') as mock_run: |
| mock_run.side_effect = fake_subprocess.run |
| |
| args = [ |
| '--type', |
| 'static_pkgs', |
| '--zbi-file', |
| test_zbi, |
| '--blobfs-manifest', |
| blobfs_manifest, |
| '--scrutiny', |
| fake_scrutiny, |
| '--far', |
| fake_far, |
| '--golden-files', |
| golden_file, |
| '--stamp', |
| stamp_file, |
| '--depfile', |
| depfile, |
| ] |
| result = verify_build.main(args) |
| |
| if result == 0: |
| # Verify stamp file is created. |
| self.assertTrue(os.path.isfile(stamp_file)) |
| # Verify depfile is created. |
| self.assertTrue(os.path.isfile(depfile)) |
| return result |
| |
| def test_verify_kernel_cmdline_sucess_normal_case(self): |
| self.assertEqual( |
| 0, |
| self.verify_kernel_cmdline( |
| 'key1=v1\nkey2=v2\nkey3=v3', b'key1=v1 key2=v2 key3=v3')) |
| |
| def test_verify_kernel_cmdline_success_order_diff(self): |
| self.assertEqual( |
| 0, |
| self.verify_kernel_cmdline( |
| 'key1=v1\nkey2=v2\nkey3=v3', b'key2=v2 key1=v1 key3=v3')) |
| |
| def test_verify_kernel_cmdline_success_no_value_option(self): |
| self.assertEqual( |
| 0, |
| self.verify_kernel_cmdline('option1\noption2', b'option1 option2')) |
| |
| def test_verify_kernel_cmdline_fail_golden_empty(self): |
| self.assertEqual( |
| 1, self.verify_kernel_cmdline('', b'key2=v2 key1=v1 key3=v3')) |
| |
| def test_verify_kernel_cmdline_fail_missing_key2(self): |
| self.assertEqual( |
| 1, self.verify_kernel_cmdline('key1=v1\nkey2=v2', b'key1=v1')) |
| |
| def test_verify_kernel_cmdline_fail_key1_mismatch(self): |
| self.assertEqual( |
| 1, |
| self.verify_kernel_cmdline('key1=v1\nkey2=v2', b'key1=v2 key2=v2')) |
| |
| def test_verify_kernel_cmdline_fail_key2_mismatch(self): |
| self.assertEqual( |
| 1, |
| self.verify_kernel_cmdline('key1=v1\nkey2=v2', b'key1=v1 key2=v1')) |
| |
| def test_verify_kernel_cmdline_fail_additional_key3(self): |
| self.assertEqual( |
| 1, |
| self.verify_kernel_cmdline( |
| 'key1=v1\nkey2=v2', b'key1=v1 key2=v2 key3=v3')) |
| |
| def test_verify_kernel_cmdline_fail_invalid_format(self): |
| self.assertEqual( |
| 1, |
| self.verify_kernel_cmdline('key1=v1\nkey2=v2', b'invalid=format=1')) |
| |
| def test_verify_kernel_cmdline_fail_option1_missing(self): |
| self.assertEqual( |
| 1, self.verify_kernel_cmdline('option1\noption2', b'option2')) |
| |
| def test_verify_kernel_cmdline_fail_additional_option3(self): |
| self.assertEqual( |
| 1, |
| self.verify_kernel_cmdline( |
| 'option1\noption2', b'option1 option2 option3')) |
| |
| def test_verify_kernel_cmdline_zbi_not_found(self): |
| with tempfile.TemporaryDirectory() as test_folder: |
| golden_file = os.path.join(test_folder, 'golden') |
| stamp_file = os.path.join(test_folder, 'stamp') |
| fuchsia_folder = os.path.join(test_folder, 'fuchsia') |
| test_zbi = os.path.join(test_folder, 'test.zbi') |
| scrutiny = os.environ['SCRUTINY'] |
| with open(golden_file, 'w+') as f: |
| f.write('option1') |
| |
| # Do not create test_zbi |
| |
| os.mkdir(fuchsia_folder) |
| |
| args = [ |
| '--type', 'kernel_cmdline', '--zbi-file', test_zbi, |
| '--scrutiny', scrutiny, '--golden-files', golden_file, |
| '--stamp', stamp_file |
| ] |
| self.assertEqual(1, verify_build.main(args)) |
| |
| def test_verify_kernel_cmdline_success_no_cmdline_found(self): |
| with tempfile.TemporaryDirectory() as test_folder: |
| golden_file = os.path.join(test_folder, 'golden') |
| stamp_file = os.path.join(test_folder, 'stamp') |
| fuchsia_folder = os.path.join(test_folder, 'fuchsia') |
| test_zbi = os.path.join(test_folder, 'test.zbi') |
| scrutiny = os.environ['SCRUTINY'] |
| |
| # Create an empty golden file |
| with open(golden_file, 'w+') as f: |
| f.write('') |
| |
| # Use ZBI to create a test.zbi with no cmdline. |
| subprocess.check_call([os.environ['ZBI'], '-o', test_zbi]) |
| |
| os.mkdir(fuchsia_folder) |
| |
| args = [ |
| '--type', 'kernel_cmdline', '--zbi-file', test_zbi, |
| '--scrutiny', scrutiny, '--golden-files', golden_file, |
| '--stamp', stamp_file |
| ] |
| self.assertEqual(0, verify_build.main(args)) |
| |
| def test_verify_kernel_cmdline_fail_golden_empty_cmdline_found(self): |
| self.assertEqual(1, self.verify_kernel_cmdline('', b'option2')) |
| |
| def test_verify_kernel_cmdline_fail_golden_not_empty_cmdline_not_found( |
| self): |
| with tempfile.TemporaryDirectory() as test_folder: |
| golden_file = os.path.join(test_folder, 'golden') |
| stamp_file = os.path.join(test_folder, 'stamp') |
| fuchsia_folder = os.path.join(test_folder, 'fuchsia') |
| test_zbi = os.path.join(test_folder, 'test.zbi') |
| scrutiny = os.environ['SCRUTINY'] |
| |
| # Create an empty golden file |
| with open(golden_file, 'w+') as f: |
| f.write('option1') |
| |
| # Use ZBI to create a test.zbi with no cmdline. |
| subprocess.check_call([os.environ['ZBI'], '-o', test_zbi]) |
| |
| os.mkdir(fuchsia_folder) |
| |
| args = [ |
| '--type', 'kernel_cmdline', '--zbi-file', test_zbi, |
| '--scrutiny', scrutiny, '--golden-files', golden_file, |
| '--stamp', stamp_file |
| ] |
| self.assertEqual(1, verify_build.main(args)) |
| |
| def test_verify_kernel_cmdline_multiple_golden_files_one_match(self): |
| with tempfile.TemporaryDirectory() as test_folder: |
| golden_file_1 = os.path.join(test_folder, 'golden_1') |
| golden_file_2 = os.path.join(test_folder, 'golden_2') |
| stamp_file = os.path.join(test_folder, 'stamp') |
| fuchsia_folder = os.path.join(test_folder, 'fuchsia') |
| test_zbi = os.path.join(test_folder, 'test.zbi') |
| scrutiny = os.environ['SCRUTINY'] |
| cmdline_file = os.path.join(test_folder, 'cmdline') |
| |
| # golden_file_1 does not match. |
| with open(golden_file_1, 'w+') as f: |
| f.write('option1') |
| |
| # golden_file_2 matches. |
| with open(golden_file_2, 'w+') as f: |
| f.write('option1 option2') |
| |
| with open(cmdline_file, 'wb+') as f: |
| f.write(b'option1 option2') |
| |
| # Use ZBI to create a test.zbi that only contains cmdline. |
| subprocess.check_call( |
| [ |
| os.environ['ZBI'], '-o', test_zbi, '-T', 'CMDLINE', |
| cmdline_file |
| ]) |
| |
| os.mkdir(fuchsia_folder) |
| |
| args = [ |
| '--type', 'kernel_cmdline', '--zbi-file', test_zbi, |
| '--scrutiny', scrutiny, '--golden-files', golden_file_1, |
| golden_file_2, '--stamp', stamp_file |
| ] |
| self.assertEqual(1, verify_build.main(args)) |
| |
| def test_verify_kernel_cmdline_three_golden_files_not_supported(self): |
| with tempfile.TemporaryDirectory() as test_folder: |
| golden_file_1 = os.path.join(test_folder, 'golden_1') |
| golden_file_2 = os.path.join(test_folder, 'golden_2') |
| golden_file_3 = os.path.join(test_folder, 'golden_3') |
| stamp_file = os.path.join(test_folder, 'stamp') |
| fuchsia_folder = os.path.join(test_folder, 'fuchsia') |
| test_zbi = os.path.join(test_folder, 'test.zbi') |
| scrutiny = os.environ['SCRUTINY'] |
| cmdline_file = os.path.join(test_folder, 'cmdline') |
| |
| with open(golden_file_1, 'w+') as f: |
| f.write('option1') |
| with open(golden_file_2, 'w+') as f: |
| f.write('option1') |
| with open(golden_file_3, 'w+') as f: |
| f.write('option1') |
| |
| with open(cmdline_file, 'wb+') as f: |
| f.write(b'option1') |
| |
| # Use ZBI to create a test.zbi that only contains cmdline. |
| subprocess.check_call( |
| [ |
| os.environ['ZBI'], '-o', test_zbi, '-T', 'CMDLINE', |
| cmdline_file |
| ]) |
| |
| os.mkdir(fuchsia_folder) |
| |
| args = [ |
| '--type', 'kernel_cmdline', '--zbi-file', test_zbi, |
| '--scrutiny', scrutiny, '--golden-files', golden_file_1, |
| golden_file_2, golden_file_3, '--stamp', stamp_file |
| ] |
| # We do not support more than two golden files. |
| self.assertEqual(0, verify_build.main(args)) |
| |
| def test_verify_bootfs_filelist_normal_case(self): |
| self.assertEqual( |
| 0, |
| self.verify_bootfs_filelist( |
| 'fileA\nfileB\nfileC', ['fileA', 'fileC', 'fileB'])) |
| |
| def test_verify_bootfs_filelist_sub_dir(self): |
| self.assertEqual( |
| 0, |
| self.verify_bootfs_filelist( |
| 'dir/fileA\ndir/fileC\nfileB', |
| ['dir/fileA', 'dir/fileC', 'fileB'])) |
| |
| def test_verify_bootfs_filelist_mismatch(self): |
| self.assertEqual( |
| 1, |
| self.verify_bootfs_filelist( |
| 'fileA\nfileB\nfileC', ['fileA', 'fileC'])) |
| |
| def test_verify_bootfs_filelist_sub_dir_mismatch(self): |
| self.assertEqual( |
| 1, |
| self.verify_bootfs_filelist( |
| 'dir/fileA\ndir/fileC\nfileB', |
| ['dir1/fileA', 'dir/fileC', 'fileB'])) |
| |
| def test_verify_static_pkgs_normal_case(self): |
| static_packages = 'pkg0/0=1\npkg1/0=1\npkg2/0=2' |
| zbi_files = { |
| 'bootfs/config/devmgr': 'zircon.system.pkgfs.cmd=bin/pkgsvr+1234' |
| } |
| blobfs_files = {'1234': 'system_image', '2345': static_packages} |
| system_image_files = {'meta/contents': 'data/static_packages=2345'} |
| self.assertEqual( |
| 0, |
| self.verify_static_pkgs( |
| 'pkg0\npkg1\npkg2', zbi_files, blobfs_files, |
| system_image_files)) |
| |
| def test_verify_static_pkgs_order(self): |
| static_packages = 'pkg2/2=1\npkg1/1=1\npkg0/0=2' |
| zbi_files = { |
| 'bootfs/config/devmgr': 'zircon.system.pkgfs.cmd=bin/pkgsvr+1234' |
| } |
| blobfs_files = {'1234': 'system_image', '2345': static_packages} |
| system_image_files = {'meta/contents': 'data/static_packages=2345'} |
| self.assertEqual( |
| 0, |
| self.verify_static_pkgs( |
| 'pkg0\npkg1\npkg2', zbi_files, blobfs_files, |
| system_image_files)) |
| |
| def test_verify_static_pkgs_mismatch(self): |
| static_packages = 'pkg0/0=1\npkg1/0=1' |
| zbi_files = { |
| 'bootfs/config/devmgr': 'zircon.system.pkgfs.cmd=bin/pkgsvr+1234' |
| } |
| blobfs_files = {'1234': 'system_image', '2345': static_packages} |
| system_image_files = {'meta/contents': 'data/static_packages=2345'} |
| self.assertEqual( |
| 1, |
| self.verify_static_pkgs( |
| 'pkg0\npkg1\npkg2', zbi_files, blobfs_files, |
| system_image_files)) |
| |
| def test_verify_static_pkgs_no_devmgr_config(self): |
| static_packages = 'pkg0/0=1\npkg1/0=1\npkg2/0=2' |
| zbi_files = {} |
| blobfs_files = {'1234': 'system_image', '2345': static_packages} |
| system_image_files = {'meta/contents': 'data/static_packages=2345'} |
| self.assertEqual( |
| 1, |
| self.verify_static_pkgs( |
| 'pkg0\npkg1\npkg2', zbi_files, blobfs_files, |
| system_image_files)) |
| |
| def test_verify_static_pkgs_invalid_devmgr_config(self): |
| static_packages = 'pkg0/0=1\npkg1/0=1\npkg2/0=2' |
| zbi_files = {'bootfs/config/devmgr': 'zircon.system.pkgfs.cmd'} |
| blobfs_files = {'1234': 'system_image', '2345': static_packages} |
| system_image_files = {'meta/contents': 'data/static_packages=2345'} |
| self.assertEqual( |
| 1, |
| self.verify_static_pkgs( |
| 'pkg0\npkg1\npkg2', zbi_files, blobfs_files, |
| system_image_files)) |
| |
| def test_verify_static_pkgs_system_image_blob_not_found(self): |
| static_packages = 'pkg0/0=1\npkg1/0=1\npkg2/0=2' |
| zbi_files = { |
| 'bootfs/config/devmgr': 'zircon.system.pkgfs.cmd=bin/pkgsvr+1234' |
| } |
| blobfs_files = {'2345': static_packages} |
| system_image_files = {'meta/contents': 'data/static_packages=2345'} |
| self.assertEqual( |
| 1, |
| self.verify_static_pkgs( |
| 'pkg0\npkg1\npkg2', zbi_files, blobfs_files, |
| system_image_files)) |
| |
| def test_verify_static_pkgs_invalid_system_image(self): |
| static_packages = 'pkg0/0=1\npkg1/0=1\npkg2/0=2' |
| zbi_files = { |
| 'bootfs/config/devmgr': 'zircon.system.pkgfs.cmd=bin/pkgsvr+1234' |
| } |
| blobfs_files = {'1234': 'system_image', '2345': static_packages} |
| system_image_files = {} |
| self.assertEqual( |
| 1, |
| self.verify_static_pkgs( |
| 'pkg0\npkg1\npkg2', zbi_files, blobfs_files, |
| system_image_files)) |
| |
| def test_verify_static_pkgs_static_pkgs_blob_not_found(self): |
| zbi_files = { |
| 'bootfs/config/devmgr': 'zircon.system.pkgfs.cmd=bin/pkgsvr+1234' |
| } |
| blobfs_files = {'1234': 'system_image'} |
| system_image_files = {'meta/contents': 'data/static_packages=2345'} |
| self.assertEqual( |
| 1, |
| self.verify_static_pkgs( |
| 'pkg0\npkg1\npkg2', zbi_files, blobfs_files, |
| system_image_files)) |
| |
| def test_verify_static_pkgs_invalid_static_pkgs_list(self): |
| static_packages = 'pkg0/0' |
| zbi_files = { |
| 'bootfs/config/devmgr': 'zircon.system.pkgfs.cmd=bin/pkgsvr+1234' |
| } |
| blobfs_files = {'1234': 'system_image', '2345': static_packages} |
| system_image_files = {'meta/contents': 'data/static_packages=2345'} |
| self.assertEqual( |
| 1, |
| self.verify_static_pkgs( |
| 'pkg0\npkg1\npkg2', zbi_files, blobfs_files, |
| system_image_files)) |
| |
| |
| class FakeSubprocess(object): |
| |
| def __init__(self, zbi_files, system_image_files): |
| self.zbi_files = zbi_files |
| self.system_image_files = system_image_files |
| |
| def _write_files(files, output): |
| for file in files: |
| dirpath = os.path.dirname(os.path.join(output, file)) |
| if not os.path.exists(dirpath): |
| os.makedirs(dirpath, exist_ok=True) |
| with open(os.path.join(output, file), 'w+') as f: |
| f.write(files[file]) |
| |
| def run(self, *argv, **kwargs): |
| del kwargs |
| command = argv[0] |
| if command[0].endswith('fake_scrutiny'): |
| output = '' |
| input = '' |
| scrutiny_commands = command[2].split(' ') |
| for i in range(0, len(scrutiny_commands) - 1): |
| if scrutiny_commands[i] == '--output': |
| output = scrutiny_commands[i + 1] |
| if scrutiny_commands[i] == '--input': |
| input = scrutiny_commands[i + 1] |
| |
| if not os.path.exists(input): |
| raise subprocess.CalledProcessError( |
| cmd=command, |
| returncode=1, |
| stderr=('input: ' + input + ' not found').encode()) |
| op = scrutiny_commands[0] |
| if op == 'tool.zbi.extract': |
| FakeSubprocess._write_files(self.zbi_files, output) |
| else: |
| raise subprocess.CalledProcessError( |
| cmd=command, |
| returncode=1, |
| stderr=('unknown scrutiny command: ' + op).encode()) |
| |
| return subprocess.CompletedProcess( |
| args=[], returncode=0, stdout=b'{"status":"ok"}') |
| elif command[0].endswith('fake_far'): |
| input = (command[2].split('='))[1] |
| if not os.path.exists(input): |
| raise subprocess.CalledProcessError( |
| cmd=command, |
| returncode=1, |
| stderr=('input: ' + input + ' not found').encode()) |
| output = (command[3].split('='))[1] |
| os.mkdir(output) |
| FakeSubprocess._write_files(self.system_image_files, output) |
| return subprocess.CompletedProcess( |
| args=[], returncode=0, stdout=b'') |
| |
| raise subprocess.CalledProcessError( |
| cmd=command, returncode=1, stderr=b'unsupported command') |
| |
| |
| if __name__ == '__main__': |
| if 'SCRUTINY' not in os.environ or 'ZBI' not in os.environ: |
| print('Please set SCRUTINY and ZBI environmental path') |
| sys.exit(1) |
| unittest.main() |