Skip to content
Merged
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
127 changes: 127 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: Coverage

# Code-coverage reporting and gate (issue #29). Collects line/branch coverage for
# the Celerity library via coverlet, renders an HTML report + badges with
# ReportGenerator, fails the build if coverage drops below the floor, comments the
# summary on PRs, and publishes the HTML report to gh-pages (/coverage) on main.

on:
push:
branches: [ main ]
paths:
- 'src/**'
- '.github/workflows/coverage.yml'
pull_request:
branches: [ main ]
paths:
- 'src/**'
- '.github/workflows/coverage.yml'

# Coverage floor. The suite sits well above this (~99.9% line); the floor is the
# contract that guards against silent regressions, not the target.
env:
MIN_LINE_COVERAGE: '95'
MIN_BRANCH_COVERAGE: '90'

jobs:
coverage:
name: coverage
runs-on: ubuntu-latest

permissions:
contents: write
pull-requests: write

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
filter: tree:0

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Collect coverage
working-directory: src
run: >
dotnet test Celerity.Tests/Celerity.Tests.csproj
--configuration Release
--collect:"XPlat Code Coverage"
--settings coverage.runsettings
--results-directory ./TestResults/coverage

# Renders the HTML report, badge, and PR summary, writes the run summary,
# and fails the job if coverage is below the floor — all in one script, so
# the report carries the project's own styling and no third-party upsell.
- name: Generate report and enforce floor
run: >
python3 scripts/coverage_report.py
--input "src/TestResults/coverage/**/coverage.cobertura.xml"
--outdir coveragereport
--min-line "$MIN_LINE_COVERAGE"
--min-branch "$MIN_BRANCH_COVERAGE"

- name: Upload coverage artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coveragereport
if-no-files-found: warn
retention-days: 14

- name: Comment coverage on PR
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const marker = '<!-- celerity-coverage-comment -->';
const summary = fs.readFileSync('coveragereport/summary.md', 'utf8');
const body = `${marker}\n${summary}`;

const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}

