Mix.Statifier.Corpus.Emitter (Statifier v2.6.0)

Copy Markdown View Source

Writes the language-neutral conformance corpus under conformance/, and checks the committed one (ADR-0070). mix statifier.corpus is its command line.

Emit

emit/1 reads the fetched and transformed upstream suites (Mix.Statifier.Corpus.Upstream) with this repository's exclusion lists and the W3C sub-document set (Mix.Statifier.Corpus.Exclusions), and the statifier cases this repository authors under conformance/cases/ (Mix.Statifier.Corpus.Authored), RUNS every case through statifier (Mix.Statifier.Corpus.Runner), and only then writes:

  • conformance/corpus/<suite>.json - one file per suite that has cases, the cases sorted by id, one case per line. A suite with no cases gets no file and no manifest entry, so no empty corpus file is ever written.
  • conformance/manifest.json - the corpus_hash, one entry per corpus file with its case count, and the upstream suites with their licences. It carries no statifier-ex version: a claim is pinned by the corpus_hash and the statifier-ex tag the corpus was vendored from (ADR-0070 decision 4), so a version bump changes nothing it holds.
  • conformance/exclusions.json - the exclusion lists, each entry with its reason atom, its prose and, where the prose cites a decision record, that record's number as adr. A SCION directory key stays one entry naming the directory: it is not expanded into the upstream cases under it, so the file does not depend on the fetched tree.
  • conformance/registry.json - statifier-ex's claim against the corpus, derived from the ratchet's SCION and W3C lists by Mix.Statifier.Corpus.Registry and pinned by the corpus_hash. A ratchet path that names no corpus case, or a ratchet that names none, stops the emit and nothing is written.

A case's configurations are the upstream's expectation, and a case that the regression ratchet (test/passing_tests.json) lists must agree with it when run: one that disagrees stops the emit and nothing is written. A case outside the ratchet is written with the upstream expectation whatever its run found; its absence from the ratchet is what says statifier-ex does not claim it, and the emit reports each such case with its outcome. An authored statifier case has no upstream expectation to defer to: its expectation is what this repository wrote, so one that disagrees when run stops the emit as a ratcheted case does.

corpus_hash is "sha256:" followed by the lowercase hex SHA-256 of the corpus files' bytes concatenated in suite order - scion, w3c, statifier - skipping a suite with no file. Nothing written depends on the time or on a filesystem path, so two emits of one tree are byte-identical.

The licence texts under conformance/LICENSES/ are committed copies of the upstream licences, not generated; emit/1 and check/1 refuse when a notice a case points at is missing.

Check

check/1 writes nothing and needs neither the network nor the upstream tree. From the committed files alone it re-runs every committed case from its committed source, recomputes every file derivable from committed inputs - each corpus file's canonical form, corpus/statifier.json from the authored cases under conformance/cases/, each case's required_features, the manifest with its corpus_hash, exclusions.json, and the registry derived from test/passing_tests.json - and fails on any difference, on a committed corpus file the inputs no longer derive, on a registry with no entries or with an entry naming a case the corpus lacks or holds under another suite, and on any ratcheted or authored case whose run disagrees. When the upstream tree is present it also rebuilds the corpus from it and fails on any case that differs; when it is absent it says that comparison was skipped. A check that finds a corpus file missing or empty, or that visited no case, fails: a green result on nothing is a defect.

Summary

Types

Where the emitter reads and writes: the project root and the upstream tree.

What an emit or a check found, for the task to print.

Functions

Checks the committed corpus, manifest, exclusions and registry without writing anything, as the moduledoc describes.

Builds a config from opts: :root (default ".") and :scratch (default tools/corpus/scratch under the root).

The corpus_hash of corpus file contents given in suite order.

Reads the upstream tree, runs every case, and writes the corpus, the manifest, the exclusions and the registry under conformance/. Writes nothing when any step refuses.

The path, relative to the project root, of the generated test module a corpus case corresponds to - the path test/passing_tests.json names it by. A statifier case has none.

Renders cases and exclusions as the files emit/1 writes, keyed by path relative to conformance/.

Types

config()

@type config() :: %{root: Path.t(), scratch: Path.t()}

Where the emitter reads and writes: the project root and the upstream tree.

report()

@type report() :: %{
  :counts => [{String.t(), non_neg_integer(), non_neg_integer()}],
  :outside_ratchet => [{String.t(), Mix.Statifier.Corpus.Runner.outcome()}],
  optional(:claims) => [{String.t(), pos_integer()}],
  optional(:written) => [Path.t()],
  optional(:upstream) => :compared | {:skipped, Path.t()}
}

What an emit or a check found, for the task to print.

Functions

check(config)

@spec check(config :: config()) :: {:ok, report()} | {:error, String.t()}

Checks the committed corpus, manifest, exclusions and registry without writing anything, as the moduledoc describes.

config(opts)

@spec config(opts :: keyword()) :: config()

Builds a config from opts: :root (default ".") and :scratch (default tools/corpus/scratch under the root).

Examples

iex> Mix.Statifier.Corpus.Emitter.config([])
%{root: ".", scratch: "./tools/corpus/scratch"}

corpus_hash(contents)

@spec corpus_hash(contents :: [binary()]) :: String.t()

The corpus_hash of corpus file contents given in suite order.

Examples

iex> Mix.Statifier.Corpus.Emitter.corpus_hash(["a", "b"])
"sha256:fb8e20fc2e4c3f248c60c39bd652f3c1347298bb977b8b4d5903b85055620603"

emit(config)

@spec emit(config :: config()) :: {:ok, report()} | {:error, String.t()}

Reads the upstream tree, runs every case, and writes the corpus, the manifest, the exclusions and the registry under conformance/. Writes nothing when any step refuses.

generated_path(arg1, root)

@spec generated_path(corpus_case :: map(), root :: Path.t()) :: Path.t() | nil

The path, relative to the project root, of the generated test module a corpus case corresponds to - the path test/passing_tests.json names it by. A statifier case has none.

render(cases, exclusions)

@spec render(
  cases :: [map()],
  exclusions :: [Mix.Statifier.Corpus.Exclusions.entry()]
) :: %{
  required(Path.t()) => binary()
}

Renders cases and exclusions as the files emit/1 writes, keyed by path relative to conformance/.