Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/e2e/demo.cpp.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/** This is a very ugly test code (doomed to fail linting) */
#include "demo.hpp"
#include <cstdio>
#include <cstddef>

// using size_t from cstddef
size_t dummyFunc(size_t i) { return i; }

int scale(int value)
{
if (value > 0)
return value*3;
return 0;
}

int main()
{
for (;;)
break;

printf("Hello world! %d\n", scale( 14 ));

return 0;
}
2 changes: 2 additions & 0 deletions .github/e2e/target.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
action-ref=main
clang-version=21
266 changes: 266 additions & 0 deletions .github/workflows/e2e-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
name: e2e on a real PR

# Tests a cpp-linter-action branch, tag or commit on a real pull request:
#
# 1. "Run workflow" (workflow_dispatch) creates a branch `e2e/run-<id>` with known lint problems
# (.github/e2e/demo.cpp.txt) and opens a PR for it.
# 2. The PR runs the action under test on 3 operating systems.
# 3. The `verify` job checks what the action left on the PR (thread comment, review, annotations,
# outputs), posts the result as a PR comment, and closes the PR if everything passed.
#
# A PR opened with the default GITHUB_TOKEN does not start workflows. Either add a repository
# secret named E2E_TOKEN (a token that may push branches and open PRs here), or close and reopen
# the PR that step 1 created.

on:
workflow_dispatch:
inputs:
action-ref:
description: Branch, tag or commit of cpp-linter-action to test
default: main
required: true
clang-version:
description: Version of the clang tools to use
default: '21'
required: true
pull_request:
branches: [master]
paths: [.github/e2e/target.env]

permissions: {}

concurrency:
group: e2e-pr-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

jobs:
open-pr:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# the credentials are kept because the next step pushes a branch
token: ${{ secrets.E2E_TOKEN || github.token }}

- name: Create the test branch and open a PR
env:
GH_TOKEN: ${{ secrets.E2E_TOKEN || github.token }}
HAS_E2E_TOKEN: ${{ secrets.E2E_TOKEN != '' }}
ACTION_REF: ${{ inputs.action-ref }}
CLANG_VERSION: ${{ inputs.clang-version }}
run: |
for value in "$ACTION_REF" "$CLANG_VERSION"; do
if [[ ! "$value" =~ ^[A-Za-z0-9._/-]+$ ]]; then
echo "::error::Unexpected characters in input: $value"
exit 1
fi
done
branch="e2e/run-${GITHUB_RUN_ID}"
git switch -c "$branch"
cp .github/e2e/demo.cpp.txt src/demo.cpp
printf 'action-ref=%s\nclang-version=%s\n' "$ACTION_REF" "$CLANG_VERSION" > .github/e2e/target.env
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add src/demo.cpp .github/e2e/target.env
git commit -m "test: e2e fixture for cpp-linter-action@${ACTION_REF}"
git push origin "$branch"
url=$(gh pr create --base master --head "$branch" \
--title "test: e2e of cpp-linter-action@${ACTION_REF} (clang ${CLANG_VERSION})" \
--body "Opened by the \`e2e on a real PR\` workflow. Do not merge. It is closed automatically when all checks pass.")
echo "Opened $url" >> "$GITHUB_STEP_SUMMARY"
if [[ "$HAS_E2E_TOKEN" != "true" ]]; then
{
echo
echo "There is no \`E2E_TOKEN\` secret, so this PR was opened with the default token"
echo "and GitHub will not start the test for it. Close and reopen the PR to start it."
} >> "$GITHUB_STEP_SUMMARY"
echo "::warning::Close and reopen $url to start the test (no E2E_TOKEN secret)."
fi

