1#!/usr/bin/env python
2# ***** BEGIN LICENSE BLOCK *****
3# This Source Code Form is subject to the terms of the Mozilla Public
4# License, v. 2.0. If a copy of the MPL was not distributed with this file,
5# You can obtain one at http://mozilla.org/MPL/2.0/.
6# ***** END LICENSE BLOCK *****
7"""configtest.py
8
9Verify the .json and .py files in the configs/ directory are well-formed.
10Further tests to verify validity would be desirable.
11
12This is also a good example script to look at to understand mozharness.
13"""
14
15import os
16import pprint
17import sys
18try:
19    import simplejson as json
20except ImportError:
21    import json
22
23sys.path.insert(1, os.path.dirname(sys.path[0]))
24
25from mozharness.base.script import BaseScript
26
27
28# ConfigTest {{{1
29class ConfigTest(BaseScript):
30    config_options = [[
31     ["--test-file", ],
32     {"action": "extend",
33      "dest": "test_files",
34      "help": "Specify which config files to test"
35      }
36    ]]
37
38    def __init__(self, require_config_file=False):
39        self.config_files = []
40        BaseScript.__init__(self, config_options=self.config_options,
41                            all_actions=['list-config-files',
42                                         'test-json-configs',
43                                         'test-python-configs',
44                                         'summary',
45                                         ],
46                            default_actions=['test-json-configs',
47                                             'test-python-configs',
48                                             'summary',
49                                             ],
50                            require_config_file=require_config_file)
51
52    def query_config_files(self):
53        """This query method, much like others, caches its runtime
54        settings in self.VAR so we don't have to figure out config_files
55        multiple times.
56        """
57        if self.config_files:
58            return self.config_files
59        c = self.config
60        if 'test_files' in c:
61            self.config_files = c['test_files']
62            return self.config_files
63        self.debug("No --test-file(s) specified; defaulting to crawling the configs/ directory.")
64        config_files = []
65        for root, dirs, files in os.walk(os.path.join(sys.path[0], "..",
66                                                      "configs")):
67            for name in files:
68                # Hardcode =P
69                if name.endswith(".json") or name.endswith(".py"):
70                    if not name.startswith("test_malformed"):
71                        config_files.append(os.path.join(root, name))
72        self.config_files = config_files
73        return self.config_files
74
75    def list_config_files(self):
76        """ Non-default action that is mainly here to demonstrate how
77        non-default actions work in a mozharness script.
78        """
79        config_files = self.query_config_files()
80        for config_file in config_files:
81            self.info(config_file)
82
83    def test_json_configs(self):
84        """ Currently only "is this well-formed json?"
85
86        """
87        config_files = self.query_config_files()
88        filecount = [0, 0]
89        for config_file in config_files:
90            if config_file.endswith(".json"):
91                filecount[0] += 1
92                self.info("Testing %s." % config_file)
93                contents = self.read_from_file(config_file, verbose=False)
94                try:
95                    json.loads(contents)
96                except ValueError:
97                    self.add_summary("%s is invalid json." % config_file,
98                                     level="error")
99                    self.error(pprint.pformat(sys.exc_info()[1]))
100                else:
101                    self.info("Good.")
102                    filecount[1] += 1
103        if filecount[0]:
104            self.add_summary("%d of %d json config files were good." %
105                             (filecount[1], filecount[0]))
106        else:
107            self.add_summary("No json config files to test.")
108
109    def test_python_configs(self):
110        """Currently only "will this give me a config dictionary?"
111
112        """
113        config_files = self.query_config_files()
114        filecount = [0, 0]
115        for config_file in config_files:
116            if config_file.endswith(".py"):
117                filecount[0] += 1
118                self.info("Testing %s." % config_file)
119                global_dict = {}
120                local_dict = {}
121                try:
122                    execfile(config_file, global_dict, local_dict)
123                except Exception:
124                    self.add_summary("%s is invalid python." % config_file,
125                                     level="error")
126                    self.error(pprint.pformat(sys.exc_info()[1]))
127                else:
128                    if 'config' in local_dict and isinstance(local_dict['config'], dict):
129                        self.info("Good.")
130                        filecount[1] += 1
131                    else:
132                        self.add_summary("%s is valid python, "
133                                         "but doesn't create a config dictionary." %
134                                         config_file, level="error")
135        if filecount[0]:
136            self.add_summary("%d of %d python config files were good." %
137                             (filecount[1], filecount[0]))
138        else:
139            self.add_summary("No python config files to test.")
140
141
142# __main__ {{{1
143if __name__ == '__main__':
144    config_test = ConfigTest()
145    config_test.run_and_exit()
146