| Index: third_party/cq_client/validate_config.py
|
| diff --git a/third_party/cq_client/validate_config.py b/third_party/cq_client/validate_config.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..5e82036ef27ef174ccd898b86ad5006d1b55a4b8
|
| --- /dev/null
|
| +++ b/third_party/cq_client/validate_config.py
|
| @@ -0,0 +1,134 @@
|
| +#!/usr/bin/env python
|
| +# Copyright 2015 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +"""Validates a given CQ config file. May format the config in place.
|
| +
|
| +This script returns 0 when config is valid, other value if not. This allows this
|
| +script to be used in automated tests, e.g. in PRESUBMIT. It can also
|
| +automatically format valid configs in place: sort keys in dictionaries and
|
| +pretty-print config with 4-space indent.
|
| +"""
|
| +
|
| +import argparse
|
| +# The 'from google import protobuf' below was replaced to fix an issue where
|
| +# some users may have built-in google package installed on their system, which
|
| +# is incompatible with cq_pb2 below. This hack can be removed after
|
| +# http://crbug.com/503067 is resolved.
|
| +import protobuf26 as protobuf
|
| +import logging
|
| +import re
|
| +import sys
|
| +
|
| +from cq_client import cq_pb2
|
| +
|
| +
|
| +REQUIRED_FIELDS = [
|
| + 'version',
|
| + 'rietveld',
|
| + 'rietveld.url',
|
| + 'verifiers',
|
| + 'cq_name',
|
| +]
|
| +
|
| +LEGACY_FIELDS = [
|
| + 'svn_repo_url',
|
| + 'server_hooks_missing',
|
| + 'verifiers_with_patch',
|
| +]
|
| +
|
| +EMAIL_REGEXP = '^[^@]+@[^@]+\.[^@]+$'
|
| +
|
| +
|
| +def parse_args():
|
| + parser = argparse.ArgumentParser(description=sys.modules['__main__'].__doc__)
|
| + parser.add_argument('config_file', type=argparse.FileType('r'),
|
| + help='Path to the CQ config file.')
|
| + return parser.parse_args()
|
| +
|
| +
|
| +def has_field(message, field_path):
|
| + """Checks that at least one field with given path exist in the proto message.
|
| +
|
| + This function correctly handles repeated fields and will make sure that each
|
| + repeated field will have required sub-path, e.g. if 'abc' is a repeated field
|
| + and field_path is 'abc.def', then the function will only return True when each
|
| + entry for 'abc' will contain at least one value for 'def'.
|
| +
|
| + Args:
|
| + message: Protocol Buffer message to check.
|
| + field_path: Path to the target field separated with ".".
|
| +
|
| + Return:
|
| + True if at least one such field is explicitly set in the message.
|
| + """
|
| + path_parts = field_path.split('.', 1)
|
| + field_name = path_parts[0]
|
| + sub_path = path_parts[1] if len(path_parts) == 2 else None
|
| +
|
| + field_labels = {fd.name: fd.label for fd in message.DESCRIPTOR.fields}
|
| + repeated_field = (field_labels[field_name] ==
|
| + protobuf.descriptor.FieldDescriptor.LABEL_REPEATED)
|
| +
|
| + if sub_path:
|
| + field = getattr(message, field_name)
|
| + if repeated_field:
|
| + if not field:
|
| + return False
|
| + return all(has_field(entry, sub_path) for entry in field)
|
| + else:
|
| + return has_field(field, sub_path)
|
| + else:
|
| + if repeated_field:
|
| + return len(getattr(message, field_name)) > 0
|
| + else:
|
| + return message.HasField(field_name)
|
| +
|
| +
|
| +def validate(cq_config):
|
| + """Validates a CQ config and prints errors/warnings to the screen.
|
| +
|
| + Args:
|
| + cq_config (string): Unparsed text format of the CQ config proto.
|
| +
|
| + Returns:
|
| + 0 if the file is valid, non-zero otherwise.
|
| + """
|
| + try:
|
| + config = cq_pb2.Config()
|
| + protobuf.text_format.Merge(cq_config, config)
|
| + except protobuf.text_format.ParseError as e:
|
| + logging.error('Failed to parse config as protobuf:\n%s', e)
|
| + return 1
|
| +
|
| + for fname in REQUIRED_FIELDS:
|
| + if not has_field(config, fname):
|
| + logging.error('%s is a required field', fname)
|
| + return 1
|
| +
|
| + for fname in LEGACY_FIELDS:
|
| + if has_field(config, fname):
|
| + logging.warn('%s is a legacy field', fname)
|
| +
|
| +
|
| + for base in config.rietveld.project_bases:
|
| + try:
|
| + re.compile(base)
|
| + except Exception:
|
| + logging.error('failed to parse "%s" in project_bases as a regexp', base)
|
| + return 1
|
| +
|
| + if not config.rietveld.HasField('url'):
|
| + logging.error('rietveld.url is a required field')
|
| + return 1
|
| +
|
| + # TODO(sergiyb): For each field, check valid values depending on its
|
| + # semantics, e.g. email addresses, regular expressions etc.
|
| +
|
| + return 0
|
| +
|
| +
|
| +if __name__ == '__main__':
|
| + args = parse_args()
|
| + with args.config_file:
|
| + sys.exit(validate(args.config_file.read()))
|
|
|