lint:
# The `paths` filter above limits this to PRs that change `.github/e2e/target.env`.
if: >-
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
permissions:
contents: read
pull-requests: write
outputs:
action-ref: ${{ steps.target.outputs.action-ref }}
action-sha: ${{ steps.action-sha.outputs.sha }}
clang-version: ${{ steps.target.outputs.clang-version }}
ubuntu-latest: ${{ steps.result.outputs.ubuntu-latest }}
macos-latest: ${{ steps.result.outputs.macos-latest }}
windows-latest: ${{ steps.result.outputs.windows-latest }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Read which action version to test
id: target
shell: bash
run: |
ref=$(sed -n 's/^action-ref=//p' .github/e2e/target.env | head -n 1)
clang=$(sed -n 's/^clang-version=//p' .github/e2e/target.env | head -n 1)
for value in "$ref" "$clang"; do
if [[ ! "$value" =~ ^[A-Za-z0-9._/-]+$ ]]; then
echo "::error::Unexpected value in .github/e2e/target.env: $value"
exit 1
fi
done
echo "action-ref=$ref" >> "$GITHUB_OUTPUT"
echo "clang-version=$clang" >> "$GITHUB_OUTPUT"

- name: Get the action under test
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: cpp-linter/cpp-linter-action
ref: ${{ steps.target.outputs.action-ref }}
path: .e2e-action
persist-credentials: false

- name: Record the action's commit
id: action-sha
shell: bash
run: echo "sha=$(git -C .e2e-action rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- name: Generate compilation database
shell: bash
run: mkdir build && cmake -Bbuild src

# Only one job posts to the PR, so that the jobs do not overwrite each other's feedback.
- name: Run the action under test
id: linter
uses: ./.e2e-action
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
style: file
lines-changed-only: true
ignore: build|.github|.e2e-action
database: build
verbosity: debug
version: ${{ steps.target.outputs.clang-version }}
extra-args: -std=c++14 -Wall
step-summary: true
thread-comments: ${{ matrix.os == 'ubuntu-latest' && 'update' || 'false' }}
file-annotations: ${{ matrix.os == 'ubuntu-latest' }}
tidy-review: ${{ matrix.os == 'ubuntu-latest' }}
format-review: ${{ matrix.os == 'ubuntu-latest' }}

- name: Record the outputs
id: result
if: always()
shell: bash
env:
OS: ${{ matrix.os }}
TOTAL: ${{ steps.linter.outputs.checks-failed }}
TIDY: ${{ steps.linter.outputs.clang-tidy-checks-failed }}
FORMAT: ${{ steps.linter.outputs.clang-format-checks-failed }}
run: echo "${OS}=${TOTAL:-none}/${TIDY:-none}/${FORMAT:-none}" >> "$GITHUB_OUTPUT"

verify:
needs: lint
if: always() && needs.lint.result != 'skipped'
runs-on: ubuntu-latest
permissions:
contents: write # to delete the test branch when the test passed
pull-requests: write
checks: read
steps:
- name: Check what the action left on the PR
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
LINT_RESULT: ${{ needs.lint.result }}
ACTION_REF: ${{ needs.lint.outputs.action-ref }}
ACTION_SHA: ${{ needs.lint.outputs.action-sha }}
CLANG_VERSION: ${{ needs.lint.outputs.clang-version }}
RESULT_UBUNTU: ${{ needs.lint.outputs.ubuntu-latest }}
RESULT_MACOS: ${{ needs.lint.outputs.macos-latest }}
RESULT_WINDOWS: ${{ needs.lint.outputs.windows-latest }}
run: |
marker='<!-- cpp linter action -->'
repo="$GITHUB_REPOSITORY"
failed=0
rows=""
check() { # check <name> <actual> <expected> <ok: true|false>
local mark=":white_check_mark:"
if [[ "$4" != "true" ]]; then mark=":x:"; failed=1; fi
rows+="| $1 | $2 | $3 | $mark |"$'\n'
}

check "lint jobs" "$LINT_RESULT" "success" "$([[ "$LINT_RESULT" == success ]] && echo true || echo false)"

for pair in "ubuntu-latest=$RESULT_UBUNTU" "macos-latest=$RESULT_MACOS" "windows-latest=$RESULT_WINDOWS"; do
os=${pair%%=*}
IFS=/ read -r total tidy format <<< "${pair#*=}"
ok=false
if [[ "$total" =~ ^[0-9]+$ && "$tidy" =~ ^[0-9]+$ && "$format" =~ ^[0-9]+$ ]] &&
(( tidy > 0 && format > 0 && total == tidy + format )); then
ok=true
fi
check "outputs on $os (total/tidy/format)" "${total:-none}/${tidy:-none}/${format:-none}" "tidy > 0, format > 0, total = tidy + format" "$ok"
done

comments=$(gh api "repos/$repo/issues/$PR/comments" --paginate |
jq -s --arg m "$marker" '[.[][] | select(.body | contains($m))] | length')
check "thread comments" "$comments" "1" "$([[ "$comments" == 1 ]] && echo true || echo false)"

reviews=$(gh api "repos/$repo/pulls/$PR/reviews" --paginate |
jq -s --arg m "$marker" '[.[][] | select(.body | contains($m))]')
review_count=$(jq 'length' <<< "$reviews")
review_state=$(jq -r '.[-1].state // "none"' <<< "$reviews")
check "PR reviews" "$review_count (latest: $review_state)" "1 or more (latest: CHANGES_REQUESTED)" \
"$([[ "$review_count" -ge 1 && "$review_state" == CHANGES_REQUESTED ]] && echo true || echo false)"

suggestions=$(gh api "repos/$repo/pulls/$PR/comments" --paginate |
jq -s --arg m "$marker" '[.[][] | select(.body | contains($m))] | length')
check "review comments" "$suggestions" "1 or more" "$([[ "$suggestions" -ge 1 ]] && echo true || echo false)"

run_id=$(gh api "repos/$repo/commits/$HEAD_SHA/check-runs" --paginate |
jq -s -r '[.[].check_runs[] | select(.name == "lint (ubuntu-latest)")] | sort_by(.started_at) | .[-1].id // empty')
annotations=0
if [[ -n "$run_id" ]]; then
annotations=$(gh api "repos/$repo/check-runs/$run_id/annotations" --paginate |
jq -s '[.[][] | select(.path | startswith("src/"))] | length')
fi
check "file annotations on src/" "$annotations" "2 or more" "$([[ "$annotations" -ge 2 ]] && echo true || echo false)"

{
echo "## e2e result: $([[ $failed == 0 ]] && echo passed || echo failed)"
echo
echo "cpp-linter-action \`$ACTION_REF\` (\`${ACTION_SHA:0:7}\`), clang $CLANG_VERSION,"
echo "[workflow run]($GITHUB_SERVER_URL/$repo/actions/runs/$GITHUB_RUN_ID)"
echo
echo "| Check | Actual | Expected | |"
echo "|---|---|---|---|"
printf '%s' "$rows"
} > result.md
cat result.md >> "$GITHUB_STEP_SUMMARY"
gh pr comment "$PR" --repo "$repo" --body-file result.md
echo "failed=$failed" >> "$GITHUB_ENV"

- name: Close the PR when everything passed
if: env.failed == '0'
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.pull_request.number }}
run: gh pr close "$PR" --repo "$GITHUB_REPOSITORY" --delete-branch

