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