HARNESS / 代理工程下载 EPUB ↓
32 / 32

附录 E:完整测试与实跑记录

测试代码

from pathlib import Path
import json
import sys
import tempfile
import threading
import unittest
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from harness import (BudgetExceeded, Call, DemoModel, Engine, HarnessError, Journal, Limits,
                     Policy, ProtocolError, Reply, ReviewRequired, Tools, TransientModelError,
                     VerificationFailed, decode_stream, digest)

class Replies:
    def __init__(self, replies):
        self.replies = replies
        self.requests = 0
    def stream(self, messages, step):
        self.requests += 1
        reply = self.replies[step]
        yield {'type': 'text.delta', 'text': reply.text}
        for call in reply.calls:
            yield {'type': 'tool.delta', 'id': call.id, 'name': call.name,
                   'arguments': json.dumps(call.arguments)}
        yield {'type': 'reply.end', 'reason': 'stop'}

class HarnessTests(unittest.TestCase):
    def setUp(self):
        self.temp = tempfile.TemporaryDirectory()
        self.addCleanup(self.temp.cleanup)
        self.root = Path(self.temp.name)
        self.file = self.root / 'hello.txt'
        self.file.write_text('helo, harness\n')
        self.journal = Journal(self.root / '.harness' / 'events.jsonl')
        self.tools = Tools(self.root, Policy(allow_write=True))
    def valid(self):
        return self.file.read_text() == 'hello, harness\n'
    def engine(self, model=None, **kwargs):
        return Engine(self.journal, self.tools, model or DemoModel(), **kwargs)
    def test_demo_has_real_effect_and_verified_end(self):
        self.engine().run('fix', self.valid)
        self.assertTrue(self.valid())
        self.assertEqual(self.journal.events[-1]['type'], 'run.finished')
        self.assertEqual([e['data']['status'] for e in self.engine().events('tool.result')], ['ok'] * 3)
    def test_resume_finished_session_does_not_repeat_tools(self):
        self.engine().run('fix', self.valid)
        count = len(self.engine().events('tool.started'))
        model = Replies([])
        self.engine(model).run('fix', self.valid)
        self.assertEqual(model.requests, 0)
        self.assertEqual(len(self.engine().events('tool.started')), count)
    def test_saved_completion_is_revalidated(self):
        self.engine().run('fix', self.valid)
        self.file.write_text('changed')
        with self.assertRaises(VerificationFailed):
            self.engine().run('fix', self.valid)
    def test_final_text_is_not_completion_evidence(self):
        with self.assertRaises(VerificationFailed):
            self.engine(Replies([Reply('I fixed it')])).run('fix', self.valid)
        self.assertFalse(self.engine().events('run.finished'))
    def test_read_only_blocks_side_effect(self):
        with self.assertRaises(VerificationFailed):
            Engine(self.journal, Tools(self.root, Policy()), DemoModel()).run('fix', self.valid)
        self.assertEqual(self.file.read_text(), 'helo, harness\n')
        self.assertIn('denied', [e['data']['status'] for e in self.engine().events('tool.result')])
    def test_parent_traversal_denied(self):
        self.assertEqual(self.tools.execute(Call('x', 'read_file', {'path': '../outside'}))['status'], 'denied')
    def test_symlink_escape_denied(self):
        (self.root / 'escape').symlink_to(self.root.parent)
        self.assertEqual(self.tools.execute(Call('x', 'read_file', {'path': 'escape/anything'}))['status'], 'denied')
    def test_reserved_journal_denied(self):
        result = self.tools.execute(Call('x', 'write_file', {'path': '.harness/events.jsonl', 'text': 'oops',
                                                          'expected_sha256': 'missing'}))
        self.assertEqual(result['status'], 'denied')
    def test_bad_schema_and_unknown_tool_never_dispatch(self):
        for call in [Call('a', 'shell', {'command': 'ignored'}), Call('b', 'read_file', {'path': 1}),
                     Call('c', 'read_file', {'path': 'hello.txt', 'extra': True})]:
            self.assertEqual(self.tools.execute(call)['status'], 'invalid')
    def test_stale_hash_prevents_write(self):
        result = self.tools.execute(Call('x', 'write_file', {'path': 'hello.txt', 'text': 'replacement',
                                                          'expected_sha256': 'stale'}))
        self.assertEqual(result['status'], 'conflict')
        self.assertEqual(self.file.read_text(), 'helo, harness\n')
    def test_truncated_model_stream_cannot_execute(self):
        class Partial:
            def stream(self, messages, step):
                yield {'type': 'tool.delta', 'id': 'x', 'name': 'write_file', 'arguments': '{"path":'}
                yield {'type': 'reply.end', 'reason': 'length'}
        with self.assertRaises(ProtocolError):
            self.engine(Partial()).run('fix', self.valid)
        self.assertFalse(self.engine().events('tool.started'))
    def test_missing_stream_end_rejected(self):
        with self.assertRaises(ProtocolError):
            decode_stream([{'type': 'text.delta', 'text': 'partial'}])
    def test_fragmented_json_reassembled(self):
        reply = decode_stream([{'type': 'tool.delta', 'id': 'x', 'name': 'read_file', 'arguments': '{"path":'},
                               {'type': 'tool.delta', 'id': 'x', 'arguments': '"hello.txt"}'},
                               {'type': 'reply.end', 'reason': 'stop'}])
        self.assertEqual(reply.calls[0].arguments, {'path': 'hello.txt'})
    def test_duplicate_call_id_in_later_reply_stops(self):
        call = Call('repeat', 'read_file', {'path': 'hello.txt'})
        model = Replies([Reply('', (call,)), Reply('', (call,))])
        with self.assertRaises(ProtocolError):
            self.engine(model).run('fix', self.valid)
        self.assertEqual(len(self.engine().events('tool.started')), 1)
    def test_model_request_budget_is_enforced(self):
        with self.assertRaises(BudgetExceeded):
            self.engine(limits=Limits(model_requests=1)).run('fix', self.valid)
        self.assertFalse(self.valid())
    def test_tool_budget_stops_before_second_side_effect(self):
        with self.assertRaises(BudgetExceeded):
            self.engine(limits=Limits(tool_calls=1)).run('fix', self.valid)
        self.assertEqual(len(self.engine().events('tool.started')), 1)
        self.assertFalse(self.valid())
    def test_unknown_result_blocks_reexecution(self):
        call = Call('maybe-written', 'write_file', {'path': 'hello.txt', 'text': 'unseen',
                                                  'expected_sha256': digest(self.file.read_bytes())})
        with self.journal.writer():
            self.journal.append('session.started', version=1, workspace=str(self.root.resolve()),
                                policy={'allow_write': True, 'max_read_bytes': 65536})
            self.journal.append('user.input', text='fix')
            self.journal.append('model.reply', text='', calls=[{'id': call.id, 'name': call.name, 'arguments': call.arguments}])
            self.journal.append('tool.started', id=call.id, name=call.name, arguments=call.arguments)
        self.file.write_text('already changed')
        model = Replies([])
        with self.assertRaises(ReviewRequired):
            self.engine(model).run('fix', self.valid)
        self.assertEqual(self.file.read_text(), 'already changed')
        self.assertEqual(model.requests, 0)
        self.assertEqual(self.engine().events('tool.result')[0]['data']['status'], 'unknown')
    def test_preview_preserves_full_artifact(self):
        self.file.write_text('甲' * 3000)
        model = Replies([Reply('', (Call('large', 'read_file', {'path': 'hello.txt'}),)), Reply('done')])
        self.engine(model, limits=Limits(preview_chars=100)).run('inspect', lambda: True)
        result = self.engine().events('tool.result')[0]['data']
        self.assertIn('[truncated]', result['content'])
        raw = (self.journal.path.parent / result['artifact']).read_bytes()
        self.assertEqual(digest(raw), result['sha256'])
        self.assertIn('甲' * 3000, raw.decode())
    def test_cancellation_is_not_success(self):
        cancel = threading.Event();cancel.set()
        with self.assertRaises(HarnessError):
            self.engine(cancel=cancel).run('fix', self.valid)
        self.assertFalse(self.engine().events('tool.started'))
        self.assertFalse(self.engine().events('run.finished'))
    def test_writer_lock_excludes_another_writer(self):
        with self.journal.writer():
            with self.assertRaises(HarnessError):
                with Journal(self.journal.path).writer():
                    pass
    def test_torn_tail_is_not_silently_discarded(self):
        self.journal.path.write_bytes(b'{"incomplete":')
        with self.assertRaises(ProtocolError):
            Journal(self.journal.path)
    def test_tampered_record_is_detected(self):
        with self.journal.writer():
            self.journal.append('example', text='original')
        self.journal.path.write_text(self.journal.path.read_text().replace('original', 'modified'))
        with self.assertRaises(ProtocolError):
            Journal(self.journal.path)
    def test_transient_model_error_retried_without_tool_retry(self):
        class Once(DemoModel):
            attempts = 0
            def stream(self, messages, step):
                self.attempts += 1
                if self.attempts == 1:
                    raise TransientModelError('temporary pre-reply failure')
                yield from super().stream(messages, step)
        self.engine(Once()).run('fix', self.valid)
        self.assertEqual(len(self.engine().events('model.retryable_error')), 1)
        self.assertEqual(len(self.engine().events('tool.started')), 3)
    def test_goal_or_policy_cannot_change_on_resume(self):
        self.engine().run('fix', self.valid)
        with self.assertRaises(ProtocolError):
            self.engine().run('different goal', self.valid)
        with self.assertRaises(ProtocolError):
            Engine(self.journal, Tools(self.root, Policy()), DemoModel()).run('fix', self.valid)

if __name__ == '__main__':
    unittest.main()

示例实跑记录

{
  "success": {
    "status": "verified",
    "answer": "工具阶段结束,是否完成由宿主验收。",
    "events": 18
  },
  "resume": {
    "status": "verified",
    "answer": "工具阶段结束,是否完成由宿主验收。",
    "events": 18
  },
  "read_only": {
    "status": "stopped",
    "reason": "model answer does not satisfy acceptance"
  },
  "read_only_exit": 2,
  "workspace_scope": "temporary directories; no model API called"
}

这些测试验证本书实验,不表示四套上游工程已接受同等运行检查。实跑不调用真实模型 API。

非官方学习读物 · 固定源码快照 · 原始代码保留各自许可。GitHub · 源码依据 · 实验代码