Skip to content

Commit 2952521

Browse files
committed
test: refactor phased dry run program contract
1 parent fd01f3b commit 2952521

1 file changed

Lines changed: 187 additions & 116 deletions

File tree

cli/cmd/ao/rpi_phased_program_test.go

Lines changed: 187 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -187,128 +187,199 @@ func TestWriteExecutionPacketSeed_UsesProgramContract(t *testing.T) {
187187
}
188188

189189
func TestRunPhasedEngine_DryRunUsesResolvedProgramContract(t *testing.T) {
190-
cases := []struct {
191-
name string
192-
setupFiles func(t *testing.T, dir string)
193-
wantPath string
194-
wantContains string
195-
}{
190+
for _, tc := range dryRunProgramContractCases() {
191+
t.Run(tc.name, func(t *testing.T) {
192+
assertDryRunUsesResolvedProgramContract(t, tc)
193+
})
194+
}
195+
}
196+
197+
type dryRunProgramContractCase struct {
198+
name string
199+
setupFiles func(t *testing.T, dir string)
200+
wantPath string
201+
wantValidationCommand string
202+
}
203+
204+
type dryRunProgramContractPacket struct {
205+
ContractSurfaces []string `json:"contract_surfaces"`
206+
AutodevProgram struct {
207+
Path string `json:"path"`
208+
ValidationCommands []string `json:"validation_commands"`
209+
} `json:"autodev_program"`
210+
}
211+
212+
func dryRunProgramContractCases() []dryRunProgramContractCase {
213+
return []dryRunProgramContractCase{
196214
{
197-
name: "PROGRAM preferred when both exist",
198-
setupFiles: func(t *testing.T, dir string) {
199-
t.Helper()
200-
programText := strings.Replace(rpiProgramMarkdown, "cd cli && go test ./cmd/ao/... ./internal/autodev/...", "echo program-preferred", 1)
201-
autodevText := strings.Replace(rpiProgramMarkdown, "cd cli && go test ./cmd/ao/... ./internal/autodev/...", "echo autodev-fallback", 1)
202-
if err := os.WriteFile(filepath.Join(dir, "PROGRAM.md"), []byte(programText), 0o644); err != nil {
203-
t.Fatal(err)
204-
}
205-
if err := os.WriteFile(filepath.Join(dir, "AUTODEV.md"), []byte(autodevText), 0o644); err != nil {
206-
t.Fatal(err)
207-
}
208-
},
209-
wantPath: "PROGRAM.md",
210-
wantContains: "echo program-preferred",
215+
name: "PROGRAM preferred when both exist",
216+
setupFiles: setupProgramPreferredContractFiles,
217+
wantPath: "PROGRAM.md",
218+
wantValidationCommand: "echo program-preferred",
211219
},
212220
{
213-
name: "AUTODEV fallback when PROGRAM missing",
214-
setupFiles: func(t *testing.T, dir string) {
215-
t.Helper()
216-
autodevText := strings.Replace(rpiProgramMarkdown, "cd cli && go test ./cmd/ao/... ./internal/autodev/...", "echo autodev-fallback", 1)
217-
if err := os.WriteFile(filepath.Join(dir, "AUTODEV.md"), []byte(autodevText), 0o644); err != nil {
218-
t.Fatal(err)
219-
}
220-
},
221-
wantPath: "AUTODEV.md",
222-
wantContains: "echo autodev-fallback",
221+
name: "AUTODEV fallback when PROGRAM missing",
222+
setupFiles: setupAutodevFallbackContractFiles,
223+
wantPath: "AUTODEV.md",
224+
wantValidationCommand: "echo autodev-fallback",
223225
},
224226
}
227+
}
225228

226-
for _, tc := range cases {
227-
t.Run(tc.name, func(t *testing.T) {
228-
tmpDir := t.TempDir()
229-
if err := os.MkdirAll(filepath.Join(tmpDir, "docs", "contracts"), 0o755); err != nil {
230-
t.Fatal(err)
231-
}
232-
if err := os.WriteFile(filepath.Join(tmpDir, "docs", "contracts", "repo-execution-profile.md"), []byte("# Repo Execution Profile\n"), 0o644); err != nil {
233-
t.Fatal(err)
234-
}
235-
tc.setupFiles(t, tmpDir)
236-
237-
prevDryRun := dryRun
238-
dryRun = true
239-
t.Cleanup(func() { dryRun = prevDryRun })
240-
241-
opts := defaultPhasedEngineOptions()
242-
opts.NoWorktree = true
243-
opts.SwarmFirst = false
244-
245-
if err := runPhasedEngine(context.Background(), tmpDir, "drive bounded autodev experiments", opts); err != nil {
246-
t.Fatalf("runPhasedEngine() error = %v", err)
247-
}
248-
249-
statePath := filepath.Join(tmpDir, ".agents", "rpi", phasedStateFile)
250-
stateData, err := os.ReadFile(statePath)
251-
if err != nil {
252-
t.Fatalf("read phased state: %v", err)
253-
}
254-
var state phasedState
255-
if err := json.Unmarshal(stateData, &state); err != nil {
256-
t.Fatalf("unmarshal phased state: %v", err)
257-
}
258-
if state.ProgramPath != tc.wantPath {
259-
t.Fatalf("ProgramPath = %q, want %q", state.ProgramPath, tc.wantPath)
260-
}
261-
262-
packetPath := filepath.Join(tmpDir, ".agents", "rpi", "execution-packet.json")
263-
packetData, err := os.ReadFile(packetPath)
264-
if err != nil {
265-
t.Fatalf("read execution packet: %v", err)
266-
}
267-
archivedPacketData, err := os.ReadFile(filepath.Join(tmpDir, ".agents", "rpi", "runs", state.RunID, executionPacketFile))
268-
if err != nil {
269-
t.Fatalf("read archived execution packet: %v", err)
270-
}
271-
if string(archivedPacketData) != string(packetData) {
272-
t.Fatalf("archived execution packet does not match latest alias:\nlatest:\n%s\narchived:\n%s", packetData, archivedPacketData)
273-
}
274-
var packet struct {
275-
ContractSurfaces []string `json:"contract_surfaces"`
276-
AutodevProgram struct {
277-
Path string `json:"path"`
278-
ValidationCommands []string `json:"validation_commands"`
279-
} `json:"autodev_program"`
280-
}
281-
if err := json.Unmarshal(packetData, &packet); err != nil {
282-
t.Fatalf("unmarshal execution packet: %v", err)
283-
}
284-
if packet.AutodevProgram.Path != tc.wantPath {
285-
t.Fatalf("packet autodev_program.path = %q, want %q", packet.AutodevProgram.Path, tc.wantPath)
286-
}
287-
if len(packet.AutodevProgram.ValidationCommands) == 0 {
288-
t.Fatalf("packet validation_commands empty: %+v", packet.AutodevProgram)
289-
}
290-
if packet.AutodevProgram.ValidationCommands[0] != tc.wantContains {
291-
t.Fatalf("packet validation command = %q, want %q", packet.AutodevProgram.ValidationCommands[0], tc.wantContains)
292-
}
293-
if !containsProgramContract(packet.ContractSurfaces, "docs/contracts/repo-execution-profile.md") {
294-
t.Fatalf("contract_surfaces = %#v, want repo execution profile", packet.ContractSurfaces)
295-
}
296-
if !containsProgramContract(packet.ContractSurfaces, tc.wantPath) {
297-
t.Fatalf("contract_surfaces = %#v, want %s", packet.ContractSurfaces, tc.wantPath)
298-
}
299-
300-
logPath := filepath.Join(tmpDir, ".agents", "rpi", "phased-orchestration.log")
301-
logData, err := os.ReadFile(logPath)
302-
if err != nil {
303-
t.Fatalf("read orchestration log: %v", err)
304-
}
305-
logText := string(logData)
306-
for _, phaseName := range []string{"discovery", "implementation", "validation"} {
307-
if !strings.Contains(logText, phaseName) {
308-
t.Fatalf("expected orchestration log to mention %s, got: %s", phaseName, logText)
309-
}
310-
}
311-
})
229+
func assertDryRunUsesResolvedProgramContract(t *testing.T, tc dryRunProgramContractCase) {
230+
t.Helper()
231+
232+
tmpDir := setupDryRunProgramContractWorkspace(t, tc)
233+
state := runDryRunPhasedEngine(t, tmpDir)
234+
assertDryRunProgramPath(t, state, tc.wantPath)
235+
236+
packetData := readDryRunExecutionPacket(t, tmpDir, state.RunID)
237+
packet := decodeDryRunProgramContractPacket(t, packetData)
238+
assertDryRunProgramContractPacket(t, packet, tc)
239+
assertDryRunOrchestrationLog(t, tmpDir)
240+
}
241+
242+
func setupDryRunProgramContractWorkspace(t *testing.T, tc dryRunProgramContractCase) string {
243+
t.Helper()
244+
245+
tmpDir := t.TempDir()
246+
if err := os.MkdirAll(filepath.Join(tmpDir, "docs", "contracts"), 0o755); err != nil {
247+
t.Fatal(err)
248+
}
249+
if err := os.WriteFile(filepath.Join(tmpDir, "docs", "contracts", "repo-execution-profile.md"), []byte("# Repo Execution Profile\n"), 0o644); err != nil {
250+
t.Fatal(err)
251+
}
252+
tc.setupFiles(t, tmpDir)
253+
return tmpDir
254+
}
255+
256+
func setupProgramPreferredContractFiles(t *testing.T, dir string) {
257+
t.Helper()
258+
259+
writeProgramContractVariant(t, dir, "PROGRAM.md", "echo program-preferred")
260+
writeProgramContractVariant(t, dir, "AUTODEV.md", "echo autodev-fallback")
261+
}
262+
263+
func setupAutodevFallbackContractFiles(t *testing.T, dir string) {
264+
t.Helper()
265+
266+
writeProgramContractVariant(t, dir, "AUTODEV.md", "echo autodev-fallback")
267+
}
268+
269+
func writeProgramContractVariant(t *testing.T, dir, name, validationCommand string) {
270+
t.Helper()
271+
272+
text := strings.Replace(rpiProgramMarkdown, "cd cli && go test ./cmd/ao/... ./internal/autodev/...", validationCommand, 1)
273+
if err := os.WriteFile(filepath.Join(dir, name), []byte(text), 0o644); err != nil {
274+
t.Fatal(err)
275+
}
276+
}
277+
278+
func runDryRunPhasedEngine(t *testing.T, tmpDir string) phasedState {
279+
t.Helper()
280+
281+
prevDryRun := dryRun
282+
dryRun = true
283+
t.Cleanup(func() { dryRun = prevDryRun })
284+
285+
opts := defaultPhasedEngineOptions()
286+
opts.NoWorktree = true
287+
opts.SwarmFirst = false
288+
289+
if err := runPhasedEngine(context.Background(), tmpDir, "drive bounded autodev experiments", opts); err != nil {
290+
t.Fatalf("runPhasedEngine() error = %v", err)
291+
}
292+
return readDryRunPhasedState(t, tmpDir)
293+
}
294+
295+
func readDryRunPhasedState(t *testing.T, tmpDir string) phasedState {
296+
t.Helper()
297+
298+
statePath := filepath.Join(tmpDir, ".agents", "rpi", phasedStateFile)
299+
stateData, err := os.ReadFile(statePath)
300+
if err != nil {
301+
t.Fatalf("read phased state: %v", err)
302+
}
303+
var state phasedState
304+
if err := json.Unmarshal(stateData, &state); err != nil {
305+
t.Fatalf("unmarshal phased state: %v", err)
306+
}
307+
return state
308+
}
309+
310+
func assertDryRunProgramPath(t *testing.T, state phasedState, wantPath string) {
311+
t.Helper()
312+
313+
if state.ProgramPath != wantPath {
314+
t.Fatalf("ProgramPath = %q, want %q", state.ProgramPath, wantPath)
315+
}
316+
}
317+
318+
func readDryRunExecutionPacket(t *testing.T, tmpDir, runID string) []byte {
319+
t.Helper()
320+
321+
packetPath := filepath.Join(tmpDir, ".agents", "rpi", "execution-packet.json")
322+
packetData, err := os.ReadFile(packetPath)
323+
if err != nil {
324+
t.Fatalf("read execution packet: %v", err)
325+
}
326+
archivedPacketData, err := os.ReadFile(filepath.Join(tmpDir, ".agents", "rpi", "runs", runID, executionPacketFile))
327+
if err != nil {
328+
t.Fatalf("read archived execution packet: %v", err)
329+
}
330+
if string(archivedPacketData) != string(packetData) {
331+
t.Fatalf("archived execution packet does not match latest alias:\nlatest:\n%s\narchived:\n%s", packetData, archivedPacketData)
332+
}
333+
return packetData
334+
}
335+
336+
func decodeDryRunProgramContractPacket(t *testing.T, packetData []byte) dryRunProgramContractPacket {
337+
t.Helper()
338+
339+
var packet dryRunProgramContractPacket
340+
if err := json.Unmarshal(packetData, &packet); err != nil {
341+
t.Fatalf("unmarshal execution packet: %v", err)
342+
}
343+
return packet
344+
}
345+
346+
func assertDryRunProgramContractPacket(
347+
t *testing.T,
348+
packet dryRunProgramContractPacket,
349+
tc dryRunProgramContractCase,
350+
) {
351+
t.Helper()
352+
353+
if packet.AutodevProgram.Path != tc.wantPath {
354+
t.Fatalf("packet autodev_program.path = %q, want %q", packet.AutodevProgram.Path, tc.wantPath)
355+
}
356+
if len(packet.AutodevProgram.ValidationCommands) == 0 {
357+
t.Fatalf("packet validation_commands empty: %+v", packet.AutodevProgram)
358+
}
359+
if packet.AutodevProgram.ValidationCommands[0] != tc.wantValidationCommand {
360+
t.Fatalf("packet validation command = %q, want %q", packet.AutodevProgram.ValidationCommands[0], tc.wantValidationCommand)
361+
}
362+
if !containsProgramContract(packet.ContractSurfaces, "docs/contracts/repo-execution-profile.md") {
363+
t.Fatalf("contract_surfaces = %#v, want repo execution profile", packet.ContractSurfaces)
364+
}
365+
if !containsProgramContract(packet.ContractSurfaces, tc.wantPath) {
366+
t.Fatalf("contract_surfaces = %#v, want %s", packet.ContractSurfaces, tc.wantPath)
367+
}
368+
}
369+
370+
func assertDryRunOrchestrationLog(t *testing.T, tmpDir string) {
371+
t.Helper()
372+
373+
logPath := filepath.Join(tmpDir, ".agents", "rpi", "phased-orchestration.log")
374+
logData, err := os.ReadFile(logPath)
375+
if err != nil {
376+
t.Fatalf("read orchestration log: %v", err)
377+
}
378+
logText := string(logData)
379+
for _, phaseName := range []string{"discovery", "implementation", "validation"} {
380+
if !strings.Contains(logText, phaseName) {
381+
t.Fatalf("expected orchestration log to mention %s, got: %s", phaseName, logText)
382+
}
312383
}
313384
}
314385

0 commit comments

Comments
 (0)