Skip to content
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

Check Chromium revision pinning scripts into repo #4220

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
## Updating the pinned Chromium revision in WPT
These scripts exists as cloud functions in GCP and will need to be redeployed
DanielRyanSmith marked this conversation as resolved.
Show resolved Hide resolved
after subsequent changes to the file.

_check_chromium_revision.py_

The purpose of this script is to find a new Chromium revision that is available
for all major platforms (Win/Mac/Linux) and trigger the WPT CI check suite to
run against this new revision.


_update_chromium_revision.py_

The purpose of this script is to check the WPT CI check suite to see if all
tests passed for the new revision, and to update the pinned revision if so.

The current PR used for running the check suites is at https://github.com/web-platform-tests/wpt/pull/50375
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add the command(s) to deploy these scripts in each section?

Also, do you know how to generate the GIT_CHECK_PR_STATUS_TOKEN stored in secret manager that is used in both of these scripts?

Also, do you know about the instructions for process_test_history?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any instructions on how to create these PRs in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add the command(s) to deploy these scripts in each section?

I've been doing it through the GCP UI, but I'll look up and add instructions for this.

Also, do you know how to generate the GIT_CHECK_PR_STATUS_TOKEN stored in secret manager that is used in both of these scripts?

These are instructions I need to add as well, but I was thinking this might be better suited to live in our rotation docs.

Also, do you know about the instructions for process_test_history?

The instructions file build_test_history.py is what describes this, but I now realize I've not coordinated the names correctly here. It should probably be that "build_test_history" should be renamed to "process_test_history" to match the Cloud Function name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any instructions on how to create these PRs in the future?

Do you mean creating new cloud functions in the future? Or making changes to these files?


## Build Test History
_build_test_history.py_

This script exists as a cloud function in GCP and will need to be redeployed
after subsequent changes to the file. The `BUCKET_NAME`, `PROJECT_NAME`,
and `RUNS_API_URL` constants will need to be changed based on which environment
Expand Down
133 changes: 133 additions & 0 deletions scripts/check_chromium_revision.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Copyright 2025 The WPT Dashboard Project. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import os
import requests
from time import time
from google.cloud import storage

DEFAULT_TIMEOUT = 600.0
BUCKET_NAME = 'wpt-versions'
NEW_REVISION_FILE = 'pinned_chromium_revision_NEW'
OLD_REVISION_FILE = 'pinned_chromium_revision'
PLATFORM_INFO = [
("Win_x64", "chrome-win.zip"),
("Win", "chrome-win.zip"),
("Linux_x64", "chrome-linux.zip"),
("Mac", "chrome-mac.zip")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outside the scope of this: But I wonder how this will work when we want Android given Chrome on Android runs on Chromium CI. We probably still want some coverage. Might be worth bringing this up to Weizhong.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very familiar with the Chrome Android work, but is this shouldn't affect run for Chrome Android because it's considered a different product and does not use the pinned revision here (I might also be misunderstanding your comment 😅)

]
SNAPSHOTS_PATH = "https://storage.googleapis.com/chromium-browser-snapshots/"
PR_NUMBER = "50375"
REPO_OWNER = "web-platform-tests"
REPO_NAME = "wpt"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on making these environment variables? A user shouldn't really need to change the code just to update these variables. Instead, they could quickly deploy the app with updated variables. You could do more of these variables but this set would be a good start.

[1]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can definitely make these environment variable instead 🙂



def trigger_ci_tests() -> str | None:
# Reopen the PR to run the CI tests.
s = requests.Session()
s.headers.update({"Authorization": f"token {get_token()}"})
url = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/pulls/{PR_NUMBER}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should either:

  1. Use the GitHub client python library
  2. Manually pin the X-GitHub-Api-Version header in each API call.


response = s.patch(url, data='{"state": "closed"}')
if response.status_code != 200:
return f'Failed to close PR {PR_NUMBER}'

response = s.patch(url, data='{"state": "open"}')
if response.status_code != 200:
return f'Failed to open PR {PR_NUMBER}'


def get_token() -> str | None:
"""Get token to check on the CI runs."""
return os.environ["GIT_CHECK_PR_STATUS_TOKEN"]


def get_start_revision() -> int:
"""Get the latest revision for Linux as a starting point to check for a
valid revision for all platforms."""
try:
url = f"{SNAPSHOTS_PATH}Linux_x64/LAST_CHANGE"
start_revision = int(requests.get(url).text.strip())
except requests.RequestException as e:
raise requests.RequestException(f"Failed LAST_CHANGE lookup: {e}")

return start_revision


def check_new_chromium_revision() -> str:
"""Find a new Chromium revision that is available for all major platforms (Win/Mac/Linux)"""
timeout = DEFAULT_TIMEOUT
start = time()

# Load existing pinned revision.
storage_client = storage.Client()
bucket = storage_client.bucket(BUCKET_NAME)
# Read new revision number.
blob = bucket.blob(OLD_REVISION_FILE)
existing_revision = int(blob.download_as_string())

start_revision = get_start_revision()

if start_revision == existing_revision:
print("No new revision.")
return "No new revision."

