blob: c42fd6168ec2a6d3fc05a56b67c76376334b18fb [file] [log] [blame]
#!/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.
"""
import argparse
import json
import os
import sys
IMPORTS = 'imports'
NAME = 'name'
OPTIONS = 'options'
PATH = 'path'
HOST = 'host'
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))
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.
raise InvalidTestMappingError(
'Failed to parse JSON file %s, error: %s' % (test_mapping_file, e))
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)
for filename in opts.files:
process_file(os.path.join(opts.project_dir, filename))
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))