Skip to content

feat(test): Add CTest infrastructure and improve UnitTestFramework #705

feat(test): Add CTest infrastructure and improve UnitTestFramework

feat(test): Add CTest infrastructure and improve UnitTestFramework #705

Workflow file for this run

name: PR Checks
on:
pull_request_target:
types: [opened, edited, synchronize, reopened, closed]
permissions:
contents: read
pull-requests: write
models: read
jobs:
title:
name: Validate PR Title
runs-on: ubuntu-latest
if: github.event.action != 'closed'
timeout-minutes: 5
steps:
- name: Check PR title format
id: title_check
continue-on-error: true
uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
feat
fix
docs
style
refactor
perf
test
build
ci
chore
revert
requireScope: false
subjectPattern: ^.{1,80}$
subjectPatternError: PR title must be 80 characters or less.
wip: true
validateSingleCommit: false
- name: Checkout prompt file
if: |
steps.title_check.outcome == 'failure' &&
github.event.pull_request.user.type != 'Bot'
uses: actions/checkout@v6
with:
sparse-checkout: .github/prompts
sparse-checkout-cone-mode: false
- name: Get changed files
id: files
if: |
steps.title_check.outcome == 'failure' &&
github.event.pull_request.user.type != 'Bot'
uses: actions/github-script@v8
with:
result-encoding: string
script: |
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
per_page: 50
});
return files.map(f => `${f.filename} (+${f.additions}/-${f.deletions})`).join('\n');
- name: Sanitize inputs for AI
id: sanitize
if: |
steps.title_check.outcome == 'failure' &&
github.event.pull_request.user.type != 'Bot'
shell: bash
env:
PR_TITLE: ${{ github.event.pull_request.title }}
PR_BODY: ${{ github.event.pull_request.body }}
CHANGED_FILES: ${{ steps.files.outputs.result }}
run: |
# Truncate inputs to prevent token flooding
SAFE_TITLE=$(echo "$PR_TITLE" | head -c 200 | tr -d '\r')
SAFE_BODY=$(echo "$PR_BODY" | head -c 2000 | tr -d '\r')
SAFE_FILES=$(echo "$CHANGED_FILES" | head -30)
# Write to outputs using heredoc with unique delimiters
{
echo "title<<TITLE_END_7f3a9c2e"
echo "$SAFE_TITLE"
echo "TITLE_END_7f3a9c2e"
echo "body<<BODY_END_8b4d1e6f"
echo "$SAFE_BODY"
echo "BODY_END_8b4d1e6f"
echo "files<<FILES_END_2c5a8d9b"
echo "$SAFE_FILES"
echo "FILES_END_2c5a8d9b"
} >> "$GITHUB_OUTPUT"
- name: Suggest title with GitHub Models
id: suggest
if: |
steps.title_check.outcome == 'failure' &&
github.event.pull_request.user.type != 'Bot'
continue-on-error: true
uses: actions/ai-inference@v2
with:
prompt-file: .github/prompts/pr-title-suggestion.prompt.yml
max-tokens: 100
input: |
current_title: ${{ steps.sanitize.outputs.title }}
pr_body: ${{ steps.sanitize.outputs.body }}
changed_files: ${{ steps.sanitize.outputs.files }}
- name: Comment title suggestion
if: steps.title_check.outcome == 'failure' && steps.suggest.outputs.response
uses: actions/github-script@v8
env:
SUGGESTED_TITLE: ${{ steps.suggest.outputs.response }}
with:
script: |
const pr = context.payload.pull_request;
let suggestedTitle = process.env.SUGGESTED_TITLE.trim();
// Validate AI output format - must be conventional commit
const validFormat = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-zA-Z0-9\-\/]+\))?:\s.{1,80}$/;
if (!validFormat.test(suggestedTitle)) {
console.log('AI response did not match conventional commit format, skipping');
return;
}
// Escape HTML entities for safety
const escapeHtml = (str) => str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/`/g, '&#96;');
const currentTitle = escapeHtml(pr.title);
suggestedTitle = escapeHtml(suggestedTitle);
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
per_page: 100
});
const botComment = comments.find(c =>
c.user?.type === 'Bot' && c.body?.includes('<!-- pr-title-suggestion -->')
);
const commentBody = `<!-- pr-title-suggestion -->
## PR Title Suggestion
Your PR title doesn't follow the [conventional commit](https://www.conventionalcommits.org/) format.
| Current Title | Suggested Title |
|---------------|-----------------|
| \`${currentTitle}\` | \`${suggestedTitle}\` |
### Format
\`<type>(<scope>): <description>\`
**Types:** \`feat\` \`fix\` \`docs\` \`refactor\` \`perf\` \`test\` \`ci\` \`chore\` \`build\` \`style\` \`revert\`
*Suggested by GitHub Models. Update your title to dismiss this suggestion.*`;
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: commentBody
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: commentBody
});
}
- name: Minimize suggestion if title valid
if: steps.title_check.outcome == 'success'
uses: actions/github-script@v8
with:
script: |
const pr = context.payload.pull_request;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
per_page: 100
});
const botComment = comments.find(c =>
c.user?.type === 'Bot' && c.body?.includes('<!-- pr-title-suggestion -->')
);
if (botComment && !botComment.body.includes('(resolved)')) {
const minimizedBody = `<!-- pr-title-suggestion -->
<details>
<summary>PR Title Suggestion (resolved)</summary>
${botComment.body.replace('<!-- pr-title-suggestion -->', '')}
</details>`;
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: minimizedBody
});
}
size:
name: Label PR Size
runs-on: ubuntu-latest
if: github.event.action != 'closed'
timeout-minutes: 5
steps:
- name: Get current size label
id: current
run: |
PR_NUMBER=${{ github.event.pull_request.number }}
CURRENT_LABEL=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/labels" \
--jq '.[] | select(.name | startswith("size/")) | .name' | head -1)
echo "label=${CURRENT_LABEL}" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Label PR by size
continue-on-error: true
uses: codelytv/pr-size-labeler@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
xs_label: 'size/XS'
xs_max_size: 150
s_label: 'size/S'
s_max_size: 500
m_label: 'size/M'
m_max_size: 1000
l_label: 'size/L'
l_max_size: 1500
xl_label: 'size/XL'
fail_if_xl: false
message_if_xl: |
## Large PR Warning
This PR has **more than 1500 lines** changed, which can be difficult to review thoroughly.
Consider:
- Splitting into smaller, focused PRs
- Separating refactoring from feature changes
- Breaking out unrelated changes into separate PRs
Smaller PRs get reviewed faster and reduce the risk of bugs slipping through.
files_to_ignore: |
package-lock.json
*.lock
translations/*
- name: Remove outdated size labels
run: |
PR_NUMBER=${{ github.event.pull_request.number }}
OLD_LABEL="${{ steps.current.outputs.label }}"
# Get all current size labels
CURRENT_LABELS=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/labels" \
--jq '.[] | select(.name | startswith("size/")) | .name')
# Count size labels - if more than one, remove the old one
LABEL_COUNT=$(echo "$CURRENT_LABELS" | grep -c . || echo 0)
if [[ "$LABEL_COUNT" -gt 1 && -n "$OLD_LABEL" ]]; then
# Multiple size labels exist - remove the old one
ENCODED_LABEL=$(echo "$OLD_LABEL" | sed 's|/|%2F|g')
gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/labels/${ENCODED_LABEL}" \
-X DELETE 2>/dev/null || true
fi
# If only one label exists, size didn't change - nothing to remove
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
label:
name: Label PR by Files
runs-on: ubuntu-latest
if: github.event.action != 'closed'
timeout-minutes: 5
steps:
- name: Label PR by changed files
uses: actions/labeler@v6
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
sync-labels: true