Reliability · Regression testing
Battle tested, not just happy-path tested.
Nift treats reliability as a set of behavioral guarantees to attack, not a test count to advertise. Its suites exercise the relationships that users, projects and integrations are meant to rely on—dependency invalidation, checked paths, project state, parser behavior, failure recovery, filesystem safety, contracts and Project Contracts—then deliberately try to falsify them.
What battle hardened means
Test hardening and field hardening are different kinds of evidence. Test hardening comes from deliberately constructed regression, negative, adversarial, invariant, resource, sanitizer and contract tests. Field hardening comes from years of real projects, users, environments, integrations and releases discovering combinations the maintainers did not think to manufacture.
Mature build tools and site generators benefit from both. Nift cannot manufacture the production history of older, more widely deployed projects, and this page does not pretend otherwise. What Nift can control is how explicitly it states its guarantees, how aggressively it attacks them before users encounter the failures, and how permanently it preserves discovered bug classes as regressions.
A successful campaign establishes a scoped statement about the project states and failure classes actually exercised. It does not prove that every possible input, filesystem, platform or future change is safe. The evidence is strongest when the guarantee, attack method, scale and remaining boundary are all visible.
From examples to guarantees
Nift's testing has progressively moved through four levels:
| Level | Question | What is preserved |
|---|---|---|
| Feature correctness | Does the intended example work? | Expected successful behavior. |
| Regression correctness | Does a discovered bug stay fixed? | The exact failure becomes permanent coverage. |
| Bug-family / adversarial testing | What neighboring states could fail for the same reason? | Malformed state, collisions, stale metadata, traversal, lifecycle and composition cases are attacked around the original bug. |
| Guarantee-first / contract-first testing | What must remain true regardless of the implementation? | The desired invariant is stated first, then positive and negative tests try to falsify it. |
This is why the contract philosophy and the testing philosophy increasingly look alike: prefer checked relationships over unchecked assumptions, define the guarantee before accumulating syntax or implementation, and treat a successful build/test as evidence that the declared relationship held for the exercised state.
Test hardening in context
Nift's deliberate hardening should be read alongside—not above—the accumulated evidence of mature projects. Astro's current repository documents unit, integration, type and browser-level E2E testing plus examples and performance benchmarks. Meson requires automatic tests for new features and bug fixes and separates unit tests from full project tests; its test tooling also exposes sanitizer-oriented defaults and wrappers such as Valgrind. Hugo's current release history shows an active cross-platform test matrix and regressions continuing to emerge from real users and releases. Ninja deliberately keeps a small build-system surface and ships across Linux, macOS and Windows. esbuild combines a focused implementation with a long release history and a large public repository. Those projects illustrate the field-hardening side of the equation as much as the test-suite side.
Nift's advantage is not that a smaller or newer project can out-count that history. Its opportunity is that a deliberately small language and build model make a larger fraction of its meaningful behavioral surface tractable to reason about and attack systematically. Small does not imply correct; it makes explicit guarantees and comprehensive adversarial reasoning more achievable.
Completed campaigns are described here as evidence. The deliberate campaign through Checkpoint 10 is complete, including memory/resource safety, incremental-vs-clean equivalence, filesystem/transaction integrity, parser fuzz/resource boundaries and scoped cross-platform behavioral equivalence. Nift has reached its planned hardening plateau; distribution, dogfooding and field exposure now provide the next source of findings.
Incremental execution should not change semantics — proven for the Checkpoint 7 corpus
Checkpoint 7 tested a stronger property than “incremental builds have many tests”: incremental execution is an optimization of a clean build, not a different semantic mode. Across modified, hash and hybrid modes, eight deterministic mutation sequences per mode performed 30 mutations each. After every mutation, Nift hashed the complete public output tree from build-updated, removed generated tracked outputs and page-build metadata, performed build-all from the same logical project state, and compared every output path and byte hash.
mutate project
↓
build-updated → hash complete public tree
↓
remove generated tracked outputs + build metadata
↓
build-all → hash complete public tree
↓
compare → 720 / 720 equivalent The mutation corpus covered content, shared templates, JSON data, JSON Schema, Project Contract sources, build-thread configuration, shared inputs, tracked-entry metadata, and tracked-page add/move/remove lifecycle operations. All 720 complete output-tree comparisons were byte-equivalent. This is scoped evidence for the generated/mutated states tested, not a claim that every possible Nift project has been enumerated.
Filesystem failure should be explicit and non-deceptive — proven for the Checkpoint 8 corpus
Checkpoint 8 attacked failure rather than successful semantics. Thirteen Linux cases covered unreadable content/templates/JSON, dangling symlinks, symlink loops, file/directory substitution, read-only output and project-state directories, metadata-write obstruction and repair, long Unicode paths, deterministic partial-write pressure, and a forced SIGKILL while Nift was writing a 48 MiB output.
The campaign found real bugs. Unreadable sources could previously collapse to the same empty-string representation as genuinely empty files; a directory reaching a file-read path could abort with std::length_error; and generated/state files were written by truncating the destination in place, which meant interruption could damage the last-good artifact. Nift now validates readable regular files at relevant input boundaries and stages generated/state writes to same-directory temporary files before replacement.
For the tested failure classes, failures return non-success instead of silently claiming invalid work is current. Failed output writes do not refresh build metadata; failed metadata writes leave the page stale; and the forced-kill test preserved the previous output and metadata byte-for-byte before a later build cleaned the orphan temporary and recovered. This does not claim recovery from every kernel, device, ENOSPC, locking or platform-specific filesystem condition.
Parser failure should stay controlled under mutation and scale — proven for the Checkpoint 9 corpus
Checkpoint 9 ran the sanitizer build through 1,217 n++ parser/resource cases: 1,200 deterministic grammar-aware mutations across three seeds plus 17 explicit depth/size boundaries. The generated cases mutated real Nift syntax around control flow, loops, inputs, metadata/value interpolation, comments, quotes, escapes, path/value directives and delimiter structure. A case could either build or fail; what was not allowed was a hang, crash, signal termination, sanitizer finding or uncontrolled resource failure.
The final corpus produced 234 successful builds and 983 controlled errors, with zero timeouts, zero crashes and zero ASan/LSan/UBSan findings. Boundary cases included recursive control flow around the 64-level parse guard, literal templates up to 8 MiB, multi-megabyte comments and content, 1 MiB parameters/interpolations, 100,000 balanced parentheses and high-volume Unicode. Depths through 64 succeeded for the nested-control fixture while 65/80/128 failed cleanly.
This campaign establishes sanitizer-backed behavior for the mutation families and resource boundaries actually exercised. Generic JSON fuzzing belongs to Jsonic++; Minify++ parser fuzzing belongs to Minify++ unless a Nift integration boundary is involved.
Portable behavior should agree across supported platforms — proven for the Checkpoint 10 corpus
Checkpoint 10 ran the same 18-case portable behavioral corpus on GitHub's ubuntu-latest, macos-latest and windows-latest runners. Every runner built Nift and emitted normalized machine-readable evidence containing complete output-tree hashes, selected tracked/build metadata, status classes, lifecycle observations, diagnostic classes and failure/recovery state. A final job downloaded all three artifacts and compared portable observations directly; it passed with zero mismatches.
The corpus covered clean and no-op incremental builds; content, template, shared-input, JSON, JSON Schema and Project Contract invalidation; @pathto requirements; tracked add/move/remove; template-less minified output; Unicode and nested paths; last-good preservation; controlled malformed input; and successful recovery. Normalization was limited to path separators and non-contractual CRLF/LF spelling. Runner, kernel, architecture, Python, compiler and Nift commit metadata remained attached to each platform artifact rather than entering the semantic equality comparison.
The campaign found a real Windows defect: tracked mv/rm could leave stale read-only generated output and metadata even after tracking state changed. Nift now restores write permission before removing its owned read-only artifacts, and the cross-platform lifecycle cases preserve that regression. Windows executable suffix and read-only deletion semantics are separately classified platform-specific contracts.
For this portable corpus, normalized Linux, macOS and Windows semantics were equivalent, and the two documented platform-specific contracts passed. This does not generalize Windows symlink privileges, filesystem case sensitivity, POSIX permissions, file locking, path-length limits or exact timestamp behavior across every operating system and filesystem.
What is proven now
The evidence already established includes focused component and executable-level contracts; the independent black-box regression/adversarial layers; contract-specific collision, invalidation and failure behavior; large tracking/scaling workloads; the completed memory/resource campaign; Checkpoint 7 whole-output incremental equivalence; Checkpoint 8 scoped filesystem/transaction integrity; Checkpoint 9 sanitizer-backed parser/resource behavior; and Checkpoint 10 normalized Linux, macOS and Windows evidence for its portable corpus plus two classified platform-specific contracts. Checkpoint 6 closed the memory campaign with a mixed Nift + embedded Jsonic++ + Minify++ workload under native, sanitizer and corrected direct-Nift Valgrind execution. The external Valgrind run monitored 19 Nift invocations across 12 rounds and 40 pages, including four deliberately expected component failures; every monitored invocation reported zero Valgrind errors and no non-zero definite, indirect or possible leak bytes, and every repaired phase plus the final clean build succeeded.
Exact memory methodology and measurements remain on Memory & resource safety; this page focuses on what those campaigns mean in the broader reliability model.
Three complementary testing layers
Nift does not rely on one giant end-to-end script to tell it everything is healthy. The current source tree is tested at three different levels, each aimed at a different kind of failure:
| Layer | Purpose | What a failure usually tells us |
|---|---|---|
| Focused source-tree smoke tests | Fast checks kept beside the C++ source. Direct C++ tests isolate JSON, JSON Schema and minification logic; small executable-level harnesses then exercise parser/content, comments, JSON bindings, control flow, schema integration, reqs/path safety, template-less tracking, project contracts, incremental transitions and cross-feature behavior through real temporary projects. | A relatively local implementation or language-semantic regression. |
| Main regression suite | Black-box coverage of normal and edge-case CLI, project, parser, filesystem, metadata and incremental-build behaviour. | A user-visible contract has changed or broken. |
| Ruthless adversarial extension | Hostile state, malformed-but-plausible inputs, collision cases, preserved mtimes, traversal attempts, lifecycle edge cases and regressions derived from source review. | A boundary assumption or defensive invariant has failed. |
The focused tests deliberately are not rolled into the black-box assertion count. They are a different layer with a different job: quicker diagnosis and direct coverage of implementation-sensitive behaviour. The broader suites then prove that those pieces still compose correctly through the real executable.
When substantial Nift work changes protected behavior or testing evidence, the relevant handovers and this page are reviewed before the checkpoint is considered complete. The goal is not to accumulate a flattering count; it is to keep the public explanation synchronized with the guarantees actually exercised.
Focused source-tree smoke tests
The current development tree exposes 24 focused Makefile targets: test-json, test-json-schema, test-minify, test-json-schema-integration, test-content, test-comments, test-json-binding, test-control-flow, test-requirements, test-path-security, test-path-safety, test-metadata-safety, test-template-optional, test-contracts, test-cross-feature, test-incremental-new-features, test-state-concurrency, test-minify-integration, test-minify-node, test-minify-generated, test-minify-jsx-generated, test-minify-formats, test-minify-cli and test-tracking-scaling. Direct C++ targets isolate reusable components; shell targets create tiny projects and drive contract boundaries through the real executable.
test-json — direct C++ JSON implementation test
tests/json_smoke.cpp includes Json.h directly, so failures here can be isolated before the project loader or template parser is involved. It verifies:
- parsing a representative nested document containing strings, numbers, booleans, null, arrays and objects;
- escaped quotes, escaped newlines and a valid UTF-16 surrogate pair decoding to the expected Unicode character;
- programmatic mutation of a parsed document followed by serialization and successful round-trip reparsing;
- duplicate object keys being rejected instead of silently overwriting an earlier value;
- trailing commas, missing colons and unquoted object keys being rejected;
- strict JSON number grammar, including rejection of leading-zero integers, incomplete decimals and incomplete exponents;
- numeric overflow such as an exponent outside finite
doublerange being rejected rather than becoming an infinity; - valid signed decimal/exponent parsing;
- invalid escape sequences and raw newlines inside JSON strings being rejected;
- lone low surrogates, lone high surrogates and malformed surrogate pairs being rejected;
- the streaming named-array reader rejecting duplicate root keys before invoking its item callback.
This is the closest thing in the current tree to a traditional C++ unit test: it attacks the JSON implementation without requiring a Nift project at all.
test-json-schema — direct C++ schema-validation test
tests/json_schema_smoke.cpp exercises the schema validator without needing a Nift project. It checks both successful validation and deliberately invalid instances/schemas across the supported Draft 2020-12-style subset:
- primitive and union
typechecks, includinginteger; - object
required,properties,additionalProperties,minPropertiesandmaxPropertiesrules; - array
items, size limits,uniqueItemsandcontainsconstraints; - string length and regular-expression
patternchecks; - numeric minimum/maximum, exclusive bounds and
multipleOf; enum,const, boolean schemas and composition throughallOf,anyOf,oneOfandnot;- reusable
$defswith local JSON-Pointer$refresolution; - useful instance paths in failures so a nested error can identify a location such as
$.products[3].price; - unsupported validation keywords and external/remote references being rejected explicitly rather than silently ignored.
This keeps the contract honest: Nift implements a documented, useful JSON Schema subset and tests that subset directly rather than accepting schema vocabulary it does not actually enforce.
test-contracts — config-declared project-contract guarantees
tests/contracts_smoke.sh protects project contracts as observable behavior rather than as an implementation detail. It verifies lazy project-level JSON resolution through $[...], config and contract-source dependency tracking, remapping/invalidation, missing and malformed sources, missing members, path containment, and integration with parameter interpolation and control flow.
It also attacks the namespace guarantee directly: configured contract names are project-wide and cannot be shadowed or overloaded by @json, loop variables, built-ins or another binding. Those negative cases matter because a contract name must retain one meaning everywhere if it is going to be useful to humans, tools and coding agents.
The same contract family is mirrored by the standalone regression repository so a future implementation can be checked against the external behavior without sharing Nift's internal assumptions. This is the intended pattern for substantial Nift features: state the guarantee, protect it locally, protect it independently where practical, and keep the failure cases as permanent evidence.
test-minify — direct standalone minifier safety test
tests/minify_smoke.cpp attacks the transformation engine independently of project configuration. The supported final-artifact families are HTML/HTM, CSS, JS/MJS/CJS, JSX, JSON, XML and SVG. Coverage concentrates on semantic boundaries rather than merely checking that output gets shorter.
A separate test-minify-node target performs semantic differential testing when Node is available: representative JavaScript programs are executed before and after minification and stdout/stderr/exit status must match. The corpus covers regex/division boundaries, ASI, empty-loop statements, Unicode identifiers, numeric member access, nested/tagged templates, class/private fields, optional calls/chaining, BigInt, regexes after control parentheses and blocks, for await, function declarations and try/catch, plus direct object-literal division as the counter-case.
test-minify-generated adds a generated semantic matrix rather than only named hand-written cases. It combines statement/control/function/class/block endings with multiple regex literal shapes—including character classes containing /, * and }—and then runs explicit division counter-cases. Every original/minified pair must produce identical Node stdout/stderr.
The maintained generated matrix covers 379 executable programs. Successive expansions added labelled blocks, else/finally/loop boundaries, do/while, try/finally, async/generator functions, default/derived classes, nested classes and harder regex forms. Those passes found real context-sensitive slash bugs around labelled blocks, function expressions and binding-less catch blocks; each fix retained the opposite division cases so the scanner could not become green by classifying every ambiguous slash the same way.
- JavaScript regex/division ambiguity, ASI-sensitive line endings, template literals, token boundaries such as
1 .toString(), and valid empty statements such aswhile(condition);; - CSS
calc(), custom properties, data URLs, license comments and deliberately unknown/future constructs such as@media2rather than a brittle syntax whitelist; - HTML comment removal, inline/inter-element whitespace, raw-text elements, malformed tags and Unicode;
- JSX text versus JavaScript
{...}, fragments, self-closing roots, nested/object/template expressions and comparisons such asvalue={a > b ? x : y}inside attributes; - JSON validation before transformation;
- XML/SVG comments, CDATA, namespace/attribute preservation and mixed-content/text whitespace;
- idempotence so a second minification pass cannot continue changing already-minified output.
Repeated hostile passes have already caught real semantic bugs before release: regex after division collapsing into //, numeric member-access boundaries losing required whitespace, non-ASCII identifiers being merged with keywords, HTML comment removal inventing visible whitespace, raw HTML blocks closing on prefixes such as </scriptx>, XML/SVG mixed-content whitespace being discarded, XML processing-instruction data being rewritten, self-closing JSX roots being treated as unterminated, compact JavaScript comparisons/generic-looking expressions being mistaken for JSX, > inside JSX attribute expressions terminating tags early, and braces inside JSX regex/comment expressions prematurely closing {...}. Every demonstrated case is now permanent coverage.
The latest hostile pass found another real ambiguity family: after a statement block, function declaration, try/catch block or for await (...) control parenthesis, a leading /regex/ statement could be treated as division and truncated into invalid JavaScript. A naive fix that treated every slash after } as regex then broke valid object-literal division such as const x = {valueOf(){return 12}} / 2. The scanner now tracks lightweight brace context so block-closing and object-expression braces lead to different slash states, and both sides are covered by Node differential tests.
The next generated pass found one more brace-context hole: a regex statement after class C {} was still interpreted like division because the scanner had lost the earlier class context by the time it reached the opening brace. The fix carries lightweight pending-class state into brace classification, while the same generated matrix keeps object-literal division as the opposite invariant.
A subsequent mirror-case pass found that class expressions need the opposite post-brace state: class C {} /regex/ can start a regex statement, while const X = class {} / 2 is division on the resulting class value. The scanner now records whether a class body belongs to a declaration or expression, with anonymous and named class-expression division covered alongside the declaration-regex cases.
Behavioral differential testing executes representative JavaScript before and after minification and compares exit status/stdout/stderr. This supplements lexical assertions with a direct semantic check and makes “looks minified” insufficient evidence of correctness.
test-json-schema-integration — schema behavior through real builds
This focused harness complements the direct validator test by proving the public @json(data, binding, schema) contract end to end. It covers data and schema dependency recording, data edits, schema edits, nested instance-path errors, lazy skipped branches, schema use through nested inputs and project-bound traversal rejection.
test-requirements — internal reqs lifecycle
This suite isolates the existence-only state recorded under the hood by @pathto. It checks deduplication, skipped branches, “modified but still exists” behavior, disappearance as a rebuild reason, repair-on-rebuild, malformed metadata and tracked-output requirements.
test-path-safety — real filesystem containment
This suite attacks the difference between a path that looks project-local and one that actually resolves there. It includes parent traversal and symlink escapes for JSON data, JSON Schema files, explicit dependencies and concrete @pathto targets. This test found a real v1.0.21-development bug: lexical containment alone allowed an in-project symlink to point outside the project. The shared containment helper now resolves symlinked prefixes before accepting the path.
test-incremental-new-features — schema/req behavior across incremental modes
This harness repeats the newer JSON Schema and internal reqs contract under modified, hash and hybrid incremental modes. It verifies that changing a required target does not invalidate the page, JSON and schema changes do, an invalidating schema edit fails through normal rendering, dropping a schema from the source repairs deleted-schema state, and a temporarily missing req becomes clean again if the target is restored before rebuilding.
test-state-concurrency — persistence, concurrency and failed-build state
This harness checks that failed rendering preserves the last successful output and page metadata, that a mixed parallel build can commit independent successes while retaining the failed page's previous good state, and that the failed page can subsequently repair without manual metadata cleanup. It also builds 120 pages sharing JSON, JSON Schema and nested input sources with 12 workers across repeated forced builds, then invalidates and repairs a shared schema to exercise cache and page-state isolation under concurrency.
test-minify-integration — minification as a real Nift build feature
This layer verifies project-wide minify-exts, per-tracked-file minify: true/false, config/tracking validation, incremental invalidation when the effective setting changes, persisted minifier-format versions, transactional failure before output/page-info commit, concurrent minification of many tracked pages, and project-independent nift minify behavior including absolute paths, mixed supported/unsupported arguments and non-destructive malformed-input failure.
test-content — tracked content and @input parser composition
tests/parser_content_smoke.sh creates a temporary project and checks that tracked content is parsed through the same language machinery as templates and partials. It covers:
@pathto,@input,@getenv,@ent,@depand$[...]while they appear in tracked content rather than only in the outer template;- nested inputs being parsed recursively and seeing tracked-page metadata such as
titleandname; - dependencies discovered in tracked content and nested inputs being recorded into page-info metadata;
- the final output containing rendered values rather than leftover Nift syntax;
- content/input recursion being detected and failing cleanly instead of recursing indefinitely;
- template insertion indentation not leaking into the literal contents of
<pre*>blocks originating from either@contentor@input.
test-comments — comment parsing and dependency suppression
tests/comments_smoke.sh isolates the comment grammar. It verifies that:
- block Nift comments using
<#-- ... --#>are removed; - single-line
@#and@//comments are removed; - Nift syntax inside an ordinary HTML comment is still parsed according to HTML-comment semantics—for example metadata interpolation remains visible in the generated HTML comment;
- the historical parsed-comment opener is treated according to the stripped language's current single-line comment rule;
- a fake
@depinside a Nift comment does not leak into recorded dependency metadata.
That last check is important for incremental correctness: ignored source must not secretly affect the dependency graph.
test-json-binding — @json bindings and JSON-path rendering
tests/json_binding_smoke.sh exercises the JSON feature through a real build rather than calling the JSON parser directly. Its happy path and failure probes cover:
- binding a JSON document with
@json(path, name)and optionally validating it with@json(path, name, schema); - schema files being project-bound and recorded as dependencies alongside the data they validate;
- schema validation failures identifying the data/schema relationship and nested instance path;
- rendering strings, numbers, booleans, null and empty strings through
$[...]; - deep mixed member/index chains such as object → array → object → array;
- bindings remaining available inside nested
@inputfiles; - automatic dependency recording for the loaded JSON file;
- malformed JSON producing a controlled build error;
- duplicate binding names, invalid identifiers and conflicts with built-in metadata names being rejected;
- missing object members, out-of-range array indices and member/index operations on the wrong JSON type producing specific failures;
- attempts to render whole arrays or objects as scalar text being rejected;
- missing JSON files producing a clear error;
../traversal attempts being refused so@jsoncannot escape the Nift project root.
This test complements test-json: the C++ test asks whether JSON itself is parsed correctly; this one asks whether JSON behaves correctly once exposed through Nift's template-language bindings, dependency tracking and project-safety rules.
test-control-flow — loops, conditions, scoping and indentation
tests/control_flow_smoke.sh is the most extensive focused parser smoke suite because control flow combines block parsing, expression evaluation, JSON bindings and lexical scope. It verifies:
- truthy conditions and
!negation; ==and!=for supported scalar values;- numeric and string ordering with
<,<=,>and>=, including equality boundaries and negative numbers; - JSON-path-to-JSON-path comparisons and comparisons against literals;
- operator-looking characters inside quoted strings not being mistaken for expression operators;
- metadata such as
titleparticipating in conditions; else ifchains and terminal plainelsebranches;- array iteration and object
(key, value)iteration; - read-only loop metadata through
$[loop.index],$[loop.index0],$[loop.first],$[loop.last]and$[loop.length]; - the reserved
loopname preventing JSON/loop bindings from colliding with built-in metadata, including nested-loop restoration; - stable
asc/descordering of numeric and string keys without mutating the underlying JSON collection; - sorted object iteration and scalar-array sorting, plus controlled failures for missing sort fields, mixed types and non-orderable values;
- nested loops, nested conditions and access to child arrays from loop values;
- truthiness of empty/non-empty arrays and objects;
- skipped branches not parsing or executing invalid operations hidden inside them;
- braces inside quoted text, Nift comments and HTML comments not prematurely terminating a block;
- compact loop-header forms with no optional whitespace;
- loop bindings disappearing after their scope ends;
- nested loops safely shadowing an outer variable and restoring the outer binding afterward;
- multiline
@for/@ifoutput aligning to the directive's insertion point rather than preserving source-only block indentation; - nested control-flow indentation composing correctly;
@inputinside a loop following the same insertion-point indentation model;- inline control-flow blocks aligning continued output from the actual inline insertion column;
- controlled errors for missing parentheses/braces, missing JSON values, invalid comparison types, malformed loop headers, invalid collection types, reserved binding names and illegal branch chains;
- ordering comparisons refusing mixed types and non-orderable values such as booleans rather than performing implicit coercion.
Because failure locality matters. If test-json breaks, the JSON implementation is immediately suspect. If test-control-flow breaks while the JSON test remains green, investigation can start in block parsing, expression evaluation or scope handling instead of searching hundreds of unrelated filesystem and CLI cases. The black-box suites then remain the final proof that those focused pieces work correctly together in real projects.
Project-contract contract evidence
At that checkpoint, all 16 focused/modern standalone contract modules passed against the candidate, including the 73-check parameter-interpolation module and the new project-contract module. The real Nift documentation site also rebuilt successfully as a 46-page integration fixture with that same candidate. These numbers are retained as checkpoint-specific evidence rather than presented as the current total test inventory.
The project-contract module exercises the behavior that makes the feature a contract rather than a global JSON convenience: explicit config declaration, lazy loading, missing/malformed sources, missing members, project-wide namespace reservation, shadowing/collision rejection, config remapping, dependency invalidation, path containment, control-flow use and parameter interpolation.
The canonical combined runner also contains the historical+ruthless corpus. In the execution environment used for this checkpoint, the all-in-one run and the legacy corpus exceeded the available execution window before returning a final report, and a sanitizer build likewise exceeded the window during compilation. Those runs did not report a behavioral failure, but this page deliberately does not turn a timeout into green evidence. Earlier historical/adversarial and sanitizer results remain useful history; the claims above are the evidence established for this checkpoint.
This distinction is part of the testing philosophy. A checkpoint can carry strong focused evidence while still recording what was not freshly re-established. Future release preparation can rerun the longer gates on an environment suited to them without weakening the smaller permanent regressions that make failures attributable.
Why black-box regression testing matters
A build tool sits at the intersection of parsing, filesystem state, persistent metadata, concurrency and user input. Many serious bugs do not appear in isolated helper functions. They appear when several perfectly reasonable states combine:
valid project
+ deleted output
+ stale page metadata
+ hash incremental mode
+ shared dependency
+ targeted build
= behaviour that still has to be correct The suite therefore invokes nift as an external program, creates real files and directories, and checks generated output, metadata, exit status and filesystem state.
Template parser coverage
The suite attacks the parser around boundaries that are easy to get subtly wrong:
@contentin templates, partials and repeated positions;- tracked content being parsed as Nift source rather than inserted raw;
- nested and relative
@input(...)composition; - direct and indirect input loops;
- function-name boundaries around HTML, digits, underscores, dashes, colons and uppercase characters;
- quoted parameters containing spaces, commas, brackets and parentheses;
- formatting whitespace and multiline function calls;
- single- and double-quote behaviour, including deliberately unsupported backtick quoting;
- escaping literal
@,$and#template syntax; - unknown
@...text passing through so ordinary CSS such as@mediaremains ordinary CSS; <pre*>handling and preservation of preformatted indentation;- Nift comments, HTML comments and unclosed-comment errors;
- entity mappings and invalid entity requests;
- missing content/templates/inputs and informative source-position errors.
Structured JSON coverage
@json(path, name) and $[name.path[0]] are tested as both a data feature and a dependency feature:
- strings, numbers, booleans, null and empty strings;
- arbitrarily chained object-member and array-index access;
- bindings remaining visible through nested
@inputprocessing; - JSON bindings declared inside control-flow blocks being lexically scoped to that block/loop iteration rather than leaking into later iterations;
- automatic dependency recording for loaded JSON files;
- malformed JSON diagnostics, duplicate object keys, invalid escapes, malformed Unicode surrogate pairs and non-finite numeric overflow;
- missing JSON files and project-root traversal attempts;
- invalid and duplicate binding names;
- reserved metadata-name conflicts;
- missing members, out-of-range indices and indexing the wrong JSON type;
- errors when attempting to render an entire object or array as scalar text;
- modified/hash/hybrid rebuild detection, including content changes with preserved mtimes.
@if coverage
Conditions are tested as a deliberately small data-oriented expression system rather than a general scripting language:
- boolean truthiness and
!negation; - string, number, boolean and null equality/inequality;
- numeric and lexicographic string ordering with
<,<=,>and>=, including strict rejection of mixed/non-orderable types; - JSON-path-to-JSON-path comparisons;
- truthiness of empty and non-empty arrays/objects;
- arbitrarily long
else if (...){...}chains; - terminal plain
else {...}; - nested conditions inside loops;
- skipped branches not being parsed or executed;
- quoted braces inside control-flow blocks;
- malformed conditions, missing braces and invalid branch chains producing controlled errors.
@for coverage
- array iteration with
@for(item : array){...}; - object iteration with
@for((key, val) : object){...}; - nested loops over child arrays;
- member/index access from loop values;
- empty collections producing zero iterations;
- spacing and no-spacing forms around
:; - loop-variable scope, shadowing and restoration after nested loops;
- bindings not leaking beyond the loop;
- reserved metadata-name conflicts;
- invalid collection types and malformed loop headers.
Path and filesystem safety
- tracked names attempting
../traversal or absolute-path escape; - different tracked names such as
/andindexbeing rejected when they resolve to the same content/output paths; - tracking, copy and move operations refusing to destroy pre-existing untracked destination files;
- duplicate targeted build names not scheduling concurrent writes to the same output;
- embedded traversal rather than only names beginning with
../; - copy/move destinations escaping project roots;
- watched-directory paths that lexically look valid but normalise outside the content tree;
- quoted filenames and paths being escaped correctly in persistent JSON;
- missing generated outputs being recreated by incremental builds;
- generated-output permissions on supported Unix environments.
Persistent JSON/state robustness
The suite deliberately feeds Nift valid JSON with the wrong structure as well as malformed JSON. Coverage includes:
config.json,tracked.json, watched-directory metadata and page build metadata;- duplicate tracked names, invalid extensions, fractional thread counts and tracked content/template path collisions being rejected;
- empty tracking sets remaining valid JSON;
- quotes, backslashes and unusual accepted strings surviving save/reopen cycles;
- wrong JSON member types returning controlled errors instead of assertions/aborts;
- watch metadata with malformed array members;
- generated page-info dependency paths being JSON escaped correctly;
- user
*.deps.jsonfiles with malformed or structurally invalid documents; - dependency sidecars themselves invalidating pages when their dependency lists change, and following copy/move/remove lifecycle operations without leaving stale state.
Incremental-build semantics
This is one of the deepest parts of the suite because “fast” is not useful if the tool skips something that changed or rebuilds forever.
| Mode | What the tests verify |
|---|---|
modified | Normal mtime changes trigger affected outputs without forcing unrelated pages, including deliberately constructed changes separated only by sub-second timestamp precision. |
hash | Content changes are detected even when mtimes are deliberately preserved; pure mtime touches with unchanged contents stay quiet. The suite also retains a deliberately constructed collision for Nift's former 32-bit FNV-1a hashing so that regression cannot return unnoticed. |
hybrid | Either mtime or content-hash evidence can trigger a rebuild. |
That coverage is repeated across tracked content, templates, shared partials, explicit @dep dependencies, *.deps.json dependencies and @json data files. Tracking metadata is also part of incremental correctness: title, template, content/output mapping and dependency-set changes are tested as rebuild causes rather than being allowed to hide behind unchanged file mtimes.
Recursive directory dependency hashing
Directory dependencies get particularly hostile tests:
- unchanged directories must not be perpetually dirty;
- changing an existing child's contents must change the dependency state;
- adding/removing children must be detected;
- nested child changes and additions must participate recursively;
- renaming a child with identical contents must still change the directory hash;
- renaming a nested directory must change the hash;
- hash and hybrid modes must both behave according to their documented semantics.
CLI and failure propagation
build-all,build-updated,build-namesand aliases;- multiple-name operations such as remove/untrack;
- custom content/output extensions through copy/move;
- unknown/invalid options and meaningless surplus positional arguments being rejected rather than silently ignored;
- project commands locating the project root from nested directories;
- failed builds returning non-zero status for shells and CI;
- targeted requests for untracked names failing detectably;
info*JSON behaviour, including multiple requested names;statusand incremental reason analysis;build-threads=0remaining a valid concurrency configuration.
Watch and long-running build behaviour
- watch/unwatch lifecycle;
- custom watched extensions and templates surviving save/reopen;
- new matching files being auto-tracked;
- removed watched sources—and removal of an entire watched directory—removing stale tracking/output/build metadata;
- corrupt watch bookkeeping being prevented from claiming/removing unrelated manually tracked pages;
- watched extension/name collisions failing safely rather than silently mapping two sources onto one tracked page;
- malformed watch metadata producing controlled failures;
- hash-mode
build-autosurviving multiple edits in one process without continuously rebuilding from stale hash state.
Concurrency is part of what gets exercised
The executable under test uses multithreaded page builds and multithreaded incremental reason analysis. The suite repeatedly runs those code paths across shared dependencies and large affected sets, so concurrency is not a separate unused implementation detail.
What this coverage does—and does not mean
The suite is intentionally broader than a collection of happy-path examples. Many cases validate several related outcomes independently: an expected failure must actually fail, report the right kind of diagnostic and avoid producing invalid output; an incremental-build case may verify both that affected pages rebuild and that unrelated pages stay untouched.
The regression suite has grown organically alongside real bugs, source-review findings and adversarial testing. When a bug is found, the reproducing case is retained so the same behaviour cannot silently regress later. New parser and build features are expected to preserve that accumulated baseline.
No finite suite proves the absence of bugs. The useful claim is narrower: Nift has substantial reproducible coverage across the parser, filesystem, persistent state, dependency graph, incremental modes, CLI, watch behaviour, structured data and control flow. Focused source-tree smoke tests make local parser/JSON regressions quick to diagnose, while the main and adversarial suites exercise the compiled program end to end.
A Nift feature is not finished when the happy-path example works. It is finished when it survives the existing project model, failure modes and incremental-build semantics around it.
Minify++ minifier integration
Nift rewrite 1.0.39 embeds the exact Minify++ 1.0.3 source subtree. Minify++ currently carries its own 15,459-program JavaScript semantic matrix, 180-program JSX/TSX gate and 39-document non-JS idempotence gate; the detailed transformation evidence lives on Minify++'s own site.
Nift embeds Minify++ through Minify++'s public C++ API. The detailed JavaScript/JSX/HTML/CSS/JSON/XML/SVG transformation evidence now belongs to Minify++'s own Battle Tested documentation rather than being duplicated here.
Nift's responsibility is the integration contract: config-driven minification, per-file overrides, incremental-build behavior, safe failure handling, and the standalone nift minify convenience command. Minify++'s responsibility is whether a source artifact is transformed correctly.
The architecture is intentionally one-way: Nift consumes Minify++; Minify++ does not depend on Nift. Keeping the test claims with the subsystem that owns them makes both projects easier to evaluate and eventually lets Minify++ live in its own repository without carrying Nift's documentation history.