普通文本  |  278行  |  9.54 KB

#!/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:])