Skip to content

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 27 Aug 05:41
· 48 commits to master since this release
aa28afb

New verbs and richer failure messages; nothing removed, nothing renamed. A
minor rather than a patch because expectSequence() is new public API, and on
0.x that is the boundary Composer's caret already treats as breaking.

  • Understudy::expectSequence() / expectSequence(): a protocol armed before
    the subject runs.
    ordered() and verifySequence() both answer in
    teardown, where the stack trace points at verifyAll() rather than at the
    call that went out of turn. An armed protocol refuses inside the offending
    call, with the subject's own frame on top of the stack. Totality is scoped to
    the doubles the protocol names: on those, a call is the step due or something
    the test configured, and anything else is refused — so a query the subject
    makes between two steps has to be stubbed. Each step is due exactly once, in
    order. Arming is also a claim: verifyAll() reports the steps the subject
    never reached, which is what still fails a test whose subject swallowed the
    refusal in a broad catch. One protocol at a time, a finished one may be
    replaced, and checkpoint() verifies it and then drops it. The failure is a
    VerificationFailed carrying FailureKind::OutOfSequence — no new kind, so
    an exhaustive match on the enum keeps compiling.

  • A strict double says what it refused and what it compared the call
    against.
    Naming only the method sent the reader back to a test that had
    configured that method — the difference was in the arguments, and the message
    did not carry them. The refusal now renders the call and every expectation
    registered for that method that did not accept it, with each rejecting
    argument marked from the expectation's side, a position the call never
    carried included. Up to five, in the order the dispatcher tried them, then a
    count. With nothing configured for the method there is nothing to compare
    against and the message is unchanged. A matcher asked while the message is
    built is asked defensively: it runs inside the code under test, and one that
    throws counts as one that did not accept rather than replacing the refusal
    with its own exception.

  • Fixed a deprecation introduced with object rendering: SplObjectStorage::contains()
    is deprecated as of PHP 8.5, and every rendered object emitted a notice.

  • Performance. Four per-call and per-double costs came off the hot paths:
    the armed-protocol check now sits behind one null check, a context is recorded
    live once where it is created rather than once per double adopted into it, and
    one ReflectionClass is built per generated class rather than per double. A
    fifth change — retiring a context by marking it instead of walking its
    doubles — was measured, found to move the cost onto creation (13-23% worse in
    the benchmark) rather than remove it, and reverted; the finding is recorded in
    AGENTS.md.

  • perf/README.md re-measured on a quiet machine. The previous figures were
    taken at a commit before 0.1.0 and described no released version. They also
    did not show a creation regression that landed before the first tag and
    shipped in 0.1.0, 0.1.1 and 0.1.2; it is bisected, documented, and still
    present.

  • An object argument renders as an alias and its public state, so the *
    that marks a differing argument points at something the reader can see:
    save(App\Book#1 {title: 'Dune'}) against save(*App\Book#2 {title: 'Dune'}*) says the call was made with a rebuilt copy rather than the
    instance the expectation named. Aliases are numbered within one message in
    order of first appearance — not object ids, which are reused after a
    collection and would print differently on each run — and the same instance
    keeps one number everywhere in that message. The braces list public
    properties only, up to five, at the message's existing depth budget: nothing
    is called to render them, so an object keeping its state behind getters
    renders as its alias alone. Failure summary text changes; the structured
    fields of VerificationFailure do not.