forked from luopingyi/xc_agent_platform_demo
live: success = verify_pass (verifyResult==1); per-cycle [live] stats line; /status stats + success_metric (76 tests)
This commit is contained in:
@@ -58,3 +58,18 @@ class TestHealth(unittest.TestCase):
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
class TestStatsStatus(unittest.TestCase):
|
||||
def test_status_stats_and_metric(self):
|
||||
from src.live_queue import compute_stats
|
||||
stats = dict(compute_stats([]), updated_at=123.0)
|
||||
for snapshot in (None, stats):
|
||||
with mock.patch('src.health.last_stats', return_value=snapshot):
|
||||
code, _, body = handle_get('/status')
|
||||
self.assertEqual(code, 200)
|
||||
payload = json.loads(body)
|
||||
self.assertEqual(payload['stats'], snapshot)
|
||||
self.assertEqual(payload['success_metric'], 'verify_pass')
|
||||
self.assertIn('strategy_id_present', payload)
|
||||
self.assertIn('external_service_token_present', payload)
|
||||
|
||||
@@ -194,3 +194,31 @@ class TestLiveCycle(unittest.TestCase):
|
||||
self.client.submit.assert_not_called()
|
||||
self.assertIn('all_gpus_done_or_unmapped', self.logs.getvalue())
|
||||
self.assertIn('placeholder', self.logs.getvalue())
|
||||
|
||||
def test_stats_logged_once_and_submission_continues(self):
|
||||
from src.live_queue import compute_stats
|
||||
stats = compute_stats([dict(status='success', verifyResult=1, stageAndNoviceTaskCredit='5.5')])
|
||||
stats_reader = Mock(return_value=(0, 'ok', set(), stats))
|
||||
run_cycle([candidate('stats')], self.client, lambda: (0, 'ok', set()), stats_reader=stats_reader)
|
||||
stats_reader.assert_called_once_with()
|
||||
lines = [s for s in self.logs.getvalue().splitlines() if s.startswith('[live] stats ')]
|
||||
self.assertEqual(lines, ['[live] stats total=1 queue=0 verify_pass=1 verify_fail=0 failed=0 unknown=0 points=5.5'])
|
||||
self.assertNotIn('synthetic-secret', self.logs.getvalue())
|
||||
self.client.submit.assert_called_once()
|
||||
|
||||
def test_stats_failure_is_safe_and_does_not_gate(self):
|
||||
for index, reader in enumerate((Mock(return_value=(None, 'platform_error', set(), None)),
|
||||
Mock(side_effect=OSError('synthetic-secret')),
|
||||
Mock(return_value=(None, 'synthetic-secret', set(), None)))):
|
||||
self.logs.truncate(0)
|
||||
self.logs.seek(0)
|
||||
run_cycle([candidate('stats-failure-' + str(index))], self.client, lambda: (0, 'ok', set()), stats_reader=reader)
|
||||
lines = [s for s in self.logs.getvalue().splitlines() if s.startswith('[live] stats ')]
|
||||
self.assertEqual(len(lines), 1)
|
||||
self.assertTrue(lines[0].startswith('[live] stats unavailable reason='))
|
||||
self.assertNotIn('synthetic-secret', self.logs.getvalue())
|
||||
self.assertEqual(self.client.submit.call_count, 3)
|
||||
|
||||
def test_injected_queue_skips_stats_network(self):
|
||||
self.cycle()
|
||||
self.assertEqual(self.logs.getvalue().count('[live] stats unavailable reason=not_read'), 1)
|
||||
|
||||
@@ -53,3 +53,52 @@ class TestLiveQueue(unittest.TestCase):
|
||||
req = call.call_args.args[0]
|
||||
self.assertEqual(req.get_header('Xc-token'), 'synthetic-secret')
|
||||
self.assertEqual(req.method, 'GET')
|
||||
|
||||
|
||||
class TestVerifyStats(unittest.TestCase):
|
||||
response = TestLiveQueue.response
|
||||
|
||||
def test_compute_stats_precedence_and_types(self):
|
||||
from src.live_queue import compute_stats
|
||||
rows = [
|
||||
dict(status='waiting', verifyResult=1, stageAndNoviceTaskCredit=100),
|
||||
dict(status='等待中', verifyResult=-1),
|
||||
dict(status='success', verifyResult=-1, stageAndNoviceTaskCredit=100),
|
||||
dict(status='failed', verifyResult=' -1 '),
|
||||
dict(status='success'), dict(status='success', verifyResult=True),
|
||||
dict(status='success', verifyResult=False),
|
||||
dict(status='success', verifyResult='garbage'),
|
||||
dict(status='error', verifyResult=None),
|
||||
dict(status=' CANCELED ', verifyResult=0),
|
||||
]
|
||||
for result, credit in [(1, '5'), ('1', 5.0), (1.0, None), (1, 'abc'), (1, True), (1, 'NaN')]:
|
||||
rows.append(dict(status='success', verifyResult=result, stageAndNoviceTaskCredit=credit))
|
||||
stats = compute_stats(rows)
|
||||
self.assertEqual(stats, dict(total=16, queue=2, verify_pass=6, verify_fail=2,
|
||||
failed=2, unknown=4, points=10.0))
|
||||
self.assertEqual(sum(stats[k] for k in ('queue', 'verify_pass', 'verify_fail', 'failed', 'unknown')), stats['total'])
|
||||
self.assertIs(type(stats['points']), float)
|
||||
self.assertTrue(all(type(v) is int for k, v in stats.items() if k != 'points'))
|
||||
|
||||
def test_paginated_stats_and_snapshot(self):
|
||||
from src.live_queue import read_queue_stats, last_stats
|
||||
pages = [self.response({'code': 0, 'data': {'total': 2, 'records': [row]}})
|
||||
for row in [dict(status='success', verifyResult=1, stageAndNoviceTaskCredit='5', body='synthetic-secret'),
|
||||
dict(status='success', verifyResult=-1)]]
|
||||
with patch.dict(os.environ, {'XC_TOKEN': 'synthetic-secret'}, clear=True), patch('urllib.request.urlopen', side_effect=pages):
|
||||
count, reason, models, stats = read_queue_stats()
|
||||
self.assertEqual((count, reason, models), (0, 'ok', set()))
|
||||
self.assertEqual(stats['verify_pass'], 1)
|
||||
self.assertEqual(stats['verify_fail'], 1)
|
||||
self.assertEqual(stats['points'], 5.0)
|
||||
snapshot = last_stats()
|
||||
self.assertIn('updated_at', snapshot)
|
||||
self.assertNotIn('synthetic-secret', json.dumps(snapshot))
|
||||
snapshot['total'] = 999
|
||||
stats['total'] = 888
|
||||
self.assertEqual(last_stats()['total'], 2)
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
self.assertEqual(read_queue_stats(), (None, 'missing/invalid_token', set(), None))
|
||||
self.assertEqual(last_stats()['total'], 2)
|
||||
with patch('src.live_queue.read_queue_stats', return_value=(3, 'ok', set(), {})):
|
||||
self.assertEqual(read_queue(), (3, 'ok', set()))
|
||||
|
||||
Reference in New Issue
Block a user