99 lines
5 KiB
Go
99 lines
5 KiB
Go
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)
|
||
}
|
||
}
|