Reuse valid workspace checkpoints on request
This commit is contained in:
@@ -2185,16 +2185,20 @@ func TestRunPipelineWritesCheckpointsWhenWorkspaceResumeEnabled(t *testing.T) {
|
||||
outputDir := t.TempDir()
|
||||
configPath := writeTestConfig(t, mvpConfigYAMLWithWorkspaceResumeEnabled("dnd-session", workspaceDir, "always"))
|
||||
inputPath := writeSeriatimInput(t)
|
||||
client := newFakeRunLLMClient(false)
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
code := RunWithOptions([]string{"run", "dnd-session", "--config", configPath, "--input", inputPath, "--output-dir", outputDir}, &stdout, &stderr, Options{
|
||||
LLMClientFactory: fakeLLMFactory(newFakeRunLLMClient(false), nil),
|
||||
LLMClientFactory: fakeLLMFactory(client, nil),
|
||||
})
|
||||
|
||||
if code != 0 {
|
||||
t.Fatalf("RunWithOptions() code = %d, stderr=%q", code, stderr.String())
|
||||
}
|
||||
if client.calls != 1 {
|
||||
t.Fatalf("LLM calls = %d, want ordinary run to execute despite checkpoint writing", client.calls)
|
||||
}
|
||||
checkpointDir := onlyCheckpointIdentityDir(t, workspaceDir)
|
||||
for _, name := range []string{
|
||||
"source/manifest.json",
|
||||
@@ -2215,6 +2219,133 @@ func TestRunPipelineWritesCheckpointsWhenWorkspaceResumeEnabled(t *testing.T) {
|
||||
assertPathNotExist(t, filepath.Join(workspaceDir, "debug"))
|
||||
}
|
||||
|
||||
func TestRunPipelineResumeRequiresWorkspaceResumeEnabled(t *testing.T) {
|
||||
workspaceDir := filepath.Join(t.TempDir(), "workspace")
|
||||
outputDir := t.TempDir()
|
||||
configPath := writeTestConfig(t, mvpConfigYAMLWithWorkspaceDiagnostics("dnd-session", workspaceDir, "always"))
|
||||
inputPath := writeSeriatimInput(t)
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
code := RunWithOptions([]string{"run", "dnd-session", "--config", configPath, "--input", inputPath, "--output-dir", outputDir, "--resume"}, &stdout, &stderr, Options{
|
||||
LLMClientFactory: fakeLLMFactory(newFakeRunLLMClient(false), nil),
|
||||
})
|
||||
|
||||
if code != 1 {
|
||||
t.Fatalf("RunWithOptions() code = %d, want 1", code)
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "--resume requires workspace.resume.enabled: true") {
|
||||
t.Fatalf("stderr = %q, want resume configuration error", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunPipelineResumeReusesWorkspaceCheckpoints(t *testing.T) {
|
||||
workspaceDir := filepath.Join(t.TempDir(), "workspace")
|
||||
outputDir := t.TempDir()
|
||||
configPath := writeTestConfig(t, mvpConfigYAMLWithWorkspaceResumeEnabled("dnd-session", workspaceDir, "always"))
|
||||
inputPath := writeSeriatimInput(t)
|
||||
firstClient := newFakeRunLLMClient(false)
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
code := RunWithOptions([]string{"run", "dnd-session", "--config", configPath, "--input", inputPath, "--output-dir", outputDir}, &stdout, &stderr, Options{
|
||||
LLMClientFactory: fakeLLMFactory(firstClient, nil),
|
||||
})
|
||||
if code != 0 {
|
||||
t.Fatalf("first RunWithOptions() code = %d, stderr=%q", code, stderr.String())
|
||||
}
|
||||
if firstClient.calls != 1 {
|
||||
t.Fatalf("first LLM calls = %d, want checkpoint seed run to execute", firstClient.calls)
|
||||
}
|
||||
|
||||
secondClient := newFakeRunLLMClient(false)
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
code = RunWithOptions([]string{"run", "dnd-session", "--config", configPath, "--input", inputPath, "--output-dir", outputDir, "--resume"}, &stdout, &stderr, Options{
|
||||
LLMClientFactory: fakeLLMFactory(secondClient, nil),
|
||||
})
|
||||
if code != 0 {
|
||||
t.Fatalf("resume RunWithOptions() code = %d, stderr=%q", code, stderr.String())
|
||||
}
|
||||
if secondClient.calls != 0 {
|
||||
t.Fatalf("resume LLM calls = %d, want checkpoint reuse", secondClient.calls)
|
||||
}
|
||||
runDirs := childDirs(t, filepath.Join(workspaceDir, "diagnostics"))
|
||||
if len(runDirs) != 2 {
|
||||
t.Fatalf("diagnostics run dirs = %v, want fresh diagnostics for each invocation", runDirs)
|
||||
}
|
||||
if !anyDiagnosticsFileContains(t, runDirs, diagnostics.ArtifactCheckpointEvents, `"action": "reused"`) {
|
||||
t.Fatalf("checkpoint event diagnostics under %v did not record reuse", runDirs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunPipelineResumeInvalidatesWhenInvocationIdentityChanges(t *testing.T) {
|
||||
t.Run("input", func(t *testing.T) {
|
||||
workspaceDir := filepath.Join(t.TempDir(), "workspace")
|
||||
configPath := writeTestConfig(t, mvpConfigYAMLWithWorkspaceResumeEnabled("dnd-session", workspaceDir, "always"))
|
||||
seedInput := writeSeriatimInput(t)
|
||||
changedInput := writeFile(t, "source-changed.json", `{
|
||||
"metadata": {"id": "session-alpha"},
|
||||
"segments": [{"id": 1, "start": 0, "end": 1, "speaker": "Aria", "text": "Aria casts Shield."}]
|
||||
}`)
|
||||
seedWorkspaceCheckpoint(t, configPath, seedInput, nil)
|
||||
client := runResumeWithClient(t, configPath, changedInput, nil)
|
||||
if client.calls == 0 {
|
||||
t.Fatal("resume LLM calls = 0, want execution after input identity change")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pipeline digest", func(t *testing.T) {
|
||||
workspaceDir := filepath.Join(t.TempDir(), "workspace")
|
||||
seedConfig := writeTestConfig(t, mvpConfigYAMLWithWorkspaceResumeEnabled("dnd-session", workspaceDir, "always"))
|
||||
changedConfig := writeTestConfig(t, mvpConfigYAMLWithWorkspaceResumeAndChunkOptions("dnd-session", workspaceDir, "always"))
|
||||
inputPath := writeSeriatimInput(t)
|
||||
seedWorkspaceCheckpoint(t, seedConfig, inputPath, nil)
|
||||
client := runResumeWithClient(t, changedConfig, inputPath, nil)
|
||||
if client.calls == 0 {
|
||||
t.Fatal("resume LLM calls = 0, want execution after pipeline digest change")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("selected lanes", func(t *testing.T) {
|
||||
workspaceDir := filepath.Join(t.TempDir(), "workspace")
|
||||
configPath := writeTestConfig(t, mvpConfigYAMLWithWorkspaceResumeLanes("dnd-session", workspaceDir, "always", "spells", "items"))
|
||||
inputPath := writeSeriatimInput(t)
|
||||
seedWorkspaceCheckpoint(t, configPath, inputPath, nil)
|
||||
client := runResumeWithClient(t, configPath, inputPath, []string{"--only", "spells"})
|
||||
if client.calls == 0 {
|
||||
t.Fatal("resume LLM calls = 0, want execution after selected lane change")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("references", func(t *testing.T) {
|
||||
workspaceDir := filepath.Join(t.TempDir(), "workspace")
|
||||
configPath := writeTestConfig(t, mvpConfigYAMLWithWorkspaceResumeEnabled("dnd-session", workspaceDir, "always"))
|
||||
inputPath := writeSeriatimInput(t)
|
||||
referencePath := writeFile(t, "players.md", "Aria is a cleric.\n")
|
||||
seedWorkspaceCheckpoint(t, configPath, inputPath, []string{"--reference", "players=" + referencePath})
|
||||
if err := os.WriteFile(referencePath, []byte("Aria is a wizard.\n"), 0o644); err != nil {
|
||||
t.Fatalf("update reference: %v", err)
|
||||
}
|
||||
client := runResumeWithClient(t, configPath, inputPath, []string{"--reference", "players=" + referencePath})
|
||||
if client.calls == 0 {
|
||||
t.Fatal("resume LLM calls = 0, want execution after reference digest change")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("llm profile override", func(t *testing.T) {
|
||||
workspaceDir := filepath.Join(t.TempDir(), "workspace")
|
||||
profilePath := writeScriptoriumProfileFile(t, "runtime", "http://profile.test/v1", "test-model")
|
||||
configPath := writeTestConfig(t, mvpConfigYAMLWithWorkspaceResumeAndProfileFile("dnd-session", workspaceDir, "always", profilePath))
|
||||
inputPath := writeSeriatimInput(t)
|
||||
seedWorkspaceCheckpoint(t, configPath, inputPath, nil)
|
||||
client := runResumeWithClient(t, configPath, inputPath, []string{"--llm-profile", "runtime"})
|
||||
if client.calls == 0 {
|
||||
t.Fatal("resume LLM calls = 0, want execution after LLM profile override change")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunPipelineSkipsDiagnosticsWhenWorkspaceDiagnosticsDisabled(t *testing.T) {
|
||||
workspaceDir := filepath.Join(t.TempDir(), "workspace")
|
||||
outputDir := t.TempDir()
|
||||
@@ -2923,6 +3054,88 @@ pipelines:
|
||||
`
|
||||
}
|
||||
|
||||
func mvpConfigYAMLWithWorkspaceResumeAndChunker(pipelineID, workspaceDir, retention, chunker string) string {
|
||||
return `version: 2
|
||||
workspace:
|
||||
directory: ` + workspaceDir + `
|
||||
diagnostics:
|
||||
enabled: true
|
||||
retention: ` + retention + `
|
||||
resume:
|
||||
enabled: true
|
||||
pipelines:
|
||||
` + pipelineID + `:
|
||||
input: seriatim
|
||||
chunk: ` + chunker + `
|
||||
artifacts:
|
||||
spells:
|
||||
extract: dnd/spells
|
||||
`
|
||||
}
|
||||
|
||||
func mvpConfigYAMLWithWorkspaceResumeAndChunkOptions(pipelineID, workspaceDir, retention string) string {
|
||||
return `version: 2
|
||||
workspace:
|
||||
directory: ` + workspaceDir + `
|
||||
diagnostics:
|
||||
enabled: true
|
||||
retention: ` + retention + `
|
||||
resume:
|
||||
enabled: true
|
||||
pipelines:
|
||||
` + pipelineID + `:
|
||||
input: seriatim
|
||||
chunk:
|
||||
module: generic
|
||||
options:
|
||||
max_units: 10
|
||||
artifacts:
|
||||
spells:
|
||||
extract: dnd/spells
|
||||
`
|
||||
}
|
||||
|
||||
func mvpConfigYAMLWithWorkspaceResumeAndProfileFile(pipelineID, workspaceDir, retention, profileFile string) string {
|
||||
return `version: 2
|
||||
scriptorium:
|
||||
profile_file: ` + profileFile + `
|
||||
workspace:
|
||||
directory: ` + workspaceDir + `
|
||||
diagnostics:
|
||||
enabled: true
|
||||
retention: ` + retention + `
|
||||
resume:
|
||||
enabled: true
|
||||
pipelines:
|
||||
` + pipelineID + `:
|
||||
input: seriatim
|
||||
artifacts:
|
||||
spells:
|
||||
extract: dnd/spells
|
||||
`
|
||||
}
|
||||
|
||||
func mvpConfigYAMLWithWorkspaceResumeLanes(pipelineID, workspaceDir, retention string, laneIDs ...string) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("version: 2\n")
|
||||
b.WriteString("workspace:\n")
|
||||
b.WriteString(" directory: " + workspaceDir + "\n")
|
||||
b.WriteString(" diagnostics:\n")
|
||||
b.WriteString(" enabled: true\n")
|
||||
b.WriteString(" retention: " + retention + "\n")
|
||||
b.WriteString(" resume:\n")
|
||||
b.WriteString(" enabled: true\n")
|
||||
b.WriteString("pipelines:\n")
|
||||
b.WriteString(" " + pipelineID + ":\n")
|
||||
b.WriteString(" input: seriatim\n")
|
||||
b.WriteString(" artifacts:\n")
|
||||
for _, laneID := range laneIDs {
|
||||
b.WriteString(" " + laneID + ":\n")
|
||||
b.WriteString(" extract: dnd/spells\n")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func mvpConfigYAMLWithWorkspaceDiagnosticsDisabled(pipelineID, workspaceDir string) string {
|
||||
return `version: 2
|
||||
workspace:
|
||||
@@ -3293,6 +3506,57 @@ func onlyCheckpointIdentityDir(t *testing.T, workspaceDir string) string {
|
||||
return onlyChildDir(t, inputDir)
|
||||
}
|
||||
|
||||
func anyDiagnosticsFileContains(t *testing.T, runDirs []string, name string, want string) bool {
|
||||
t.Helper()
|
||||
for _, runDir := range runDirs {
|
||||
data, err := os.ReadFile(filepath.Join(runDir, name))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
t.Fatalf("read diagnostics artifact %q under %q: %v", name, runDir, err)
|
||||
}
|
||||
if strings.Contains(string(data), want) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func seedWorkspaceCheckpoint(t *testing.T, configPath string, inputPath string, extraArgs []string) {
|
||||
t.Helper()
|
||||
client := newFakeRunLLMClient(false)
|
||||
args := []string{"run", "dnd-session", "--config", configPath, "--input", inputPath, "--output-dir", t.TempDir()}
|
||||
args = append(args, extraArgs...)
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
code := RunWithOptions(args, &stdout, &stderr, Options{
|
||||
LLMClientFactory: fakeLLMFactory(client, nil),
|
||||
})
|
||||
if code != 0 {
|
||||
t.Fatalf("seed RunWithOptions() code = %d, stderr=%q", code, stderr.String())
|
||||
}
|
||||
if client.calls == 0 {
|
||||
t.Fatal("seed LLM calls = 0, want checkpoint seed run to execute")
|
||||
}
|
||||
}
|
||||
|
||||
func runResumeWithClient(t *testing.T, configPath string, inputPath string, extraArgs []string) *fakeRunLLMClient {
|
||||
t.Helper()
|
||||
client := newFakeRunLLMClient(false)
|
||||
args := []string{"run", "dnd-session", "--config", configPath, "--input", inputPath, "--output-dir", t.TempDir(), "--resume"}
|
||||
args = append(args, extraArgs...)
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
code := RunWithOptions(args, &stdout, &stderr, Options{
|
||||
LLMClientFactory: fakeLLMFactory(client, nil),
|
||||
})
|
||||
if code != 0 {
|
||||
t.Fatalf("resume RunWithOptions() code = %d, stderr=%q", code, stderr.String())
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func childDirs(t *testing.T, root string) []string {
|
||||
t.Helper()
|
||||
entries, err := os.ReadDir(root)
|
||||
|
||||
Reference in New Issue
Block a user