# Step backwards through revision numbers until we find one
# that is available for all platforms.
candidate_revision = start_revision
new_revision = -1
timed_out = False
while new_revision == -1 and candidate_revision > existing_revision:
available_for_all = True
# For each platform, check if Chromium is available for download from snapshots.
for platform, filename in PLATFORM_INFO:
try:
url = (f"{SNAPSHOTS_PATH}{platform}/"
f"{candidate_revision}/{filename}")
# Check the headers of each possible download URL.
r = requests.head(url)
# If the file is not available for download, decrement the revision and try again.
if r.status_code != 200:
candidate_revision -= 1
available_for_all = False
break
except requests.RequestException:
print(f"Failed to fetch headers for revision {candidate_revision}. Skipping it.")
candidate_revision -= 1
available_for_all = False
break

if available_for_all:
new_revision = candidate_revision
if time() - start > timeout:
timed_out = True
break

end = time()
if timed_out:
raise Exception(f"Reached timeout {timeout}s while checking revision {candidate_revision}")

if new_revision <= existing_revision:
message = ("No new mutually available revision found after "
f"{'{:.2f}'.format(end - start)} seconds. Keeping revision {existing_revision}.")
print(message)
return message


# Replace old revision number with new number.
blob = bucket.blob(NEW_REVISION_FILE)
blob.upload_from_string(str(new_revision))
pr_error_msg = trigger_ci_tests()
message = (f"Found mutually available revision at {new_revision}.\n"
f"This process started at {start_revision} and checked "
f"{start_revision - new_revision} revisions.\n"
f"The whole process took {'{:.2f}'.format(end - start)} seconds.\n")
if pr_error_msg:
raise Exception(f"PR interaction error: {pr_error_msg}")
print(message)
return message


def main(args, _) -> None:
return check_new_chromium_revision()
97 changes: 97 additions & 0 deletions scripts/update_chromium_revision.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Copyright 2025 The WPT Dashboard Project. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.


import os
import requests
from datetime import date
from google.cloud import storage
from google.cloud import secretmanager


BUCKET_NAME = 'wpt-versions'
NEW_REVISION_FILE = 'pinned_chromium_revision_NEW'
OLD_REVISION_FILE = 'pinned_chromium_revision'
REPO_OWNER = "web-platform-tests"
REPO_NAME = "wpt"
PR_NUMBER = "50375"


def all_passing_checks() -> bool:
"""Check if all CI tests passed."""
s = requests.Session()
s.headers.update({
"Authorization": f"token {get_token()}"
})
url = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/commits/{get_sha()}/check-suites"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should either:

  1. Use the GitHub client python library
  2. Manually pin the X-GitHub-Api-Version header in each API call.

Right now, we are expecting the response to be a certain format. If the API version changes and the shape of the response changes, we will start to get KeyError. (for example when we go to check the conclusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. I think I'll approach using #2 and make changes once I confirm everything functions as expected.

response = s.get(url)
if response.status_code != 200:
print(f"Received response status {response.status_code} from {url}")
check_info = response.json()
for check in check_info["check_suites"]:
if check["conclusion"] != "success":
return False
return True


def update_pr_body(new_revision: str, tests_passed: bool) -> bool:
outcome = "Passed" if tests_passed else "Failed"
body = (
"This pull request is used for automated runs of the WPT check suites "
"against a new available Chromium revision. If all tests pass, the new "
"revision will be pinned for use.\\n\\nLast revision checked: "
f"{new_revision.decode('utf-8')}\\nCheck run date: {date.today()}"
f"\\nOutcome: **{outcome}**"
)

body = '{"body":"' + body + '"}'
s = requests.Session()
s.headers.update({"Authorization": f"token {get_token()}"})
url = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/pulls/{PR_NUMBER}"
response = s.patch(url, data=body)
return response.status_code == 200


def get_new_revision() -> str:
storage_client = storage.Client()
bucket = storage_client.bucket(BUCKET_NAME)
blob = bucket.blob(NEW_REVISION_FILE)
return blob.download_as_string()


def update_chromium_revision(new_revision) -> None:
storage_client = storage.Client()
bucket = storage_client.bucket(BUCKET_NAME)

# Replace old revision number with new number.
blob = bucket.blob(OLD_REVISION_FILE)
blob.upload_from_string(new_revision)


def get_token() -> str:
"""Get token to check on the CI runs."""
return os.environ["GIT_CHECK_PR_STATUS_TOKEN"]


def get_sha() -> str:
"""Get head sha from PR."""
url = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/pulls/{PR_NUMBER}"
s = requests.Session()
response = s.get(url)
pr_info = response.json()
return pr_info["head"]["sha"]


def main(args, _):
tests_passed = all_passing_checks()
new_revision = get_new_revision()
if tests_passed:
update_chromium_revision(new_revision)
if not update_pr_body(new_revision, tests_passed):
print("Failed to update PR body description.")
if tests_passed:
print(f"Revision updated to {new_revision}.")
return f"Revision updated to {new_revision}."
print(f"Some checks failed for PR {PR_NUMBER}. Revision not updated.")
return f"Some checks failed for PR {PR_NUMBER}. Revision not updated."