#!/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 sys import xml.etree.ElementTree import xml.parsers.expat import aidegen.lib.errors from aidegen import aidegen_main from aidegen.lib.common_util import get_related_paths from aidegen.lib.common_util import time_logged from atest import constants from atest import module_info from atest import atest_utils _ANDROID_ROOT_PATH = os.environ.get(constants.ANDROID_BUILD_TOP) _ROOT_DIR = os.path.join(_ANDROID_ROOT_PATH, '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') _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 class instance holding parsed args. """ parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, usage='aidegen_functional_test [-c | -v]') 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.')) group.add_argument( '-v', '--verify', action='store_true', dest='verify_aidegen', help='Verify various use cases of executing aidegen.') 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. """ data = {} try: tree = xml.etree.ElementTree.parse(filename) data[_SRCS] = [] root = tree.getroot() for element in root.iter('sourceFolder'): src = element.get(_URL).replace(_ANDROID_ROOT_PATH, _PRODUCT_DIR) data[_SRCS].append(src) data[_JARS] = [] for element in root.iter('root'): jar = element.get(_URL).replace(_ANDROID_ROOT_PATH, _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 and write into a json file.""" atest_module_info = module_info.ModuleInfo() data = {} for target, filelist in _TEST_IML_DICT.items(): aidegen_main.main([target, '-n']) _, abs_path = 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' % (atest_utils.colorize( 'Test error...', constants.RED), _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(atest_utils.colorize(_ALL_PASS, constants.GREEN)) 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. 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 @time_logged def _verify_aidegen(): """Verify various use cases of executing aidegen.""" with open(_VERIFY_COMMANDS_JSON, 'r') as jsfile: data = json.load(jsfile) for use_case in data: for cmd in data[use_case]: try: eval(cmd) except (aidegen.lib.errors.ProjectOutsideAndroidRootError, aidegen.lib.errors.ProjectPathNotExistError, aidegen.lib.errors.NoModuleDefinedInModuleInfoError, aidegen.lib.errors.IDENotExistError) as err: print('{} command has raise error: {}.'.format(use_case, err)) except Exception as exp: print('{}.{} command {}.'.format( use_case, cmd, atest_utils.colorize('executes failed', constants.RED))) raise Exception( 'Unexpected command {} exception {}.'.format(use_case, exp)) print('{} command {}!'.format( use_case, atest_utils.colorize('test passed', constants.GREEN))) print(atest_utils.colorize(_ALL_PASS, constants.GREEN)) 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) if args.create_sample: _create_sample_json_file() elif args.verify_aidegen: _verify_aidegen() else: test_some_sample_iml() if __name__ == '__main__': main(sys.argv[1:])