test(reconcile): the comparison and the deciding phase report real counts, and a shutdown is neither done nor failed (red)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
package reconcile_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"git.d-ma.be/mathias/tapir/internal/progress"
|
||||
"git.d-ma.be/mathias/tapir/internal/reconcile"
|
||||
)
|
||||
|
||||
// What Reconcile reports while it works (ADR-035): the comparison, then the deciding
|
||||
// phase with the real number of model decisions as its total.
|
||||
|
||||
type repRec struct {
|
||||
mu sync.Mutex
|
||||
events []string
|
||||
}
|
||||
|
||||
func (r *repRec) add(e string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.events = append(r.events, e)
|
||||
}
|
||||
|
||||
func (r *repRec) Begin(key string, total int) { r.add(fmt.Sprintf("begin:%s:%d", key, total)) }
|
||||
func (r *repRec) Advance(key string, done int) { r.add(fmt.Sprintf("advance:%s:%d", key, done)) }
|
||||
func (r *repRec) Done(key string) { r.add("done:" + key) }
|
||||
func (r *repRec) Fail(key string, _ error) { r.add("fail:" + key) }
|
||||
func (r *repRec) Note(string, ...any) {}
|
||||
func (r *repRec) joined() string {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return strings.Join(r.events, ",")
|
||||
}
|
||||
func withRep(r *repRec) context.Context { return progress.With(context.Background(), r) }
|
||||
func reconcileWith(t *testing.T, ctx context.Context, base, alt string, adj reconcile.Adjudicator) error {
|
||||
t.Helper()
|
||||
_, err := reconcile.Reconcile(ctx, tr(base), tr(alt), adj, opts)
|
||||
return err
|
||||
}
|
||||
|
||||
// two compound/filler disputes the model is never asked about, then two it is.
|
||||
const (
|
||||
progBase = "i crossborder delen ska vi ju betala totalt 1015 kronor till SEB idag"
|
||||
progAlt = "i crossborderdelen ska vi betala totalt 1050 kronor till SCB idag"
|
||||
)
|
||||
|
||||
func TestReconcileProgress_TheDecidingPhaseTotalIsTheModelDecisionsNotTheDisputes(t *testing.T) {
|
||||
rep := &repRec{}
|
||||
|
||||
require.NoError(t, reconcileWith(t, withRep(rep), progBase, progAlt, always(reconcile.VerdictAlt)))
|
||||
|
||||
assert.Equal(t,
|
||||
"begin:compare:0,done:compare,begin:decide:2,advance:decide:1,advance:decide:2,done:decide",
|
||||
rep.joined(),
|
||||
"4 disputes, 2 settled without the model: the bar counts the 2 the model decides, so it can reach its end")
|
||||
}
|
||||
|
||||
func TestReconcileProgress_TextsThatAgreeStillClosePhasesSoNothingIsLeftRunning(t *testing.T) {
|
||||
rep := &repRec{}
|
||||
|
||||
require.NoError(t, reconcileWith(t, withRep(rep), "samma text hela vägen", "samma text hela vägen", always(reconcile.VerdictAlt)))
|
||||
|
||||
assert.Equal(t, "begin:compare:0,done:compare,begin:decide:0,done:decide", rep.joined())
|
||||
}
|
||||
|
||||
func TestReconcileProgress_AModelErrorStillCountsAsADecisionTaken(t *testing.T) {
|
||||
rep := &repRec{}
|
||||
adj := &scripted{fn: func(i int, _ reconcile.Request) (reconcile.Verdict, error) {
|
||||
if i == 0 {
|
||||
return reconcile.VerdictBase, errors.New("one timeout")
|
||||
}
|
||||
return reconcile.VerdictAlt, nil
|
||||
}}
|
||||
|
||||
require.NoError(t, reconcileWith(t, withRep(rep), progBase, progAlt, adj))
|
||||
|
||||
assert.Contains(t, rep.joined(), "advance:decide:1,advance:decide:2", "a spot that errored was still handled; the bar must move past it")
|
||||
}
|
||||
|
||||
func TestReconcileProgress_TheBreakerTrippingShowsTheDecidingPhaseFailedNotDone(t *testing.T) {
|
||||
rep := &repRec{}
|
||||
adj := &scripted{fn: func(int, reconcile.Request) (reconcile.Verdict, error) {
|
||||
return reconcile.VerdictBase, errors.New("model down")
|
||||
}}
|
||||
small := opts
|
||||
small.BreakAfter = 2
|
||||
|
||||
_, err := reconcile.Reconcile(withRep(rep), tr(progBase), tr(progAlt), adj, small)
|
||||
|
||||
require.NoError(t, err, "a model that is down degrades the result; it does not fail the job")
|
||||
assert.Contains(t, rep.joined(), "fail:decide")
|
||||
assert.NotContains(t, rep.joined(), "done:decide")
|
||||
}
|
||||
|
||||
func TestReconcileProgress_AShutdownIsNeitherDoneNorFailed(t *testing.T) {
|
||||
rep := &repRec{}
|
||||
ctx, cancel := context.WithCancel(withRep(rep))
|
||||
adj := &scripted{fn: func(int, reconcile.Request) (reconcile.Verdict, error) {
|
||||
cancel()
|
||||
return reconcile.VerdictAlt, nil
|
||||
}}
|
||||
|
||||
err := reconcileWith(t, ctx, progBase, progAlt, adj)
|
||||
|
||||
require.ErrorIs(t, err, context.Canceled)
|
||||
assert.NotContains(t, rep.joined(), "fail:decide")
|
||||
assert.NotContains(t, rep.joined(), "done:decide")
|
||||
}
|
||||
Reference in New Issue
Block a user