1#!/usr/bin/env python3
2#
3# Copyright 2018 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Functional test for aidegen project files."""
18
19from __future__ import absolute_import
20
21import argparse
22import functools
23import itertools
24import json
25import logging
26import os
27import subprocess
28import sys
29import xml.etree.ElementTree
30import xml.parsers.expat
31
32from aidegen import aidegen_main
33from aidegen import constant
34from aidegen.lib import clion_project_file_gen
35from aidegen.lib import common_util
36from aidegen.lib import errors
37from aidegen.lib import module_info_util
38from aidegen.lib import project_config
39from aidegen.lib import project_file_gen
40
41from atest import module_info
42
43_PRODUCT_DIR = '$PROJECT_DIR$'
44_ROOT_DIR = os.path.join(common_util.get_android_root_dir(),
45                         'tools/asuite/aidegen_functional_test')
46_TEST_DATA_PATH = os.path.join(_ROOT_DIR, 'test_data')
47_VERIFY_COMMANDS_JSON = os.path.join(_TEST_DATA_PATH, 'verify_commands.json')
48_GOLDEN_SAMPLES_JSON = os.path.join(_TEST_DATA_PATH, 'golden_samples.json')
49_VERIFY_BINARY_JSON = os.path.join(_TEST_DATA_PATH, 'verify_binary_upload.json')
50_ANDROID_COMMON = 'android_common'
51_LINUX_GLIBC_COMMON = 'linux_glibc_common'
52_SRCS = 'srcs'
53_JARS = 'jars'
54_URL = 'url'
55_TEST_ERROR = 'AIDEGen functional test error: {}-{} is different.'
56_MSG_NOT_IN_PROJECT_FILE = ('{} is expected, but not found in the created '
57                            'project file: {}')
58_MSG_NOT_IN_SAMPLE_DATA = ('{} is unexpected, but found in the created project '
59                           'file: {}')
60_ALL_PASS = 'All tests passed!'
61_GIT_COMMIT_ID_JSON = os.path.join(
62    _TEST_DATA_PATH, 'baseline_code_commit_id.json')
63_GIT = 'git'
64_CHECKOUT = 'checkout'
65_BRANCH = 'branch'
66_COMMIT = 'commit'
67_LOG = 'log'
68_ALL = '--all'
69_COMMIT_ID_NOT_EXIST_ERROR = ('Commit ID: {} does not exist in path: {}. '
70                              'Please use "git log" command to check if it '
71                              'exists. If it does not, try to update your '
72                              'source files to the latest version or do not '
73                              'use "repo init --depth=1" command.')
74_GIT_LOG_ERROR = 'Command "git log -n 1" failed.'
75_BE_REPLACED = '${config.X86_64GccRoot}'
76_TO_REPLACE = 'prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9'
77
78
79def _parse_args(args):
80    """Parse command line arguments.
81
82    Args:
83        args: A list of arguments.
84
85    Returns:
86        An argparse.Namespace object holding parsed args.
87    """
88    parser = argparse.ArgumentParser(
89        description=__doc__,
90        formatter_class=argparse.RawDescriptionHelpFormatter,
91        usage='aidegen_functional_test [-c | -u | -b | -a] -v -r')
92    group = parser.add_mutually_exclusive_group()
93    parser.required = False
94    parser.add_argument(
95        'targets',
96        type=str,
97        nargs='*',
98        default=[''],
99        help='Android module name or path.e.g. frameworks/base')
100    group.add_argument(
101        '-c',
102        '--create-sample',
103        action='store_true',
104        dest='create_sample',
105        help=('Create aidegen project files and write data to sample json file '
106              'for aidegen_functional_test to compare.'))
107    parser.add_argument(
108        '-v',
109        '--verbose',
110        action='store_true',
111        help='Show DEBUG level logging.')
112    parser.add_argument(
113        '-r',
114        '--remove_bp_json',
115        action='store_true',
116        help='Remove module_bp_java_deps.json for each use case test.')
117    group.add_argument(
118        '-u',
119        '--use_cases',
120        action='store_true',
121        dest='use_cases_verified',
122        help='Verify various use cases of executing aidegen.')
123    group.add_argument(
124        '-b',
125        action='store_true',
126        dest='binary_upload_verified',
127        help=('Verify aidegen\'s use cases by executing different aidegen '
128              'commands.'))
129    group.add_argument(
130        '-a',
131        '--test-all',
132        action='store_true',
133        dest='test_all_samples',
134        help='Test all modules listed in module-info.json.')
135    group.add_argument(
136        '-n',
137        '--compare-sample-native',
138        action='store_true',
139        dest='compare_sample_native',
140        help=('Compare if sample native project file is the same as generated '
141              'by the build system.'))
142    return parser.parse_args(args)
143
144
145def _import_project_file_xml_etree(filename):
146    """Import iml project file and load its data into a dictionary.
147
148    Args:
149        filename: The input project file name.
150
151    Returns:
152        A dictionary contains dependent files' data of project file's contents.
153        The samples are like:
154        "srcs": [
155            ...
156            "file://$PROJECT_DIR$/frameworks/base/cmds/am/src",
157            "file://$PROJECT_DIR$/frameworks/base/cmds/appwidget/src",
158            ...
159        ]
160        "jars": [
161            ...
162            "jar://$PROJECT_DIR$/out/host/common/obj/**/classes-header.jar!/"
163            ...
164        ]
165
166    Raises:
167        EnvironmentError and xml parsing or format errors.
168    """
169    data = {}
170    try:
171        tree = xml.etree.ElementTree.parse(filename)
172        data[_SRCS] = []
173        root = tree.getroot()
174        for element in root.iter('sourceFolder'):
175            src = element.get(_URL).replace(common_util.get_android_root_dir(),
176                                            _PRODUCT_DIR)
177            data[_SRCS].append(src)
178        data[_JARS] = []
179        for element in root.iter('root'):
180            jar = element.get(_URL).replace(common_util.get_android_root_dir(),
181                                            _PRODUCT_DIR)
182            data[_JARS].append(jar)
183    except (EnvironmentError, ValueError, LookupError,
184            xml.parsers.expat.ExpatError) as err:
185        print("{0}: import error: {1}".format(os.path.basename(filename), err))
186        raise
187    return data
188
189
190def _get_project_file_names(abs_path):
191    """Get project file name and depenencies name by relative path.
192
193    Args:
194        abs_path: an absolute module's path.
195
196    Returns:
197        file_name: a string of the project file name.
198        dep_name: a string of the merged project and dependencies file's name,
199                  e.g., frameworks-dependencies.iml.
200    """
201    code_name = project_file_gen.ProjectFileGenerator.get_unique_iml_name(
202        abs_path)
203    file_name = ''.join([code_name, '.iml'])
204    dep_name = ''.join([constant.KEY_DEPENDENCIES, '.iml'])
205    return file_name, dep_name
206
207
208def _get_unique_module_name(rel_path, filename):
209    """Get a unique project file name or dependencies name by relative path.
210
211    Args:
212        rel_path: a relative module's path to aosp root path.
213        filename: the file name to be generated in module_in type file name.
214
215    Returns:
216        A string, the unique file name for the whole module-info.json.
217    """
218    code_names = rel_path.split(os.sep)
219    code_names[-1] = filename
220    return '-'.join(code_names)
221
222
223def _get_git_current_commit_id(abs_path):
224    """Get target's git checkout command list.
225
226    When we run command 'git log -n 1' and get the top first git log record, the
227    commit id is next to the specific word 'commit'.
228
229    Args:
230        abs_path: a string of the absolute path of the target branch.
231
232    Return:
233        The current git commit id.
234
235    Raises:
236        Call subprocess.check_output cause subprocess.CalledProcessError.
237    """
238    cwd = os.getcwd()
239    os.chdir(abs_path)
240    git_log_cmds = [_GIT, _LOG, '-n', '1']
241    try:
242        out_put = subprocess.check_output(git_log_cmds).decode("utf-8")
243    except subprocess.CalledProcessError:
244        logging.error(_GIT_LOG_ERROR)
245        raise
246    finally:
247        os.chdir(cwd)
248    com_list = out_put.split()
249    return com_list[com_list.index(_COMMIT) + 1]
250
251
252def _get_commit_id_dictionary():
253    """Get commit id from dictionary of key, value 'module': 'commit id'."""
254    data_id_dict = {}
255    with open(_GIT_COMMIT_ID_JSON, 'r') as jsfile:
256        data_id_dict = json.load(jsfile)
257    return data_id_dict
258
259
260def _git_checkout_commit_id(abs_path, commit_id):
261    """Command to checkout specific commit id.
262
263    Change directory to the module's absolute path which users want to get its
264    specific commit id.
265
266    Args:
267        abs_path: the absolute path of the target branch. E.g., abs_path/.git
268        commit_id: the commit id users want to checkout.
269
270    Raises:
271        Call git checkout commit id failed, raise errors.CommitIDNotExistError.
272    """
273    git_chekout_cmds = [_GIT, _CHECKOUT, commit_id]
274    cwd = os.getcwd()
275    try:
276        os.chdir(abs_path)
277        subprocess.check_output(git_chekout_cmds)
278    except subprocess.CalledProcessError:
279        err = _COMMIT_ID_NOT_EXIST_ERROR.format(commit_id, abs_path)
280        logging.error(err)
281        raise errors.CommitIDNotExistError(err)
282    finally:
283        os.chdir(cwd)
284
285
286def _git_checkout_target_commit_id(target, commit_id):
287    """Command to checkout target commit id.
288
289    Switch code base to specific commit id which is kept in data_id_dict with
290    target: commit_id as key: value. If the data is missing in data_id_dict, the
291    target isn't a selected golden sample raise error for it.
292
293    Args:
294        target: the string of target's module name or module path to checkout
295                the relevant git to its specific commit id.
296        commit_id: a string represent target's specific commit id.
297
298    Returns:
299        current_commit_id: the current commit id of the target which should be
300            switched back to.
301    """
302    atest_module_info = module_info.ModuleInfo()
303    _, abs_path = common_util.get_related_paths(atest_module_info, target)
304    current_commit_id = _get_git_current_commit_id(abs_path)
305    _git_checkout_commit_id(abs_path, commit_id)
306    return current_commit_id
307
308
309def _checkout_baseline_code_to_spec_commit_id():
310    """Get a dict of target, commit id listed in baseline_code_commit_id.json.
311
312    To make sure all samples run on the same environment, we need to keep all
313    the baseline code in a specific commit id. For example, all samples should
314    be created in the same specific commit id of the baseline code
315    'frameworks/base' for comparison except 'frameworks/base' itself.
316
317    Returns:
318        A dictionary contains target, specific and current commit id.
319    """
320    spec_and_cur_commit_id_dict = {}
321    data_id_dict = _get_commit_id_dictionary()
322    for target in data_id_dict:
323        commit_id = data_id_dict.get(target, '')
324        current_commit_id = _git_checkout_target_commit_id(target, commit_id)
325        spec_and_cur_commit_id_dict[target] = {}
326        spec_and_cur_commit_id_dict[target]['current'] = current_commit_id
327    return spec_and_cur_commit_id_dict
328
329
330def _generate_target_real_iml_data(target):
331    """Generate single target's real iml file content's data.
332
333    Args:
334        target: Android module name or path to be create iml data.
335
336    Returns:
337        data: A dictionary contains key: unique file name and value: iml
338              content.
339    """
340    data = {}
341    try:
342        aidegen_main.main([target, '-s', '-n', '-v'])
343    except (errors.FakeModuleError,
344            errors.ProjectOutsideAndroidRootError,
345            errors.ProjectPathNotExistError,
346            errors.NoModuleDefinedInModuleInfoError) as err:
347        logging.error(str(err))
348        return data
349
350    atest_module_info = module_info.ModuleInfo()
351    rel_path, abs_path = common_util.get_related_paths(atest_module_info,
352                                                       target)
353    for filename in iter(_get_project_file_names(abs_path)):
354        real_iml_file = os.path.join(abs_path, filename)
355        item_name = _get_unique_module_name(rel_path, filename)
356        data[item_name] = _import_project_file_xml_etree(real_iml_file)
357    return data
358
359
360def _generate_sample_json(test_list):
361    """Generate sample iml data.
362
363    We use all baseline code samples on the version of their own specific commit
364    id which is kept in _GIT_COMMIT_ID_JSON file. We need to switch back to
365    their current commit ids after generating golden samples' data.
366
367    Args:
368        test_list: a list of module name and module path.
369    Returns:
370        data: a dictionary contains dependent files' data of project file's
371              contents.
372        The sample is like:
373            "frameworks-base.iml": {
374                "srcs": [
375                    ....
376                    "file://$PROJECT_DIR$/frameworks/base/cmds/am/src",
377                    "file://$PROJECT_DIR$/frameworks/base/cmds/appwidget/src",
378                    ....
379                ],
380                "jars": [
381                    ....
382                    "jar://$PROJECT_DIR$/out/target/common/**/aapt2.srcjar!/",
383                    ....
384                ]
385            }
386    """
387    _make_clean()
388    data = {}
389    spec_and_cur_commit_id_dict = _checkout_baseline_code_to_spec_commit_id()
390    for target in test_list:
391        data.update(_generate_target_real_iml_data(target))
392    atest_module_info = module_info.ModuleInfo()
393    for target in spec_and_cur_commit_id_dict:
394        _, abs_path = common_util.get_related_paths(atest_module_info, target)
395        _git_checkout_commit_id(
396            abs_path, spec_and_cur_commit_id_dict[target]['current'])
397    return data
398
399
400def _create_some_sample_json_file(targets):
401    """Write some samples' iml data into a json file.
402
403    Args:
404        targets: Android module name or path to be create iml data.
405
406    linked_function: _generate_sample_json()
407    """
408    data = _generate_sample_json(targets)
409    data_sample = {}
410    with open(_GOLDEN_SAMPLES_JSON, 'r') as infile:
411        try:
412            data_sample = json.load(infile)
413        except json.JSONDecodeError as err:
414            print("Json decode error: {}".format(err))
415            data_sample = {}
416    data_sample.update(data)
417    with open(_GOLDEN_SAMPLES_JSON, 'w') as outfile:
418        json.dump(data_sample, outfile, indent=4, sort_keys=False)
419
420
421def test_samples(func):
422    """Decorate a function to deal with preparing and verifying staffs of it.
423
424    Args:
425        func: a function is to be compared its iml data with the json file's
426              data.
427
428    Returns:
429        The wrapper function.
430    """
431
432    @functools.wraps(func)
433    def wrapper(*args, **kwargs):
434        """A wrapper function."""
435
436        test_successful = True
437        with open(_GOLDEN_SAMPLES_JSON, 'r') as outfile:
438            data_sample = json.load(outfile)
439
440        data_real = func(*args, **kwargs)
441
442        for name in data_real:
443            for item in [_SRCS, _JARS]:
444                s_items = data_sample[name][item]
445                r_items = data_real[name][item]
446                if set(s_items) != set(r_items):
447                    diff_iter = _compare_content(name, item, s_items, r_items)
448                    if diff_iter:
449                        print('\n{} {}'.format(
450                            common_util.COLORED_INFO('Test error:'),
451                            _TEST_ERROR.format(name, item)))
452                        print('{} {} contents are different:'.format(
453                            name, item))
454                        for diff in diff_iter:
455                            print(diff)
456                        test_successful = False
457        if test_successful:
458            print(common_util.COLORED_PASS(_ALL_PASS))
459        return test_successful
460
461    return wrapper
462
463
464@test_samples
465def _test_some_sample_iml(targets=None):
466    """Compare with sample iml's data to assure the project's contents is right.
467
468    Args:
469        targets: Android module name or path to be create iml data.
470    """
471    if targets:
472        return _generate_sample_json(targets)
473    return _generate_sample_json(_get_commit_id_dictionary().keys())
474
475
476@test_samples
477def _test_all_samples_iml():
478    """Compare all imls' data with all samples' data.
479
480    It's to make sure each iml's contents is right. The function is implemented
481    but hasn't been used yet.
482    """
483    all_module_list = module_info.ModuleInfo().name_to_module_info.keys()
484    return _generate_sample_json(all_module_list)
485
486
487def _compare_content(module_name, item_type, s_items, r_items):
488    """Compare src or jar files' data of two dictionaries.
489
490    Args:
491        module_name: the test module name.
492        item_type: the type is src or jar.
493        s_items: sample jars' items.
494        r_items: real jars' items.
495
496    Returns:
497        An iterator of not equal sentences after comparison.
498    """
499    if item_type == _SRCS:
500        cmp_iter1 = _compare_srcs_content(module_name, s_items, r_items,
501                                          _MSG_NOT_IN_PROJECT_FILE)
502        cmp_iter2 = _compare_srcs_content(module_name, r_items, s_items,
503                                          _MSG_NOT_IN_SAMPLE_DATA)
504    else:
505        cmp_iter1 = _compare_jars_content(module_name, s_items, r_items,
506                                          _MSG_NOT_IN_PROJECT_FILE)
507        cmp_iter2 = _compare_jars_content(module_name, r_items, s_items,
508                                          _MSG_NOT_IN_SAMPLE_DATA)
509    return itertools.chain(cmp_iter1, cmp_iter2)
510
511
512def _compare_srcs_content(module_name, s_items, r_items, msg):
513    """Compare src or jar files' data of two dictionaries.
514
515    Args:
516        module_name: the test module name.
517        s_items: sample jars' items.
518        r_items: real jars' items.
519        msg: the message will be written into log file.
520
521    Returns:
522        An iterator of not equal sentences after comparison.
523    """
524    for sample in s_items:
525        if sample not in r_items:
526            yield msg.format(sample, module_name)
527
528
529def _compare_jars_content(module_name, s_items, r_items, msg):
530    """Compare src or jar files' data of two dictionaries.
531
532    AIDEGen treats the jars in folder names 'linux_glib_common' and
533    'android_common' as the same content. If the paths are different only
534    because of these two names, we ignore it.
535
536    Args:
537        module_name: the test module name.
538        s_items: sample jars' items.
539        r_items: real jars' items.
540        msg: the message will be written into log file.
541
542    Returns:
543        An iterator of not equal sentences after comparison.
544    """
545    for sample in s_items:
546        if sample not in r_items:
547            lnew = sample
548            if constant.LINUX_GLIBC_COMMON in sample:
549                lnew = sample.replace(constant.LINUX_GLIBC_COMMON,
550                                      constant.ANDROID_COMMON)
551            else:
552                lnew = sample.replace(constant.ANDROID_COMMON,
553                                      constant.LINUX_GLIBC_COMMON)
554            if not lnew in r_items:
555                yield msg.format(sample, module_name)
556
557
558# pylint: disable=broad-except
559# pylint: disable=eval-used
560@common_util.back_to_cwd
561@common_util.time_logged
562def _verify_aidegen(verified_file_path, forced_remove_bp_json):
563    """Verify various use cases of executing aidegen.
564
565    There are two types of running commands:
566    1. Use 'eval' to run the commands for present codes in aidegen_main.py,
567       such as:
568           aidegen_main.main(['tradefed', '-n', '-v'])
569    2. Use 'subprocess.check_call' to run the commands for the binary codes of
570       aidegen such as:
571       aidegen tradefed -n -v
572
573    Remove module_bp_java_deps.json in the beginning of running use cases. If
574    users need to remove module_bp_java_deps.json between each use case they
575    can set forced_remove_bp_json true.
576
577    Args:
578        verified_file_path: The json file path to be verified.
579        forced_remove_bp_json: Remove module_bp_java_deps.json for each use case
580                               test.
581
582    Raises:
583        There are two type of exceptions:
584        1. aidegen.lib.errors for projects' or modules' issues such as,
585           ProjectPathNotExistError.
586        2. Any exceptions other than aidegen.lib.errors such as,
587           subprocess.CalledProcessError.
588    """
589    bp_json_path = common_util.get_blueprint_json_path(
590        constant.BLUEPRINT_JAVA_JSONFILE_NAME)
591    use_eval = (verified_file_path == _VERIFY_COMMANDS_JSON)
592    try:
593        with open(verified_file_path, 'r') as jsfile:
594            data = json.load(jsfile)
595    except IOError as err:
596        raise errors.JsonFileNotExistError(
597            '%s does not exist, error: %s.' % (verified_file_path, err))
598
599    _make_clean()
600
601    _compare_sample_native_content()
602    os.chdir(common_util.get_android_root_dir())
603    for use_case in data:
604        print('Use case "{}" is running.'.format(use_case))
605        if forced_remove_bp_json and os.path.exists(bp_json_path):
606            os.remove(bp_json_path)
607        for cmd in data[use_case]:
608            print('Command "{}" is running.'.format(cmd))
609            try:
610                if use_eval:
611                    eval(cmd)
612                else:
613                    subprocess.check_call(cmd, shell=True)
614            except (errors.ProjectOutsideAndroidRootError,
615                    errors.ProjectPathNotExistError,
616                    errors.NoModuleDefinedInModuleInfoError,
617                    errors.IDENotExistError) as err:
618                print('"{}" raises error: {}.'.format(use_case, err))
619                continue
620            except BaseException:
621                exc_type, _, _ = sys.exc_info()
622                print('"{}.{}" command {}.'.format(
623                    use_case, cmd, common_util.COLORED_FAIL('executes failed')))
624                raise BaseException(
625                    'Unexpected command "{}" exception: {}.'.format(
626                        use_case, exc_type))
627        print('"{}" command {}!'.format(
628            use_case, common_util.COLORED_PASS('test passed')))
629    print(common_util.COLORED_PASS(_ALL_PASS))
630
631
632@common_util.back_to_cwd
633def _make_clean():
634    """Make a command to clean out folder for a pure environment to test.
635
636    Raises:
637        Call subprocess.check_call to execute
638        'build/soong/soong_ui.bash --make-mode clean' and cause
639        subprocess.CalledProcessError.
640    """
641    try:
642        os.chdir(common_util.get_android_root_dir())
643        subprocess.check_call(
644            ['build/soong/soong_ui.bash --make-mode clean', '-j'],
645            shell=True)
646    except subprocess.CalledProcessError:
647        print('"build/soong/soong_ui.bash --make-mode clean" command failed.')
648        raise
649
650
651def _read_file_content(path):
652    """Read file's content.
653
654    Args:
655        path: Path of input file.
656
657    Returns:
658        A list of content strings.
659    """
660    with open(path, encoding='utf8') as template:
661        contents = []
662        for cnt in template:
663            if cnt.startswith('#'):
664                continue
665            contents.append(cnt.rstrip())
666        return contents
667
668
669# pylint: disable=protected-access
670def _compare_sample_native_content():
671    """Compares 'libui' sample module's project file.
672
673    Compares 'libui' sample module's project file generated by AIDEGen with that
674    generated by the soong build system. Check if their contents are the same.
675    There should be only one different:
676        ${config.X86_64GccRoot} # in the soong build sytem
677        becomes
678        prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9 # in AIDEGen
679    """
680    target_arch_variant = 'x86_64'
681    env_on = {
682        'TARGET_PRODUCT': 'aosp_x86_64',
683        'TARGET_BUILD_VARIANT': 'eng',
684        'TARGET_ARCH_VARIANT': target_arch_variant,
685        'SOONG_COLLECT_JAVA_DEPS': 'true',
686        'SOONG_GEN_CMAKEFILES': '1',
687        'SOONG_COLLECT_CC_DEPS': '1'
688    }
689
690    try:
691        project_config.ProjectConfig(
692            aidegen_main._parse_args(['-n', '-v'])).init_environment()
693        module_info_util.generate_merged_module_info(env_on)
694        cc_path = os.path.join(common_util.get_soong_out_path(),
695                               constant.BLUEPRINT_CC_JSONFILE_NAME)
696        mod_name = 'libui'
697        mod_info = common_util.get_json_dict(cc_path)['modules'][mod_name]
698        if mod_info:
699            clion_project_file_gen.CLionProjectFileGenerator(
700                mod_info).generate_cmakelists_file()
701            out_dir = os.path.join(common_util.get_android_root_dir(),
702                                   common_util.get_android_out_dir(),
703                                   constant.RELATIVE_NATIVE_PATH,
704                                   mod_info['path'][0])
705            content1 = _read_file_content(os.path.join(
706                out_dir, mod_name, constant.CLION_PROJECT_FILE_NAME))
707            cc_file_name = ''.join(
708                [mod_name, '-', target_arch_variant, '-android'])
709            cc_file_path = os.path.join(
710                out_dir, cc_file_name, constant.CLION_PROJECT_FILE_NAME)
711            content2 = _read_file_content(cc_file_path)
712            same = True
713            for lino, (cnt1, cnt2) in enumerate(
714                    zip(content1, content2), start=1):
715                if _BE_REPLACED in cnt2:
716                    cnt2 = cnt2.replace(_BE_REPLACED, _TO_REPLACE)
717                if cnt1 != cnt2:
718                    print('Contents {} and {} are different in line:{}.'.format(
719                        cnt1, cnt2, lino))
720                    same = False
721            if same:
722                print('Files {} and {} are the same.'.format(
723                    mod_name, cc_file_name))
724    except errors.BuildFailureError:
725        print('Compare native content failed.')
726
727
728def main(argv):
729    """Main entry.
730
731    1. Create the iml file data of each module in module-info.json and write it
732       into single_module.json.
733    2. Verify every use case of AIDEGen.
734    3. Compare all or some iml project files' data to the data recorded in
735       single_module.json.
736
737    Args:
738        argv: A list of system arguments.
739    """
740    args = _parse_args(argv)
741    common_util.configure_logging(args.verbose)
742    os.environ[constant.AIDEGEN_TEST_MODE] = 'true'
743    if args.create_sample:
744        _create_some_sample_json_file(args.targets)
745    elif args.use_cases_verified:
746        _verify_aidegen(_VERIFY_COMMANDS_JSON, args.remove_bp_json)
747    elif args.binary_upload_verified:
748        _verify_aidegen(_VERIFY_BINARY_JSON, args.remove_bp_json)
749    elif args.test_all_samples:
750        _test_all_samples_iml()
751    elif args.compare_sample_native:
752        _compare_sample_native_content()
753    else:
754        if not args.targets[0]:
755            _test_some_sample_iml()
756        else:
757            _test_some_sample_iml(args.targets)
758    del os.environ[constant.AIDEGEN_TEST_MODE]
759
760
761if __name__ == '__main__':
762    main(sys.argv[1:])
763