OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 """Validates a given CQ config file. May format the config in place. |
| 6 |
| 7 This script returns 0 when config is valid, other value if not. This allows this |
| 8 script to be used in automated tests, e.g. in PRESUBMIT. It can also |
| 9 automatically format valid configs in place: sort keys in dictionaries and |
| 10 pretty-print config with 4-space indent. |
| 11 """ |
| 12 |
| 13 import argparse |
| 14 # The 'from google import protobuf' below was replaced to fix an issue where |
| 15 # some users may have built-in google package installed on their system, which |
| 16 # is incompatible with cq_pb2 below. This hack can be removed after |
| 17 # http://crbug.com/503067 is resolved. |
| 18 import protobuf26 as protobuf |
| 19 import logging |
| 20 import re |
| 21 import sys |
| 22 |
| 23 from cq_client import cq_pb2 |
| 24 |
| 25 |
| 26 REQUIRED_FIELDS = [ |
| 27 'version', |
| 28 'rietveld', |
| 29 'rietveld.url', |
| 30 'verifiers', |
| 31 'cq_name', |
| 32 ] |
| 33 |
| 34 LEGACY_FIELDS = [ |
| 35 'svn_repo_url', |
| 36 'server_hooks_missing', |
| 37 'verifiers_with_patch', |
| 38 ] |
| 39 |
| 40 EMAIL_REGEXP = '^[^@]+@[^@]+\.[^@]+$' |
| 41 |
| 42 |
| 43 def parse_args(): |
| 44 parser = argparse.ArgumentParser(description=sys.modules['__main__'].__doc__) |
| 45 parser.add_argument('config_file', type=argparse.FileType('r'), |
| 46 help='Path to the CQ config file.') |
| 47 return parser.parse_args() |
| 48 |
| 49 |
| 50 def has_field(message, field_path): |
| 51 """Checks that at least one field with given path exist in the proto message. |
| 52 |
| 53 This function correctly handles repeated fields and will make sure that each |
| 54 repeated field will have required sub-path, e.g. if 'abc' is a repeated field |
| 55 and field_path is 'abc.def', then the function will only return True when each |
| 56 entry for 'abc' will contain at least one value for 'def'. |
| 57 |
| 58 Args: |
| 59 message: Protocol Buffer message to check. |
| 60 field_path: Path to the target field separated with ".". |
| 61 |
| 62 Return: |
| 63 True if at least one such field is explicitly set in the message. |
| 64 """ |
| 65 path_parts = field_path.split('.', 1) |
| 66 field_name = path_parts[0] |
| 67 sub_path = path_parts[1] if len(path_parts) == 2 else None |
| 68 |
| 69 field_labels = {fd.name: fd.label for fd in message.DESCRIPTOR.fields} |
| 70 repeated_field = (field_labels[field_name] == |
| 71 protobuf.descriptor.FieldDescriptor.LABEL_REPEATED) |
| 72 |
| 73 if sub_path: |
| 74 field = getattr(message, field_name) |
| 75 if repeated_field: |
| 76 if not field: |
| 77 return False |
| 78 return all(has_field(entry, sub_path) for entry in field) |
| 79 else: |
| 80 return has_field(field, sub_path) |
| 81 else: |
| 82 if repeated_field: |
| 83 return len(getattr(message, field_name)) > 0 |
| 84 else: |
| 85 return message.HasField(field_name) |
| 86 |
| 87 |
| 88 def validate(cq_config): |
| 89 """Validates a CQ config and prints errors/warnings to the screen. |
| 90 |
| 91 Args: |
| 92 cq_config (string): Unparsed text format of the CQ config proto. |
| 93 |
| 94 Returns: |
| 95 0 if the file is valid, non-zero otherwise. |
| 96 """ |
| 97 try: |
| 98 config = cq_pb2.Config() |
| 99 protobuf.text_format.Merge(cq_config, config) |
| 100 except protobuf.text_format.ParseError as e: |
| 101 logging.error('Failed to parse config as protobuf:\n%s', e) |
| 102 return 1 |
| 103 |
| 104 for fname in REQUIRED_FIELDS: |
| 105 if not has_field(config, fname): |
| 106 logging.error('%s is a required field', fname) |
| 107 return 1 |
| 108 |
| 109 for fname in LEGACY_FIELDS: |
| 110 if has_field(config, fname): |
| 111 logging.warn('%s is a legacy field', fname) |
| 112 |
| 113 |
| 114 for base in config.rietveld.project_bases: |
| 115 try: |
| 116 re.compile(base) |
| 117 except Exception: |
| 118 logging.error('failed to parse "%s" in project_bases as a regexp', base) |
| 119 return 1 |
| 120 |
| 121 if not config.rietveld.HasField('url'): |
| 122 logging.error('rietveld.url is a required field') |
| 123 return 1 |
| 124 |
| 125 # TODO(sergiyb): For each field, check valid values depending on its |
| 126 # semantics, e.g. email addresses, regular expressions etc. |
| 127 |
| 128 return 0 |
| 129 |
| 130 |
| 131 if __name__ == '__main__': |
| 132 args = parse_args() |
| 133 with args.config_file: |
| 134 sys.exit(validate(args.config_file.read())) |
OLD | NEW |