Complete runner lifecycle refactor
This commit is contained in:
@@ -38,11 +38,7 @@ func TestExecuteRunStagePublishPropagatesSelectedArtifacts(t *testing.T) {
|
||||
|
||||
var capturedStages []string
|
||||
var capturedArtifacts []string
|
||||
origExecuteStagesFn := executeStagesFn
|
||||
t.Cleanup(func() {
|
||||
executeStagesFn = origExecuteStagesFn
|
||||
})
|
||||
executeStagesFn = func(_ context.Context, _ *config.Config, plan BoundedPlan, opts RunOptions) (*RunSummary, error) {
|
||||
executor := func(_ context.Context, _ *config.Config, plan BoundedPlan, opts RunOptions) (*RunSummary, error) {
|
||||
for _, s := range plan.Stages() {
|
||||
capturedStages = append(capturedStages, s.Name())
|
||||
}
|
||||
@@ -52,7 +48,7 @@ func TestExecuteRunStagePublishPropagatesSelectedArtifacts(t *testing.T) {
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
code := Execute(
|
||||
code := executeWithPipelineExecutor(
|
||||
[]string{
|
||||
"run-stage", "publish", "2026-05-03",
|
||||
"--config", pipelinePath,
|
||||
@@ -62,6 +58,7 @@ func TestExecuteRunStagePublishPropagatesSelectedArtifacts(t *testing.T) {
|
||||
},
|
||||
&stdout,
|
||||
&stderr,
|
||||
executor,
|
||||
)
|
||||
if code != 0 {
|
||||
t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String())
|
||||
@@ -165,11 +162,7 @@ func TestExecuteAnalyzeForceRunsAnalyze(t *testing.T) {
|
||||
|
||||
var capturedStages []string
|
||||
var capturedForce bool
|
||||
origExecuteStagesFn := executeStagesFn
|
||||
t.Cleanup(func() {
|
||||
executeStagesFn = origExecuteStagesFn
|
||||
})
|
||||
executeStagesFn = func(_ context.Context, _ *config.Config, plan BoundedPlan, opts RunOptions) (*RunSummary, error) {
|
||||
executor := func(_ context.Context, _ *config.Config, plan BoundedPlan, opts RunOptions) (*RunSummary, error) {
|
||||
for _, s := range plan.Stages() {
|
||||
capturedStages = append(capturedStages, s.Name())
|
||||
}
|
||||
@@ -182,10 +175,11 @@ func TestExecuteAnalyzeForceRunsAnalyze(t *testing.T) {
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
code := Execute(
|
||||
code := executeWithPipelineExecutor(
|
||||
[]string{"analyze", "2026-05-03", "--config", pipelinePath, "--campaign-file", campaignPath, "--session", sessionPath},
|
||||
&stdout,
|
||||
&stderr,
|
||||
executor,
|
||||
)
|
||||
if code != 0 {
|
||||
t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String())
|
||||
@@ -206,18 +200,14 @@ func TestExecuteAnalyzePropagatesSelectedArtifacts(t *testing.T) {
|
||||
pipelinePath, campaignPath, sessionPath := writeValidConfigFilesWithScriptoriumArtifacts(t, workspaceRoot)
|
||||
|
||||
var capturedArtifacts []string
|
||||
origExecuteStagesFn := executeStagesFn
|
||||
t.Cleanup(func() {
|
||||
executeStagesFn = origExecuteStagesFn
|
||||
})
|
||||
executeStagesFn = func(_ context.Context, _ *config.Config, _ BoundedPlan, opts RunOptions) (*RunSummary, error) {
|
||||
executor := func(_ context.Context, _ *config.Config, _ BoundedPlan, opts RunOptions) (*RunSummary, error) {
|
||||
capturedArtifacts = append([]string(nil), opts.SelectedArtifacts...)
|
||||
return &RunSummary{ManifestPath: filepath.Join(workspaceRoot, "manifest.json"), Executed: []string{"analyze"}}, nil
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
code := Execute(
|
||||
code := executeWithPipelineExecutor(
|
||||
[]string{
|
||||
"analyze",
|
||||
"2026-05-03",
|
||||
@@ -228,6 +218,7 @@ func TestExecuteAnalyzePropagatesSelectedArtifacts(t *testing.T) {
|
||||
},
|
||||
&stdout,
|
||||
&stderr,
|
||||
executor,
|
||||
)
|
||||
if code != 0 {
|
||||
t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String())
|
||||
@@ -299,11 +290,7 @@ func TestExecutePublishForceRunsPublish(t *testing.T) {
|
||||
var capturedStages []string
|
||||
var capturedForce bool
|
||||
var capturedArtifacts []string
|
||||
origExecuteStagesFn := executeStagesFn
|
||||
t.Cleanup(func() {
|
||||
executeStagesFn = origExecuteStagesFn
|
||||
})
|
||||
executeStagesFn = func(_ context.Context, _ *config.Config, plan BoundedPlan, opts RunOptions) (*RunSummary, error) {
|
||||
executor := func(_ context.Context, _ *config.Config, plan BoundedPlan, opts RunOptions) (*RunSummary, error) {
|
||||
for _, s := range plan.Stages() {
|
||||
capturedStages = append(capturedStages, s.Name())
|
||||
}
|
||||
@@ -317,10 +304,11 @@ func TestExecutePublishForceRunsPublish(t *testing.T) {
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
code := Execute(
|
||||
code := executeWithPipelineExecutor(
|
||||
[]string{"publish", "2026-05-03", "--config", pipelinePath, "--campaign-file", campaignPath, "--session", sessionPath, "--artifacts", "session_recap"},
|
||||
&stdout,
|
||||
&stderr,
|
||||
executor,
|
||||
)
|
||||
if code != 0 {
|
||||
t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String())
|
||||
|
||||
@@ -48,9 +48,7 @@ func TestAssembledCanonicalAndAliasArtifactRegenerationRequestsMatch(t *testing.
|
||||
force bool
|
||||
}
|
||||
var captured []capturedRequest
|
||||
original := executeStagesFn
|
||||
t.Cleanup(func() { executeStagesFn = original })
|
||||
executeStagesFn = func(_ context.Context, _ *config.Config, plan BoundedPlan, options RunOptions) (*RunSummary, error) {
|
||||
executor := func(_ context.Context, _ *config.Config, plan BoundedPlan, options RunOptions) (*RunSummary, error) {
|
||||
captured = append(captured, capturedRequest{
|
||||
stages: plan.Names(), artifacts: append([]string(nil), options.SelectedArtifacts...), force: options.Force,
|
||||
})
|
||||
@@ -60,11 +58,11 @@ func TestAssembledCanonicalAndAliasArtifactRegenerationRequestsMatch(t *testing.
|
||||
"2026-05-03", "--force", "--from", "extract", "--through", "analyze",
|
||||
"--config", pipelinePath, "--campaign-file", campaignPath, "--session", sessionPath,
|
||||
}
|
||||
if err := Run(context.Background(), base, &bytes.Buffer{}); err != nil {
|
||||
if err := runWithPipelineExecutor(context.Background(), base, &bytes.Buffer{}, executor); err != nil {
|
||||
t.Fatalf("canonical unselected Run() error = %v", err)
|
||||
}
|
||||
selected := append(append([]string(nil), base...), "--artifacts", "session_recap")
|
||||
if err := Run(context.Background(), selected, &bytes.Buffer{}); err != nil {
|
||||
if err := runWithPipelineExecutor(context.Background(), selected, &bytes.Buffer{}, executor); err != nil {
|
||||
t.Fatalf("canonical selected Run() error = %v", err)
|
||||
}
|
||||
var stdout, stderr bytes.Buffer
|
||||
@@ -72,7 +70,7 @@ func TestAssembledCanonicalAndAliasArtifactRegenerationRequestsMatch(t *testing.
|
||||
"regenerate-artifacts", "2026-05-03", "--artifacts", "session_recap",
|
||||
"--config", pipelinePath, "--campaign-file", campaignPath, "--session", sessionPath,
|
||||
}
|
||||
if code := Execute(alias, &stdout, &stderr); code != 0 {
|
||||
if code := executeWithPipelineExecutor(alias, &stdout, &stderr, executor); code != 0 {
|
||||
t.Fatalf("alias exit=%d stderr=%q", code, stderr.String())
|
||||
}
|
||||
if len(captured) != 3 {
|
||||
@@ -289,12 +287,10 @@ func TestAssembledSplitBundleCommandsUseOneEffectiveFamilyConfiguration(t *testi
|
||||
t.Fatalf("plan output = %q, want testing provenance", planned.String())
|
||||
}
|
||||
|
||||
original := executeStagesFn
|
||||
t.Cleanup(func() { executeStagesFn = original })
|
||||
var capturedCfg *config.Config
|
||||
var capturedPlan BoundedPlan
|
||||
var capturedOptions RunOptions
|
||||
executeStagesFn = func(_ context.Context, cfg *config.Config, plan BoundedPlan, options RunOptions) (*RunSummary, error) {
|
||||
executor := func(_ context.Context, cfg *config.Config, plan BoundedPlan, options RunOptions) (*RunSummary, error) {
|
||||
capturedCfg = cfg
|
||||
capturedPlan = plan
|
||||
capturedOptions = options
|
||||
@@ -302,7 +298,7 @@ func TestAssembledSplitBundleCommandsUseOneEffectiveFamilyConfiguration(t *testi
|
||||
}
|
||||
runArgs := append(append([]string(nil), common...), "--from", "analyze", "--through", "analyze", "--artifacts", "character_items_arannis")
|
||||
var runOut bytes.Buffer
|
||||
if err := Run(context.Background(), runArgs, &runOut); err != nil {
|
||||
if err := runWithPipelineExecutor(context.Background(), runArgs, &runOut, executor); err != nil {
|
||||
t.Fatalf("bounded family run: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(capturedPlan.Names(), []string{"analyze"}) {
|
||||
|
||||
@@ -140,9 +140,7 @@ func TestRunPassesBoundedPlanToRunner(t *testing.T) {
|
||||
var capturedStages []string
|
||||
var capturedPlan BoundedPlan
|
||||
var capturedOptions RunOptions
|
||||
original := executeStagesFn
|
||||
t.Cleanup(func() { executeStagesFn = original })
|
||||
executeStagesFn = func(_ context.Context, _ *config.Config, plan BoundedPlan, options RunOptions) (*RunSummary, error) {
|
||||
executor := func(_ context.Context, _ *config.Config, plan BoundedPlan, options RunOptions) (*RunSummary, error) {
|
||||
capturedStages = plan.Names()
|
||||
capturedPlan = plan
|
||||
capturedOptions = options
|
||||
@@ -150,7 +148,7 @@ func TestRunPassesBoundedPlanToRunner(t *testing.T) {
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
err := Run(context.Background(), []string{
|
||||
err := runWithPipelineExecutor(context.Background(), []string{
|
||||
"2026-05-03",
|
||||
"--from", "render",
|
||||
"--through", "extract",
|
||||
@@ -158,7 +156,7 @@ func TestRunPassesBoundedPlanToRunner(t *testing.T) {
|
||||
"--config", pipelinePath,
|
||||
"--campaign-file", campaignPath,
|
||||
"--session", sessionPath,
|
||||
}, &out)
|
||||
}, &out, executor)
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ import (
|
||||
|
||||
var supportedCommands = []string{"version", "run", "regenerate-artifacts", "run-stage", "analyze", "publish", "clean", "session", "config"}
|
||||
|
||||
var runCommandFn = Run
|
||||
|
||||
// Execute dispatches CLI commands and returns a process exit code.
|
||||
func Execute(args []string, stdout, stderr io.Writer) int {
|
||||
return executeWithPipelineExecutor(args, stdout, stderr, executePlan)
|
||||
}
|
||||
|
||||
func executeWithPipelineExecutor(args []string, stdout, stderr io.Writer, executor pipelineExecutor) int {
|
||||
if len(args) == 0 {
|
||||
printUsage(stderr)
|
||||
return 1
|
||||
@@ -27,15 +29,15 @@ func Execute(args []string, stdout, stderr io.Writer) int {
|
||||
case "version":
|
||||
err = Version(cmdArgs, stdout)
|
||||
case "run":
|
||||
err = runCommandFn(ctx, cmdArgs, stdout)
|
||||
err = runWithPipelineExecutor(ctx, cmdArgs, stdout, executor)
|
||||
case "regenerate-artifacts":
|
||||
err = RegenerateArtifacts(ctx, cmdArgs, stdout)
|
||||
err = regenerateArtifactsWithPipelineExecutor(ctx, cmdArgs, stdout, executor)
|
||||
case "run-stage":
|
||||
err = RunStage(ctx, cmdArgs, stdout)
|
||||
err = runStageWithPipelineExecutor(ctx, cmdArgs, stdout, executor)
|
||||
case "analyze":
|
||||
err = Analyze(ctx, cmdArgs, stdout)
|
||||
err = analyzeWithPipelineExecutor(ctx, cmdArgs, stdout, executor)
|
||||
case "publish":
|
||||
err = Publish(ctx, cmdArgs, stdout)
|
||||
err = publishWithPipelineExecutor(ctx, cmdArgs, stdout, executor)
|
||||
case "session":
|
||||
err = Session(ctx, cmdArgs, stdout)
|
||||
case "clean":
|
||||
|
||||
@@ -16,26 +16,24 @@ func TestRunProfileSelectionFlowsThroughSharedLoader(t *testing.T) {
|
||||
pipelinePath, campaignPath, sessionPath := writeValidConfigFiles(t, workspaceRoot)
|
||||
enableCommandTestProfiles(t, pipelinePath)
|
||||
|
||||
original := executeStagesFn
|
||||
t.Cleanup(func() { executeStagesFn = original })
|
||||
var language string
|
||||
executeStagesFn = func(_ context.Context, cfg *config.Config, _ BoundedPlan, _ RunOptions) (*RunSummary, error) {
|
||||
executor := func(_ context.Context, cfg *config.Config, _ BoundedPlan, _ RunOptions) (*RunSummary, error) {
|
||||
language = cfg.Pipeline.WhisperX.Language
|
||||
return &RunSummary{SessionID: cfg.Session.SessionID, ManifestPath: "manifest.json"}, nil
|
||||
}
|
||||
|
||||
if err := Run(context.Background(), []string{
|
||||
if err := runWithPipelineExecutor(context.Background(), []string{
|
||||
"2026-05-03", "--config", pipelinePath, "--campaign-file", campaignPath, "--session", sessionPath, "--profile", "testing",
|
||||
}, io.Discard); err != nil {
|
||||
}, io.Discard, executor); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if language != "fr" {
|
||||
t.Fatalf("explicit profile language = %q, want fr", language)
|
||||
}
|
||||
|
||||
if err := Run(context.Background(), []string{
|
||||
if err := runWithPipelineExecutor(context.Background(), []string{
|
||||
"2026-05-03", "--config", pipelinePath, "--campaign-file", campaignPath, "--session", sessionPath,
|
||||
}, io.Discard); err != nil {
|
||||
}, io.Discard, executor); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if language != "en" {
|
||||
@@ -43,9 +41,9 @@ func TestRunProfileSelectionFlowsThroughSharedLoader(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, profile := range []string{"unknown", ""} {
|
||||
err := Run(context.Background(), []string{
|
||||
err := runWithPipelineExecutor(context.Background(), []string{
|
||||
"2026-05-03", "--config", pipelinePath, "--campaign-file", campaignPath, "--session", sessionPath, "--profile", profile,
|
||||
}, io.Discard)
|
||||
}, io.Discard, executor)
|
||||
if err == nil || !strings.Contains(err.Error(), "profile") {
|
||||
t.Fatalf("profile %q error = %v, want selection rejection", profile, err)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ import (
|
||||
// RegenerateArtifacts expands the convenience command into its canonical run
|
||||
// invocation. The run command remains the sole owner of parsing and execution.
|
||||
func RegenerateArtifacts(ctx context.Context, args []string, out io.Writer) error {
|
||||
return regenerateArtifactsWithPipelineExecutor(ctx, args, out, executePlan)
|
||||
}
|
||||
|
||||
func regenerateArtifactsWithPipelineExecutor(ctx context.Context, args []string, out io.Writer, executor pipelineExecutor) error {
|
||||
if containsHelpOption(args) {
|
||||
printRegenerateArtifactsHelp(out)
|
||||
return nil
|
||||
@@ -21,7 +25,7 @@ func RegenerateArtifacts(ctx context.Context, args []string, out io.Writer) erro
|
||||
}
|
||||
expanded = append(expanded, "--force", "--from", "extract", "--through", "analyze")
|
||||
expanded = append(expanded, args...)
|
||||
return runCommandFn(ctx, expanded, out)
|
||||
return runWithPipelineExecutor(ctx, expanded, out, executor)
|
||||
}
|
||||
|
||||
func containsHelpOption(args []string) bool {
|
||||
|
||||
@@ -7,82 +7,80 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
||||
)
|
||||
|
||||
func TestRegenerateArtifactsForwardsExactCanonicalRunArguments(t *testing.T) {
|
||||
original := runCommandFn
|
||||
t.Cleanup(func() { runCommandFn = original })
|
||||
var captured []string
|
||||
runCommandFn = func(_ context.Context, args []string, _ io.Writer) error {
|
||||
captured = append([]string(nil), args...)
|
||||
return nil
|
||||
func TestRegenerateArtifactsForwardsCanonicalPipelineRequest(t *testing.T) {
|
||||
workspaceRoot := t.TempDir()
|
||||
pipelinePath, campaignPath, sessionPath := writeValidConfigFilesWithScriptoriumArtifacts(t, workspaceRoot)
|
||||
enableCommandTestProfiles(t, pipelinePath)
|
||||
var capturedPlan BoundedPlan
|
||||
var capturedOptions RunOptions
|
||||
executor := func(_ context.Context, cfg *config.Config, plan BoundedPlan, options RunOptions) (*RunSummary, error) {
|
||||
capturedPlan = plan
|
||||
capturedOptions = options
|
||||
return &RunSummary{SessionID: cfg.Session.SessionID, ManifestPath: "manifest.json"}, nil
|
||||
}
|
||||
|
||||
code := Execute([]string{
|
||||
code := executeWithPipelineExecutor([]string{
|
||||
"regenerate-artifacts", "2026-05-03",
|
||||
"--artifacts", "session_recap,player_handout",
|
||||
"--artifacts=player_handout",
|
||||
"--config", "pipeline.yml",
|
||||
"--campaign", "sample-campaign",
|
||||
"--config", pipelinePath,
|
||||
"--campaign-file", campaignPath,
|
||||
"--session", sessionPath,
|
||||
"--profile", "testing",
|
||||
}, io.Discard, io.Discard)
|
||||
}, io.Discard, io.Discard, executor)
|
||||
if code != 0 {
|
||||
t.Fatalf("Execute() code = %d, want 0", code)
|
||||
t.Fatalf("executeWithPipelineExecutor() code = %d, want 0", code)
|
||||
}
|
||||
want := []string{
|
||||
"2026-05-03", "--force", "--from", "extract", "--through", "analyze",
|
||||
"--artifacts", "session_recap,player_handout",
|
||||
"--artifacts=player_handout",
|
||||
"--config", "pipeline.yml",
|
||||
"--campaign", "sample-campaign",
|
||||
"--profile", "testing",
|
||||
if got, want := capturedPlan.Names(), []string{"extract", "analyze"}; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("plan = %#v, want %#v", got, want)
|
||||
}
|
||||
if !reflect.DeepEqual(captured, want) {
|
||||
t.Fatalf("forwarded args = %#v, want %#v", captured, want)
|
||||
if !capturedOptions.Force {
|
||||
t.Fatal("force = false, want true")
|
||||
}
|
||||
if got, want := capturedOptions.SelectedArtifacts, []string{"player_handout", "session_recap"}; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("selected artifacts = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegenerateArtifactsForwardsSessionIDCompatibilityFlag(t *testing.T) {
|
||||
original := runCommandFn
|
||||
t.Cleanup(func() { runCommandFn = original })
|
||||
var captured []string
|
||||
runCommandFn = func(_ context.Context, args []string, _ io.Writer) error {
|
||||
captured = append([]string(nil), args...)
|
||||
return nil
|
||||
workspaceRoot := t.TempDir()
|
||||
pipelinePath, campaignPath, sessionPath := writeValidConfigFilesWithScriptoriumArtifacts(t, workspaceRoot)
|
||||
var capturedSessionID string
|
||||
executor := func(_ context.Context, cfg *config.Config, _ BoundedPlan, _ RunOptions) (*RunSummary, error) {
|
||||
capturedSessionID = cfg.Session.SessionID
|
||||
return &RunSummary{SessionID: cfg.Session.SessionID, ManifestPath: "manifest.json"}, nil
|
||||
}
|
||||
|
||||
code := Execute([]string{
|
||||
code := executeWithPipelineExecutor([]string{
|
||||
"regenerate-artifacts",
|
||||
"--config", "pipeline.yml",
|
||||
"--config", pipelinePath,
|
||||
"--campaign-file", campaignPath,
|
||||
"--session", sessionPath,
|
||||
"--session-id", "2026-05-03",
|
||||
"--artifacts", "session_recap",
|
||||
}, io.Discard, io.Discard)
|
||||
}, io.Discard, io.Discard, executor)
|
||||
if code != 0 {
|
||||
t.Fatalf("Execute() code = %d, want 0", code)
|
||||
t.Fatalf("executeWithPipelineExecutor() code = %d, want 0", code)
|
||||
}
|
||||
want := []string{
|
||||
"--force", "--from", "extract", "--through", "analyze",
|
||||
"--config", "pipeline.yml",
|
||||
"--session-id", "2026-05-03",
|
||||
"--artifacts", "session_recap",
|
||||
}
|
||||
if !reflect.DeepEqual(captured, want) {
|
||||
t.Fatalf("forwarded args = %#v, want %#v", captured, want)
|
||||
if capturedSessionID != "2026-05-03" {
|
||||
t.Fatalf("session id = %q, want compatibility flag value", capturedSessionID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegenerateArtifactsHelpDoesNotInvokeRun(t *testing.T) {
|
||||
original := runCommandFn
|
||||
t.Cleanup(func() { runCommandFn = original })
|
||||
called := false
|
||||
runCommandFn = func(_ context.Context, _ []string, _ io.Writer) error {
|
||||
executor := func(context.Context, *config.Config, BoundedPlan, RunOptions) (*RunSummary, error) {
|
||||
called = true
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
if code := Execute([]string{"regenerate-artifacts", "--help"}, &stdout, &stderr); code != 0 {
|
||||
if code := executeWithPipelineExecutor([]string{"regenerate-artifacts", "--help"}, &stdout, &stderr, executor); code != 0 {
|
||||
t.Fatalf("Execute() code = %d, stderr = %q", code, stderr.String())
|
||||
}
|
||||
if called {
|
||||
|
||||
@@ -92,7 +92,7 @@ func TestRemoteSessionConfigIsRemovedAfterEveryCommandExit(t *testing.T) {
|
||||
name string
|
||||
sessionYAML string
|
||||
command []string
|
||||
configureRun func()
|
||||
executor pipelineExecutor
|
||||
wantSuccessful bool
|
||||
}{
|
||||
{
|
||||
@@ -126,10 +126,8 @@ inputs:
|
||||
prefix: audio/
|
||||
`,
|
||||
command: []string{"run", "2026-05-03"},
|
||||
configureRun: func() {
|
||||
executeStagesFn = func(context.Context, *config.Config, BoundedPlan, RunOptions) (*RunSummary, error) {
|
||||
return nil, errors.New("adapter failed")
|
||||
}
|
||||
executor: func(context.Context, *config.Config, BoundedPlan, RunOptions) (*RunSummary, error) {
|
||||
return nil, errors.New("adapter failed")
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -140,10 +138,8 @@ inputs:
|
||||
prefix: audio/
|
||||
`,
|
||||
command: []string{"run", "2026-05-03"},
|
||||
configureRun: func() {
|
||||
executeStagesFn = func(context.Context, *config.Config, BoundedPlan, RunOptions) (*RunSummary, error) {
|
||||
return nil, context.Canceled
|
||||
}
|
||||
executor: func(context.Context, *config.Config, BoundedPlan, RunOptions) (*RunSummary, error) {
|
||||
return nil, context.Canceled
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -159,16 +155,15 @@ inputs:
|
||||
|
||||
var downloadedPath string
|
||||
captureRemoteSessionTempPath(t, &downloadedPath)
|
||||
if tt.configureRun != nil {
|
||||
origExecuteStagesFn := executeStagesFn
|
||||
t.Cleanup(func() { executeStagesFn = origExecuteStagesFn })
|
||||
tt.configureRun()
|
||||
executor := tt.executor
|
||||
if executor == nil {
|
||||
executor = executePlan
|
||||
}
|
||||
|
||||
args := append(append([]string(nil), tt.command...), "--config", pipelinePath, "--campaign-file", campaignPath)
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
code := Execute(args, &stdout, &stderr)
|
||||
code := executeWithPipelineExecutor(args, &stdout, &stderr, executor)
|
||||
if tt.wantSuccessful && code != 0 {
|
||||
t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String())
|
||||
}
|
||||
|
||||
@@ -29,11 +29,7 @@ func TestRestoreThenRunStageForceAnalyzeUsesRestoredDurableState(t *testing.T) {
|
||||
|
||||
restoreWithStoreAndRealPhases(t, fake)
|
||||
|
||||
origExecuteStagesFn := executeStagesFn
|
||||
t.Cleanup(func() {
|
||||
executeStagesFn = origExecuteStagesFn
|
||||
})
|
||||
executeStagesFn = func(ctx context.Context, cfg *config.Config, plan BoundedPlan, opts RunOptions) (*RunSummary, error) {
|
||||
executor := func(ctx context.Context, cfg *config.Config, plan BoundedPlan, opts RunOptions) (*RunSummary, error) {
|
||||
if opts.Env == nil {
|
||||
opts.Env = &Env{}
|
||||
}
|
||||
@@ -81,7 +77,7 @@ func TestRestoreThenRunStageForceAnalyzeUsesRestoredDurableState(t *testing.T) {
|
||||
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
runStageCode := Execute(
|
||||
runStageCode := executeWithPipelineExecutor(
|
||||
[]string{
|
||||
"run-stage", "analyze", cfg.Session.SessionID,
|
||||
"--config", pipelinePath,
|
||||
@@ -92,6 +88,7 @@ func TestRestoreThenRunStageForceAnalyzeUsesRestoredDurableState(t *testing.T) {
|
||||
},
|
||||
&stdout,
|
||||
&stderr,
|
||||
executor,
|
||||
)
|
||||
if runStageCode != 0 {
|
||||
t.Fatalf("run-stage exit code = %d, want 0; stderr=%q", runStageCode, stderr.String())
|
||||
@@ -209,14 +206,12 @@ previous_session_id: 2026-04-26
|
||||
mustReadEquals(t, filepath.Join(sessionRoot, "previous", "artifacts", "session_recap.md"), "# previous recap\n")
|
||||
|
||||
scriptoriumFake := &scriptorium.FakeRunner{}
|
||||
origExecuteStagesFn := executeStagesFn
|
||||
origObjectStoreFn := newObjectStoreFromConfigFn
|
||||
objectStoreConstructed := false
|
||||
t.Cleanup(func() {
|
||||
executeStagesFn = origExecuteStagesFn
|
||||
newObjectStoreFromConfigFn = origObjectStoreFn
|
||||
})
|
||||
executeStagesFn = func(ctx context.Context, cfg *config.Config, plan BoundedPlan, opts RunOptions) (*RunSummary, error) {
|
||||
executor := func(ctx context.Context, cfg *config.Config, plan BoundedPlan, opts RunOptions) (*RunSummary, error) {
|
||||
if opts.Env == nil {
|
||||
opts.Env = &Env{}
|
||||
}
|
||||
@@ -230,7 +225,7 @@ previous_session_id: 2026-04-26
|
||||
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
runStageCode := Execute(
|
||||
runStageCode := executeWithPipelineExecutor(
|
||||
[]string{
|
||||
"run-stage", "analyze", cfg.Session.SessionID,
|
||||
"--config", pipelinePath,
|
||||
@@ -241,6 +236,7 @@ previous_session_id: 2026-04-26
|
||||
},
|
||||
&stdout,
|
||||
&stderr,
|
||||
executor,
|
||||
)
|
||||
if runStageCode != 0 {
|
||||
t.Fatalf("run-stage exit code = %d, want 0; stderr=%q", runStageCode, stderr.String())
|
||||
|
||||
@@ -12,6 +12,10 @@ import (
|
||||
|
||||
// Run executes the pipeline plan and persists manifest state.
|
||||
func Run(ctx context.Context, args []string, out io.Writer) error {
|
||||
return runWithPipelineExecutor(ctx, args, out, executePlan)
|
||||
}
|
||||
|
||||
func runWithPipelineExecutor(ctx context.Context, args []string, out io.Writer, executor pipelineExecutor) error {
|
||||
request, err := parseBoundedRunRequest("run", args, out)
|
||||
if err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
@@ -37,7 +41,7 @@ func Run(ctx context.Context, args []string, out io.Writer) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("run: %w", err)
|
||||
}
|
||||
summary, err := executeStagesFn(ctx, cfg, request.Plan, RunOptions{
|
||||
summary, err := executor(ctx, cfg, request.Plan, RunOptions{
|
||||
Force: request.Force,
|
||||
SelectedArtifacts: selectedArtifacts,
|
||||
EffectiveArtifacts: effectiveArtifacts,
|
||||
|
||||
@@ -12,6 +12,10 @@ import (
|
||||
|
||||
// RunStage executes exactly one selected stage.
|
||||
func RunStage(ctx context.Context, args []string, out io.Writer) error {
|
||||
return runStageWithPipelineExecutor(ctx, args, out, executePlan)
|
||||
}
|
||||
|
||||
func runStageWithPipelineExecutor(ctx context.Context, args []string, out io.Writer, executor pipelineExecutor) error {
|
||||
var stageName string
|
||||
var positionalSessionID string
|
||||
if len(args) >= 2 && !isCLIFlagToken(args[0]) && !isCLIFlagToken(args[1]) {
|
||||
@@ -74,7 +78,7 @@ func RunStage(ctx context.Context, args []string, out io.Writer) error {
|
||||
PreviousSessionID: flags.previousSessionID,
|
||||
Force: force,
|
||||
SelectedArtifacts: normalizedArtifacts,
|
||||
})
|
||||
}, executor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -93,6 +97,10 @@ func RunStage(ctx context.Context, args []string, out io.Writer) error {
|
||||
|
||||
// Analyze force-runs the analyze stage.
|
||||
func Analyze(ctx context.Context, args []string, out io.Writer) error {
|
||||
return analyzeWithPipelineExecutor(ctx, args, out, executePlan)
|
||||
}
|
||||
|
||||
func analyzeWithPipelineExecutor(ctx context.Context, args []string, out io.Writer, executor pipelineExecutor) error {
|
||||
fs := flag.NewFlagSet("analyze", flag.ContinueOnError)
|
||||
fs.SetOutput(io.Discard)
|
||||
|
||||
@@ -123,7 +131,7 @@ func Analyze(ctx context.Context, args []string, out io.Writer) error {
|
||||
PreviousSessionID: flags.previousSessionID,
|
||||
Force: true,
|
||||
SelectedArtifacts: normalizedArtifacts,
|
||||
})
|
||||
}, executor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -140,6 +148,10 @@ func Analyze(ctx context.Context, args []string, out io.Writer) error {
|
||||
|
||||
// Publish force-runs the publish stage.
|
||||
func Publish(ctx context.Context, args []string, out io.Writer) error {
|
||||
return publishWithPipelineExecutor(ctx, args, out, executePlan)
|
||||
}
|
||||
|
||||
func publishWithPipelineExecutor(ctx context.Context, args []string, out io.Writer, executor pipelineExecutor) error {
|
||||
fs := flag.NewFlagSet("publish", flag.ContinueOnError)
|
||||
fs.SetOutput(io.Discard)
|
||||
|
||||
@@ -170,7 +182,7 @@ func Publish(ctx context.Context, args []string, out io.Writer) error {
|
||||
PreviousSessionID: flags.previousSessionID,
|
||||
Force: true,
|
||||
SelectedArtifacts: normalizedArtifacts,
|
||||
})
|
||||
}, executor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -198,7 +210,7 @@ type singleStageCommand struct {
|
||||
SelectedArtifacts []string
|
||||
}
|
||||
|
||||
func runSingleStageCommand(ctx context.Context, req singleStageCommand) (*RunSummary, error) {
|
||||
func runSingleStageCommand(ctx context.Context, req singleStageCommand, executor pipelineExecutor) (*RunSummary, error) {
|
||||
plan, err := buildSingleStageExecutionPlan(req.StageName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", req.CommandName, err)
|
||||
@@ -224,7 +236,7 @@ func runSingleStageCommand(ctx context.Context, req singleStageCommand) (*RunSum
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", req.CommandName, err)
|
||||
}
|
||||
summary, err := executeStagesFn(ctx, cfg, plan, RunOptions{
|
||||
summary, err := executor(ctx, cfg, plan, RunOptions{
|
||||
Force: req.Force,
|
||||
SelectedArtifacts: selectedArtifacts,
|
||||
EffectiveArtifacts: effectiveArtifacts,
|
||||
|
||||
@@ -34,7 +34,7 @@ type RunSummary struct {
|
||||
Skipped []string
|
||||
}
|
||||
|
||||
var executeStagesFn = executePlan
|
||||
type pipelineExecutor func(context.Context, *config.Config, BoundedPlan, RunOptions) (*RunSummary, error)
|
||||
|
||||
func executePlan(ctx context.Context, cfg *config.Config, plan BoundedPlan, opts RunOptions) (summary *RunSummary, resultErr error) {
|
||||
return executePlanWithDependencies(ctx, cfg, plan, opts, invocationDependencies{})
|
||||
|
||||
@@ -18,9 +18,7 @@ func TestExecuteRunAcceptsPositionalSessionID(t *testing.T) {
|
||||
pipelinePath, campaignPath, sessionPath := writeValidConfigFiles(t, workspaceRoot)
|
||||
|
||||
var capturedSessionID string
|
||||
origExecuteStagesFn := executeStagesFn
|
||||
t.Cleanup(func() { executeStagesFn = origExecuteStagesFn })
|
||||
executeStagesFn = func(_ context.Context, cfg *config.Config, _ BoundedPlan, _ RunOptions) (*RunSummary, error) {
|
||||
executor := func(_ context.Context, cfg *config.Config, _ BoundedPlan, _ RunOptions) (*RunSummary, error) {
|
||||
capturedSessionID = cfg.Session.SessionID
|
||||
return &RunSummary{
|
||||
SessionID: cfg.Session.SessionID,
|
||||
@@ -31,13 +29,13 @@ func TestExecuteRunAcceptsPositionalSessionID(t *testing.T) {
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
code := Execute([]string{
|
||||
code := executeWithPipelineExecutor([]string{
|
||||
"run",
|
||||
"2026-05-03",
|
||||
"--config", pipelinePath,
|
||||
"--campaign-file", campaignPath,
|
||||
"--session", sessionPath,
|
||||
}, &stdout, &stderr)
|
||||
}, &stdout, &stderr, executor)
|
||||
if code != 0 {
|
||||
t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String())
|
||||
}
|
||||
@@ -114,9 +112,7 @@ inputs:
|
||||
audio_s3:
|
||||
prefix: audio/
|
||||
`)
|
||||
origExecuteStagesFn := executeStagesFn
|
||||
t.Cleanup(func() { executeStagesFn = origExecuteStagesFn })
|
||||
executeStagesFn = func(_ context.Context, cfg *config.Config, _ BoundedPlan, _ RunOptions) (*RunSummary, error) {
|
||||
executor := func(_ context.Context, cfg *config.Config, _ BoundedPlan, _ RunOptions) (*RunSummary, error) {
|
||||
return &RunSummary{
|
||||
SessionID: cfg.Session.SessionID,
|
||||
ManifestPath: filepath.Join(workspaceRoot, "manifest.json"),
|
||||
@@ -126,12 +122,12 @@ inputs:
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
code := Execute([]string{
|
||||
code := executeWithPipelineExecutor([]string{
|
||||
"run",
|
||||
"2026-06-07",
|
||||
"--config", pipelinePath,
|
||||
"--campaign-file", campaignPath,
|
||||
}, &stdout, &stderr)
|
||||
}, &stdout, &stderr, executor)
|
||||
if code != 0 {
|
||||
t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String())
|
||||
}
|
||||
@@ -191,9 +187,7 @@ func TestExecuteWorkflowCommandsAcceptPositionalSessionID(t *testing.T) {
|
||||
var capturedStages []string
|
||||
var capturedForce bool
|
||||
var capturedArtifacts []string
|
||||
origExecuteStagesFn := executeStagesFn
|
||||
t.Cleanup(func() { executeStagesFn = origExecuteStagesFn })
|
||||
executeStagesFn = func(_ context.Context, _ *config.Config, plan BoundedPlan, opts RunOptions) (*RunSummary, error) {
|
||||
executor := func(_ context.Context, _ *config.Config, plan BoundedPlan, opts RunOptions) (*RunSummary, error) {
|
||||
for _, s := range plan.Stages() {
|
||||
capturedStages = append(capturedStages, s.Name())
|
||||
}
|
||||
@@ -208,7 +202,7 @@ func TestExecuteWorkflowCommandsAcceptPositionalSessionID(t *testing.T) {
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
code := Execute(tt.args, &stdout, &stderr)
|
||||
code := executeWithPipelineExecutor(tt.args, &stdout, &stderr, executor)
|
||||
if code != 0 {
|
||||
t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user