Separate stage order from invalidation dependencies
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/stage"
|
||||
)
|
||||
|
||||
func TestDecideStageActions(t *testing.T) {
|
||||
@@ -30,40 +33,82 @@ func TestDecideStageActions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownstreamStageNames(t *testing.T) {
|
||||
got := downstreamStageNames("polish")
|
||||
want := []string{"normalize", "trim", "extract", "render", "analyze", "publish", "notify"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("downstreamStageNames(polish) = %#v, want %#v", got, want)
|
||||
func TestInvalidationDependents(t *testing.T) {
|
||||
tests := []struct {
|
||||
stage string
|
||||
want []string
|
||||
}{
|
||||
{"prepare", []string{"transcribe", "merge", "polish", "normalize", "trim", "render", "extract", "analyze", "publish", "notify"}},
|
||||
{"transcribe", []string{"merge", "polish", "normalize", "trim", "render", "extract", "analyze", "publish", "notify"}},
|
||||
{"merge", []string{"polish", "normalize", "trim", "render", "extract", "analyze", "publish", "notify"}},
|
||||
{"polish", []string{"normalize", "trim", "render", "extract", "analyze", "publish", "notify"}},
|
||||
{"normalize", []string{"trim", "render", "extract", "analyze", "publish", "notify"}},
|
||||
{"trim", []string{"render", "extract", "analyze", "publish", "notify"}},
|
||||
{"render", []string{"analyze", "publish", "notify"}},
|
||||
{"extract", []string{"analyze", "publish", "notify"}},
|
||||
{"analyze", []string{"publish", "notify"}},
|
||||
{"publish", []string{"notify"}},
|
||||
{"notify", []string{}},
|
||||
}
|
||||
|
||||
missing := downstreamStageNames("unknown")
|
||||
if len(missing) != 0 {
|
||||
t.Fatalf("downstreamStageNames(unknown) = %#v, want empty", missing)
|
||||
for _, test := range tests {
|
||||
t.Run(test.stage, func(t *testing.T) {
|
||||
got, err := dependentStageNames(test.stage)
|
||||
if err != nil {
|
||||
t.Fatalf("dependentStageNames(%q) error = %v", test.stage, err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, test.want) {
|
||||
t.Fatalf("dependentStageNames(%q) = %#v, want %#v", test.stage, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
if _, err := dependentStageNames("unknown"); err == nil || !strings.Contains(err.Error(), "unknown stage") {
|
||||
t.Fatalf("dependentStageNames(unknown) error = %v, want unknown-stage error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidateDownstreamSucceededStagesWithReason(t *testing.T) {
|
||||
func TestInvalidationRelationRejectsInvalidInventory(t *testing.T) {
|
||||
canonical := []stage.Stage{
|
||||
invalidationTestStage("one"),
|
||||
invalidationTestStage("two"),
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
registry []stage.Stage
|
||||
edges map[string][]string
|
||||
want string
|
||||
}{
|
||||
{name: "duplicate registry name", registry: append(canonical, invalidationTestStage("one")), edges: map[string][]string{"one": {"two"}, "two": {}}, want: "duplicate stage"},
|
||||
{name: "unknown source", registry: canonical, edges: map[string][]string{"one": {"two"}, "two": {}, "three": {}}, want: "unknown stage"},
|
||||
{name: "unknown target", registry: canonical, edges: map[string][]string{"one": {"three"}, "two": {}}, want: "unknown stage"},
|
||||
{name: "missing classification", registry: canonical, edges: map[string][]string{"one": {"two"}}, want: "missing classification"},
|
||||
{name: "cycle", registry: canonical, edges: map[string][]string{"one": {"two"}, "two": {"one"}}, want: "cycle"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
_, err := newInvalidationRelation(test.registry, test.edges)
|
||||
if err == nil || !strings.Contains(err.Error(), test.want) {
|
||||
t.Fatalf("newInvalidationRelation() error = %v, want %q", err, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidateDependentSucceededStagesWithReason(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
m := manifest.New("2026-05-03", now)
|
||||
m.MarkStageSucceeded("prepare", now, nil)
|
||||
m.MarkStageSucceeded("transcribe", now, nil)
|
||||
m.MarkStageSucceeded("merge", now, nil)
|
||||
m.MarkStageSucceeded("polish", now, nil)
|
||||
m.MarkStageSucceeded("normalize", now, nil)
|
||||
m.MarkStageSucceeded("trim", now, nil)
|
||||
m.MarkStageSucceeded("extract", now, nil)
|
||||
m.MarkStageSucceeded("render", now, nil)
|
||||
m.MarkStageFailed("analyze", now, "analysis failed")
|
||||
m.MarkStageSucceeded("publish", now, nil)
|
||||
m.MarkStageSucceeded("notify", now, nil)
|
||||
|
||||
got := invalidateDownstreamSucceededStagesWithReason(m, "polish", now.Add(1*time.Second), staleReasonChangedResult)
|
||||
want := []string{"normalize", "trim", "extract", "render", "publish", "notify"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("invalidateDownstreamSucceededStagesWithReason() = %#v, want %#v", got, want)
|
||||
for _, name := range canonicalStageNames() {
|
||||
m.MarkStageSucceeded(name, now, nil)
|
||||
}
|
||||
m.MarkStageFailed("analyze", now, "analysis failed")
|
||||
|
||||
got, err := invalidateDependentSucceededStagesWithReason(m, "polish", now.Add(time.Second), staleReasonChangedResult)
|
||||
if err != nil {
|
||||
t.Fatalf("invalidateDependentSucceededStagesWithReason() error = %v", err)
|
||||
}
|
||||
want := []string{"normalize", "trim", "render", "extract", "publish", "notify"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("invalidateDependentSucceededStagesWithReason() = %#v, want %#v", got, want)
|
||||
}
|
||||
for _, stageName := range want {
|
||||
if m.Stages[stageName].Status != manifest.StatusStale {
|
||||
t.Fatalf("%s status = %q, want stale", stageName, m.Stages[stageName].Status)
|
||||
@@ -72,34 +117,38 @@ func TestInvalidateDownstreamSucceededStagesWithReason(t *testing.T) {
|
||||
if m.Stages["analyze"].Status != manifest.StatusFailed {
|
||||
t.Fatalf("analyze status = %q, want failed", m.Stages["analyze"].Status)
|
||||
}
|
||||
if m.Stages["prepare"].Status != manifest.StatusSucceeded {
|
||||
t.Fatalf("prepare status = %q, want succeeded", m.Stages["prepare"].Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractionPositionControlsForceInvalidation(t *testing.T) {
|
||||
func TestRenderAndExtractInvalidationAreIndependent(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
tests := []struct {
|
||||
upstream string
|
||||
want []string
|
||||
}{
|
||||
{upstream: "trim", want: []string{"extract", "render", "analyze", "publish", "notify"}},
|
||||
{upstream: "extract", want: []string{"render", "analyze", "publish", "notify"}},
|
||||
{upstream: "render", want: []string{"analyze", "publish", "notify"}},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.upstream, func(t *testing.T) {
|
||||
for _, upstream := range []string{"render", "extract"} {
|
||||
t.Run(upstream, func(t *testing.T) {
|
||||
m := manifest.New("2026-05-03", now)
|
||||
for _, name := range canonicalStageNames() {
|
||||
m.MarkStageSucceeded(name, now, nil)
|
||||
}
|
||||
got := invalidateDownstreamSucceededStagesWithReason(m, test.upstream, now.Add(time.Second), staleReasonForcedReplacement)
|
||||
if !reflect.DeepEqual(got, test.want) {
|
||||
t.Fatalf("invalidated = %#v, want %#v", got, test.want)
|
||||
got, err := invalidateDependentSucceededStagesWithReason(m, upstream, now.Add(time.Second), staleReasonForcedReplacement)
|
||||
if err != nil {
|
||||
t.Fatalf("invalidate dependents: %v", err)
|
||||
}
|
||||
if test.upstream == "render" && m.Stages["extract"].Status != manifest.StatusSucceeded {
|
||||
t.Fatalf("forcing render changed extract: %#v", m.Stages["extract"])
|
||||
want := []string{"analyze", "publish", "notify"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("invalidated = %#v, want %#v", got, want)
|
||||
}
|
||||
sibling := "render"
|
||||
if upstream == "render" {
|
||||
sibling = "extract"
|
||||
}
|
||||
if m.Stages[sibling].Status != manifest.StatusSucceeded {
|
||||
t.Fatalf("%s invalidated sibling %s: %#v", upstream, sibling, m.Stages[sibling])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type invalidationTestStage string
|
||||
|
||||
func (s invalidationTestStage) Name() string { return string(s) }
|
||||
func (s invalidationTestStage) Run(_ context.Context, _ *stage.Env, _ *manifest.Manifest) (*stage.StageResult, error) {
|
||||
return &stage.StageResult{}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user