feat: upload audio, get a private Swedish transcript, discuss it (#33, ADR-033) #39
@@ -0,0 +1,139 @@
|
||||
package reconcile_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"git.d-ma.be/mathias/tapir/internal/reconcile"
|
||||
)
|
||||
|
||||
func words(s string) []string { return strings.Fields(s) }
|
||||
|
||||
// apply rebuilds b from a using the ops, copying equal runs from a and the
|
||||
// replacement/insertion runs from b. If Diff is right this must equal b.
|
||||
func apply(a, b []string, ops []reconcile.Op) []string {
|
||||
var out []string
|
||||
for _, o := range ops {
|
||||
switch o.Kind {
|
||||
case reconcile.Equal:
|
||||
out = append(out, a[o.AStart:o.AEnd]...)
|
||||
default:
|
||||
out = append(out, b[o.BStart:o.BEnd]...)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestDiff_Cases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a, b string
|
||||
want []reconcile.Op
|
||||
}{
|
||||
{"identical", "a b c", "a b c", []reconcile.Op{{Kind: reconcile.Equal, AStart: 0, AEnd: 3, BStart: 0, BEnd: 3}}},
|
||||
{"both empty", "", "", nil},
|
||||
{"one word replaced", "a b c", "a x c", []reconcile.Op{
|
||||
{Kind: reconcile.Equal, AStart: 0, AEnd: 1, BStart: 0, BEnd: 1},
|
||||
{Kind: reconcile.Replace, AStart: 1, AEnd: 2, BStart: 1, BEnd: 2},
|
||||
{Kind: reconcile.Equal, AStart: 2, AEnd: 3, BStart: 2, BEnd: 3},
|
||||
}},
|
||||
{"word only in b", "a c", "a b c", []reconcile.Op{
|
||||
{Kind: reconcile.Equal, AStart: 0, AEnd: 1, BStart: 0, BEnd: 1},
|
||||
{Kind: reconcile.Insert, AStart: 1, AEnd: 1, BStart: 1, BEnd: 2},
|
||||
{Kind: reconcile.Equal, AStart: 1, AEnd: 2, BStart: 2, BEnd: 3},
|
||||
}},
|
||||
{"word only in a", "a b c", "a c", []reconcile.Op{
|
||||
{Kind: reconcile.Equal, AStart: 0, AEnd: 1, BStart: 0, BEnd: 1},
|
||||
{Kind: reconcile.Delete, AStart: 1, AEnd: 2, BStart: 1, BEnd: 1},
|
||||
{Kind: reconcile.Equal, AStart: 2, AEnd: 3, BStart: 1, BEnd: 2},
|
||||
}},
|
||||
{"nothing in common", "a b", "x y z", []reconcile.Op{
|
||||
{Kind: reconcile.Replace, AStart: 0, AEnd: 2, BStart: 0, BEnd: 3},
|
||||
}},
|
||||
{"a is empty", "", "x y", []reconcile.Op{{Kind: reconcile.Insert, AStart: 0, AEnd: 0, BStart: 0, BEnd: 2}}},
|
||||
{"b is empty", "x y", "", []reconcile.Op{{Kind: reconcile.Delete, AStart: 0, AEnd: 2, BStart: 0, BEnd: 0}}},
|
||||
{"adjacent delete and insert read as one replace", "a b c d", "a x y d", []reconcile.Op{
|
||||
{Kind: reconcile.Equal, AStart: 0, AEnd: 1, BStart: 0, BEnd: 1},
|
||||
{Kind: reconcile.Replace, AStart: 1, AEnd: 3, BStart: 1, BEnd: 3},
|
||||
{Kind: reconcile.Equal, AStart: 3, AEnd: 4, BStart: 3, BEnd: 4},
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, reconcile.Diff(words(tt.a), words(tt.b)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// lcsLen is a plain dynamic-programming reference. Myers must find an edit script
|
||||
// whose equal runs add up to exactly this: a diff that is merely valid but not
|
||||
// minimal would turn real agreement into disputes the adjudicator is then asked about.
|
||||
func lcsLen(a, b []string) int {
|
||||
prev := make([]int, len(b)+1)
|
||||
for i := 1; i <= len(a); i++ {
|
||||
cur := make([]int, len(b)+1)
|
||||
for j := 1; j <= len(b); j++ {
|
||||
if a[i-1] == b[j-1] {
|
||||
cur[j] = prev[j-1] + 1
|
||||
} else {
|
||||
cur[j] = max(prev[j], cur[j-1])
|
||||
}
|
||||
}
|
||||
prev = cur
|
||||
}
|
||||
return prev[len(b)]
|
||||
}
|
||||
|
||||
func TestDiff_RebuildsBAndKeepsEveryCommonWordOnRandomInputs(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(7))
|
||||
alphabet := []string{"a", "b", "c", "d", "e", "f"}
|
||||
gen := func(n int) []string {
|
||||
out := make([]string, n)
|
||||
for i := range out {
|
||||
out[i] = alphabet[rng.Intn(len(alphabet))]
|
||||
}
|
||||
return out
|
||||
}
|
||||
for i := 0; i < 400; i++ {
|
||||
a, b := gen(rng.Intn(25)), gen(rng.Intn(25))
|
||||
|
||||
ops := reconcile.Diff(a, b)
|
||||
|
||||
require.Equal(t, append([]string{}, b...), append([]string{}, apply(a, b, ops)...), "a=%v b=%v ops=%v", a, b, ops)
|
||||
common := 0
|
||||
prevA, prevB := 0, 0
|
||||
for _, o := range ops {
|
||||
require.Equal(t, prevA, o.AStart, "ops must tile a without gaps: %v", ops)
|
||||
require.Equal(t, prevB, o.BStart, "ops must tile b without gaps: %v", ops)
|
||||
prevA, prevB = o.AEnd, o.BEnd
|
||||
if o.Kind == reconcile.Equal {
|
||||
common += o.AEnd - o.AStart
|
||||
require.Equal(t, a[o.AStart:o.AEnd], b[o.BStart:o.BEnd], "an Equal op must really be equal")
|
||||
}
|
||||
}
|
||||
require.Equal(t, len(a), prevA)
|
||||
require.Equal(t, len(b), prevB)
|
||||
require.Equal(t, lcsLen(a, b), common, "the diff must be minimal: a=%v b=%v", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiff_HandlesAnHoursWorthOfWordsFast(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(3))
|
||||
a := make([]string, 6500)
|
||||
for i := range a {
|
||||
a[i] = string(rune('a'+rng.Intn(26))) + string(rune('a'+rng.Intn(26)))
|
||||
}
|
||||
b := append([]string{}, a...)
|
||||
for i := 0; i < 380; i++ { // ~380 disputes, as measured on a real hour
|
||||
b[rng.Intn(len(b))] = "zz" + string(rune('a'+rng.Intn(26)))
|
||||
}
|
||||
|
||||
ops := reconcile.Diff(a, b)
|
||||
|
||||
assert.NotEmpty(t, ops)
|
||||
assert.Equal(t, append([]string{}, b...), append([]string{}, apply(a, b, ops)...))
|
||||
}
|
||||
Reference in New Issue
Block a user