textmachine/backend/internal/membank/postcheckdenominator_test.go

99 lines
5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package membank
import (
"strings"
"testing"
"textmachine/backend/internal/store"
)
// TestTheUnverifiedDenominatorIsNotACountOfRowsShown pins what Shown is a count OF, which is narrower than
// the sentence that described it in four places ("how many unsigned rows were put in front of the model").
// A sticky carry IS put in front of the model and is deliberately not counted, so the two populations are
// different numbers on the same chunk — and Followed is a fraction of the smaller one.
//
// The wire the same selection renders is asserted beside the counts, because that is what makes this a
// measurement rather than a restatement: the row missing from the denominator is visibly present in the
// block that goes to the model.
func TestTheUnverifiedDenominatorIsNotACountOfRowsShown(t *testing.T) {
fired := gl("阿Q", "А-кью", "", "draft")
fired.Decl = declJSON(true, "А-кью")
// Carried by scene inertia from the previous chunk: its src is NOT in this one.
carried := gl("王胡", "Бородатый Ван", "", "draft")
carried.Decl = declJSON(true, "Бородатый Ван")
b := bankFrom([]store.GlossaryEntry{fired, carried})
carriedID := "王胡\x1f\x1f0\x1f0" // src, sense, since_ch, until_ch — the UNIQUE key, as MaterializeBank builds it
sel := b.Select("阿Q在场。", 2, map[string]InjectionTrust{carriedID: {Disp: Ambiguous, KeyTrusted: true}}, 0)
// Premise: both records are injected and exactly one of them is a sticky carry. Without this the count
// below could come out right on a selection that never held the exclusion.
if len(sel.Injected) != 2 {
t.Fatalf("premise broken: want both records injected, got %d: %+v", len(sel.Injected), sel.Injected)
}
var sticky int
for _, p := range sel.Injected {
if p.Sticky {
sticky++
if p.entry.src != "王胡" {
t.Fatalf("premise broken: the sticky carry must be the row absent from this chunk, got %q", p.entry.src)
}
}
}
if sticky != 1 {
t.Fatalf("premise broken: want exactly one sticky carry, got %d", sticky)
}
// The model used BOTH renderings it was given — so a count of "rows shown that the model followed"
// would be 2 of 2 here, and the channel actually reports 1 of 1.
res := b.Postcheck(sel.Injected, "А-кью пришёл, и Бородатый Ван с ним.")
if res.Shown != 1 {
t.Errorf("Shown counts only the unsigned rows whose key fired HERE, want 1, got %d", res.Shown)
}
if res.Followed != 1 {
t.Errorf("Followed is a fraction of Shown, want 1, got %d", res.Followed)
}
block := RenderGlossaryBlock(sel.Injected, ruTX())
if !strings.Contains(block, "Бородатый Ван") {
t.Errorf("premise broken: the sticky carry must be IN the block — that it is shown and not counted is the whole point:\n%s", block)
}
// CONTROL: the same two rows with both keys firing here give 2. It proves the 1 above is the sticky
// exclusion answering, and not a bank that could only ever count one row.
selBoth := b.Select("阿Q和王胡在场。", 2, nil, 0)
if n := len(selBoth.Injected); n != 2 {
t.Fatalf("control premise: want two injected, got %d", n)
}
if got := b.Postcheck(selBoth.Injected, "А-кью пришёл, и Бородатый Ван с ним.").Shown; got != 2 {
t.Errorf("control: two unsigned rows firing here are two, got %d", got)
}
}
// TestARowWithNoRenderingNeverReachesTheDenominator pins the OTHER skip in Postcheck as what it is —
// defensive, not a narrowing of the count. MaterializeBank gives a dst-less row no source surfaces at all,
// so it can neither fire nor be carried, and no selection can put one in front of Postcheck. Saying it
// narrows the denominator would misdescribe the number as badly as "rows shown" did, and the day the
// materializer starts admitting such a row this test is where that shows up.
func TestARowWithNoRenderingNeverReachesTheDenominator(t *testing.T) {
dstless := gl("小尼姑", "", "", "auto")
rendered := gl("阿Q", "А-кью", "", "draft")
rendered.Decl = declJSON(true, "А-кью")
b := bankFrom([]store.GlossaryEntry{dstless, rendered})
sel := b.Select("阿Q和小尼姑在场。", 1, nil, 0)
for _, p := range sel.Injected {
if p.entry.src == "小尼姑" {
t.Fatalf("a row with no rendering became matchable; the Postcheck skip for it is no longer defensive: %+v", sel.Injected)
}
}
// CONTROL: the same source surface with a rendering DOES fire on the same chunk, so the absence above
// is the empty dst and not a chunk the matcher cannot read.
dstless.Dst = "монашка"
dstless.Decl = declJSON(true, "монашка")
with := bankFrom([]store.GlossaryEntry{dstless, rendered})
selWith := with.Select("阿Q和小尼姑在场。", 1, nil, 0)
if len(selWith.Injected) != 2 {
t.Fatalf("control: with a rendering both rows must fire, got %d: %+v", len(selWith.Injected), selWith.Injected)
}
if got := with.Postcheck(selWith.Injected, "А-кью и монашка пришли.").Shown; got != 2 {
t.Errorf("control: both unsigned rows fired and both carry renderings, want 2, got %d", got)
}
}