huangcm
2025-02-28 b45e871a67cd1272e3da9ba5bd383f832b0f1824
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/usr/bin/python
# -*- coding:utf-8 -*-
# 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.
 
"""Validate TEST_MAPPING files in Android source code.
 
The goal of this script is to validate the format of TEST_MAPPING files:
1. It must be a valid json file.
2. Each test group must have a list of test that containing name and options.
3. Each import must have only one key `path` and one value for the path to
   import TEST_MAPPING files.
"""
 
from __future__ import print_function
 
import argparse
import json
import os
import sys
 
IMPORTS = 'imports'
NAME = 'name'
OPTIONS = 'options'
PATH = 'path'
HOST = 'host'
PREFERRED_TARGETS = 'preferred_targets'
FILE_PATTERNS = 'file_patterns'
TEST_MAPPING_URL = (
    'https://source.android.com/compatibility/tests/development/'
    'test-mapping')
 
 
class Error(Exception):
    """Base exception for all custom exceptions in this module."""
 
 
class InvalidTestMappingError(Error):
    """Exception to raise when detecting an invalid TEST_MAPPING file."""
 
 
def _validate_import(entry, test_mapping_file):
    """Validate an import setting.
 
    Args:
        entry: A dictionary of an import setting.
        test_mapping_file: Path to the TEST_MAPPING file to be validated.
 
    Raises:
        InvalidTestMappingError: if the import setting is invalid.
    """
    if len(entry) != 1:
        raise InvalidTestMappingError(
            'Invalid import config in test mapping file %s. each import can '
            'only have one `path` setting. Failed entry: %s' %
            (test_mapping_file, entry))
    if entry.keys()[0] != PATH:
        raise InvalidTestMappingError(
            'Invalid import config in test mapping file %s. import can only '
            'have one `path` setting. Failed entry: %s' %
            (test_mapping_file, entry))
 
 
def _validate_test(test, test_mapping_file):
    """Validate a test declaration.
 
    Args:
        entry: A dictionary of a test declaration.
        test_mapping_file: Path to the TEST_MAPPING file to be validated.
 
    Raises:
        InvalidTestMappingError: if the a test declaration is invalid.
    """
    if NAME not in test:
        raise InvalidTestMappingError(
            'Invalid test config in test mapping file %s. test config must '
            'a `name` setting. Failed test config: %s' %
            (test_mapping_file, test))
    if not isinstance(test.get(HOST, False), bool):
        raise InvalidTestMappingError(
            'Invalid test config in test mapping file %s. `host` setting in '
            'test config can only have boolean value of `true` or `false`. '
            'Failed test config: %s' % (test_mapping_file, test))
    preferred_targets = test.get(PREFERRED_TARGETS, [])
    if (not isinstance(preferred_targets, list) or
            any(not isinstance(t, basestring) for t in preferred_targets)):
        raise InvalidTestMappingError(
            'Invalid test config in test mapping file %s. `preferred_targets` '
            'setting in test config can only be a list of strings. Failed test '
            'config: %s' % (test_mapping_file, test))
    file_patterns = test.get(FILE_PATTERNS, [])
    if (not isinstance(file_patterns, list) or
            any(not isinstance(p, basestring) for p in file_patterns)):
        raise InvalidTestMappingError(
            'Invalid test config in test mapping file %s. `file_patterns` '
            'setting in test config can only be a list of strings. Failed test '
            'config: %s' % (test_mapping_file, test))
    for option in test.get(OPTIONS, []):
        if len(option) != 1:
            raise InvalidTestMappingError(
                'Invalid option setting in test mapping file %s. each option '
                'setting can only have one key-val setting. Failed entry: %s' %
                (test_mapping_file, option))
 
 
def _load_file(test_mapping_file):
    """Load a TEST_MAPPING file as a json file."""
    try:
        with open(test_mapping_file) as file_obj:
            return json.load(file_obj)
    except ValueError as e:
        # The file is not a valid JSON file.
        print(
            'Failed to parse JSON file %s, error: %s' % (test_mapping_file, e),
            file=sys.stderr)
        raise
 
 
def process_file(test_mapping_file):
    """Validate a TEST_MAPPING file."""
    test_mapping = _load_file(test_mapping_file)
    # Validate imports.
    for import_entry in test_mapping.get(IMPORTS, []):
        _validate_import(import_entry, test_mapping_file)
    # Validate tests.
    all_tests = [test for group, tests in test_mapping.items()
                 if group != IMPORTS for test in tests]
    for test in all_tests:
        _validate_test(test, test_mapping_file)
 
 
def get_parser():
    """Return a command line parser."""
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('project_dir')
    parser.add_argument('files', nargs='+')
    return parser
 
 
def main(argv):
    parser = get_parser()
    opts = parser.parse_args(argv)
    try:
        for filename in opts.files:
            process_file(os.path.join(opts.project_dir, filename))
    except:
        print('Visit %s for details about the format of TEST_MAPPING '
              'file.' % TEST_MAPPING_URL, file=sys.stderr)
        raise
 
 
if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))