Skip to main content
Version: v0.1.15

Testing and quality

This page describes how we test @cosyte/dicom, what each technique catches, and what it cannot. The shared rules every Cosyte package follows (the coverage and mutation floors, the property-based runners, what a pull request must prove) are written down in the testing standard in cosyte/config, the repository our shared configuration lives in. The numbers for the current release are in Current numbers below.

The failure we test hardest against is the quiet one: a header read wrong but plausible, such as a defaulted rescale slope or a patient identifier from the wrong element, or a de-identified object that reports itself clean while a name survives in it. So most of what follows checks values and what leaves the library, not only that the parser did not throw.

What we run, and what each technique catches​

TechniqueWhat it catchesWhat it cannot catch
Unit and integration testseach documented behavior and failure path of the parser, the dataset model, the value decoders, the writer, the profiles and deidentify, run against the sourcean input nobody thought to write down
Synthetic corporaa regression on a known object shape: synthetic Part 10 objects under every supported transfer syntax, every fatal input, nested and undefined-length Sequences, DICOMDIR files, encapsulated and JPIP Referenced objectsbehavior on real vendor files, since every fixture is synthetic
Output equivalenceany change to what a caller can observe, for a fixed corpus: each read, decoded value, warning, view, write and de-identification is hashed and compared with digests captured from an earlier commita wrong output that was already wrong when the digests were captured
Property-based testsa whole class of input breaking a stated rule: a parsed object writes back and reads the same, lenient reading never throws except on a fatal code, the model cannot be mutated, and a parse stopped before a tag matches the whole parse up to ita rule nobody stated; a case outside the cases run; a gap in what the generators produce
Fuzzinga crash or an unexpected throw on hostile input: arbitrary bytes, and synthetic objects with lengths, tags and values mutateda wrong but plausible result, unless a property states what right is
Coverage gatecode no test runs: lines, branches, functions and statements, held at a floor that only risesa test that runs code without checking what it returns
Mutation testinga test that runs code but would not notice it changing: every mutant in the changed lines of a pull request must be killed or excused inline with its reasona mutant the mutation tool does not generate
Generated data checksa data dictionary or de-identification table that drifts from the standard: both are regenerated from the SHA-256 pinned NEMA sources under vendor/nema/ and must come back byte for bytean error in the published standard itself
Spec citation checka clause cited on these pages that does not exist in the pinned edition, or does not carry the sentence we rely on, and an attribute named differently from the registrya citation written away from its part, which a person reads instead
Doc samplesa documentation sample that no longer matches the code: the samples on these pages run against the built package and their // => values are checkedprose that is wrong beside a sample that is right
PHI scana realistic patient identifier or a recent date in a fixture, a doc page or a staged file, against a declared allow-list of synthetic valuesanything outside the fields and formats it knows; it detects after a write, it does not prevent one
Scaling gatesparsing, reading every tag or writing that grows faster than its inputa constant slowdown that keeps the same growth

How each one runs​

  • Unit tests, corpora, output equivalence, properties and doc samples run in one Vitest suite on every pull request and on main, on Node.js 22, 24 and 26. Each property runs 100 cases on a pull request and 10,000 in a nightly run, from a new seed each time. Each prints its seed before it runs, and a failure prints the seed and path that replay it (COSYTE_PROPERTY_SEED, COSYTE_PROPERTY_PATH). The properties draw from the generators we publish at @cosyte/dicom/arbitraries; Property-test your code shows them on code of your own.
  • Fuzzing runs nightly, apart from the pull request suite.
  • The coverage gate runs with the suite on every pull request. A floor that is not met, or a changed line no test ran, fails the pull request.
  • Mutation testing runs on the changed source files of each pull request, and over all of src/ twice a week, whose score is held to its floor.
  • The generated data checks run when vendor/, the generators or the generated files change.
  • The PHI scan runs on every commit (a pre-commit hook) and in CI.
  • The scaling gates run in their own CI job, never under coverage.

Run them yourself​

pnpm install --frozen-lockfile
pnpm test # unit tests, corpora, equivalence, properties and doc samples
pnpm test:coverage # the same suite with coverage
pnpm quality:check # hold coverage to the floor in quality-floor.json
pnpm test:property # the properties, at the depth COSYTE_PROPERTY_RUNS sets
pnpm fuzz # the fuzz targets
pnpm test:perf # the scaling gates
pnpm phi-scan # the PHI scan over the repository
pnpm gen:clean && pnpm gen:all && git diff --exit-code src/dictionary/generated/

Current numbers​

The numbers for this section are measured on main at each release and filled in when the release is built. They are never typed by hand.