131 lines
4.9 KiB
Go
131 lines
4.9 KiB
Go
package credits
|
|
|
|
import "testing"
|
|
|
|
// Confidence, and the stability rule that decides whether anything is written at all.
|
|
//
|
|
// The governing principle under test throughout: prefer no marker to a wrong marker. Almost
|
|
// every case here is about the combination refusing to answer.
|
|
|
|
func visual(startMs int64, confidence float64) Detection {
|
|
return Detection{Found: true, StartMs: startMs, Confidence: confidence, Method: MethodVisual}
|
|
}
|
|
|
|
func cluster(startMs int64, users int, next bool) BehaviourEvidence {
|
|
nextCount := 0
|
|
if next {
|
|
nextCount = users
|
|
}
|
|
return BehaviourEvidence{
|
|
Found: true, StartMs: startMs, UserCount: users,
|
|
NextEpisodeCount: nextCount, SpreadMs: 4000,
|
|
}
|
|
}
|
|
|
|
// The brief's worked example: a visual detector at 44:01 and a Tracearr cluster at 44:05
|
|
// should produce a marker around 44:03 with very high confidence.
|
|
func TestAgreeingSignalsStrengthenEachOther(t *testing.T) {
|
|
detection, ok := Combine(visual(2_641_000, 0.80), cluster(2_645_000, 3, true))
|
|
if !ok {
|
|
t.Fatal("two agreeing signals were rejected")
|
|
}
|
|
if detection.Method != MethodCombined {
|
|
t.Fatalf("method = %q, want %q", detection.Method, MethodCombined)
|
|
}
|
|
if detection.Confidence <= 0.80 {
|
|
t.Fatalf("confidence %.2f; agreement must beat either signal alone", detection.Confidence)
|
|
}
|
|
// The earlier of the two. A skip that starts at the first frame of the roll is right;
|
|
// one that starts a few seconds in has already shown what it was asked to skip.
|
|
if detection.StartMs != 2_641_000 {
|
|
t.Fatalf("StartMs = %d, want the earlier reading", detection.StartMs)
|
|
}
|
|
}
|
|
|
|
func TestDisagreeingSignalsUsuallyProduceNothing(t *testing.T) {
|
|
// Three minutes apart is not two readings of one transition.
|
|
_, ok := Combine(visual(2_400_000, 0.80), cluster(2_580_000, 3, true))
|
|
if ok {
|
|
t.Fatal("contradicting signals produced a marker; nothing is the right answer")
|
|
}
|
|
}
|
|
|
|
func TestWeakVisualAloneIsRejected(t *testing.T) {
|
|
if _, ok := Combine(visual(2_600_000, 0.55), BehaviourEvidence{}); ok {
|
|
t.Fatal("a detection below the confidence threshold was accepted")
|
|
}
|
|
}
|
|
|
|
func TestBehaviourAloneCanWriteAMarker(t *testing.T) {
|
|
// The whole reason the behavioural half is worth having: on a well-watched show it
|
|
// settles the question with no media access at all.
|
|
detection, ok := Combine(Detection{}, cluster(2_645_000, 4, true))
|
|
if !ok {
|
|
t.Fatal("a four-viewer cluster produced no marker")
|
|
}
|
|
if detection.Method != MethodBehaviour {
|
|
t.Fatalf("method = %q, want %q", detection.Method, MethodBehaviour)
|
|
}
|
|
}
|
|
|
|
func TestThinBehaviourAloneIsRejected(t *testing.T) {
|
|
if _, ok := Combine(Detection{}, cluster(2_645_000, 2, false)); ok {
|
|
t.Fatal("two plain stops wrote a marker with nothing else agreeing")
|
|
}
|
|
}
|
|
|
|
func TestNothingFoundIsNotAMarker(t *testing.T) {
|
|
if _, ok := Combine(Detection{}, BehaviourEvidence{}); ok {
|
|
t.Fatal("no evidence at all produced a marker")
|
|
}
|
|
}
|
|
|
|
// Marker stability: readings of the same episode wobble by a few seconds, and rewriting the
|
|
// row each time would break the "one write per episode, ever" promise.
|
|
func TestSmallVariationDoesNotRewrite(t *testing.T) {
|
|
existing := Marker{CreditsStartMs: 2_644_000, Confidence: 0.90, DetectionMethod: MethodCombined}
|
|
for _, offset := range []int64{-4000, -1000, 2000, 3000} {
|
|
if ShouldRewrite(existing, visual(existing.CreditsStartMs+offset, 0.91)) {
|
|
t.Fatalf("a %dms difference triggered a rewrite", offset)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSubstantiallyBetterEvidenceRewrites(t *testing.T) {
|
|
existing := Marker{CreditsStartMs: 2_644_000, Confidence: 0.71, DetectionMethod: MethodVisual}
|
|
better := Detection{
|
|
Found: true, StartMs: 2_644_500, Confidence: 0.95, Method: MethodCombined,
|
|
}
|
|
if !ShouldRewrite(existing, better) {
|
|
t.Fatal("a much stronger reading of the same position did not rewrite")
|
|
}
|
|
}
|
|
|
|
func TestADifferentPositionNeedsBetterEvidence(t *testing.T) {
|
|
existing := Marker{CreditsStartMs: 2_644_000, Confidence: 0.90, DetectionMethod: MethodCombined}
|
|
// A different answer that is no better must not win, or two detectors that disagree
|
|
// would rewrite the row past each other for ever.
|
|
if ShouldRewrite(existing, visual(2_500_000, 0.85)) {
|
|
t.Fatal("a weaker reading at a different position overwrote a strong marker")
|
|
}
|
|
if !ShouldRewrite(existing, Detection{
|
|
Found: true, StartMs: 2_500_000, Confidence: 0.96, Method: MethodCombined,
|
|
}) {
|
|
t.Fatal("stronger evidence at a different position should win")
|
|
}
|
|
}
|
|
|
|
func TestManualMarkersAreNeverOverwritten(t *testing.T) {
|
|
existing := Marker{CreditsStartMs: 2_600_000, Confidence: 0.5, DetectionMethod: MethodManual}
|
|
if ShouldRewrite(existing, visual(2_400_000, 0.98)) {
|
|
t.Fatal("an automatic detection overwrote an operator's correction")
|
|
}
|
|
}
|
|
|
|
func TestBelowThresholdNeverRewrites(t *testing.T) {
|
|
existing := Marker{CreditsStartMs: 2_644_000, Confidence: 0.72, DetectionMethod: MethodVisual}
|
|
if ShouldRewrite(existing, visual(2_644_100, 0.50)) {
|
|
t.Fatal("a sub-threshold detection was written over an accepted marker")
|
|
}
|
|
}
|