Skip to content

Commit e747cf0

Browse files
radicalCopilot
andcommitted
feat(ci): post a sticky PR comment with the selected tests and jobs
The selector already wrote its selection (which test projects and jobs will run, and what is skipped) to the job step summary, but that is only visible by opening the run. Surface it as a sticky PR comment so reviewers see the selection up front. SelectTests now also writes the same summary markdown to the file named by SELECT_TESTS_COMMENT_FILE. tests.yml's setup_for_tests sets that env and, on pull_request, posts/updates a marker-keyed comment via actions/github-script. The step is continue-on-error so a read-only token (e.g. a fork PR) never fails CI. tests.yml is granted pull-requests: write, and ci.yml grants that scope on the tests job (reusable-workflow tokens are capped by the caller). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 231a98a commit e747cf0

3 files changed

Lines changed: 63 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ jobs:
7373
uses: ./.github/workflows/tests.yml
7474
name: Tests
7575
needs: [prepare_for_ci]
76+
# pull-requests: write lets tests.yml post the sticky "selected tests" PR comment; contents: read
77+
# keeps the rest at least-privilege. Reusable-workflow tokens are capped by the caller, so this is
78+
# where the elevated scope must be granted.
79+
permissions:
80+
contents: read
81+
pull-requests: write
7682
if: ${{ github.repository_owner == 'microsoft' && needs.prepare_for_ci.outputs.skip_workflow != 'true' }}
7783
with:
7884
versionOverrideArg: ${{ needs.prepare_for_ci.outputs.VERSION_SUFFIX_OVERRIDE }}

.github/workflows/tests.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ on:
1111
required: false
1212
type: string
1313

14+
permissions:
15+
contents: read
16+
# Needed by the "Comment selected tests on PR" step below to post/update the sticky selection
17+
# comment. The token is still capped by the caller (ci.yml grants this on the tests job).
18+
pull-requests: write
19+
1420
jobs:
1521
setup_for_tests:
1622
name: Setup for tests
@@ -84,6 +90,9 @@ jobs:
8490
# Absolute path so eng/Build.props ($(BeforeBuildPropsPath)) resolves it regardless of cwd.
8591
# Under artifacts/ (gitignored) so it is not picked up as a source change.
8692
BEFORE_BUILD_PROPS: ${{ github.workspace }}/artifacts/BeforeBuildProps.props
93+
# Where the selector writes the human-readable selection summary so the step below can post
94+
# it as a sticky PR comment (the same content also goes to the job step summary).
95+
SELECT_TESTS_COMMENT_FILE: ${{ github.workspace }}/artifacts/select-tests-comment.md
8796
# The ONE audit/enforce knob. 'false' (audit): no restriction props are written, so the
8897
# enumerate-tests step below builds the FULL matrix and run_* are all true; the advisory
8998
# summary still reports what enforcing would have skipped. 'true' (enforce): a non-ALL
@@ -129,6 +138,37 @@ jobs:
129138
# the run rather than mask the bug behind a silent fallback.
130139
./dotnet.sh run --project tools/SelectTests/SelectTests.csproj -- "${args[@]}"
131140
141+
# Post (or update) a sticky PR comment listing which test projects and jobs the selector chose,
142+
# so reviewers see the selection without opening the run's job summary. Informational only -- a
143+
# token/permission hiccup (e.g. a fork PR with a read-only token) must never fail CI.
144+
- name: Comment selected tests on PR
145+
if: ${{ github.event_name == 'pull_request' }}
146+
continue-on-error: true
147+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
148+
env:
149+
SELECT_TESTS_COMMENT_FILE: ${{ github.workspace }}/artifacts/select-tests-comment.md
150+
with:
151+
script: |
152+
const fs = require('fs');
153+
const file = process.env.SELECT_TESTS_COMMENT_FILE;
154+
if (!file || !fs.existsSync(file)) {
155+
core.info(`No selection summary at ${file}; skipping comment.`);
156+
return;
157+
}
158+
const marker = '<!-- select-tests-comment -->';
159+
const body = `${marker}\n${fs.readFileSync(file, 'utf8')}`;
160+
const { owner, repo } = context.repo;
161+
const issue_number = context.issue.number;
162+
const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number });
163+
const existing = comments.find(c => c.body && c.body.includes(marker));
164+
if (existing) {
165+
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
166+
core.info(`Updated selection comment ${existing.id}.`);
167+
} else {
168+
await github.rest.issues.createComment({ owner, repo, issue_number, body });
169+
core.info('Created selection comment.');
170+
}
171+
132172
# enumerate-tests reuses this job's checkout + restore (checkout/restore=false) so the props
133173
# file the selector wrote survives -- a fresh checkout would git-clean it away. When a
134174
# restriction was written, beforeBuildPropsPath points enumerate at the selected subset; in

tools/SelectTests/Program.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -461,14 +461,29 @@ static void AppendProjectList(StringBuilder builder, string title, IReadOnlyList
461461

462462
static void WriteOut(StringBuilder builder)
463463
{
464+
var markdown = builder.ToString();
464465
var summaryPath = Environment.GetEnvironmentVariable("GITHUB_STEP_SUMMARY");
465466
if (summaryPath is not null)
466467
{
467-
File.AppendAllText(summaryPath, builder.ToString());
468+
File.AppendAllText(summaryPath, markdown);
468469
}
469470
else
470471
{
471-
Console.Error.Write(builder.ToString());
472+
Console.Error.Write(markdown);
473+
}
474+
475+
// Also emit the full markdown to a standalone file when requested, so the workflow can post
476+
// it as a sticky PR comment. The job summary alone is not visible without opening the run,
477+
// and the selection (which test projects + jobs run) is the thing reviewers want up front.
478+
var commentPath = Environment.GetEnvironmentVariable("SELECT_TESTS_COMMENT_FILE");
479+
if (!string.IsNullOrEmpty(commentPath))
480+
{
481+
var dir = Path.GetDirectoryName(Path.GetFullPath(commentPath));
482+
if (!string.IsNullOrEmpty(dir))
483+
{
484+
Directory.CreateDirectory(dir);
485+
}
486+
File.WriteAllText(commentPath, markdown);
472487
}
473488
}
474489
}

0 commit comments

Comments
 (0)