From 55487053519712d651bcbb168e41556437e05406 Mon Sep 17 00:00:00 2001 From: Sam Sneddon Date: Tue, 10 Sep 2024 14:14:17 -0700 Subject: [PATCH] Trigger Safari & Safari Technology Preview runs after epochs GitHub Actions doesn't create new workflow runs for events dispatched from a workflow (or, pedantically, using the provided GITHUB_TOKEN), thus we need to manually trigger the Safari and Safari Technology Preview workflows after the epochs workflow has run. While we're at it, we should allow these workflows to be manually run via workflow_dispatch. --- .github/workflows/check-workflow-run.yml | 36 ++++++ .github/workflows/epochs.yml | 7 ++ .github/workflows/safari_stable.yml | 21 ++++ .../workflows/safari_technology_preview.yml | 17 +++ tools/ci/check_for_updated_refs.py | 47 ++++++++ tools/ci/epochs_update.sh | 2 +- tools/ci/tests/test_check_for_updated_refs.py | 106 ++++++++++++++++++ 7 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/check-workflow-run.yml create mode 100644 tools/ci/check_for_updated_refs.py create mode 100644 tools/ci/tests/test_check_for_updated_refs.py diff --git a/.github/workflows/check-workflow-run.yml b/.github/workflows/check-workflow-run.yml new file mode 100644 index 00000000000000..439f93a85865cf --- /dev/null +++ b/.github/workflows/check-workflow-run.yml @@ -0,0 +1,36 @@ +name: Check workflow_run + +on: + workflow_call: + inputs: + check-refs: + description: "Refs to check whether they've been updated" + required: true + type: string + outputs: + updated-refs: + description: "Refs which have been updated" + value: ${{ jobs.check-workflow-run.outputs.output }} + +jobs: + check-workflow-run: + name: "Check for appropriate epochs" + if: ${{ github.event_name == 'workflow_run' }} + runs-on: + - ubuntu-22.04 + permissions: + actions: read + outputs: + output: ${{ steps.check.outputs.test }} + steps: + - uses: actions/download-artifact@v4 + with: + name: git-push-output + path: ${{ runner.temp }}/git-push-output.txt + run-id: ${{ github.event.workflow_run.id }} + - id: check + run: |- + python3 tools/ci/check_for_updated_refs.py >> "$GITHUB_OUTPUT" + env: + GIT_PUSH_OUTPUT: ${{ runner.temp }}/git-push-output.txt + REFS: ${{ inputs.check-refs }} diff --git a/.github/workflows/epochs.yml b/.github/workflows/epochs.yml index 840d08ffe15459..8e85ee75a843b9 100644 --- a/.github/workflows/epochs.yml +++ b/.github/workflows/epochs.yml @@ -18,3 +18,10 @@ jobs: run: ./tools/ci/epochs_update.sh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload git-push output + uses: actions/upload-artifact@v4 + with: + name: git-push-output + path: ${{ runner.temp }}/git-push-output.txt + if-no-files-found: error + compression-level: 1 diff --git a/.github/workflows/safari_stable.yml b/.github/workflows/safari_stable.yml index 781d79e11017b3..7ff9ace3188e29 100644 --- a/.github/workflows/safari_stable.yml +++ b/.github/workflows/safari_stable.yml @@ -5,6 +5,11 @@ name: "All Tests: Safari (stable)" permissions: {} on: + workflow_dispatch: + workflow_run: + workflows: [epochs] + types: + - completed push: branches: - epochs/daily @@ -17,8 +22,24 @@ env: SAFARIDRIVER_DIAGNOSE: false jobs: + check-workflow-run: + name: "Check for appropriate epochs" + uses: ./.github/workflows/check-workflow-run.yml + with: + check-refs: '["refs/heads/epochs/daily"]' + permissions: + actions: read + safari-stable-results: name: "All Tests: Safari (stable)" + needs: check-workflow-run + if: | + # We need always() here to then check for success/skipped from the dependency, as otherwise + # the skip cascades. See + # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-jobs-in-a-workflow#defining-prerequisite-jobs. + always() && + (needs.check-workflow-run.result == 'success' || needs.check-workflow-run.result == 'skipped') && + (github.event_name != 'workflow_run' || fromJSON(needs.check-workflow-run.outputs.updated-refs)[0] != null) runs-on: - self-hosted - webkit-ews diff --git a/.github/workflows/safari_technology_preview.yml b/.github/workflows/safari_technology_preview.yml index e0732a3e5460bd..5d26f8d78a1198 100644 --- a/.github/workflows/safari_technology_preview.yml +++ b/.github/workflows/safari_technology_preview.yml @@ -5,6 +5,7 @@ name: "All Tests: Safari Technology Preview" permissions: {} on: + workflow_dispatch: push: branches: - epochs/three_hourly @@ -17,8 +18,24 @@ env: SAFARIDRIVER_DIAGNOSE: false jobs: + check-workflow-run: + name: "Check for appropriate epochs" + uses: ./.github/workflows/check-workflow-run.yml + with: + check-refs: '["refs/heads/epochs/daily"]' + permissions: + actions: read + safari-technology-preview-results: name: "All Tests: Safari Technology Preview" + needs: check-workflow-run + if: | + # We need always() here to then check for success/skipped from the dependency, as otherwise + # the skip cascades. See + # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-jobs-in-a-workflow#defining-prerequisite-jobs. + always() && + (needs.check-workflow-run.result == 'success' || needs.check-workflow-run.result == 'skipped') && + (github.event_name != 'workflow_run' || fromJSON(needs.check-workflow-run.outputs.updated-refs)[0] != null) runs-on: - self-hosted - webkit-ews diff --git a/tools/ci/check_for_updated_refs.py b/tools/ci/check_for_updated_refs.py new file mode 100644 index 00000000000000..b7b3cfdc72efe9 --- /dev/null +++ b/tools/ci/check_for_updated_refs.py @@ -0,0 +1,47 @@ +import json +import os +import re +import sys +from typing import IO, Container, Dict, Iterable, List, Optional + +GIT_PUSH = re.compile( + r"^(?P.)\t(?P[^\t:]*):(?P[^\t:]*)\t(?P.*[^\)])(?: \((?P[^\)]*)\))?\n$" +) + + +def parse_push(fd: IO[str]) -> Iterable[Dict[str, Optional[str]]]: + for line in fd: + m = GIT_PUSH.match(line) + if m is not None: + yield m.groupdict() + + +def process_push(fd: IO[str], refs: Container[str]) -> List[str]: + updated_refs = [] + + for ref_status in parse_push(fd): + flag = ref_status["flag"] + if flag not in (" ", "+", "-", "*"): + continue + + to = ref_status["to"] + assert to is not None + if to in refs: + updated_refs.append(to) + + return updated_refs + + +def main() -> None: + git_push_output = os.environ["GIT_PUSH_OUTPUT"] + refs = json.loads(os.environ["REFS"]) + + with open(git_push_output, "r") as fd: + updated_refs = process_push(fd, refs) + + json.dump(updated_refs, sys.stdout, indent=2) + sys.stdout.write("\n") + + +if __name__ == "__main__": + main() diff --git a/tools/ci/epochs_update.sh b/tools/ci/epochs_update.sh index 1c7edf15aaf8c4..cc1a536f22ebb8 100755 --- a/tools/ci/epochs_update.sh +++ b/tools/ci/epochs_update.sh @@ -43,7 +43,7 @@ main () { done # This is safe because `git push` will by default fail for a non-fast-forward # push, for example if the remote branch is ahead of the local branch. - git push --tags ${REMOTE} ${ALL_BRANCHES_NAMES} + git push --porcelain --tags ${REMOTE} ${ALL_BRANCHES_NAMES} | tee "${RUNNER_TEMP}/git-push-output.txt" } cd $WPT_ROOT diff --git a/tools/ci/tests/test_check_for_updated_refs.py b/tools/ci/tests/test_check_for_updated_refs.py new file mode 100644 index 00000000000000..e6562c1202c32d --- /dev/null +++ b/tools/ci/tests/test_check_for_updated_refs.py @@ -0,0 +1,106 @@ +# mypy: allow-untyped-defs + +import io + +from tools.ci import check_for_updated_refs + + +def test_parse_push(): + s = io.StringIO( + """ +To github.com:gsnedders/web-platform-tests.git += refs/heads/a:refs/heads/a [up to date] +- :refs/heads/b [deleted] ++ refs/heads/c:refs/heads/c a6eb923e19...9b6507e295 (forced update) +* refs/heads/d:refs/heads/d [new branch] +\x20 refs/heads/e:refs/heads/e 0acd8f62f1..6188942729 +! refs/heads/f:refs/heads/f [rejected] (atomic push failed) +Done + """ + ) + + actual = list(check_for_updated_refs.parse_push(s)) + print(repr(actual)) + expected = [ + { + "flag": "=", + "from": "refs/heads/a", + "to": "refs/heads/a", + "summary": "[up to date]", + "reason": None, + }, + { + "flag": "-", + "from": "", + "to": "refs/heads/b", + "summary": "[deleted]", + "reason": None, + }, + { + "flag": "+", + "from": "refs/heads/c", + "to": "refs/heads/c", + "summary": "a6eb923e19...9b6507e295", + "reason": "forced update", + }, + { + "flag": "*", + "from": "refs/heads/d", + "to": "refs/heads/d", + "summary": "[new branch]", + "reason": None, + }, + { + "flag": " ", + "from": "refs/heads/e", + "to": "refs/heads/e", + "summary": "0acd8f62f1..6188942729", + "reason": None, + }, + { + "flag": "!", + "from": "refs/heads/f", + "to": "refs/heads/f", + "summary": "[rejected]", + "reason": "atomic push failed", + }, + ] + + assert expected == actual + + +def test_process_push(): + s = io.StringIO( + """ +To github.com:gsnedders/web-platform-tests.git += refs/heads/a:refs/heads/a [up to date] +- :refs/heads/b [deleted] ++ refs/heads/c:refs/heads/c a6eb923e19...9b6507e295 (forced update) +* refs/heads/d:refs/heads/d [new branch] +\x20 refs/heads/e:refs/heads/e 0acd8f62f1..6188942729 +! refs/heads/f:refs/heads/f [rejected] (atomic push failed) +Done + """ + ) + + actual = list( + check_for_updated_refs.process_push( + s, + [ + "refs/heads/e", + "refs/heads/b", + "refs/heads/c", + "refs/heads/d", + "refs/heads/e", + "refs/heads/x", + ], + ) + ) + expected = [ + "refs/heads/b", + "refs/heads/c", + "refs/heads/d", + "refs/heads/e", + ] + + assert expected == actual