Files
RuView/tools/bfi_learning/test_codex_iteration.py
rUv aac4155584 feat(bfi): add validated capture and bounded research tooling (#2055)
* feat(bfi): add validated capture and bounded research tooling

* fix(deps): patch Wasmtime for RUSTSEC-2026-0316
2026-09-29 23:53:48 -04:00

77 lines
4.3 KiB
Python

import json
import io
from pathlib import Path
import sys
import tempfile
import unittest
from unittest.mock import patch
import codex_iteration as c
class ReviewBoundaryTests(unittest.TestCase):
def test_snapshot_allows_only_bfi_source_and_binds_hash(self):
with tempfile.TemporaryDirectory() as td:
root=Path(td);(root/'.git').mkdir();(root/'AGENTS.md').write_text('contract')
tools=root/'tools/bfi';tools.mkdir(parents=True);(tools/'a.py').write_text('x=1')
self.assertEqual(c.snapshot(root,['tools/bfi/a.py'])[0]['content'],'x=1')
(root/'private.env').write_text('not for review')
with self.assertRaises(ValueError):c.snapshot(root,['private.env'])
with self.assertRaises(ValueError):c.snapshot(root,[])
def test_private_output_rejects_checkout(self):
with tempfile.TemporaryDirectory() as td:
root=Path(td).resolve();(root/'.git').mkdir()
with self.assertRaises(ValueError):c.checked_output_root(root/'logs',root)
def test_child_policy_keeps_exec_rules_and_read_only(self):
cmd=c.command(Path('codex'),Path('repo'))
self.assertIn('read-only',cmd)
self.assertNotIn('--ignore-rules',cmd)
self.assertFalse(any('bypass' in a for a in cmd))
self.assertIn('shell_tool',cmd)
def test_review_rejects_tools_and_malformed_events(self):
with tempfile.TemporaryDirectory() as td:
p=Path(td)/'events.jsonl'
p.write_text(json.dumps({'type':'item.completed','item':{'type':'agent_message','text':'review'}})+'\n'+json.dumps({'type':'turn.completed'})+'\n')
self.assertEqual(c.audit_events(p),(['review'],[]))
with p.open('a') as f:
f.write(json.dumps({'type':'item.started','item':{'type':'command_execution'}})+'\ninvalid\n')
findings,errors=c.audit_events(p)
self.assertEqual(findings,['review'])
self.assertEqual([e['type'] for e in errors],['prohibited_activity','invalid_json_event'])
p.write_text('{"type":null}\n')
self.assertEqual([e['type'] for e in c.audit_events(p)[1]],['invalid_event_type','missing_turn_completed'])
def test_source_bound_and_nonregular_file(self):
with tempfile.TemporaryDirectory() as td:
root=Path(td);(root/'.git').mkdir();(root/'AGENTS.md').write_text('contract')
folder=root/'tools/bfi';folder.mkdir(parents=True)
(folder/'large.py').write_bytes(b'x'*(c.MAX_SOURCE+1))
with self.assertRaises(ValueError):c.snapshot(root,['tools/bfi/large.py'])
(folder/'directory.py').mkdir()
with self.assertRaises(ValueError):c.snapshot(root,['tools/bfi/directory.py'])
def test_output_failure_reaps_child(self):
class BrokenSink:
def write(self,data):raise OSError('simulated disk full')
original=c.subprocess.Popen;children=[]
def spawn(*a,**k):
child=original(*a,**k);children.append(child);return child
program="import sys,time;sys.stdout.buffer.write(b'x'*8192);sys.stdout.flush();time.sleep(20)"
with patch.object(c,'command',return_value=[sys.executable,'-c',program]), patch.object(c.subprocess,'Popen',side_effect=spawn):
with self.assertRaises(OSError):
c.run_with_sinks(Path(sys.executable),Path('.'),'input',5,{'stdout':BrokenSink(),'stderr':io.BytesIO()})
self.assertIsNotNone(children[0].poll())
def test_stdin_receipt_and_deadline(self):
with tempfile.TemporaryDirectory() as td:
root=Path(td);ok=root/'ok';ok.mkdir()
program="import sys,json; x=sys.stdin.read(); print(json.dumps({'received':len(x)}))"
with patch.object(c,'command',return_value=[sys.executable,'-c',program]):
result=c.run(Path(sys.executable),root,ok,'abc',5)
self.assertEqual(result['exit_code'],0)
self.assertEqual(json.loads((ok/'events.jsonl').read_text())['received'],3)
slow=root/'slow';slow.mkdir()
with patch.object(c,'command',return_value=[sys.executable,'-c','import time;time.sleep(10)']):
result=c.run(Path(sys.executable),root,slow,'x'*240000,.25)
self.assertEqual(result['stop_reason'],'timeout')
self.assertLess(result['elapsed_seconds'],3)
if __name__=='__main__':unittest.main()