- name: Publish report to gh-pages
# Same gate as the benchmark dashboard sync: main pushes only. Drops the
# HTML report under /coverage, leaving the benchmark data untouched.
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
set -euo pipefail
git fetch origin gh-pages
git worktree add /tmp/gh-pages gh-pages
rm -rf /tmp/gh-pages/coverage
mkdir -p /tmp/gh-pages/coverage
cp -r coveragereport/* /tmp/gh-pages/coverage/
cd /tmp/gh-pages
if git diff --quiet && git diff --cached --quiet; then
echo "No coverage changes to sync"
exit 0
fi
git -c user.name="github-actions" -c user.email="github-actions@github.com" add coverage
git -c user.name="github-actions" -c user.email="github-actions@github.com" \
commit -m "Sync coverage report from ${GITHUB_SHA:0:7}"
git push origin gh-pages
63 changes: 63 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Fuzz

# Differential fuzzing of the collections (issue #29). This is a soak job, not a
# per-PR gate: it runs nightly and on demand so a long random walk has time to
# surface a divergence the bounded per-PR property tests might miss. A failure
# prints a caseSeed that reproduces deterministically with
# `dotnet run -c Release -- --seed <caseSeed> --iterations 1`.

on:
schedule:
# 04:17 UTC daily (off the hour to dodge scheduler congestion).
- cron: '17 4 * * *'
workflow_dispatch:
inputs:
seed:
description: 'Base seed (blank = time-derived, printed in the log).'
required: false
default: ''
time:
description: 'Wall-clock budget in seconds.'
required: false
default: '300'
target:
description: 'Single target to focus (blank = all). See --list.'
required: false
default: ''

jobs:
fuzz:
name: fuzz
runs-on: ubuntu-latest

defaults:
run:
working-directory: src

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
filter: tree:0

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Build fuzzer
run: dotnet build Celerity.Fuzz/Celerity.Fuzz.csproj --configuration Release

- name: Run differential fuzzer
working-directory: src/Celerity.Fuzz
run: |
set -euo pipefail
ARGS="--iterations 100000000 --time ${{ github.event.inputs.time || '300' }}"
if [ -n "${{ github.event.inputs.seed }}" ]; then
ARGS="$ARGS --seed ${{ github.event.inputs.seed }}"
fi
if [ -n "${{ github.event.inputs.target }}" ]; then
ARGS="$ARGS --target ${{ github.event.inputs.target }}"
fi
echo "Fuzzer args: $ARGS"
dotnet run --configuration Release --no-build -- $ARGS
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ coverage*.json
coverage*.xml
coverage*.info

# ReportGenerator HTML output (generated by the coverage workflow / locally)
coveragereport/

# Visual Studio code coverage results
*.coverage
*.coveragexml
Expand Down
6 changes: 5 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ src/
│ ├── Collections/ CelerityDictionary, IntDictionary, ...
│ ├── Hashing/ IHashProvider<T> and built-in implementations.
│ └── FastUtils.cs Low-level helpers (e.g. NextPowerOfTwo).
├── Celerity.Tests/ xUnit test project. Mirrors the main project's layout.
├── Celerity.Tests/ xUnit tests (behavioural, edge-case, and property-based). Mirrors the main project's layout.
├── Celerity.Benchmarks/ BenchmarkDotNet project. Runs in CI on every PR and main push.
├── Celerity.Fuzz/ Differential fuzz harness. Nightly soak; reproduces failures from a seed.
├── Celerity.AotSmokeTest/ Native AOT publish + run target. Proves AOT/trim compatibility.
└── Celerity.sln
```

Expand Down Expand Up @@ -53,6 +55,8 @@ These are enforced by review, not by an analyzer. Reading the existing code is t
- Name tests `Method_ShouldExpectedBehavior_WhenCondition`.
- Prefer `[Fact]` for a single case, `[Theory] + [InlineData]` for parameterized cases.
- When fixing a bug, add a test that fails on `main` and passes on your branch. It's fine to reference the issue number in a comment.
- New collections are expected to carry parity coverage at every layer: behavioural tests, a CsCheck property test against the closest BCL oracle, and a `Celerity.Fuzz` target. See the [Testing & coverage guide](docs/testing.md) for how each layer works and how to run them.
- Coverage is gated in CI (`.github/workflows/coverage.yml`); keep line coverage ≥ 95% and branch ≥ 90%. The suite normally sits near 100%.

## Benchmarks

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Celerity
[![NuGet version (Celerity.Collections)](https://img.shields.io/nuget/v/Celerity.Collections.svg?style=flat-square)](https://www.nuget.org/packages/Celerity.Collections/) [![NuGet version (Celerity.Collections)](https://img.shields.io/nuget/vpre/Celerity.Collections.svg?style=flat-square)](https://www.nuget.org/packages/Celerity.Collections/) [![Live benchmarks](https://img.shields.io/badge/benchmarks-live-0d6e6e?style=flat-square)](https://marius-bughiu.github.io/Celerity/dev/bench/)
[![NuGet version (Celerity.Collections)](https://img.shields.io/nuget/v/Celerity.Collections.svg?style=flat-square)](https://www.nuget.org/packages/Celerity.Collections/) [![NuGet version (Celerity.Collections)](https://img.shields.io/nuget/vpre/Celerity.Collections.svg?style=flat-square)](https://www.nuget.org/packages/Celerity.Collections/) [![Live benchmarks](https://img.shields.io/badge/benchmarks-live-0d6e6e?style=flat-square)](https://marius-bughiu.github.io/Celerity/dev/bench/) [![Coverage](https://marius-bughiu.github.io/Celerity/coverage/badge.svg)](https://marius-bughiu.github.io/Celerity/coverage/)

Celerity is a .NET library that provides specialized high-performance collections optimized for specific use cases. It includes data structures designed for better speed or memory efficiency compared to standard .NET collections. The package supports configurable load factors, multiple built-in hash functions, and allows users to define custom hash functions for fine-tuned performance.

Expand Down Expand Up @@ -236,6 +236,7 @@ For full API details — constructors, method signatures, parameters, exceptions
- [Performance tuning guide](docs/performance.md) — capacity, load factor, hasher selection, and benchmarking.
- [Migration guide](docs/migration.md) — moving from `Dictionary<,>`, `HashSet<>`, `ILookup<,>`, and `FrozenDictionary<,>`.
- [Troubleshooting](docs/troubleshooting.md) and [FAQ](docs/faq.md).
- [Testing & coverage guide](docs/testing.md) — test layers, property-based and fuzz harnesses, coverage gating.
- [API reference](docs/README.md#api-reference) — collections, hashing, utilities.
- [`ROADMAP.md`](ROADMAP.md) — planned milestones and long-term vision.
- [`CHANGELOG.md`](CHANGELOG.md) — release notes.
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ The next release rounds out the `Celerity.Collections` package with missing coll
- Comprehensive benchmark suite: uniform, clustered, and adversarial key distributions. Status: `done` — `DistributionBenchmark` sweeps uniform/sequential/clustered shapes and `AdversarialHasherBenchmark` shows the naive hasher degrading to O(n) while Murmur3 recovers. Tracked in [#60](https://github.com/marius-bughiu/Celerity/issues/60).
- Benchmark suite expansion: realistic workloads, memory-allocation, concurrent-access, cache-locality, large-dataset (millions), and `FrozenDictionary<,>` comparison benchmarks. Status: `done` — added as an extended, on-demand suite kept out of the per-PR CI regression run; see [`docs/performance.md`](docs/performance.md#extended-benchmark-suite). Tracked in [#26](https://github.com/marius-bughiu/Celerity/issues/26).
- Cross-platform testing (Windows, Linux, macOS). Status: `done`.
- Improve code coverage.
- Improve code coverage. Status: `done` — coverage reporting is gated in CI (`coverage.yml`, 100% line coverage on the library, rendered by an in-repo generator and published to the [coverage dashboard](https://marius-bughiu.github.io/Celerity/coverage/)), edge-case tests close the non-generic enumerator / throw / backward-shift corners, property-based parity tests (CsCheck) and a seedable differential fuzzer (`Celerity.Fuzz`, nightly soak) check every collection against its BCL oracle, and the approach is written up in [`docs/testing.md`](docs/testing.md). Tracked in [#29](https://github.com/marius-bughiu/Celerity/issues/29).
- Improve documentation. Status: `done` — added a performance tuning guide, a BCL migration guide, a troubleshooting guide, and a FAQ, alongside the existing README usage examples, "choosing a collection" table, and API reference. Tracked in [#15](https://github.com/marius-bughiu/Celerity/issues/15).
- Bump XML doc coverage; treat missing docs as warning-as-error. Status: `done` — `Celerity.csproj` promotes CS1591 to error.

Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This folder contains reference documentation for the Celerity high-performance c
- [Migration from BCL collections](migration.md) — mapping `Dictionary<,>`, `HashSet<>`, `ILookup<,>`, and `FrozenDictionary<,>` to Celerity types.
- [Troubleshooting](troubleshooting.md) — common errors and behavioural surprises, with fixes.
- [FAQ](faq.md) — conceptual questions about the design.
- [Testing & coverage](testing.md) — the test layers, property-based and fuzz harnesses, and how coverage is measured and gated.

## API reference

Expand Down
Loading
Loading