|  | #!/usr/bin/env python2.7 | 
|  | # 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. | 
|  |  | 
|  | import filecmp | 
|  | import imp | 
|  | import json | 
|  | import os | 
|  | import shutil | 
|  | import subprocess | 
|  | import sys | 
|  | import tarfile | 
|  | import tempfile | 
|  | import unittest | 
|  |  | 
|  | SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) | 
|  | GENERATE_FILEPATH = os.path.join(SCRIPT_DIR, 'generate.py') | 
|  | generate = imp.load_source('generate', GENERATE_FILEPATH) | 
|  |  | 
|  | TMP_DIR_NAME = tempfile.mkdtemp(prefix='tmp_unittest_%s_' % 'GNGenerateTest') | 
|  | TMP_ARCH_DIR = tempfile.mkdtemp(prefix='tmp_unittest_%s_' % 'GNGenArchiveTest') | 
|  | TMP_ARCHIVE_PATH = os.path.join(TMP_ARCH_DIR, 'gn.tar.gz') | 
|  |  | 
|  | EXPECTED_PREBUILTS = { | 
|  | 'aemu': 'git_revision:a123456789abcdef0123456789abcdef01234567', | 
|  | 'grpcwebproxy': 'git_revision:b123456789abcdef0123456789abcdef01234567', | 
|  | 'device_launcher': 'g3-revision:vdl_fuchsia_20200819_RC00', | 
|  | } | 
|  |  | 
|  |  | 
|  | class GNGenerateTest(unittest.TestCase): | 
|  |  | 
|  | def setUp(self): | 
|  | # make sure TMP_DIR_NAME is empty | 
|  | if os.path.exists(TMP_DIR_NAME): | 
|  | shutil.rmtree(TMP_DIR_NAME) | 
|  | os.makedirs(TMP_DIR_NAME) | 
|  |  | 
|  | def tearDown(self): | 
|  | if os.path.exists(TMP_DIR_NAME): | 
|  | shutil.rmtree(TMP_DIR_NAME) | 
|  |  | 
|  | def testEmptyArchive(self): | 
|  | # Run the generator. | 
|  | generate.main( | 
|  | [ | 
|  | "--output", | 
|  | TMP_DIR_NAME, | 
|  | "--output-archive", | 
|  | TMP_ARCHIVE_PATH, | 
|  | "--directory", | 
|  | os.path.join(SCRIPT_DIR, 'testdata'), | 
|  | "--jiri-manifest", | 
|  | os.path.join( | 
|  | SCRIPT_DIR, 'testdata', '.jiri_root', 'update_history', | 
|  | 'latest'), | 
|  | ]) | 
|  | self.verify_contents(TMP_DIR_NAME) | 
|  | # verify tarball | 
|  | tar = tarfile.open(TMP_ARCHIVE_PATH) | 
|  | tar.extractall(TMP_ARCH_DIR) | 
|  | tar.close() | 
|  | os.remove(TMP_ARCHIVE_PATH) | 
|  | self.verify_manifest(TMP_ARCH_DIR) | 
|  |  | 
|  | def testPrebuilts(self): | 
|  | INPUT_PREBUILTS = generate.EXTRA_PREBUILTS | 
|  | prebuilt_results = generate.get_prebuilts( | 
|  | INPUT_PREBUILTS, | 
|  | os.path.join( | 
|  | SCRIPT_DIR, 'testdata', '.jiri_root', 'update_history', | 
|  | 'latest')) | 
|  | if cmp(prebuilt_results, EXPECTED_PREBUILTS) != 0: | 
|  | self.fail( | 
|  | "Expected output %s but returned %s instead" % | 
|  | (EXPECTED_PREBUILTS, prebuilt_results)) | 
|  |  | 
|  | def verify_contents(self, outdir): | 
|  | # update_golden.py doesn't copy bin and build subdirectories because we | 
|  | # don't want duplicates of things in base, so ignore them here too. | 
|  | dcmp = filecmp.dircmp( | 
|  | outdir, os.path.join(SCRIPT_DIR, 'golden'), ignore=['bin', 'build']) | 
|  | self.verify_contents_recursive(dcmp) | 
|  |  | 
|  | # Special case: outdir/build/test_targets.gni is a generated file. | 
|  | generated_file = os.path.join(outdir, 'build', 'test_targets.gni') | 
|  | golden_file = os.path.join( | 
|  | SCRIPT_DIR, 'golden', 'build', 'test_targets.gni') | 
|  | if not filecmp.cmp(generated_file, golden_file, False): | 
|  | self.fail(_gen_diff(generated_file, golden_file)) | 
|  |  | 
|  | # Special case: Check the prebuilts *.version files generated from the jiri manifest | 
|  | for prebuilt, version in EXPECTED_PREBUILTS.items(): | 
|  | in_path = os.path.join(outdir, 'bin', prebuilt + '.version') | 
|  | with open(in_path, 'r') as in_file: | 
|  | in_version = in_file.read().strip() | 
|  | if in_version != version: | 
|  | self.fail( | 
|  | "Generated %s in %s does not match expected %s" % | 
|  | (in_version, in_path)) | 
|  |  | 
|  | def verify_contents_recursive(self, dcmp): | 
|  | """Recursively checks for differences between two directories. | 
|  |  | 
|  | Fails if the directories do not appear to be deeply identical in | 
|  | structure and content. | 
|  |  | 
|  | Args: | 
|  | dcmp (filecmp.dircmp): A dircmp of the directories. | 
|  | """ | 
|  | if dcmp.left_only or dcmp.right_only: | 
|  | self.fail("Generated SDK does not match golden files. " \ | 
|  | "You can run ./update_golden.py to update them.\n" \ | 
|  | "Only in {}:\n{}\n\n" \ | 
|  | "Only in {}:\n{}\n\n" | 
|  | .format(dcmp.left, dcmp.left_only, dcmp.right, dcmp.right_only)) | 
|  | elif dcmp.diff_files: | 
|  | # Show a diff of the culprit files. Need to run diff for each pair. | 
|  | diff_result = '' | 
|  | for file in dcmp.diff_files: | 
|  | diff_result += _gen_diff( | 
|  | os.path.join(dcmp.left, file), | 
|  | os.path.join(dcmp.right, file)) | 
|  | self.fail("Generated SDK does not match golden files. " \ | 
|  | "You can run ./update_golden.py to update them.\n" \ | 
|  | "Left : {}\n" \ | 
|  | "Right: {}\n" \ | 
|  | "Different files: {}\n" \ | 
|  | "{}" | 
|  | .format(dcmp.left, dcmp.right, dcmp.diff_files, diff_result)) | 
|  |  | 
|  | for sub_dcmp in dcmp.subdirs.values(): | 
|  | self.verify_contents_recursive(sub_dcmp) | 
|  |  | 
|  | def verify_manifest(self, sdk_dir): | 
|  | """Read the manifest and verify all files are referenced.""" | 
|  | metafile = os.path.join(sdk_dir, 'meta', 'manifest.json') | 
|  | fileset = set() | 
|  | fileset.add(os.path.relpath(metafile, sdk_dir)) | 
|  | with open(metafile, 'r') as input: | 
|  | metadata = json.load(input) | 
|  | for atom in metadata['parts']: | 
|  | fileset.add(atom['meta']) | 
|  | with open(os.path.join(sdk_dir, atom['meta']), 'r') as input: | 
|  | atom_meta = json.load(input) | 
|  | fileset.update(self.get_atom_files(atom_meta)) | 
|  | self.assertTrue(len(fileset) != 0) | 
|  | # walk the sdk_dir matching the files in the set. | 
|  | for dir_name, _, file_list in os.walk(sdk_dir): | 
|  | for f in file_list: | 
|  | found_file = os.path.relpath(os.path.join(dir_name, f), sdk_dir) | 
|  | self.assertIn(found_file, fileset) | 
|  | fileset.remove(found_file) | 
|  | self.assertTrue( | 
|  | len(fileset) == 0, "Files missing from manifest: %s" % str(fileset)) | 
|  |  | 
|  | def get_atom_files(self, atom): | 
|  | files = set() | 
|  | if 'headers' in atom: | 
|  | files.update(atom['headers']) | 
|  | if 'sources' in atom: | 
|  | files.update(atom['sources']) | 
|  | if 'data' in atom: | 
|  | files.update(atom['data']) | 
|  | if 'docs' in atom: | 
|  | files.update(atom['docs']) | 
|  | if 'files' in atom: | 
|  | files.update(atom['files']) | 
|  | if 'resources' in atom: | 
|  | files.update(atom['resources']) | 
|  | if 'target_files' in atom: | 
|  | for a in atom['target_files']: | 
|  | files.update(atom['target_files'][a]) | 
|  | if 'binaries' in atom: | 
|  | for a in atom['binaries']: | 
|  | arch_atom = atom['binaries'][a] | 
|  | if 'debug' in arch_atom: | 
|  | files.add(arch_atom['debug']) | 
|  | if 'dist' in arch_atom: | 
|  | files.add(arch_atom['dist']) | 
|  | if 'link' in arch_atom: | 
|  | files.add(arch_atom['link']) | 
|  | if type(arch_atom) is list: | 
|  | files.update(arch_atom) | 
|  | if 'versions' in atom: | 
|  | for a in atom['versions']: | 
|  | arch_atom = atom['versions'][a] | 
|  | if 'debug_libs' in arch_atom: | 
|  | files.update(arch_atom['debug_libs']) | 
|  | if 'dist_libs' in arch_atom: | 
|  | files.update(arch_atom['dist_libs']) | 
|  | if 'headers' in arch_atom: | 
|  | files.update(arch_atom['headers']) | 
|  | if 'link_libs' in arch_atom: | 
|  | files.update(arch_atom['link_libs']) | 
|  |  | 
|  | return files | 
|  |  | 
|  |  | 
|  | def _gen_diff(a, b): | 
|  | cmd_args = ['diff', '-U', '2', a, b] | 
|  | pipe = subprocess.Popen(cmd_args, stdout=subprocess.PIPE) | 
|  | out, err = pipe.communicate() | 
|  | return "diff of '{}':\n{}\n".format(os.path.basename(a), out) | 
|  |  | 
|  |  | 
|  | def TestMain(): | 
|  | unittest.main() | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | TestMain() |