Skip to content

Commit 72e6537

Browse files
Professionalize the C++ exercise repository
1 parent 00899cf commit 72e6537

37 files changed

Lines changed: 529 additions & 134 deletions

File tree

.gitattributes

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
* text=auto eol=lf
2+
3+
*.cpp text eol=lf
4+
*.h text eol=lf
5+
*.hpp text eol=lf
6+
*.mk text eol=lf
7+
Makefile text eol=lf
8+
*.sh text eol=lf
9+
*.yml text eol=lf
10+
*.yaml text eol=lf

.github/FUNDING.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
github: doctorlai
22
patreon: doctorlai
33
buy_me_a_coffee: y0BtG5R
4-
thanks_dev: doctorlai
4+
custom:
5+
["https://www.buymeacoffee.com/y0BtG5R", "https://paypal.me/doctorlai/5"]

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,20 @@ assignees: ''
1010
**Describe the bug**
1111
A clear and concise description of what the bug is.
1212

13-
**To Reproduce**
14-
Steps to reproduce the behavior:
15-
1. Go to '...'
16-
2. Click on '....'
17-
3. Scroll down to '....'
18-
4. See error
13+
**To reproduce**
14+
List the example, command, arguments, and sanitizer configuration that reproduce the behavior.
1915

2016
**Expected behavior**
2117
A clear and concise description of what you expected to happen.
2218

23-
**Screenshots**
24-
If applicable, add screenshots to help explain your problem.
19+
**Environment**
20+
- OS and version:
21+
- Compiler and version:
22+
- Sanitizer (`address`, `thread`, `undefined`, or none):
23+
- Commit SHA:
2524

26-
**Desktop (please complete the following information):**
27-
- OS: [e.g. iOS]
28-
- Browser [e.g. chrome, safari]
29-
- Version [e.g. 22]
30-
31-
**Smartphone (please complete the following information):**
32-
- Device: [e.g. iPhone6]
33-
- OS: [e.g. iOS8.1]
34-
- Browser [e.g. stock browser, safari]
35-
- Version [e.g. 22]
25+
**Output**
26+
Include the complete compiler error, sanitizer report, or program output.
3627

3728
**Additional context**
3829
Add any other context about the problem here.

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
## Summary
2+
3+
Describe the problem and the approach taken.
4+
5+
## Validation
6+
7+
- [ ] `make lint`
8+
- [ ] Relevant example tests pass
9+
- [ ] `make check` passes, or any unavailable check is explained below
10+
- [ ] `make coverage` remains at or above the required threshold
11+
12+
## Checklist
13+
14+
- [ ] The change is focused and follows the existing C++ style
15+
- [ ] New behavior has meaningful tests
16+
- [ ] Documentation and `CHANGELOG.md` are updated when appropriate
17+
- [ ] No generated binaries or coverage files are included
18+
19+
## Additional context
20+
21+
Include sanitizer output, performance notes, or follow-up work when relevant.

.github/dependabot.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: github-actions
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
labels:
8+
- dependencies
9+
- github-actions
10+
open-pull-requests-limit: 5

.github/workflows/ci.yml

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,53 @@ on:
66
pull_request:
77
branches: [main]
88

9+
permissions:
10+
contents: read
11+
12+
concurrency:
13+
group: ci-${{ github.workflow }}-${{ github.ref }}
14+
cancel-in-progress: true
15+
916
jobs:
10-
build-and-run:
17+
check:
1118
runs-on: ubuntu-latest
1219
env:
13-
ASAN_OPTIONS: detect_leaks=1 # automatically detect memory leaks
20+
ASAN_OPTIONS: detect_leaks=1
1421
steps:
15-
# 1. Checkout repo
1622
- name: Checkout code
17-
uses: actions/checkout@v4
23+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
1824

19-
# 2. Install build tools
2025
- name: Install build tools
2126
run: |
2227
sudo apt-get update
23-
sudo apt-get install -y build-essential g++-14 clang clang-format libtbb-dev
28+
sudo apt-get install -y g++-14 make clang-format libtbb-dev
2429
25-
# 3. Clang-format check
26-
- name: Clang-format Check
27-
run: |
28-
./clang-check.sh *.cpp *.hpp *.c *.h
30+
- name: Run all checks
31+
run: make check SANITIZERS="address thread undefined"
2932