- name: Fail when a check did not pass
if: env.failed != '0'
run: |
echo "::error::The e2e test failed. The PR stays open so that it can be inspected."
exit 1
32 changes: 24 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
# C/C++ Lint Action Test Repository

[![cpp-linter as action](https://github.com/cpp-linter/test-cpp-linter-action/actions/workflows/cpp-lint-action.yml/badge.svg)](https://github.com/cpp-linter/test-cpp-linter-action/actions/workflows/cpp-lint-action.yml)
<!-- [![cpp-linter as pkg](https://github.com/cpp-linter/test-cpp-linter-action/actions/workflows/cpp-lint-package.yml/badge.svg)](https://github.com/cpp-linter/test-cpp-linter-action/actions/workflows/cpp-lint-package.yml) -->

This is a test repository which has added `cpp-linter.yml`, so when an unformatted c/c++ source file was committed and create a Pull Request will automatically recognize and add warning comments.

If you have any suggestions or contributions, welcome to create issues/PRs [here](https://github.com/cpp-linter/cpp-linter-action).
# C/C++ Lint Action Test Repository

[![cpp-linter as action](https://github.com/cpp-linter/test-cpp-linter-action/actions/workflows/cpp-lint-action.yml/badge.svg)](https://github.com/cpp-linter/test-cpp-linter-action/actions/workflows/cpp-lint-action.yml)
<!-- [![cpp-linter as pkg](https://github.com/cpp-linter/test-cpp-linter-action/actions/workflows/cpp-lint-package.yml/badge.svg)](https://github.com/cpp-linter/test-cpp-linter-action/actions/workflows/cpp-lint-package.yml) -->

This is a test repository which has added `cpp-linter.yml`, so when an unformatted c/c++ source file was committed and create a Pull Request will automatically recognize and add warning comments.

If you have any suggestions or contributions, welcome to create issues/PRs [here](https://github.com/cpp-linter/cpp-linter-action).

## End-to-end test on a real pull request

The [`e2e on a real PR`](https://github.com/cpp-linter/test-cpp-linter-action/actions/workflows/e2e-pr.yml)
workflow tests a branch, tag or commit of cpp-linter-action on a real pull request in this repository:

1. Start the workflow with "Run workflow" and enter the action ref (for example `main` or `v3.0.0-rc0`)
and the clang version.
2. It creates a branch `e2e/run-<id>` with known lint problems and opens a PR for it.
3. The PR runs the action on Linux, macOS and Windows, then checks the thread comment, the PR
review, the file annotations and the outputs, and posts the result as a PR comment.
4. If everything passed, the PR is closed and its branch deleted. If not, it stays open for a look.

GitHub does not start workflows for a PR that was opened with the default token. Until a
repository secret named `E2E_TOKEN` exists (a token that may push branches and open PRs here),
close and reopen the PR from step 2 to start the test.
9 changes: 8 additions & 1 deletion src/demo.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/** This is a very ugly test code (doomed to fail linting) */
#include "demo.hpp"
#include <cstdio>
Expand All @@ -6,12 +6,19 @@
// using size_t from cstddef
size_t dummyFunc(size_t i) { return i; }

int scale(int value)

Check warning on line 9 in src/demo.cpp

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

src/demo.cpp:9:5 [modernize-use-trailing-return-type]

use a trailing return type for this function

Check warning on line 9 in src/demo.cpp

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

src/demo.cpp:9:5 [misc-use-internal-linkage]

function 'scale' can be made static or moved into an anonymous namespace to enforce internal linkage

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy diagnostics

Suggested change
int scale(int value)
static auto scale(int value) -> int

{
if (value > 0)

Check warning on line 11 in src/demo.cpp

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

src/demo.cpp:11:19 [readability-braces-around-statements]

statement should be inside braces
return value*3;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-format suggestion

Suggested change
return value*3;
return value * 3;

Comment on lines +11 to +12

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy diagnostics

Suggested change
if (value > 0)
return value*3;
if (value > 0) {
return value * 3;
}

return 0;
}

int main()
{
for (;;)
break;

printf("Hello world!\n");
printf("Hello world! %d\n", scale( 14 ));

Check warning on line 21 in src/demo.cpp

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

src/demo.cpp:21:40 [cppcoreguidelines-avoid-magic-numbers]

14 is a magic number; consider replacing it with a named constant

Check warning on line 21 in src/demo.cpp

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

src/demo.cpp:21:5 [cppcoreguidelines-pro-type-vararg]

do not call c-style vararg functions

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-format suggestion

Suggested change
printf("Hello world! %d\n", scale( 14 ));
printf("Hello world! %d\n", scale(14));

clang-tidy diagnostic

src/demo.cpp:21:5: warning: [cppcoreguidelines-pro-type-vararg]

do not call c-style vararg functions

   21 |     printf("Hello world! %d\n", scale( 14 ));
      |     ^

clang-tidy diagnostic

src/demo.cpp:21:40: warning: [cppcoreguidelines-avoid-magic-numbers]

14 is a magic number; consider replacing it with a named constant

   21 |     printf("Hello world! %d\n", scale( 14 ));
      |                                        ^


return 0;
}
Loading