| # 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. |
| """Presubmit script validating field trial configs. |
| |
| See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts |
| for more details on the presubmit API built into depot_tools. |
| """ |
| |
| import json |
| import sys |
| |
| VALID_GROUP_KEYS = ['group_name', |
| 'params', |
| 'enable_features', |
| 'disable_features'] |
| |
| def PrettyPrint(contents): |
| """Pretty prints a fieldtrial configuration. |
| |
| Args: |
| contents: File contents as a string. |
| |
| Returns: |
| Pretty printed file contents. |
| """ |
| return json.dumps(json.loads(contents), |
| sort_keys=True, indent=4, |
| separators=(',', ': ')) + '\n' |
| |
| def ValidateData(json_data, file_path, message_type): |
| """Validates the format of a fieldtrial configuration. |
| |
| Args: |
| json_data: Parsed JSON object representing the fieldtrial config. |
| file_path: String representing the path to the JSON file. |
| message_type: Type of message from |output_api| to return in the case of |
| errors/warnings. |
| |
| Returns: |
| A list of |message_type| messages. In the case of all tests passing with no |
| warnings/errors, this will return []. |
| """ |
| if not isinstance(json_data, dict): |
| return [message_type( |
| 'Malformed config file %s: Expecting dict' % file_path)] |
| for (study, groups) in json_data.iteritems(): |
| if not isinstance(study, unicode): |
| return [message_type( |
| 'Malformed config file %s: Expecting keys to be string, got %s' |
| % (file_path, type(study)))] |
| if not isinstance(groups, list): |
| return [message_type( |
| 'Malformed config file %s: Expecting list for study %s' |
| % (file_path, study))] |
| for group in groups: |
| if not isinstance(group, dict): |
| return [message_type( |
| 'Malformed config file %s: Expecting dict for group in ' |
| 'Study[%s]' % (file_path, study))] |
| if not 'group_name' in group or not isinstance(group['group_name'], |
| unicode): |
| return [message_type( |
| 'Malformed config file %s: Missing valid group_name for group' |
| ' in Study[%s]' % (file_path, study))] |
| if 'params' in group: |
| params = group['params'] |
| if not isinstance(params, dict): |
| return [message_type( |
| 'Malformed config file %s: Invalid params for Group[%s]' |
| ' in Study[%s]' % (file_path, group['group_name'], |
| study))] |
| for (key, value) in params.iteritems(): |
| if not isinstance(key, unicode) or not isinstance(value, |
| unicode): |
| return [message_type( |
| 'Malformed config file %s: Invalid params for Group[%s]' |
| ' in Study[%s]' % (file_path, group['group_name'], |
| study))] |
| for key in group.keys(): |
| if key not in VALID_GROUP_KEYS: |
| return [message_type( |
| 'Malformed config file %s: Key[%s] in Group[%s] in Study[%s] ' |
| 'is not a valid key.' % ( |
| file_path, key, group['group_name'], study))] |
| |
| return [] |
| |
| def CheckPretty(contents, file_path, message_type): |
| """Validates the pretty printing of fieldtrial configuration. |
| |
| Args: |
| contents: File contents as a string. |
| file_path: String representing the path to the JSON file. |
| message_type: Type of message from |output_api| to return in the case of |
| errors/warnings. |
| |
| Returns: |
| A list of |message_type| messages. In the case of all tests passing with no |
| warnings/errors, this will return []. |
| """ |
| pretty = PrettyPrint(contents) |
| if contents != pretty: |
| return [message_type( |
| 'Pretty printing error: Run ' |
| 'python testing/variations/PRESUBMIT.py %s' % file_path)] |
| return [] |
| |
| def CommonChecks(input_api, output_api): |
| affected_files = input_api.AffectedFiles( |
| include_deletes=False, |
| file_filter=lambda x: x.LocalPath().endswith('.json')) |
| for f in affected_files: |
| contents = input_api.ReadFile(f) |
| try: |
| json_data = input_api.json.loads(contents) |
| result = CheckPretty(contents, f.LocalPath(), output_api.PresubmitError) |
| if len(result): |
| return result |
| result = ValidateData(json_data, f.LocalPath(), |
| output_api.PresubmitError) |
| if len(result): |
| return result |
| except ValueError: |
| return [output_api.PresubmitError( |
| 'Malformed JSON file: %s' % f.LocalPath())] |
| return [] |
| |
| def CheckChangeOnUpload(input_api, output_api): |
| return CommonChecks(input_api, output_api) |
| |
| def CheckChangeOnCommit(input_api, output_api): |
| return CommonChecks(input_api, output_api) |
| |
| |
| def main(argv): |
| content = open(argv[1]).read() |
| pretty = PrettyPrint(content) |
| open(argv[1],'w').write(pretty) |
| |
| if __name__ == "__main__": |
| sys.exit(main(sys.argv)) |