| 1 | import copy
|
| 2 | from pathlib import Path
|
| 3 | import sys
|
| 4 | import unittest
|
| 5 | sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
|
| 6 | from oakbench.public_output import workflow_evidence_failures, workflow_qualification_failures
|
| 7 | from oakbench.populations import comparison_identity, comparison_conflicts
|
| 8 | from oakbench.workflow_oracles import VERSION
|
| 9 |
|
| 10 |
|
| 11 | def rows():
|
| 12 | return [dict(subject=subject, scenario='bugfix_test_loop', operation='workflow.total', run=run,
|
| 13 | bench_id='study', returncode=0, workflow_integrity_passed=True, oracle_version=VERSION,
|
| 14 | workflow_integrity_evidence_class='independent_committed_payload', workflow_integrity_source='git_committed_payload',
|
| 15 | git_head_instrument_sha256='a'*64, workflow_subject_binary_sha256='b'*64,
|
| 16 | env_isolation_version='3', randomized_subject_order=True, subject_order=['git','oak'], benchmark_track='agent-default')
|
| 17 | for subject in ('git','oak') for run in range(30)]
|
| 18 |
|
| 19 |
|
| 20 |
|
| 21 | # These tests assert the DEFAULT evidence path (subject Oak export, PATH Git).
|
| 22 | # Isolate them from an operator-configured independent reader/instrument so
|
| 23 | # the full suite is green with and without the optional reader (QA P1 A5).
|
| 24 | _ISOLATED_ENV = ("OAK_BENCH_OAK_READER_PYTHON", "OAK_BENCH_GIT_INSTRUMENT")
|
| 25 | _SAVED_ENV = {}
|
| 26 |
|
| 27 |
|
| 28 | def setUpModule():
|
| 29 | import os as _os
|
| 30 | for _name in _ISOLATED_ENV:
|
| 31 | if _name in _os.environ:
|
| 32 | _SAVED_ENV[_name] = _os.environ.pop(_name)
|
| 33 |
|
| 34 |
|
| 35 | def tearDownModule():
|
| 36 | import os as _os
|
| 37 | _os.environ.update(_SAVED_ENV)
|
| 38 |
|
| 39 | class WorkflowPublicationQualificationTest(unittest.TestCase):
|
| 40 | def test_qualified_observations_are_allowed(self):
|
| 41 | self.assertEqual(workflow_qualification_failures(rows()), [])
|
| 42 | self.assertEqual(workflow_evidence_failures(rows()), [])
|
| 43 |
|
| 44 | def test_thin_duplicate_missing_and_mixed_environment_block(self):
|
| 45 | for data in (rows()[:1], rows()+[rows()[0]], [{**row,'env_isolation_version': None} for row in rows()],
|
| 46 | [{**row,'randomized_subject_order':False} for row in rows()],
|
| 47 | [rows()[0], {**rows()[1], 'env_isolation_version':'2'}]):
|
| 48 | self.assertTrue(workflow_qualification_failures(data))
|
| 49 |
|
| 50 | def test_different_conditions_cannot_disappear_into_singletons(self):
|
| 51 | for changes in ({'benchmark_track':'core-equivalent'}, {'instruction_level':'zero-shot'},
|
| 52 | {'agent':{'name':'x','model':'different'}},
|
| 53 | {'workflow_integrity_evidence_class':'subject_produced_committed_payload'}):
|
| 54 | data=rows()
|
| 55 | for row in data:
|
| 56 | if row['subject']=='oak': row.update(changes)
|
| 57 | self.assertTrue(workflow_evidence_failures(data),changes)
|
| 58 |
|
| 59 | def test_comparison_identity_includes_correctness_class(self):
|
| 60 | left=rows()[0]
|
| 61 | right={**left,'workflow_integrity_evidence_class':'subject_produced_committed_payload'}
|
| 62 | self.assertIn('workflow_integrity_evidence_class',comparison_conflicts(comparison_identity(left),comparison_identity(right)))
|
| 63 |
|
| 64 | def test_failed_agent_trials_keep_failure_denominator(self):
|
| 65 | data=rows()
|
| 66 | for row in data:
|
| 67 | row.update(operation='agent.workflow', agent_launched=True,outcome_attribution='agent',success=True,outcome='pass')
|
| 68 | data[-1].update(workflow_integrity_passed=False,workflow_integrity_evidence_class='worktree_payload',
|
| 69 | success=False,outcome='fail',returncode=1)
|
| 70 | self.assertEqual(workflow_evidence_failures(data),[])
|
| 71 | self.assertEqual(workflow_qualification_failures(data),[])
|
| 72 | data[-1]['outcome_attribution']='harness'
|
| 73 | self.assertTrue(workflow_evidence_failures(data))
|
| 74 |
|
| 75 | class RealMixedOutcomePublicationTest(unittest.TestCase):
|
| 76 | def test_real_producers_preserve_failed_arm_in_publication_and_pair(self):
|
| 77 | import contextlib
|
| 78 | import io
|
| 79 | import json
|
| 80 | import os
|
| 81 | import shutil
|
| 82 | import subprocess
|
| 83 | import tempfile
|
| 84 | from unittest import mock
|
| 85 | import agent_workflow as aw
|
| 86 | import workflow_ab
|
| 87 | import publish_gate
|
| 88 | from oakbench.campaign_report import report_campaign
|
| 89 | if not os.environ.get('OAK_BENCH_OAK_READER_PYTHON'):
|
| 90 | self.skipTest('configure independent reader Python to exercise real decoder')
|
| 91 | with tempfile.TemporaryDirectory() as tmp:
|
| 92 | root=Path(tmp); fixture=root/'fixture'
|
| 93 | workflow_ab.make_bugfix_fixture(fixture)
|
| 94 | agent=aw.AgentConfig('mock','deterministic_mock','local',None,None,'1')
|
| 95 | original=aw.run_mock_agent
|
| 96 | def wrong_agent(subject, workflow, repo, *args, **kwargs):
|
| 97 | result=original(subject, workflow, repo, *args, **kwargs)
|
| 98 | (repo/'app/pricing.py').write_text('def apply_discount(cents, percent):\n return cents\n')
|
| 99 | for command in ([str(subject.bin),'add','.'],[str(subject.bin),'commit','-m','incorrect answer']):
|
| 100 | subprocess.run(command,cwd=repo,env=workflow_ab.base_env(),check=True,capture_output=True)
|
| 101 | return result
|
| 102 | observations=[]
|
| 103 | for kind in ('git','oak'):
|
| 104 | subject=workflow_ab.Subject(kind,kind,kind,Path(shutil.which(kind)))
|
| 105 | with mock.patch.object(aw,'run_mock_agent',side_effect=wrong_agent if kind=='git' else original):
|
| 106 | row=aw.run_one(agent,subject,workflow_ab.WORKFLOWS['bugfix_test_loop'],fixture,0,
|
| 107 | root/'work',root/'artifacts',60,'agent-default','minimal','full-docs','study','2026-09-18T00:00:00Z',
|
| 108 | {'oak_hash':'test-binary-provenance','oak_status':'clean'},
|
| 109 | {'randomized_subject_order':True,'subject_order':['git','oak']})
|
| 110 | observations.append(row)
|
| 111 | self.assertEqual([r['success'] for r in observations],[False,True])
|
| 112 | self.assertEqual(workflow_evidence_failures(observations),[])
|
| 113 | plan={'schema_version':1,'campaign_id':'study','plan_sha256':'bound','manifest':{},'schedule':[]}
|
| 114 | events=[]
|
| 115 | for index,row in enumerate(observations):
|
| 116 | trial_id=str(index); row['task_id']=trial_id
|
| 117 | plan['schedule'].append(dict(trial_id=trial_id,block_id='pair',ordinal=index,repetition=0,
|
| 118 | workflow='bugfix_test_loop',instruction_level='full-docs',agent='mock',model=None,subject=row['subject']))
|
| 119 | events.extend([dict(event='started',trial_id=trial_id,plan_sha256='bound'),
|
| 120 | dict(event='finished',trial_id=trial_id,plan_sha256='bound',outcome=row['outcome'],row=row)])
|
| 121 | report=report_campaign(plan,events,bootstrap_samples=20)
|
| 122 | comparison=report['groups'][0]['paired_comparisons'][0]
|
| 123 | self.assertEqual(comparison['complete_pairs'],1)
|
| 124 | self.assertEqual(comparison['success_delta_percentage_points'],100.0)
|
| 125 | # Repeated IDs below test the gate's mechanics only: not 30 measured trials.
|
| 126 | gate_rows=[{**row,'run':rep,'task_id':f'{rep}-{row["subject"]}'} for rep in range(30) for row in observations]
|
| 127 | path=root/'rows.jsonl';path.write_text('\n'.join(map(json.dumps,gate_rows)))
|
| 128 | output=io.StringIO()
|
| 129 | with contextlib.redirect_stdout(output): result=publish_gate.main([str(path)])
|
| 130 | self.assertEqual(result,0,output.getvalue())
|
| 131 | candidate=Path('/Applications/Xcode.app/Contents/Developer/usr/bin/git')
|
| 132 | instrument=Path('/Library/Developer/CommandLineTools/usr/bin/git')
|
| 133 | if candidate.exists() and instrument.exists():
|
| 134 | # Distinct installed vendor builds test configured identity plumbing,
|
| 135 | # not independent Git implementation or statistical power.
|
| 136 | with mock.patch.dict(os.environ, {'OAK_BENCH_GIT_INSTRUMENT': str(instrument)}):
|
| 137 | passed_git=aw.run_one(agent,workflow_ab.Subject('git','git','Git candidate',candidate),
|
| 138 | workflow_ab.WORKFLOWS['bugfix_test_loop'],fixture,0,root/'success-work',root/'success-artifacts',
|
| 139 | 60,'agent-default','minimal','full-docs','study','2026-09-18T00:00:00Z',
|
| 140 | {'oak_hash':'test-binary-provenance','oak_status':'clean'},
|
| 141 | {'randomized_subject_order':True,'subject_order':['git','oak']})
|
| 142 | self.assertTrue(passed_git['success'],passed_git.get('failure_reason'))
|
| 143 | self.assertEqual(passed_git['workflow_integrity_evidence_class'],'independent_committed_payload')
|
| 144 | self.assertNotEqual(passed_git['workflow_subject_binary_sha256'],passed_git['git_head_instrument_sha256'])
|
| 145 | successful_pair=[passed_git, observations[1]]
|
| 146 | self.assertEqual(workflow_evidence_failures(successful_pair),[])
|
| 147 | path.write_text('\n'.join(json.dumps({**row,'run':rep,'task_id':f'{rep}-{row["subject"]}'})
|
| 148 | for rep in range(30) for row in successful_pair))
|
| 149 | output=io.StringIO()
|
| 150 | with contextlib.redirect_stdout(output): result=publish_gate.main([str(path)])
|
| 151 | self.assertEqual(result,0,output.getvalue())
|
| 152 |
|