30-
# 4. Build and run all examples dynamically
31-
- name: Build and Run
33+
coverage:
34+
runs-on: ubuntu-latest
35+
steps:
36+
- name: Checkout code
37+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
38+
39+
- name: Install coverage tools
3240
run: |
33-
for san in address thread undefined; do
34-
echo "=== Building all examples with sanitizer $san ==="
35-
make SANITIZE=$san
36-
echo "=== Running all examples with sanitizer $san ==="
37-
make run
38-
done
39-
40-
- name: Clean up build artifacts
41+
sudo apt-get update
42+
sudo apt-get install -y g++-14 make gcovr jq libtbb-dev
43+
44+
- name: Generate coverage report
45+
run: make coverage COVERAGE_MIN=0 COVERAGE_FUNCTION_MIN=0 COVERAGE_BRANCH_MIN=0
46+
47+
- name: Upload coverage report
48+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
49+
with:
50+
name: coverage-report
51+
path: coverage/
52+
retention-days: 14
53+
54+
- name: Enforce coverage thresholds
4155
run: |
42-
make clean
56+
jq --exit-status \
57+
'.line_percent >= 80 and .function_percent >= 80 and .branch_percent >= 70' \
58+
coverage/summary.json
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
name: Coverage Comment
2+
3+
on:
4+
workflow_run:
5+
workflows: [C++ CI]
6+
types: [completed]
7+
8+
permissions:
9+
actions: read
10+
contents: read
11+
pull-requests: write
12+
13+
concurrency:
14+
group: coverage-comment-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }}
15+
cancel-in-progress: true
16+
17+
jobs:
18+
comment:
19+
if: ${{ github.event.workflow_run.event == 'pull_request' }}
20+
runs-on: ubuntu-latest
21+
steps:
22+
- name: Download coverage summary
23+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
24+
with:
25+
name: coverage-report
26+
path: coverage-data
27+
repository: ${{ github.repository }}
28+
run-id: ${{ github.event.workflow_run.id }}
29+
github-token: ${{ secrets.GITHUB_TOKEN }}
30+
31+
- name: Create or update coverage comment
32+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
33+
env:
34+
LINE_COVERAGE_THRESHOLD: '80'
35+
FUNCTION_COVERAGE_THRESHOLD: '80'
36+
BRANCH_COVERAGE_THRESHOLD: '70'
37+
with:
38+
script: |
39+
const fs = require('fs');
40+
const summary = JSON.parse(fs.readFileSync('coverage-data/summary.json', 'utf8'));
41+
42+
function readMetric(prefix, label, threshold) {
43+
const percent = Number(summary[`${prefix}_percent`]);
44+
const covered = Number(summary[`${prefix}_covered`]);
45+
const total = Number(summary[`${prefix}_total`]);
46+
const valid = Number.isFinite(percent)
47+
&& Number.isInteger(covered)
48+
&& Number.isInteger(total)
49+
&& percent >= 0
50+
&& percent <= 100
51+
&& covered >= 0
52+
&& total >= covered;
53+
54+
if (!valid) {
55+
throw new Error(`Invalid ${label} coverage summary`);
56+
}
57+
58+
return { label, percent, covered, total, threshold };
59+
}
60+
61+
const metrics = [
62+
readMetric('line', 'Lines', Number(process.env.LINE_COVERAGE_THRESHOLD)),
63+
readMetric('function', 'Functions', Number(process.env.FUNCTION_COVERAGE_THRESHOLD)),
64+
readMetric('branch', 'Branches', Number(process.env.BRANCH_COVERAGE_THRESHOLD)),
65+
];
66+
const rows = metrics.map((metric) => {
67+
const status = metric.percent >= metric.threshold ? 'PASS' : 'FAIL';
68+
return `| ${status} | ${metric.label} | ${metric.percent.toFixed(2)}% (${metric.threshold}% target) | ${metric.covered} / ${metric.total} |`;
69+
});
70+
const run = context.payload.workflow_run;
71+
const associatedPulls = await github.paginate(
72+
github.rest.repos.listPullRequestsAssociatedWithCommit,
73+
{
74+
...context.repo,
75+
commit_sha: run.head_sha,
76+
per_page: 100,
77+
},
78+
);
79+
const pull = associatedPulls.find((candidate) =>
80+
candidate.state === 'open' && candidate.head.sha === run.head_sha);
81+
if (!pull) {
82+
core.notice(`No open pull request currently points to ${run.head_sha}`);
83+
return;
84+
}
85+
86+
const marker = '<!-- cpp-coverage-report -->';
87+
const body = [
88+
marker,
89+
'## Coverage Report',
90+
'',
91+
'| Status | Category | Percentage | Covered / Total |',
92+
'|:--:|---|---:|---:|',
93+
...rows,
94+
'',
95+
`[Generated in workflow #${run.run_number}](${run.html_url}) for commit \`${run.head_sha.slice(0, 7)}\` by the C++ coverage workflow.`,
96+
].join('\n');
97+
98+
const issue_number = pull.number;
99+
const comments = await github.paginate(github.rest.issues.listComments, {
100+
...context.repo,
101+
issue_number,
102+
per_page: 100,
103+
});
104+
const previous = comments.find((comment) =>
105+
comment.user.type === 'Bot' && comment.body.includes(marker));
106+
107+
if (previous) {
108+
await github.rest.issues.updateComment({
109+
...context.repo,
110+
comment_id: previous.id,
111+
body,
112+
});
113+
} else {
114+
await github.rest.issues.createComment({
115+
...context.repo,
116+
issue_number,
117+
body,
118+
});
119+
}

