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