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
15from __future__ import absolute_import
16import os
17import pprint
18import sys
19
20try:
21    import simplejson as json
22except ImportError:
23    import json
24
25sys.path.insert(1, os.path.dirname(sys.path[0]))
26
27from mozharness.base.script import BaseScript
28
29
30# ConfigTest {{{1
31class ConfigTest(BaseScript):
32    config_options = [
33        [
34            [
35                "--test-file",
36            ],
37            {
38                "action": "extend",
39                "dest": "test_files",
40                "help": "Specify which config files to test",
41            },
42        ]
43    ]
44
45    def __init__(self, require_config_file=False):
46        self.config_files = []
47        BaseScript.__init__(
48            self,
49            config_options=self.config_options,
50            all_actions=[
51                "list-config-files",
52                "test-json-configs",
53                "test-python-configs",
54                "summary",
55            ],
56            default_actions=[
57                "test-json-configs",
58                "test-python-configs",
59                "summary",
60            ],
61            require_config_file=require_config_file,
62        )
63
64    def query_config_files(self):
65        """This query method, much like others, caches its runtime
66        settings in self.VAR so we don't have to figure out config_files
67        multiple times.
68        """
69        if self.config_files:
70            return self.config_files
71        c = self.config
72        if "test_files" in c:
73            self.config_files = c["test_files"]
74            return self.config_files
75        self.debug(
76            "No --test-file(s) specified; defaulting to crawling the configs/ directory."
77        )
78        config_files = []
79        for root, dirs, files in os.walk(os.path.join(sys.path[0], "..", "configs")):
80            for name in files:
81                # Hardcode =P
82                if name.endswith(".json") or name.endswith(".py"):
83                    if not name.startswith("test_malformed"):
84                        config_files.append(os.path.join(root, name))
85        self.config_files = config_files
86        return self.config_files
87
88    def list_config_files(self):
89        """Non-default action that is mainly here to demonstrate how
90        non-default actions work in a mozharness script.
91        """
92        config_files = self.query_config_files()
93        for config_file in config_files:
94            self.info(config_file)
95
96    def test_json_configs(self):
97        """Currently only "is this well-formed json?" """
98        config_files = self.query_config_files()
99        filecount = [0, 0]
100        for config_file in config_files:
101            if config_file.endswith(".json"):
102                filecount[0] += 1
103                self.info("Testing %s." % config_file)
104                contents = self.read_from_file(config_file, verbose=False)
105                try:
106                    json.loads(contents)
107                except ValueError:
108                    self.add_summary("%s is invalid json." % config_file, level="error")
109                    self.error(pprint.pformat(sys.exc_info()[1]))
110                else:
111                    self.info("Good.")
112                    filecount[1] += 1
113        if filecount[0]:
114            self.add_summary(
115                "%d of %d json config files were good." % (filecount[1], filecount[0])
116            )
117        else:
118            self.add_summary("No json config files to test.")
119
120    def test_python_configs(self):
121        """Currently only "will this give me a config dictionary?" """
122        config_files = self.query_config_files()
123        filecount = [0, 0]
124        for config_file in config_files:
125            if config_file.endswith(".py"):
126                filecount[0] += 1
127                self.info("Testing %s." % config_file)
128                global_dict = {}
129                local_dict = {}
130                try:
131                    with open(config_file, "r") as f:
132                        exec(f.read(), global_dict, local_dict)
133                except Exception:
134                    self.add_summary(
135                        "%s is invalid python." % config_file, level="error"
136                    )
137                    self.error(pprint.pformat(sys.exc_info()[1]))
138                else:
139                    if "config" in local_dict and isinstance(
140                        local_dict["config"], dict
141                    ):
142                        self.info("Good.")
143                        filecount[1] += 1
144                    else:
145                        self.add_summary(
146                            "%s is valid python, "
147                            "but doesn't create a config dictionary." % config_file,
148                            level="error",
149                        )
150        if filecount[0]:
151            self.add_summary(
152                "%d of %d python config files were good." % (filecount[1], filecount[0])
153            )
154        else:
155            self.add_summary("No python config files to test.")
156
157
158# __main__ {{{1
159if __name__ == "__main__":
160    config_test = ConfigTest()
161    config_test.run_and_exit()
162