.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,34 @@
11
# Object files
22
*.o
3+
*.d
4+
*.gcda
5+
*.gcno
36

47
# Executable
58
thread_safe_queue
69
unique_ptr_basics
710
*.out
11+
fold-left-fold-right/fold-left-fold-right
12+
integer-factorization/integer-factorization
13+
map-with-unknown-key/map-with-unknown-key
14+
multithread-sum/multithread-sum
15+
oop-with-exception/oop-with-exception
16+
parallel-integral-pi/parallel-integral-pi
17+
parallel-transform/parallel-transform
18+
pi-monte-carlo/pi-monte-carlo
19+
pi/pi
20+
prefix-sum/prefix-sum
21+
ranges-find-in-vector-string/ranges-find-in-vector-string
22+
rot47/rot47
23+
smart-ptr/smart-ptr
24+
sudoku-solver/sudoku-solver
25+
thread-safe-queue/thread-safe-queue
26+
unique-ptr-basics/unique-ptr-basics
27+
views-zip-enumerate/views-zip-enumerate
28+
virtual-interface/virtual-interface
29+
30+
# Generated reports
31+
coverage/
832

933
# Backup files
1034
*~

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Changelog
2+
3+
All notable changes to this repository are documented in this file.
4+
5+
The project uses date-based versions because it does not publish a package or release artifact.
6+
7+
## [2026-07-16]
8+
9+
### Added
10+
11+
- Make targets for formatting, linting, testing, sanitizer checks, and coverage.
12+
- Aggregate coverage gates of 80% for lines and functions and 70% for branches, with HTML, XML, JSON, and text reports.
13+
- Secure pull request coverage comments and uploaded coverage artifacts.
14+
- Immutable commit pins for third-party GitHub Actions used by CI.
15+
- Contribution, support, security, privacy, pull request, and dependency update policies.
16+
17+
### Changed
18+
19+
- CI now performs clean builds for AddressSanitizer, ThreadSanitizer, and UndefinedBehaviorSanitizer.
20+
- UndefinedBehaviorSanitizer findings now terminate the affected test immediately.
21+
- Local WSL2 checks skip the unsupported GCC ThreadSanitizer runtime by default while native Linux CI retains it.
22+
- Example link rules now honor shared linker flags.
23+
- Pi tests validate numeric output without requiring `bc`.
24+
- Sudoku and smart pointer examples print real line breaks instead of escaped text.
25+
- README build instructions, CI documentation, project commands, and badges were expanded and corrected.

CONTRIBUTING.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Contributing
2+
3+
Contributions that add focused C++ examples, improve correctness, or strengthen existing tests are welcome.
4+
5+
## Development setup
6+
7+
Install GCC 14, GNU Make, clang-format, gcovr, and oneTBB on an Ubuntu-compatible system:
8+
9+
```bash
10+
sudo apt update
11+
sudo apt install g++-14 make clang-format gcovr jq libtbb-dev
12+
```
13+
14+
Build and run the complete repository:
15+
16+
```bash
17+
make build
18+
make test
19+
```
20+
21+
## Adding an example
22+
23+
1. Create a descriptive directory containing a `Makefile` and source files.
24+
2. Include `../common.mk` and follow a neighboring example's build targets.
25+
3. Add a `tests.sh` script when the behavior has testable output or error handling.
26+
4. Add the example to the appropriate README category.
27+
5. Keep the example self-contained and avoid dependencies unless they demonstrate an essential concept.
28+
29+
## Before opening a pull request
30+
31+
Run the same checks used by CI:
32+
33+
```bash
34+
make lint
35+
make check
36+
make coverage
37+
```
38+
39+
`make coverage` requires at least 80% aggregate line and function coverage and 70% branch coverage. Add tests for new
40+
behavior rather than lowering the thresholds.
41+
42+
Use a focused commit history and explain the motivation, behavior change, and validation in the pull request. Do not include compiled binaries, object files, or generated coverage reports.
43+
44+
## Reporting problems
45+
46+
Use the bug report template for reproducible defects. Report security vulnerabilities privately according to [SECURITY.md](./SECURITY.md).

0 commit comments

Comments
 (0)