172 lines
8.8 KiB
Go
172 lines
8.8 KiB
Go
package langscreen
|
||
|
||
import (
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
"testing"
|
||
"unicode"
|
||
)
|
||
|
||
func cyr() []*unicode.RangeTable { return []*unicode.RangeTable{unicode.Cyrillic} }
|
||
|
||
// pad repeats s until it clears the evidence floor, so a case can be about the SHARE without also being
|
||
// about the length.
|
||
func pad(s string, letters int) string {
|
||
var b strings.Builder
|
||
for n := 0; n < letters; n += len([]rune(s)) {
|
||
b.WriteString(s)
|
||
}
|
||
return b.String()
|
||
}
|
||
|
||
func TestTheScreenAnswersTheClosedSet(t *testing.T) {
|
||
for _, tc := range []struct {
|
||
name string
|
||
text string
|
||
want Verdict
|
||
}{
|
||
{"target prose", pad("текстанаязыке", 400), OnTarget},
|
||
{"a foreign completion", pad("englishtextinstead", 400), OffTarget},
|
||
{"the source echoed back", pad("源文本汉字", 400), OffTarget},
|
||
{"too short to judge", "коротко", Abstain},
|
||
} {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
if got := Screen(tc.text, cyr()).Verdict; got != tc.want {
|
||
t.Fatalf("verdict %q, want %q", got, tc.want)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestATargetWithNoDataIsNeverJudged is the layer-7 discipline and the review question of the whole
|
||
// project in one assertion: a pair the repository does not carry must be INERT, not condemned. The screen
|
||
// abstains on an empty script set however foreign the text looks, so adding a pair is a data row and never
|
||
// an edit here.
|
||
func TestATargetWithNoDataIsNeverJudged(t *testing.T) {
|
||
r := Screen(pad("englishtextinstead", 400), nil)
|
||
if r.Verdict != Abstain {
|
||
t.Fatalf("a target that declares no script must not be judged: %+v", r)
|
||
}
|
||
if r.Share != 0 {
|
||
t.Fatalf("an abstention must carry no score to be misread as one: %+v", r)
|
||
}
|
||
}
|
||
|
||
// TestTheEvidenceFloorIsAFloorAndNotAVerdict pins the abstain boundary on both sides. Below it the screen
|
||
// says nothing — never «off target», which on a chapter heading would be a flag on a healthy unit — and one
|
||
// letter above it the same text is judged normally.
|
||
func TestTheEvidenceFloorIsAFloorAndNotAVerdict(t *testing.T) {
|
||
foreign := strings.Repeat("a", MinLetters-1)
|
||
if got := Screen(foreign, cyr()); got.Verdict != Abstain || got.Letters != MinLetters-1 {
|
||
t.Fatalf("under the floor the screen must abstain and say how little it saw: %+v", got)
|
||
}
|
||
if got := Screen(strings.Repeat("a", MinLetters), cyr()); got.Verdict != OffTarget {
|
||
t.Fatalf("at the floor the screen must judge: %+v", got)
|
||
}
|
||
// Non-letters carry no evidence and must not lift a text over the floor: a table of numbers is not
|
||
// 200 letters of anything.
|
||
padded := strings.Repeat("a", MinLetters-1) + strings.Repeat("1234567890 ", 100)
|
||
if got := Screen(padded, cyr()); got.Verdict != Abstain {
|
||
t.Fatalf("digits and spacing must not count as evidence: %+v", got)
|
||
}
|
||
}
|
||
|
||
// TestTheThresholdSitsInTheMeasuredGap holds the constant against the corpus it was placed from (backlog
|
||
// row 113): every off-target completion on the stand scores 0.0000 and the worst healthy one 0.9767. Both
|
||
// sides are asserted, because a threshold is only a threshold if BOTH are on the right side of it.
|
||
func TestTheThresholdSitsInTheMeasuredGap(t *testing.T) {
|
||
const worstHealthyMeasured, bestOffTargetMeasured = 0.9767, 0.0000
|
||
if OnTargetFloor >= worstHealthyMeasured {
|
||
t.Errorf("the floor %.4f is at or above the worst HEALTHY completion measured (%.4f): it would flag clean prose", OnTargetFloor, worstHealthyMeasured)
|
||
}
|
||
if OnTargetFloor <= bestOffTargetMeasured {
|
||
t.Errorf("the floor %.4f is at or below the best OFF-TARGET completion measured (%.4f): it would catch nothing", OnTargetFloor, bestOffTargetMeasured)
|
||
}
|
||
}
|
||
|
||
// TestTheScoreIsOverLettersOnly is the measurement rule made explicit. Sharing punctuation, digits and
|
||
// whitespace across languages is exactly why they are excluded: counted, a heavily formatted reply and a
|
||
// dense one would be judged on different scales.
|
||
func TestTheScoreIsOverLettersOnly(t *testing.T) {
|
||
letters := pad("текст", 400)
|
||
plain := Screen(letters, cyr())
|
||
noisy := Screen(strings.Join(strings.Split(letters, ""), " — 42, "), cyr())
|
||
if plain.Share != noisy.Share || plain.Letters != noisy.Letters {
|
||
t.Fatalf("formatting changed the score: %+v vs %+v", plain, noisy)
|
||
}
|
||
}
|
||
|
||
// TestTheSupersetLimitIsRealAndNamed asserts the limitation the package doccomment declares, because a
|
||
// limit that is only written down is a limit somebody will later be surprised by. A target whose declared
|
||
// scripts CONTAIN another language's — ja declares han — cannot tell that language's output apart here.
|
||
func TestTheSupersetLimitIsRealAndNamed(t *testing.T) {
|
||
ja := []*unicode.RangeTable{unicode.Han, unicode.Hiragana, unicode.Katakana}
|
||
if got := Screen(pad("源文本汉字", 400), ja).Verdict; got != OnTarget {
|
||
t.Fatalf("the doccomment says a purely Chinese output scores 1.000 for a ja target; it now says %q — if the rule gained a datum, say so there", got)
|
||
}
|
||
}
|
||
|
||
// TestTheVersionActuallyTracksTheCalibration closes three plantings that the interval pins could not see,
|
||
// and it is the difference between a mechanism and a claim about one.
|
||
//
|
||
// ⚠ Version's own doc comment says it is derived «by construction» so that a slid threshold takes a loud
|
||
// --resnapshot with it. Nothing checked that. Planted: the var rewritten as the LITERAL it currently
|
||
// prints, then the floor slid 0.50 → 0.45 — the string stayed byte-identical, the golden stayed
|
||
// byte-identical, both interval pins passed (0.45 is inside the empty band), and the whole pipeline package
|
||
// was green. A resumed chunk would be re-verdicted under a threshold nobody re-billed for. The property was
|
||
// documented, believed, and held by nothing.
|
||
func TestTheVersionActuallyTracksTheCalibration(t *testing.T) {
|
||
want := fmt.Sprintf("langscreen-v1-target-script-share+floor%.2f+min%d", OnTargetFloor, MinLetters)
|
||
if Version != want {
|
||
t.Fatalf("Version has stopped tracking the constants it names:\n is: %s\n want: %s\n"+
|
||
"A literal here means a calibration change moves no snapshot, and a resumed chunk is re-judged "+
|
||
"by a rule nobody paid to change.", Version, want)
|
||
}
|
||
|
||
// ⚠ AND THE PRECISION WINDOW, which is the same hole one decimal down. `%.2f` cannot express a floor of
|
||
// 0.504: planted, that slide rendered the identical `floor0.50` and survived everything above. The fix
|
||
// is NOT to widen the format — that would change the shipped string and cost a --resnapshot of every
|
||
// book for a rule that did not change. It is to refuse a floor the version cannot express. A future
|
||
// calibration to three decimals is then a deliberate act: widen the format AND take the re-snapshot.
|
||
// The test is a ROUND TRIP, not a string comparison: what matters is whether the printed floor still
|
||
// denotes the same number, not whether it is spelled the same way (`%.2f` of 0.5 is "0.50", and that is
|
||
// fine — it reads back as 0.5). A floor of 0.504 also prints "0.50" and reads back as 0.5, which is not.
|
||
printed := fmt.Sprintf("%.2f", OnTargetFloor)
|
||
back, err := strconv.ParseFloat(printed, 64)
|
||
if err != nil {
|
||
t.Fatalf("the version's floor field %q does not parse: %v", printed, err)
|
||
}
|
||
if back != OnTargetFloor {
|
||
t.Fatalf("OnTargetFloor is %v but the version can only say %s, which reads back as %v — a change "+
|
||
"inside the last decimal would move no snapshot at all. Either keep the floor at two decimals, "+
|
||
"or widen Version's format and take the --resnapshot deliberately.", OnTargetFloor, printed, back)
|
||
}
|
||
}
|
||
|
||
// TestTheFloorIsAFloorAndNotACeiling pins the BOUNDARY comparison, which no constant expresses and which
|
||
// therefore moves nothing when it changes: planted, `share < OnTargetFloor` → `share <= OnTargetFloor`
|
||
// survived the whole pipeline package, because no fixture sat exactly on the line. The rule is «below the
|
||
// floor is off target», so a completion sitting EXACTLY on it is on target.
|
||
func TestTheFloorIsAFloorAndNotACeiling(t *testing.T) {
|
||
// Exactly 200 letters — the evidence floor — of which exactly 100 are Cyrillic: share is 0.50 exactly,
|
||
// with no floating-point slack (100/200 is representable).
|
||
text := strings.Repeat("а", 100) + strings.Repeat("z", 100)
|
||
got := Screen(text, cyr())
|
||
if got.Letters != 200 {
|
||
t.Fatalf("premise broken: the fixture must weigh exactly 200 letters, got %d", got.Letters)
|
||
}
|
||
if got.Share != OnTargetFloor {
|
||
t.Fatalf("premise broken: the fixture must sit exactly ON the floor, got %v", got.Share)
|
||
}
|
||
if got.Verdict != OnTarget {
|
||
t.Fatalf("a share exactly AT the floor must be on-target — the rule is «below the floor», and a "+
|
||
"comparison that quietly became «at or below» moves neither a constant nor the version: %+v", got)
|
||
}
|
||
// One letter less on target and it falls: the assertion above must not be passing because the screen
|
||
// says OnTarget to everything.
|
||
below := strings.Repeat("а", 99) + strings.Repeat("z", 101)
|
||
if b := Screen(below, cyr()); b.Verdict != OffTarget {
|
||
t.Fatalf("one letter below the floor must be off-target, got %+v", b)
|
||
}
|
||
}
|