@@ -187,128 +187,199 @@ func TestWriteExecutionPacketSeed_UsesProgramContract(t *testing.T) {
187187}
188188
189189func 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:\n latest:\n %s\n archived:\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:\n latest:\n %s\n archived:\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