| #!/usr/bin/env python3 |
| # |
| # Copyright 2018 - The Android Open Source Project |
| # |
| # 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 |
| # |
| # http://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. |
| |
| """Functional test for aidegen project files.""" |
| |
| from __future__ import absolute_import |
| |
| import argparse |
| import itertools |
| import json |
| import os |
| import subprocess |
| import sys |
| import xml.etree.ElementTree |
| import xml.parsers.expat |
| |
| from aidegen import aidegen_main |
| from aidegen.lib import common_util |
| from aidegen.lib import errors |
| from atest import module_info |
| |
| |
| _ROOT_DIR = os.path.join(common_util.get_android_root_dir(), |
| 'tools/asuite/aidegen_functional_test') |
| _TEST_DATA_PATH = os.path.join(_ROOT_DIR, 'test_data') |
| _ANDROID_SINGLE_PROJECT_JSON = os.path.join(_TEST_DATA_PATH, |
| 'single_module.json') |
| _VERIFY_COMMANDS_JSON = os.path.join(_TEST_DATA_PATH, 'verify_commands.json') |
| _VERIFY_BINARY_JSON = os.path.join(_TEST_DATA_PATH, 'verify_binary_upload.json') |
| _PRODUCT_DIR = '$PROJECT_DIR$' |
| _ANDROID_COMMON = 'android_common' |
| _LINUX_GLIBC_COMMON = 'linux_glibc_common' |
| _SRCS = 'srcs' |
| _JARS = 'jars' |
| _URL = 'url' |
| _TEST_ERROR = ('AIDEGen functional test error: %s-%s is different.') |
| _MSG_NOT_IN_PROJECT_FILE = ('%s is expected, but not found in the created ' |
| 'project file: %s') |
| _MSG_NOT_IN_SAMPLE_DATA = ('%s is unexpected, but found in the created project ' |
| 'file: %s') |
| _TEST_IML_DICT = { |
| 'SystemUI': ['SystemUI.iml', 'dependencies-SystemUI.iml'], |
| 'tradefed': ['core.iml', 'dependencies-core.iml'] |
| } |
| _ALL_PASS = 'All tests passed!' |
| |
| |
| def _parse_args(args): |
| """Parse command line arguments. |
| |
| Args: |
| args: A list of arguments. |
| |
| Returns: |
| An argparse.Namespace object holding parsed args. |
| """ |
| parser = argparse.ArgumentParser( |
| description=__doc__, |
| formatter_class=argparse.RawDescriptionHelpFormatter, |
| usage='aidegen_functional_test [-c | -u | -b] -v -r') |
| group = parser.add_mutually_exclusive_group() |
| parser.required = False |
| group.add_argument( |
| '-c', |
| '--create-sample', |
| action='store_true', |
| dest='create_sample', |
| help=('Create aidegen project files and write data to sample json file ' |
| 'for aidegen_functional_test to compare.')) |
| parser.add_argument( |
| '-v', |
| '--verbose', |
| action='store_true', |
| help='Show DEBUG level logging.') |
| parser.add_argument( |
| '-r', |
| '--remove_bp_json', |
| action='store_true', |
| help='Remove module_bp_java_deps.json for each use case test.') |
| group.add_argument( |
| '-u', |
| action='store_true', |
| dest='use_cases_verified', |
| help='Verify various use cases of executing aidegen.') |
| group.add_argument( |
| '-b', |
| action='store_true', |
| dest='binary_upload_verified', |
| help=('Verify aidegen\'s use cases by executing different aidegen ' |
| 'commands.')) |
| return parser.parse_args(args) |
| |
| |
| def _import_project_file_xml_etree(filename): |
| """Import iml project file and load data into a dictionary. |
| |
| Args: |
| filename: The input project file name. |
| |
| Returns: |
| A dictionary contains json data. |
| """ |
| data = {} |
| try: |
| tree = xml.etree.ElementTree.parse(filename) |
| data[_SRCS] = [] |
| root = tree.getroot() |
| for element in root.iter('sourceFolder'): |
| src = element.get(_URL).replace(common_util.get_android_root_dir(), |
| _PRODUCT_DIR) |
| data[_SRCS].append(src) |
| data[_JARS] = [] |
| for element in root.iter('root'): |
| jar = element.get(_URL).replace(common_util.get_android_root_dir(), |
| _PRODUCT_DIR) |
| data[_JARS].append(jar) |
| except (EnvironmentError, ValueError, LookupError, |
| xml.parsers.expat.ExpatError) as err: |
| print("{0}: import error: {1}".format(os.path.basename(filename), err)) |
| raise |
| return data |
| |
| |
| def _generate_sample_json(): |
| """Generate sample iml data from a iml file into a dictionary. |
| |
| Returns: |
| A dictionary contains sample iml data. |
| """ |
| atest_module_info = module_info.ModuleInfo() |
| data = {} |
| for target, filelist in _TEST_IML_DICT.items(): |
| aidegen_main.main([target, '-n']) |
| _, abs_path = common_util.get_related_paths(atest_module_info, target) |
| for filename in filelist: |
| real_iml_file = os.path.join(abs_path, filename) |
| item_name = os.path.basename(real_iml_file) |
| data[item_name] = _import_project_file_xml_etree(real_iml_file) |
| return data |
| |
| |
| def _create_sample_json_file(): |
| """Write samples' iml data into a json file. |
| |
| linked_function: _generate_sample_json() |
| """ |
| data = _generate_sample_json() |
| with open(_ANDROID_SINGLE_PROJECT_JSON, 'w') as outfile: |
| json.dump(data, outfile, indent=4, sort_keys=False) |
| |
| |
| def test_some_sample_iml(): |
| """Compare sample iml data to assure project iml file contents is right.""" |
| test_successful = True |
| with open(_ANDROID_SINGLE_PROJECT_JSON, 'r') as outfile: |
| data_sample = json.load(outfile) |
| data_real = _generate_sample_json() |
| for name in data_real: |
| for item in [_SRCS, _JARS]: |
| s_items = data_sample[name][item] |
| r_items = data_real[name][item] |
| if set(s_items) != set(r_items): |
| diff_iter = _compare_content(name, item, s_items, r_items) |
| if diff_iter: |
| print( |
| '\n%s\n%s' % (common_util.COLORED_FAIL('Test error...'), |
| _TEST_ERROR % (name, item))) |
| print('%s %s contents are different:' % (name, item)) |
| for diff in diff_iter: |
| print(diff) |
| test_successful = False |
| if test_successful: |
| print(common_util.COLORED_PASS(_ALL_PASS)) |
| |
| |
| def _compare_content(module_name, item_type, s_items, r_items): |
| """Compare src or jar files' data of two dictionaries. |
| |
| Args: |
| module_name: the test module name. |
| item_type: the type is src or jar. |
| s_items: sample jars' items. |
| r_items: real jars' items. |
| |
| Returns: |
| An iterator of not equal sentences after comparison. |
| """ |
| if item_type == _SRCS: |
| cmp_iter1 = _compare_srcs_content(module_name, s_items, r_items, |
| _MSG_NOT_IN_PROJECT_FILE) |
| cmp_iter2 = _compare_srcs_content(module_name, r_items, s_items, |
| _MSG_NOT_IN_SAMPLE_DATA) |
| else: |
| cmp_iter1 = _compare_jars_content(module_name, s_items, r_items, |
| _MSG_NOT_IN_PROJECT_FILE) |
| cmp_iter2 = _compare_jars_content(module_name, r_items, s_items, |
| _MSG_NOT_IN_SAMPLE_DATA) |
| return itertools.chain(cmp_iter1, cmp_iter2) |
| |
| |
| def _compare_srcs_content(module_name, s_items, r_items, msg): |
| """Compare src or jar files' data of two dictionaries. |
| |
| Args: |
| module_name: the test module name. |
| s_items: sample jars' items. |
| r_items: real jars' items. |
| msg: the message will be written into log file. |
| |
| Returns: |
| An iterator of not equal sentences after comparison. |
| """ |
| for sample in s_items: |
| if not sample in r_items: |
| yield msg % (sample, module_name) |
| |
| |
| def _compare_jars_content(module_name, s_items, r_items, msg): |
| """Compare src or jar files' data of two dictionaries. |
| |
| AIDEGen treats the jars in folder names 'linux_glib_common' and |
| 'android_common' as the same content. If the paths are different only |
| because of these two names, we ignore it. |
| |
| Args: |
| module_name: the test module name. |
| s_items: sample jars' items. |
| r_items: real jars' items. |
| msg: the message will be written into log file. |
| |
| Returns: |
| An iterator of not equal sentences after comparison. |
| """ |
| for sample in s_items: |
| if not sample in r_items: |
| lnew = sample |
| if _LINUX_GLIBC_COMMON in sample: |
| lnew = sample.replace(_LINUX_GLIBC_COMMON, _ANDROID_COMMON) |
| else: |
| lnew = sample.replace(_ANDROID_COMMON, _LINUX_GLIBC_COMMON) |
| if not lnew in r_items: |
| yield msg % (sample, module_name) |
| |
| |
| # pylint: disable=broad-except |
| # pylint: disable=eval-used |
| @common_util.back_to_cwd |
| @common_util.time_logged |
| def _verify_aidegen(verified_file_path, forced_remove_bp_json): |
| """Verify various use cases of executing aidegen. |
| |
| There are two types of running commands: |
| 1. Use 'eval' to run the commands for present codes in aidegen_main.py, |
| such as: |
| aidegen_main.main(['tradefed', '-n', '-v']) |
| 2. Use 'subprocess.check_call' to run the commands for the binary codes of |
| aidegen such as: |
| aidegen tradefed -n -v |
| |
| Remove module_bp_java_deps.json in the beginning of running use cases. If |
| users need to remove module_bp_java_deps.json between each use case they |
| can set forced_remove_bp_json true. |
| |
| args: |
| verified_file_path: The json file path to be verified. |
| forced_remove_bp_json: Remove module_bp_java_deps.json for each use case |
| test. |
| |
| raises: |
| There are two type of exceptions: |
| 1. aidegen.lib.errors for projects' or modules' issues such as, |
| ProjectPathNotExistError. |
| 2. Any exceptions other than aidegen.lib.errors such as, |
| subprocess.CalledProcessError. |
| """ |
| os.chdir(common_util.get_android_root_dir()) |
| bp_json_path = common_util.get_blueprint_json_path() |
| use_eval = (verified_file_path == _VERIFY_COMMANDS_JSON) |
| try: |
| with open(verified_file_path, 'r') as jsfile: |
| data = json.load(jsfile) |
| except IOError as err: |
| raise errors.JsonFileNotExistError( |
| '%s does not exist, error: %s.' % (verified_file_path, err)) |
| |
| try: |
| subprocess.check_call( |
| ['build/soong/soong_ui.bash --make-mode clean', '-j'], |
| shell=True) |
| except subprocess.CalledProcessError: |
| print('"make clean" command failed.') |
| raise |
| |
| for use_case in data: |
| print('Use case "{}" is running.'.format(use_case)) |
| if forced_remove_bp_json and os.path.exists(bp_json_path): |
| os.remove(bp_json_path) |
| for cmd in data[use_case]: |
| print('Command "{}" is running.'.format(cmd)) |
| try: |
| if use_eval: |
| eval(cmd) |
| else: |
| subprocess.check_call(cmd, shell=True) |
| except (errors.ProjectOutsideAndroidRootError, |
| errors.ProjectPathNotExistError, |
| errors.NoModuleDefinedInModuleInfoError, |
| errors.IDENotExistError) as err: |
| print('"{}" raises error: {}.'.format(use_case, err)) |
| raise |
| except BaseException: |
| exc_type, _, _ = sys.exc_info() |
| print('"{}.{}" command {}.'.format( |
| use_case, cmd, common_util.COLORED_FAIL('executes failed'))) |
| raise BaseException( |
| 'Unexpected command "{}" exception: {}.'.format( |
| use_case, exc_type)) |
| print('"{}" command {}!'.format( |
| use_case, common_util.COLORED_PASS('test passed'))) |
| print(common_util.COLORED_PASS(_ALL_PASS)) |
| |
| |
| def main(argv): |
| """Main entry. |
| |
| Compare iml project files to the data recorded in single_module.json. |
| |
| Args: |
| argv: A list of system arguments. |
| """ |
| args = _parse_args(argv) |
| common_util.configure_logging(args.verbose) |
| if args.create_sample: |
| _create_sample_json_file() |
| elif args.use_cases_verified: |
| _verify_aidegen(_VERIFY_COMMANDS_JSON, args.remove_bp_json) |
| elif args.binary_upload_verified: |
| _verify_aidegen(_VERIFY_BINARY_JSON, args.remove_bp_json) |
| else: |
| test_some_sample_iml() |
| |
| |
| if __name__ == '__main__': |
| main(sys.argv[1:]) |