You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,6 +8,10 @@ All notable changes to Celerity are documented here. This project follows [Keep
8
8
9
9
-**`BTreeDictionary<TKey, TValue, TComparer>` and `BTreeSet<T, TComparer>`** (with `BTreeDictionary<TKey, TValue>` / `BTreeSet<T>` aliases and the `DefaultComparer<T>` struct comparer) in `Celerity.Collections` — the library's first sorted map and set, and the B-tree the BCL lacks. Up to 31 keys per node keep a lookup `log₃₂(n)` node visits deep instead of chasing the `log₂(n)` pointers a red-black tree costs, and both add the ordered surface a hash table cannot answer: `Min` / `Max`, lower / upper bound, `EnumerateRange` in `O(log n + k)`, and in-order enumeration. They win on the interleaved insert + lookup + range-scan workload and on memory, and lose slightly on a delete-dominated one. Not thread-safe. Closes [#305](https://github.com/marius-bughiu/Celerity/issues/305).
10
10
11
+
### Fixed
12
+
13
+
-**The coverage gate measured only one of the six shipped packages.** Coverlet's assembly filter is exact-match, so `Celerity.Hashing`, `Celerity.Primitives`, and the three showcase packages had been outside the gate since the 2.0.0 package split — any of them could have dropped to 0% with CI green. All six are now measured, the gaps that exposed are backfilled to **100% line and branch** coverage, and the floor is raised from 95%/90% to match. Closes [#314](https://github.com/marius-bughiu/Celerity/issues/314).
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -29,7 +29,7 @@ dotnet test # xUnit
29
29
```
30
30
31
31
-`net8.0` is the floor. Shared code must not use net9/net10-only APIs unguarded — gate newer paths with `#if NET9_0_OR_GREATER` / `NET10_0_OR_GREATER` and keep a net8.0 fallback. The target list lives in `src/Directory.Build.props`.
32
-
- Coverage is gated in CI: keep line ≥ 95%, branch ≥ 90%.
32
+
- Coverage is gated in CI at 100% line and 100% branch across all six shipping packages. New code needs its tests. For a branch no test can reach, use `[ExcludeFromCodeCoverage(Justification = "…")]` rather than lowering the floor, and add a new shipping package's assembly to `src/coverage.runsettings` (the filter is exact-match, so an unlisted package is silently unmeasured).
33
33
- Every public type/member needs an XML doc comment (`GenerateDocumentationFile` is on; missing docs warn).
34
34
- Hashers are `struct`s passed as generic constraints (`where THasher : struct, IHashProvider<T>`) so the JIT devirtualizes them — do not turn them into classes/interfaces.
Copy file name to clipboardExpand all lines: CONTRIBUTING.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -59,7 +59,8 @@ These are enforced by review, not by an analyzer. Reading the existing code is t
59
59
- Prefer `[Fact]` for a single case, `[Theory] + [InlineData]` for parameterized cases.
60
60
- 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.
61
61
- 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.
62
-
- Coverage is gated in CI (`.github/workflows/coverage.yml`); keep line coverage ≥ 95% and branch ≥ 90%. The suite normally sits near 100%.
62
+
- Coverage is gated in CI (`.github/workflows/coverage.yml`) at **100% line and 100% branch**, across all six shipping packages. New code arrives with its tests, or the gate goes red. If you hit a branch no test can reach, exclude it at the source with `[ExcludeFromCodeCoverage(Justification = "…")]` explaining why — do not lower the floor. See the [Testing & coverage guide](docs/testing.md) for the current exclusions and the reasoning behind each.
63
+
- Adding a new shipping package? Add its assembly to `src/coverage.runsettings` and its test project to the coverage workflow. Coverlet's assembly filter is exact-match, so an unlisted package is silently unmeasured.
Copy file name to clipboardExpand all lines: docs/testing.md
+41-7Lines changed: 41 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,7 +12,7 @@ Celerity's first guiding principle is *correctness first* — "a fast collection
12
12
| Differential fuzzer |`Celerity.Fuzz`| A long random walk finds no divergence from the BCL; failures replay deterministically from a seed. |`dotnet run -c Release`|
13
13
| Native AOT smoke test |`Celerity.AotSmokeTest`| Every collection/hasher works in a trimmed, AOT-compiled native binary. | see [aot.md](aot.md)|
14
14
15
-
All of these run in CI. Coverage is measured on the library assembly and gated; the rendered report is published to [the coverage dashboard](https://marius-bughiu.github.io/Celerity/coverage/).
15
+
All of these run in CI. Coverage is measured on all six shipping assemblies and gated at 100% line and branch; the rendered report is published to [the coverage dashboard](https://marius-bughiu.github.io/Celerity/coverage/).
16
16
17
17
## Philosophy: example tests, then adversarial tests
18
18
@@ -107,24 +107,58 @@ Add an entry to `Differential.All` in `src/Celerity.Fuzz/Differential.cs` and wr
107
107
108
108
## Code coverage
109
109
110
-
Coverage is collected with [coverlet](https://github.com/coverlet-coverage/coverlet) (already referenced by the test project) and scoped to the shipping `Celerity` assembly via [`src/coverage.runsettings`](../src/coverage.runsettings) — the test, benchmark, fuzz, and AOT-smoke assemblies are tooling, not the subject under measurement.
110
+
Coverage is collected with [coverlet](https://github.com/coverlet-coverage/coverlet) and scoped to all six shipping assemblies — `Celerity`, `Celerity.Hashing`, `Celerity.Primitives`, `Celerity.Ring`, `Celerity.Sentinel`, `Celerity.Cardinality` — via [`src/coverage.runsettings`](../src/coverage.runsettings). The test, benchmark, fuzz, and AOT-smoke assemblies are tooling, not the subject under measurement.
111
+
112
+
Four test projects contribute: `Celerity.Tests` for the three core packages, plus `Celerity.Ring.Tests` / `Celerity.Sentinel.Tests` / `Celerity.Cardinality.Tests` for the showcase tier. Their Cobertura reports are merged on (source file, line number), so a line covered by any run counts as covered — which matters because the showcase projects also exercise `Celerity.Collections` transitively.
113
+
114
+
The suite covers **100% of lines and 100% of branches** across all six. A small number of guards are excluded at the source with `[ExcludeFromCodeCoverage(Justification = "…")]`, and only where no test could ever reach them:
115
+
116
+
| Guard | Why no test can reach it |
117
+
|---|---|
118
+
|`Deque.ClampToArrayMaxLength`| Needs a backing array above 2³⁰ elements. Pinned by a real `[MemoryIntensiveFact(3100)]` test in `DequeGrowthTests`, which allocates ~3 GiB and skips on memory-capped runners — excluded so the gate does not depend on whether the runner had the headroom. |
119
+
|`IndexedPriorityQueue.ClampGrowth`| Needs 2³⁰ *live* entries with distinct elements, pushing the backing array past the 2 GiB single-object limit. `EnsureCapacity` calls `Resize` directly, so capacity cannot be pre-inflated into it. |
120
+
|`FrozenCelerityDictionary.ThrowIfKeyCountExceedsCeiling`, `FrozenCeleritySet.ThrowIfElementCountExceedsCeiling`| The count is taken *after* materializing the source into a `List<string>`, so reaching 2³⁰ needs an 8.6 GB `string[]` — past the 2 GiB array limit. A source that merely reports a huge `ICollection.Count` cannot reach it; that count is only a capacity hint. |
121
+
|`CuckooFilter.AtLeastOne`, `XorFilter.AtLeastOne`| Dead by construction: the constructors' own argument validation already forces both sizing expressions above the floor. |
122
+
|`XorFilter.BuildOrThrow`, `XorFilter.TryBuild`| The peel retry schedule is independent of the element set, so no hasher can stall all `MaxConstructionAttempts` seeds. Individual attempts *do* stall and retry — that path lives in `TryPeel`, which stays measured. |
123
+
|`Hash64Source.CreateNative`| Its `null` arm is unobservable. `Native` is read only by `Hash64`, and every caller guards that on `IsNative64` being true, so the class is never initialized for a 32-bit-only `THasher` — the arm is evaluated only if the runtime runs the `beforefieldinit` initializer eagerly, which is its option and not a contract. |
124
+
125
+
That table is the complete set; `grep -rn "ExcludeFromCodeCoverage" src/Celerity*/` should return nothing beyond it.
126
+
127
+
The rule for new code: exclusions are for genuinely unreachable code and must carry a `Justification` that says *why*. Anything a test can reach gets a test.
128
+
129
+
> **Adding a shipping package?** Add its assembly to the `<Include>` list in `src/coverage.runsettings`, and its test project to `.github/workflows/coverage.yml`. Coverlet's assembly filter is **exact-match, not a prefix** — `[Celerity]*` compiles to `^Celerity$` and matches only the `Celerity.Collections` assembly. That is how the 2.0.0 package split left five of six packages silently outside the gate until [#314](https://github.com/marius-bughiu/Celerity/issues/314). An unlisted package is unmeasured, and the gate stays green no matter what its coverage is.
111
130
112
131
Collect and render a report locally:
113
132
114
133
```bash
115
-
# 1. collect Cobertura coverage for the library only
134
+
# 1. collect Cobertura coverage for the three core packages.
135
+
# Clear stale results first: the four reports are merged by source-file path, and
136
+
# SourceLink resolves those paths from the build's git state — so mixing reports
137
+
# from different commits makes the same file appear twice under two spellings and
The report is rendered by [`scripts/coverage_report.py`](../scripts/coverage_report.py) — a small generator that reads the Cobertura XML coverlet produces and emits an `index.html` styled like the rest of the Celerity site, a `badge.svg`, and a `summary.md`. It exists so the report carries the project's own look and no third-party "sponsors only" upsell; there is no dependency on ReportGenerator.
@@ -134,7 +168,7 @@ The report is rendered by [`scripts/coverage_report.py`](../scripts/coverage_rep
134
168
The `coverage` workflow (`.github/workflows/coverage.yml`) runs on every PR and on `main`:
135
169
136
170
- Collects coverage, renders the report + badge with `scripts/coverage_report.py`, and uploads it as a build artifact.
137
-
-**Fails the build** if line coverage drops below `MIN_LINE_COVERAGE` (95%) or branch coverage below `MIN_BRANCH_COVERAGE` (90%). The suite sits far above these (100% line and branch) — the floor guards against silent regressions; it is not the target.
171
+
-**Fails the build** if line coverage drops below `MIN_LINE_COVERAGE` (100%) or branch coverage below `MIN_BRANCH_COVERAGE` (100%). The floor is deliberately a hair-trigger: new code arrives with its tests, or the gate goes red. If you hit a genuinely unreachable branch, exclude it at the source with a justification rather than lowering the floor.
138
172
- Posts a coverage summary comment on the PR.
139
173
- On `main`, publishes the HTML report to `gh-pages` under [`/coverage`](https://marius-bughiu.github.io/Celerity/coverage/) and refreshes the README badge.
0 commit comments