|
| 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