testing/mozharness/scripts/configtest.py
author Sandor Molnar <smolnar@mozilla.com>
Fri, 24 Sep 2021 00:43:42 +0300
changeset 593102 4eda9eb8926bdd50f4b80128ce3475eb7c6d9a4d
parent 560738 69dcf3394520f8755d8b88d230fc7cff68f066d3
permissions -rwxr-xr-x
Merge autoland to mozilla-central. a=merge

#!/usr/bin/env python
# ***** BEGIN LICENSE BLOCK *****
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
# ***** END LICENSE BLOCK *****
"""configtest.py

Verify the .json and .py files in the configs/ directory are well-formed.
Further tests to verify validity would be desirable.

This is also a good example script to look at to understand mozharness.
"""

from __future__ import absolute_import
import os
import pprint
import sys

try:
    import simplejson as json
except ImportError:
    import json

sys.path.insert(1, os.path.dirname(sys.path[0]))

from mozharness.base.script import BaseScript


# ConfigTest {{{1
class ConfigTest(BaseScript):
    config_options = [
        [
            [
                "--test-file",
            ],
            {
                "action": "extend",
                "dest": "test_files",
                "help": "Specify which config files to test",
            },
        ]
    ]

    def __init__(self, require_config_file=False):
        self.config_files = []
        BaseScript.__init__(
            self,
            config_options=self.config_options,
            all_actions=[
                "list-config-files",
                "test-json-configs",
                "test-python-configs",
                "summary",
            ],
            default_actions=[
                "test-json-configs",
                "test-python-configs",
                "summary",
            ],
            require_config_file=require_config_file,
        )

    def query_config_files(self):
        """This query method, much like others, caches its runtime
        settings in self.VAR so we don't have to figure out config_files
        multiple times.
        """
        if self.config_files:
            return self.config_files
        c = self.config
        if "test_files" in c:
            self.config_files = c["test_files"]
            return self.config_files
        self.debug(
            "No --test-file(s) specified; defaulting to crawling the configs/ directory."
        )
        config_files = []
        for root, dirs, files in os.walk(os.path.join(sys.path[0], "..", "configs")):
            for name in files:
                # Hardcode =P
                if name.endswith(".json") or name.endswith(".py"):
                    if not name.startswith("test_malformed"):
                        config_files.append(os.path.join(root, name))
        self.config_files = config_files
        return self.config_files

    def list_config_files(self):
        """Non-default action that is mainly here to demonstrate how
        non-default actions work in a mozharness script.
        """
        config_files = self.query_config_files()
        for config_file in config_files:
            self.info(config_file)

    def test_json_configs(self):
        """Currently only "is this well-formed json?" """
        config_files = self.query_config_files()
        filecount = [0, 0]
        for config_file in config_files:
            if config_file.endswith(".json"):
                filecount[0] += 1
                self.info("Testing %s." % config_file)
                contents = self.read_from_file(config_file, verbose=False)
                try:
                    json.loads(contents)
                except ValueError:
                    self.add_summary("%s is invalid json." % config_file, level="error")
                    self.error(pprint.pformat(sys.exc_info()[1]))
                else:
                    self.info("Good.")
                    filecount[1] += 1
        if filecount[0]:
            self.add_summary(
                "%d of %d json config files were good." % (filecount[1], filecount[0])
            )
        else:
            self.add_summary("No json config files to test.")

    def test_python_configs(self):
        """Currently only "will this give me a config dictionary?" """
        config_files = self.query_config_files()
        filecount = [0, 0]
        for config_file in config_files:
            if config_file.endswith(".py"):
                filecount[0] += 1
                self.info("Testing %s." % config_file)
                global_dict = {}
                local_dict = {}
                try:
                    with open(config_file, "r") as f:
                        exec(f.read(), global_dict, local_dict)
                except Exception:
                    self.add_summary(
                        "%s is invalid python." % config_file, level="error"
                    )
                    self.error(pprint.pformat(sys.exc_info()[1]))
                else:
                    if "config" in local_dict and isinstance(
                        local_dict["config"], dict
                    ):
                        self.info("Good.")
                        filecount[1] += 1
                    else:
                        self.add_summary(
                            "%s is valid python, "
                            "but doesn't create a config dictionary." % config_file,
                            level="error",
                        )
        if filecount[0]:
            self.add_summary(
                "%d of %d python config files were good." % (filecount[1], filecount[0])
            )
        else:
            self.add_summary("No python config files to test.")


# __main__ {{{1
if __name__ == "__main__":
    config_test = ConfigTest()
    config_test.run_and_exit()