-
Notifications
You must be signed in to change notification settings - Fork 61
Add a new CI step to validate the output of conver.py against staging #181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
babolivier
wants to merge
3
commits into
master
Choose a base branch
from
babolivier/validate_staging
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| name: Validate output against staging | ||
|
|
||
| on: [pull_request] | ||
|
|
||
| jobs: | ||
| validate_against_stating: | ||
| permissions: | ||
| pull-requests: write | ||
|
|
||
| runs-on: ubuntu-latest | ||
|
|
||
| env: | ||
| PYTHONDEVMODE: 1 | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
||
| - name: Run deployment script | ||
| run: | | ||
| pip install lxml | ||
| mkdir tmp/ | ||
| python tools/convert.py -a -d tmp ispdb/* | ||
|
|
||
| - name: Compare output against staging | ||
| run: | | ||
| pip install requests | ||
| python tools/compare_out_files.py -b https://autoconfig-stage.thunderbird.net/v1.1/ tmp/ | ||
|
|
||
| - name: Calculate generated_files.json diff with prod | ||
| run: | | ||
| python tools/calculate_generated_files_diff.py -b https://autoconfig.thunderbird.net/v1.1 -t ${{ secrets.GITHUB_TOKEN }} -r ${{ github.repository }} -n ${{ github.event.pull_request.number }} | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| import argparse | ||
| import difflib | ||
| import os.path | ||
| import requests | ||
|
|
||
| GENERATED_FILES_NAME = "generated_files.json" | ||
|
|
||
| GITHUB_COMMENT_TEMPLATE_WITH_DIFF = """This PR will cause the following changes to the production `generated_files.json` file: | ||
|
|
||
| <details> | ||
| <summary> | ||
| Expand to view diff | ||
| </summary> | ||
|
|
||
| ```diff | ||
| {deltas} | ||
| ``` | ||
|
|
||
| </details> | ||
| """ | ||
|
|
||
| GITHUB_COMMENT_TEMPLATE_NO_DIFF = ( | ||
| "This PR will not cause any change to the production `generated_files.json` file." | ||
| ) | ||
|
|
||
|
|
||
| def main(): | ||
| parser = argparse.ArgumentParser() | ||
| parser.add_argument("-b", metavar="base_url", help="base URL serving ISPDB files") | ||
| parser.add_argument("-t", metavar="api_token", help="Github API token") | ||
| parser.add_argument("-r", metavar="repo", help="Github repository") | ||
| parser.add_argument("-n", metavar="number", help="The Github or issue number") | ||
| parser.add_argument( | ||
| "folder", help="the folder containing the local ISPDB files to compare" | ||
| ) | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| # Strip out any trailing slash in the base URL so we don't accidentally end | ||
| # up doubling it. | ||
| base_url: str = args.b.strip("/") | ||
|
|
||
| resp = requests.get(f"{base_url}/{GENERATED_FILES_NAME}") | ||
|
|
||
| # At the time of writing, all of the domains in ISPDB are made up of ASCII | ||
| # characters, but that might not stay true forever. | ||
| resp.encoding = "utf-8" | ||
|
|
||
| with open(os.path.join(args.folder, GENERATED_FILES_NAME), "r") as fp: | ||
| local_list = fp.readlines() | ||
|
|
||
| # We call the local version "staging" as a shortcut, because by this | ||
| # time we expect to have already validated that the | ||
| deltas = list( | ||
| difflib.unified_diff( | ||
| local_list, | ||
| resp.text.splitlines(keepends=True), | ||
| fromfile="staging", | ||
| tofile="production", | ||
| ) | ||
| ) | ||
|
|
||
| comment = ( | ||
| GITHUB_COMMENT_TEMPLATE_WITH_DIFF.format(deltas="".join(deltas)) | ||
| if len(deltas) > 0 | ||
| else GITHUB_COMMENT_TEMPLATE_NO_DIFF | ||
| ) | ||
|
|
||
| # Create the comment via the Github API. | ||
| # See <https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#create-an-issue-comment> | ||
| resp = requests.post( | ||
| f"https://api.github.com/repos/{args.r}/issues/{args.n}/comments", | ||
| headers={ | ||
| "Accept": "application/vnd.github+json", | ||
| "Authorization": f"Bearer {args.t}", | ||
| }, | ||
| json={"body": comment}, | ||
| ) | ||
|
|
||
| print(f"Posted comment {resp.json()["html_url"]}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| # 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 difflib | ||
| import json | ||
| import os.path | ||
| import sys | ||
| import requests | ||
| from typing import Dict, List | ||
|
|
||
| GENERATED_FILES_NAME = "generated_files.json" | ||
|
|
||
|
|
||
| def get_and_compare(file_name: str, base_url: str, local_folder: str) -> str: | ||
| """Reads a local file and compare it with its remote copy before returning | ||
| its content. | ||
|
|
||
| Returns: | ||
| The file's content as served by the remote server, decoded as UTF-8 | ||
| text. | ||
|
|
||
| Raises: | ||
| RuntimeError if the local file's content doesn't match the remote copy. | ||
| """ | ||
| resp = requests.get(f"{base_url}/{file_name}") | ||
|
|
||
| # The response might not include an content-type header, and there are some | ||
| # non-ASCII characters in our XML files (e.g. in display names), so we need | ||
| # to explicitly tell `resp` what its encoding is. | ||
| resp.encoding = "utf-8" | ||
|
|
||
| with open(os.path.join(local_folder, file_name), "r") as fp: | ||
| local_list = fp.readlines() | ||
|
|
||
| deltas = list( | ||
| difflib.unified_diff( | ||
| local_list, | ||
| resp.text.splitlines(keepends=True), | ||
| fromfile="local", | ||
| tofile="remote", | ||
| ) | ||
| ) | ||
|
|
||
| if len(deltas) > 0: | ||
| print(f"Diff deltas:\n\n{"".join(deltas)}", file=sys.stderr) | ||
| raise RuntimeError("local file list does not match staging copy") | ||
|
|
||
| return resp.text | ||
|
|
||
|
|
||
| def get_file_list(base_url: str, local_folder: str) -> List[str]: | ||
| """Gets the list of files to compare. | ||
|
|
||
| Returns: | ||
| The list of file names as per the `generated_files.json` file. | ||
|
|
||
| Raises: | ||
| RuntimeError if the local `generated_files.json` file does not match the | ||
| remote copy. | ||
| """ | ||
| file_list = get_and_compare(GENERATED_FILES_NAME, base_url, local_folder) | ||
| return json.loads(file_list) | ||
|
|
||
|
|
||
| def main(): | ||
| parser = argparse.ArgumentParser() | ||
| parser.add_argument("-b", metavar="base_url", help="base URL serving ISPDB files") | ||
| parser.add_argument( | ||
| "folder", help="the folder containing the local ISPDB files to compare" | ||
| ) | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| # Strip out any trailing slash in the base URL so we don't accidentally end | ||
| # up doubling it. | ||
| base_url: str = args.b.strip("/") | ||
|
|
||
| print("Fetching and comparing file list") | ||
|
|
||
| listed_files = get_file_list(base_url, args.folder) | ||
|
|
||
| failed_files: Dict[str, Exception] = {} | ||
| for file in listed_files: | ||
| print(f"Fetching and comparing {file}") | ||
|
|
||
| try: | ||
| get_and_compare(file, base_url, args.folder) | ||
| except Exception as e: | ||
| print(f"Comparison failed for file {file}: {e}", file=sys.stderr) | ||
| failed_files[file] = e | ||
|
|
||
| if len(failed_files) > 0: | ||
| # Print the failed files, preceded by an empty line to separate them | ||
| # from the previous logs. | ||
| print("\nComparing the following file(s) has failed:", file=sys.stderr) | ||
|
|
||
| for file, exc in failed_files.items(): | ||
| print(f"{file}: {exc}", file=sys.stderr) | ||
|
|
||
| # Check if we can find files that exist in the local directory but isn't | ||
| # listed in `generated_files.json`. We could also do this check in the other | ||
| # direction (i.e. check if a file in `generated_files.json` is missing from | ||
| # the local directory), but if a file from the list is missing then trying | ||
| # to open it earlier will have raised an exception and will already cause | ||
| # the script to fail. | ||
| local_files = os.listdir(args.folder) | ||
|
|
||
| # Make sure we don't try to find the JSON list file in itself. | ||
| local_files.remove(GENERATED_FILES_NAME) | ||
|
|
||
| unknown_files = [] | ||
| for local_file in local_files: | ||
| if local_file not in listed_files: | ||
| unknown_files.append(local_file) | ||
|
|
||
| if len(unknown_files) > 0: | ||
| print("\nUnknown file(s) in local directory:", file=sys.stderr) | ||
|
|
||
| for file in unknown_files: | ||
| print(file, file=sys.stderr) | ||
| else: | ||
| print("No unknown files found") | ||
|
|
||
| # Fail the script if either a comparison has failed or we found an unknown | ||
| # file. We could fail earlier, but it's more helpful for troubleshooting if | ||
| # we have the script point out as many issues in one run as possible. | ||
| if len(failed_files) > 0 or len(unknown_files) > 0: | ||
| sys.exit(1) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.