diff --git a/.github/workflows/golden-test-build.yml b/.github/workflows/golden-test-build.yml
new file mode 100644
index 00000000..6fcb0121
--- /dev/null
+++ b/.github/workflows/golden-test-build.yml
@@ -0,0 +1,47 @@
+name: Golden Test
+
+# NilAway output may change due to introduction of new feature or bug fixes. Since NilAway is still
+# at early stage of development, constantly updating / maintaining the golden test output will be
+# a burden. Therefore, we run this as a separate CI job and post the differences as a PR comment
+# for manual reviews.
+#
+# Note that this workflow is triggered on `pull_request` event, where if the PR is created from
+# forked repository, the GITHUB_TOKEN will not have necessary write permission to post the comments.
+# To work around this (and to provide proper isolation), we follow the recommended approach [1] of
+# separating job into two parts: (1) build and upload results as artifacts in untrusted environment
+# (here), and then (2) trigger a follow-up job that downloads the artifacts and posts the comment in
+# trusted environment (see .github/workflows/golden-test-comment.yml).
+#
+# [1]: https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
+on:
+ pull_request:
+
+jobs:
+ golden-test:
+ name: Golden Test
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ name: Check out repository
+
+ - name: Fetch base branch (${{ github.event.pull_request.base.ref }}) locally
+ run: git fetch origin ${{ github.event.pull_request.base.ref }}:${{ github.event.pull_request.base.ref }}
+
+ - name: Set up Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: 1.22.x
+ cache: false
+
+ - name: Golden Test
+ id: golden_test
+ # Run golden test by comparing HEAD and the base branch (the target branch of the PR).
+ # GitHub Actions terminates the job if it hits the resource limits. Here we limit the
+ # memory usage to 8GiB to avoid that.
+ run: |
+ make golden-test GOMEMLIMIT=8192MiB ARGS="-base-branch ${{ github.event.pull_request.base.ref }} -result-file ${{ runner.temp }}/golden-test-result.md"
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: golden-test-comment.md
+ path: ${{ runner.temp }}/golden-test-result.md
diff --git a/.github/workflows/golden-test-comment.yml b/.github/workflows/golden-test-comment.yml
new file mode 100644
index 00000000..cdedaff7
--- /dev/null
+++ b/.github/workflows/golden-test-comment.yml
@@ -0,0 +1,107 @@
+name: Golden Test [Comment]
+
+# See ".github/workflows/golden-test-build.yml" for more details.
+on:
+ workflow_run:
+ workflows: ["Golden Test"]
+ types:
+ - completed
+
+jobs:
+ upload:
+ runs-on: ubuntu-latest
+ if: >
+ github.event.workflow_run.event == 'pull_request' &&
+ github.event.workflow_run.conclusion == 'success'
+ steps:
+ # We do not have a good way to find the PR number from the workflow run event [1]. Therefore,
+ # we try to use the search API to find the PR that has the same SHA as the workflow run.
+ # This is a workaround until GitHub provides a better way to find the associated PR.
+ #
+ # [1]: https://github.com/orgs/community/discussions/25220
+ - name: Find associated pull request
+ id: pr
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const response = await github.rest.search.issuesAndPullRequests({
+ q: 'repo:${{ github.repository }} is:pr is:merged sha:${{ github.event.workflow_run.head_sha }}',
+ per_page: 1,
+ });
+ const items = response.data.items;
+ if (items.length < 1) {
+ core.setFailed('No pull request found for the workflow run')
+ return;
+ }
+ const prNumber = items[0].number;
+ console.info("Pull request number is", prNumber);
+ return prNumber;
+
+ - name: Download Golden Test result artifact
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const artifacts = await github.actions.listWorkflowRunArtifacts({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: ${{github.event.workflow_run.id }},
+ });
+ const matchArtifact = artifacts.data.artifacts.filter((artifact) => {
+ return artifact.name == "golden-test-result.md";
+ })[0];
+ const download = await github.actions.downloadArtifact({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ artifact_id: matchArtifact.id,
+ });
+ const fsp = require('fs').promises;
+ await fsp.writeFile('${{github.workspace}}/golden-test-result.md', Buffer.from(download.data));
+
+ - name: Upload the Golden Test result
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const fsp = require('fs').promises;
+
+ const issueNumber = ${{ steps.pr.outputs.result }};
+ const owner = context.repo.owner;
+ const repo = context.repo.repo;
+ const rawData = await fsp.readFile('./golden-test-result.md', 'utf8');
+
+ // GitHub API has a limit of 65536 bytes for a comment body, so here we shrink the
+ // diff part (anything between and ) to 10,000 characters if it
+ // is too long.
+ const pattern = /()([\s\S]*?)(<\/details>)/;
+
+ const body = rawData.replace(pattern, function(match, p1, p2, p3) {
+ if (p2.length > 10000) {
+ return p1 + p2.substring(0, 5000) + '\n\n ...(truncated)...\n\n' + p2.substring(p2.length - 5000) + p3;
+ }
+ // No need to change anything if it is not too long.
+ return match;
+ });
+
+ // First find the comments made by the bot.
+ const comments = await github.rest.issues.listComments({
+ owner: owner,
+ repo: repo,
+ issue_number: issueNumber
+ });
+ const botComment = comments.data.find(comment => comment.user.login === 'github-actions[bot]' && comment.body.startsWith('## Golden Test'));
+
+ // Update or create the PR comment.
+ if (botComment) {
+ await github.rest.issues.updateComment({
+ owner: owner,
+ repo: repo,
+ comment_id: botComment.id,
+ body: body
+ });
+ } else {
+ await github.rest.issues.createComment({
+ owner: owner,
+ repo: repo,
+ issue_number: issueNumber,
+ body: body
+ });
+ }
diff --git a/.github/workflows/golden-test.yml b/.github/workflows/golden-test.yml
deleted file mode 100644
index 7fd98863..00000000
--- a/.github/workflows/golden-test.yml
+++ /dev/null
@@ -1,81 +0,0 @@
-name: Golden Test
-
-# NilAway output may change due to introduction of new feature or bug fixes. Since NilAway is still
-# at early stage of development, constantly updating / maintaining the golden test output will be
-# a burden. Therefore, we run this as a separate CI job and post the differences as a PR comment
-# for manual reviews.
-on:
- pull_request:
-
-jobs:
- golden-test:
- name: Golden Test
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- name: Check out repository
-
- - name: Fetch base branch (${{ github.event.pull_request.base.ref }}) locally
- run: git fetch origin ${{ github.event.pull_request.base.ref }}:${{ github.event.pull_request.base.ref }}
-
- - name: Set up Go
- uses: actions/setup-go@v5
- with:
- go-version: 1.22.x
- cache: false
-
- - name: Golden Test
- id: golden_test
- # Run golden test by comparing HEAD and the base branch (the target branch of the PR).
- # GitHub Actions terminates the job if it hits the resource limits. Here we limit the
- # memory usage to 8GiB to avoid that.
- run: |
- make golden-test GOMEMLIMIT=8192MiB ARGS="-base-branch ${{ github.event.pull_request.base.ref }} -result-file ${{ runner.temp }}/golden-test-result.md"
-
- - uses: actions/github-script@v7
- with:
- script: |
- const fsp = require('fs').promises;
-
- const issueNumber = context.issue.number;
- const owner = context.repo.owner;
- const repo = context.repo.repo;
- const rawData = await fsp.readFile(`${{ runner.temp }}/golden-test-result.md`, 'utf8');
-
- // GitHub API has a limit of 65536 bytes for a comment body, so here we shrink the
- // diff part (anything between and ) to 10,000 characters if it
- // is too long.
- const pattern = /()([\s\S]*?)(<\/details>)/;
-
- const body = rawData.replace(pattern, function(match, p1, p2, p3) {
- if (p2.length > 10000) {
- return p1 + p2.substring(0, 5000) + '\n\n ...(truncated)...\n\n' + p2.substring(p2.length - 5000) + p3;
- }
- // No need to change anything if it is not too long.
- return match;
- });
-
- // First find the comments made by the bot.
- const comments = await github.rest.issues.listComments({
- owner: owner,
- repo: repo,
- issue_number: issueNumber
- });
- const botComment = comments.data.find(comment => comment.user.login === 'github-actions[bot]' && comment.body.startsWith('## Golden Test'));
-
- // Update or create the PR comment.
- if (botComment) {
- await github.rest.issues.updateComment({
- owner: owner,
- repo: repo,
- comment_id: botComment.id,
- body: body
- });
- } else {
- await github.rest.issues.createComment({
- owner: owner,
- repo: repo,
- issue_number: issueNumber,
- body: body
- });
- }