Commit Graph

3 Commits

Author SHA1 Message Date
A.Shakhmatov
88ac010c3c tests/spec: record Tier 2's spec -- the stages that rewrite a document
24 cases for `$addFields`/`$set`, `$unset`, `$replaceRoot`, `$unwind` and
`$project`'s computed fields, recorded from mongod 8.3.7 before any of them is
written. The file starts at 0 pass / 24 fail, which is the honest number for
five stages this server does not have.

Eight things the recording settled, none of them guessable from the manual:

    $addFields whose expression is missing   the field is not added at all
    $addFields: {"n.z": 1}                   sets the nested path, keeps siblings
    $replaceRoot of missing or non-document  error 40228
    $unwind of an empty array or missing     the document is dropped
    $unwind of a non-array                   the document is kept whole
    $unwind path without a $                 error 28818
    includeArrayIndex                        0-based
    $project: {n: {x: 1}}                    narrows it; a document without
                                             `n` keeps only `_id`

That last one is the `query.project` gap recorded during M2 as broken rather
than unimplemented -- a nested inclusion currently reads as falsy and returns
the whole document minus the field. It now has a measured expectation to be
fixed against, in the corpus, rather than a note in a commit message.

Worth stating before the implementation: the design review expected Tier 2 to
need a per-stage iterator, on the grounds that `$unwind` is 1->N and "there is
no way to express that in a window over the input". That was true of the window
as it stood, and stopped being true when `$project` was made to rebuild the
stream -- a stage that rebuilds can emit any number of documents it likes. So
Tier 2 looks like five stages rather than a rewrite. The corpus is what will
say whether that holds.
2026-08-09 22:30:36 +03:00
A.Shakhmatov
1c72aa8938 tests/spec: record the expression evaluator's spec from mongod
27 cases, recorded before a line of the evaluator is written, which is the
whole point of having built the recorder first: the expectations come from
mongod 8.3.7 rather than from what the implementation is about to do.

The corpus reads 1 pass / 26 fail, and the one pass is the unknown-operator
refusal M2 already answers correctly. Expressions are exercised through
`$group` because `_id` and the accumulator arguments are the only expression
positions that exist until `$addFields` and `$project`'s computed fields land;
testing them anywhere else would be testing a stage that is not there.

Ten things the recording settled that guessing would have got wrong:

    $add over a missing field or null   null -- not an error, and not 0
    $add over a string                  error 7157723
    $divide by zero                     error 4848401
    $mod of -5 by 4                     -1, the dividend's sign
    $lt of a number and a string        true, canonical type order
    $and over -5                        truthy
    $not of a missing field             true
    $switch, no branch and no default   error 40069
    two operators in one expression     error 15983, *not* $group's 40238
    $subtract with one operand          error 16020

The six error codes are in `ErrorCode` already, so the evaluator's refusals and
its runtime failures have somewhere measured to land. The evaluator itself is
the next commit and is not started.

191/191 unit tests, crud corpus unchanged at 201/90/196.
2026-08-09 22:12:48 +03:00
A.Shakhmatov
3044a38d1c tests/spec: an aggregation corpus, recorded from mongod
M2.5's gate, built before the milestone it gates -- the same order that put
`expectEvents` before the free list in M1 and Tier 0 before everything in M2.

`mongodb/specifications` has no aggregation suite, which is amendment A6's
central finding, so this milestone has to bring its own. The hazard in a corpus
we author is obvious and fatal: it can encode our own bugs as expectations and
then agree with us forever. So the split is enforced by the tooling.
`sources/*.json` holds documents and pipelines and nothing else; `record.js`
asks a real mongod 8.3.7 what each pipeline answers and writes the unified-
format file from the reply. Inputs authored, expectations measured -- the
discipline that corrected three assumptions in M1's session work and every
error code in M2, where the alternative would have shipped both times.

No second runner. `run.js --suite-dir` points the existing one somewhere else,
so the entity model, the matchers, the skip accounting and `expectEvents` come
for free; a second runner would drift from the first exactly where it mattered.
`--scorecard` is refused with `--suite-dir`, because `scorecard.txt` is the crud
corpus's record and the milestones are compared against it -- writing it from an
unrelated run would replace that record silently.

Errors record the code and not the message: message text is mongod's to change
between releases. Group pipelines end in a `$sort`, because group output order
is unspecified and a case depending on it would fail for the wrong reason on
either server.

The first source covers `$group`: nine accumulators including the edge cases
that decide an implementation -- `$avg` over a group whose values are not
numbers, `$min` of a field no document has, `$push` skipping a missing field,
`$first`/`$last` against input order, grouping on an array, a compound `_id`.

Where it starts, run against the M2 tip:

    group-accumulators.json    9 pass   10 fail   0 skip

The nine include the four refusals M2 added, which answer with mongod's own
codes -- so the corpus already confirms that half. The ten are the milestone.
The crud corpus is unchanged at 201/90/196.
2026-08-09 21:59:59 +03:00