scripts/check_rules_on_wiki.py (134 lines of code) (raw):
# 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/.
import argparse
import os
import re
from os import path
from typing import Optional
from urllib.request import Request, urlopen
class CheckWikiPage:
"""Check if the rules on the wiki page are up-to-date."""
wiki_page_url = "https://wiki.mozilla.org/BugBot"
github_tree_address = "https://github.com/mozilla/bugbot/blob/master/bugbot/rules/"
rules_path = "bugbot/rules/"
deleted_rules = {
"fuzzing_bisection_without_regressed_by.py", # replaced with `bisection_without_regressed_by.py`
"severity_tracked.py", # dropped in favor of `tracked_attention.py`
"tracked_bad_severity.py", # dropped in favor of `tracked_attention.py`
"newbie_with_ni.py",
}
skipped_rules = {
# Disabled rules:
"workflow/p1.py",
"workflow/p2.py",
"workflow/p2_no_activity.py",
"workflow/p3_p4_p5.py",
# Temporary scripts:
"survey_sec_bugs.py", # not running by cron
# Not user-facing rules:
"stepstoreproduce.py", # the autofix is currently disabled
"triage_owner_rotations.py",
"triage_rotations_outdated.py",
"vacant_team_manager.py",
"defect_with_please_or_enable.py",
"missed_landing_comment.py",
"meta_defect.py",
"feature_but_type_defect.py",
"workflow/multi_nag.py",
"multi_nag.py",
"multifix_regression.py",
"several_dups.py",
"several_votes.py",
"several_cc.py",
"several_comments.py",
"several_see_also.py",
"pdfjs_update.py",
"leave_open_sec.py",
"webcompat_score.py",
# Experimental rules:
"accessibilitybug.py",
"performancebug.py",
}
def __init__(self) -> None:
self.missed_tree: Optional[list] = None
self.missed_wiki: Optional[list] = None
def get_rules_on_wiki_page(self) -> set:
"""Get the list of rules on the wiki page."""
req = Request(self.wiki_page_url)
# When running on GitHub Actions, we need to add the token to the request
# to access the wiki page. Otherwise, we get a 403 error.
wiki_token = os.environ.get("WIKI_TOKEN_GHA")
if wiki_token:
req.add_header("bugbotgha", wiki_token)
with urlopen(req) as resp:
wiki_page_content = resp.read().decode("utf-8")
pat = re.compile(rf"""['"]{re.escape(self.github_tree_address)}(.*)['"]""")
rules = pat.findall(wiki_page_content)
if not rules:
raise Exception(f"No rules found on the wiki page {self.wiki_page_url}")
return set(rules)
def get_rules_in_the_tree(self) -> set:
"""Get the list of rules in the tree."""
rules = {
os.path.join(root, file)[len(self.rules_path) :].strip()
for root, dirs, files in os.walk(self.rules_path)
for file in files
if file.endswith(".py") and file != "__init__.py"
}
if not rules:
raise Exception(f"No rules found in the tree {self.rules_path}")
return rules
def check(self) -> None:
"""Check if the rules on the wiki page are up-to-date."""
rules_in_the_tree = self.get_rules_in_the_tree()
rules_on_wiki_page = self.get_rules_on_wiki_page()
self.missed_wiki = sorted(
rule
for rule in rules_in_the_tree
if rule not in rules_on_wiki_page and rule not in self.skipped_rules
)
self.missed_tree = sorted(
rule
for rule in rules_on_wiki_page
if rule not in rules_in_the_tree
and rule not in self.deleted_rules
and not (
rule.startswith("..") and path.exists(path.join(self.rules_path, rule))
)
)
def print_markdown_output(self) -> None:
"""Print the output in markdown format."""
if self.missed_wiki is None or self.missed_tree is None:
self.check()
if self.missed_wiki:
print("## The following rules are not on the wiki page:")
for rule in self.missed_wiki:
print(f"- [{rule}]({self.github_tree_address + rule})")
if self.missed_tree:
print("## The following rules are not in the tree:")
for rule in self.missed_tree:
wiki_id = rule.replace("/", ".2F")
print(f"- [{rule}]({self.wiki_page_url}#{wiki_id})")
def raise_on_mismatch(self) -> None:
"""Raise an exception if the rules on the wiki page are not up-to-date."""
if self.missed_wiki is None or self.missed_tree is None:
self.check()
if self.missed_wiki or self.missed_tree:
raise Exception(
"The rules in the tree and on the wiki page are not in sync."
)
def check_with_markdown_output(self):
"""Check if the rules on the wiki page are up-to-date and return a markdown output."""
if self.missed_wiki is None or self.missed_tree is None:
self.check()
if self.missed_wiki:
print("## The following rules are not on the wiki page:")
for rule in self.missed_wiki:
print(f"- [{rule}]({self.github_tree_address + rule})")
if self.missed_tree:
print("## The following rules are not in the tree:")
for rule in self.missed_tree:
wiki_id = rule.replace("/", ".2F")
print(f"- [{rule}]({self.wiki_page_url}#{wiki_id})")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Check if the rules on the wiki page are up-to-date."
)
parser.add_argument(
"-ci",
"--error-on-mismatch",
dest="error_on_mismatch",
action="store_true",
default=False,
help="Throw an error if the rules are not up-to-date.",
)
args = parser.parse_args()
check_wiki_page = CheckWikiPage()
check_wiki_page.print_markdown_output()
if args.error_on_mismatch:
check_wiki_page.raise_on_mismatch()