| #!/usr/bin/env fuchsia-vendored-python |
| # 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 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": "bid:7927554", |
| "grpcwebproxy": "git_revision:b123456789abcdef0123456789abcdef01234567", |
| } |
| |
| |
| 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 prebuilt_results != EXPECTED_PREBUILTS: |
| 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, text=True) |
| out, err = pipe.communicate() |
| return "diff of '{}':\n{}\n".format(os.path.basename(a), out) |
| |
| |
| def TestMain(): |
| unittest.main() |
| |
| |
| if __name__ == "__main__": |
| TestMain() |