Skip to content

Commit 00959db

Browse files
shenxianpengclaude
andcommitted
test: add auto-fix end-to-end workflow
Exercises the auto-fix feature without merging either PR it depends on (cpp-linter-action#443 and cpp-linter#202). The action is checked out at its PR branch and consumed as a local action, and the cpp-linter version it pins is rewritten in the runner's workspace to install from the cpp-linter PR branch, so neither upstream repository needs a throwaway branch. The job commits a deliberately malformed source file, runs the action with auto-fix enabled, then asserts the file was reformatted, committed with the configured message, and pushed to the remote. The fixture is removed again on the way out. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012uXrN1wk5EaqK5GimN3deT
1 parent db329f7 commit 00959db

1 file changed

Lines changed: 195 additions & 0 deletions

File tree

.github/workflows/auto-fix-e2e.yml

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
name: auto-fix e2e
2+
3+
# End-to-end test for the `auto-fix` feature, exercised WITHOUT merging either
4+
# of the two pull requests it depends on:
5+
#
6+
# - cpp-linter/cpp-linter-action#443 (the `auto-fix` input)
7+
# - cpp-linter/cpp-linter#202 (the `--fix` CLI flag)
8+
#
9+
# The action is checked out at its PR branch and used as a local action, and the
10+
# `cpp-linter` version it pins is rewritten in the workspace to install from the
11+
# cpp-linter PR branch instead. Nothing is patched anywhere but this runner.
12+
#
13+
# Triggered by pushing to the test branch: a `workflow_dispatch`-only workflow is
14+
# not dispatchable until it lands on the default branch, which is exactly what we
15+
# are avoiding here. Pushes made with GITHUB_TOKEN do not start new workflow
16+
# runs, so the auto-fix commit cannot re-trigger this job.
17+
18+
on:
19+
push:
20+
branches: ['claude/cpp-linter-org-gaps-z9fuep']
21+
workflow_dispatch:
22+
inputs:
23+
action-ref:
24+
description: 'cpp-linter-action ref to test'
25+
default: 'feature/auto-fix'
26+
cpp-linter-ref:
27+
description: 'cpp-linter ref providing --fix'
28+
default: 'feature/auto-fix'
29+
30+
permissions:
31+
contents: write
32+
33+
env:
34+
CPP_LINTER_REF: ${{ inputs.cpp-linter-ref || 'feature/auto-fix' }}
35+
ACTION_DIR: .action-under-test
36+
TEST_FILE: src/e2e_autofix_demo.cpp
37+
CLANG_VERSION: '18'
38+
COMMIT_MSG: 'style: apply clang-format fixes'
39+
40+
jobs:
41+
auto-fix:
42+
runs-on: ubuntu-latest
43+
steps:
44+
# `persist-credentials` must stay enabled (the default): auto-fix pushes
45+
# using the credentials this step leaves behind. Full history keeps the
46+
# later push from being rejected as a shallow update.
47+
- uses: actions/checkout@v7
48+
with:
49+
fetch-depth: 0
50+
51+
# Checked out into a fixed path because `uses:` cannot take an expression.
52+
- name: Check out the action under test
53+
uses: actions/checkout@v7
54+
with:
55+
repository: cpp-linter/cpp-linter-action
56+
ref: ${{ inputs.action-ref || 'feature/auto-fix' }}
57+
path: .action-under-test
58+
persist-credentials: false
59+
60+
- name: Point the action at the cpp-linter PR branch
61+
run: |
62+
set -euo pipefail
63+
cd "$ACTION_DIR"
64+
python3 - "$CPP_LINTER_REF" <<'PY'
65+
import pathlib
66+
import re
67+
import sys
68+
69+
ref = sys.argv[1]
70+
spec = f"cpp-linter @ git+https://github.com/cpp-linter/cpp-linter.git@{ref}"
71+
path = pathlib.Path("pyproject.toml")
72+
text = path.read_text(encoding="utf-8")
73+
patched, count = re.subn(r'"cpp-linter==[^"]+"', f'"{spec}"', text)
74+
if count != 1:
75+
sys.exit(f"expected exactly 1 cpp-linter pin, patched {count}")
76+
path.write_text(patched, encoding="utf-8")
77+
print(patched)
78+
PY
79+
# `uv sync` runs without --frozen/--locked, but drop the lock anyway so
80+
# it is forced to re-resolve against the git dependency.
81+
rm -f uv.lock
82+
83+
# The fixture has to be committed while still malformed: clang-format fixes
84+
# it in place and the action stages the result with `git add -u`, which only
85+
# picks up modifications to tracked files.
86+
- name: Commit a deliberately malformed source file
87+
run: |
88+
set -euo pipefail
89+
cat > "$TEST_FILE" <<'CPP'
90+
#include <stdio.h>
91+
int main( ){int x=0 ;for(;;){break;}printf("Hello world!\n") ;return x;}
92+
CPP
93+
git config user.name 'github-actions[bot]'
94+
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
95+
git add "$TEST_FILE"
96+
git commit -m "test: add malformed source for auto-fix e2e [skip ci]"
97+
git push origin "HEAD:refs/heads/${GITHUB_REF_NAME}"
98+
{
99+
echo "MALFORMED_SHA=$(git rev-parse HEAD)"
100+
echo "MALFORMED_BLOB=$(git hash-object "$TEST_FILE")"
101+
} >> "$GITHUB_ENV"
102+
103+
# clang-tidy is switched off (`tidy-checks: '-*'`) so this exercises the
104+
# clang-format auto-fix path only, and needs no compilation database.
105+
- name: Run cpp-linter with auto-fix
106+
uses: ./.action-under-test
107+
id: linter
108+
env:
109+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
110+
with:
111+
style: file
112+
tidy-checks: '-*'
113+
files-changed-only: false
114+
lines-changed-only: false
115+
ignore: build|.action-under-test
116+
version: '18'
117+
verbosity: debug
118+
auto-fix: true
119+
auto-fix-commit-msg: 'style: apply clang-format fixes'
120+
121+
- name: Verify the fix was applied, committed and pushed
122+
run: |
123+
set -uo pipefail
124+
failed=0
125+
126+
echo "::group::resulting file"
127+
cat "$TEST_FILE"
128+
echo "::endgroup::"
129+
130+
if [ "$(git hash-object "$TEST_FILE")" = "$MALFORMED_BLOB" ]; then
131+
echo "::error title=Not reformatted::$TEST_FILE is byte-identical to the malformed input; --fix did not rewrite it."
132+
failed=1
133+
else
134+
echo "PASS: file was reformatted"
135+
fi
136+
137+
head_sha="$(git rev-parse HEAD)"
138+
subject="$(git log -1 --pretty=%s)"
139+
if [ "$head_sha" = "$MALFORMED_SHA" ]; then
140+
echo "::error title=No commit::auto-fix produced no commit (HEAD is still the malformed commit)."
141+
failed=1
142+
else
143+
echo "PASS: auto-fix commit $head_sha"
144+
echo " subject: $subject"
145+
echo " author: $(git log -1 --pretty='%an <%ae>')"
146+
fi
147+
148+
if [ "$subject" != "$COMMIT_MSG" ]; then
149+
echo "::error title=Wrong commit message::expected '$COMMIT_MSG', got '$subject'"
150+
failed=1
151+
else
152+
echo "PASS: commit message matches auto-fix-commit-msg"
153+
fi
154+
155+
# The committed result must itself be clang-format clean. The action
156+
# installs the tool into its own prefix, so only check when reachable.
157+
fmt="$(command -v "clang-format-${CLANG_VERSION}" || command -v clang-format || true)"
158+
if [ -n "$fmt" ]; then
159+
if "$fmt" --style=file --dry-run --Werror "$TEST_FILE"; then
160+
echo "PASS: committed file satisfies .clang-format"
161+
else
162+
echo "::error title=Still unformatted::the committed file still violates .clang-format"
163+
failed=1
164+
fi
165+
else
166+
echo "note: clang-format not on PATH here; skipped the re-check"
167+
fi
168+
169+
# The fix must exist on the remote, not only in this workspace.
170+
git fetch -q origin "$GITHUB_REF_NAME"
171+
if [ "$(git rev-parse FETCH_HEAD)" != "$head_sha" ]; then
172+
echo "::error title=Not pushed::the auto-fix commit is not on origin/$GITHUB_REF_NAME"
173+
failed=1
174+
else
175+
echo "PASS: auto-fix commit is present on origin/$GITHUB_REF_NAME"
176+
fi
177+
178+
if [ "$failed" -eq 0 ]; then
179+
echo "auto-fix e2e PASSED"
180+
fi
181+
exit "$failed"
182+
183+
- name: Remove the test fixture
184+
if: always()
185+
run: |
186+
set -euo pipefail
187+
if [ ! -f "$TEST_FILE" ]; then
188+
echo "nothing to clean up"
189+
exit 0
190+
fi
191+
git config user.name 'github-actions[bot]'
192+
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
193+
git rm -q "$TEST_FILE"
194+
git commit -m "test: drop auto-fix e2e fixture [skip ci]"
195+
git push origin "HEAD:refs/heads/${GITHUB_REF_NAME}"

0 commit comments

Comments
 (0)