forked from luopingyi/xc_agent_platform_demo
live: baked reject-marks (8) + read-only search-by-model-id precheck (已验证→60027/验证中→60028 skip, no submit); in-run marks; drop /tmp state; sync_reject_marks.py (95 tests)
This commit is contained in:
@@ -1,11 +1,8 @@
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
from src.live_cycle import run_cycle, _submitted, _rejected
|
||||
from src.live_cycle import run_cycle, _submitted, _marked
|
||||
|
||||
|
||||
def candidate(name, section='B', foothold='unknown'):
|
||||
@@ -14,13 +11,11 @@ def candidate(name, section='B', foothold='unknown'):
|
||||
|
||||
class TestLiveCycle(unittest.TestCase):
|
||||
def setUp(self):
|
||||
directory = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(directory.cleanup)
|
||||
self.env = patch.dict(os.environ, {'STRATEGY_ID': 'sid', 'LIVE_STATE_FILE': directory.name + '/state.json', 'CONFIG_PARAMS_SOURCE': 'sample', 'XC_TOKEN': 'synthetic-secret'}, clear=True)
|
||||
self.env = patch.dict(os.environ, {'LIVE_PRECHECK': '0', 'STRATEGY_ID': 'sid', 'CONFIG_PARAMS_SOURCE': 'sample', 'XC_TOKEN': 'synthetic-secret'}, clear=True)
|
||||
self.env.start()
|
||||
self.addCleanup(self.env.stop)
|
||||
_submitted.clear()
|
||||
_rejected.clear()
|
||||
_marked.clear()
|
||||
self.client = Mock()
|
||||
self.client.submit.return_value = dict(http_status=200, platform_code=0)
|
||||
network = patch('urllib.request.urlopen', side_effect=AssertionError('network forbidden'))
|
||||
@@ -34,10 +29,10 @@ class TestLiveCycle(unittest.TestCase):
|
||||
reader = Mock(side_effect=[(n, 'platform_error' if n is None else 'ok', set()) for n in counts])
|
||||
return run_cycle(rows or [candidate('a')], self.client, reader)
|
||||
|
||||
def state_pairs(self):
|
||||
return json.loads(Path(os.environ['LIVE_STATE_FILE']).read_text())
|
||||
def submitted_pairs(self):
|
||||
return sorted(m + '|' + g for m, g in _submitted)
|
||||
|
||||
def test_done_rejection_keeps_reservation_and_continues(self):
|
||||
def test_done_rejection_marks_pair_and_continues(self):
|
||||
for code in (60027, 60028):
|
||||
with self.subTest(code=code):
|
||||
self.client.submit.side_effect = [
|
||||
@@ -46,14 +41,14 @@ class TestLiveCycle(unittest.TestCase):
|
||||
with patch.dict(os.environ, {'LIVE_MAX_SUBMITS_PER_CYCLE': '1'}):
|
||||
decisions = self.cycle([candidate(str(code)), candidate('ok' + str(code))], (1, 1))
|
||||
self.assertEqual([d['action'] for d in decisions], ['rejected', 'submitted'])
|
||||
self.assertIn('org/' + str(code) + '|Biren_166m', self.state_pairs())
|
||||
self.assertEqual(_marked[('org/' + str(code), 'Biren_166m')], code)
|
||||
self.assertIn('rejected', self.logs.getvalue())
|
||||
self.assertIn('CREATE_CONTEST_TASK_MODEL_VERIFY_RUNNING', self.logs.getvalue())
|
||||
|
||||
def test_rejection_removed_but_not_retried_in_process(self):
|
||||
def test_rejection_marked_and_not_retried_in_process(self):
|
||||
self.client.submit.return_value = dict(http_status=200, platform_code=60020)
|
||||
self.cycle()
|
||||
self.assertEqual(self.state_pairs(), [])
|
||||
self.assertEqual(self.submitted_pairs(), [])
|
||||
self.cycle(counts=())
|
||||
self.client.submit.assert_called_once()
|
||||
|
||||
@@ -62,12 +57,12 @@ class TestLiveCycle(unittest.TestCase):
|
||||
decisions = self.cycle()
|
||||
self.assertEqual(decisions[0]['action'], 'rejected')
|
||||
self.assertEqual(decisions[0]['reason'], 'unknown')
|
||||
self.assertEqual(self.state_pairs(), [])
|
||||
self.assertEqual(self.submitted_pairs(), [])
|
||||
|
||||
def test_attempt_cap_and_invalid_fallback(self):
|
||||
for value, expected in ((None, 15), ('bad', 15), ('0', 15), ('-1', 15), ('2', 2)):
|
||||
with self.subTest(value=value):
|
||||
_rejected.clear()
|
||||
_marked.clear()
|
||||
self.client.reset_mock()
|
||||
self.client.submit.return_value = dict(http_status=200, platform_code=60020)
|
||||
with patch.dict(os.environ, {} if value is None else {'LIVE_MAX_ATTEMPTS_PER_CYCLE': value}):
|
||||
@@ -87,17 +82,16 @@ class TestLiveCycle(unittest.TestCase):
|
||||
decisions = self.cycle([candidate('a'), candidate('b')])
|
||||
self.assertEqual([d['action'] for d in decisions], ['rejected', 'hold'])
|
||||
self.assertEqual(decisions[-1]['reason'], 'task_num_limit')
|
||||
self.assertEqual(self.state_pairs(), [])
|
||||
self.assertEqual(self.submitted_pairs(), [])
|
||||
self.client.submit.assert_called_once()
|
||||
|
||||
def test_unknown_outcomes_count_and_reserve_before_submit(self):
|
||||
for result in (dict(http_status=200, platform_code='0'), dict(http_status=None, platform_code=None), OSError()):
|
||||
with self.subTest(result=result):
|
||||
_submitted.clear()
|
||||
Path(os.environ['LIVE_STATE_FILE']).unlink(missing_ok=True)
|
||||
self.client.reset_mock()
|
||||
def submit(payload):
|
||||
self.assertIn('org/a|Biren_166m', self.state_pairs())
|
||||
self.assertIn('org/a|Biren_166m', self.submitted_pairs())
|
||||
if isinstance(result, Exception):
|
||||
raise result
|
||||
return result
|
||||
@@ -134,10 +128,9 @@ class TestLiveCycle(unittest.TestCase):
|
||||
self.cycle(counts=(99,))
|
||||
self.client.submit.assert_called_once()
|
||||
|
||||
def test_banned_duplicate_persistence(self):
|
||||
def test_banned_and_duplicate_memory(self):
|
||||
rows = [candidate('GPT-2'), candidate('ok'), candidate('ok')]
|
||||
self.cycle(rows)
|
||||
_submitted.clear()
|
||||
self.cycle([candidate('ok')], ())
|
||||
self.client.submit.assert_called_once()
|
||||
self.assertIn('banned:', self.logs.getvalue())
|
||||
@@ -160,7 +153,7 @@ class TestLiveCycle(unittest.TestCase):
|
||||
self.assertIn('[live] token_probe EXTERNAL_SERVICE_TOKEN=absent XC_TOKEN_FILE=absent XC_TOKEN=absent MODELHUB_XC_TOKEN=absent', self.logs.getvalue())
|
||||
self.assertNotIn('[live] token source=', self.logs.getvalue())
|
||||
|
||||
def test_rejected_token_probe_does_not_leak(self):
|
||||
def test_marked_token_probe_does_not_leak(self):
|
||||
with patch.dict(os.environ, {'XC_TOKEN': '<private-secret-fragment>'}):
|
||||
self.cycle()
|
||||
self.client.submit.assert_not_called()
|
||||
@@ -184,7 +177,7 @@ class TestLiveCycle(unittest.TestCase):
|
||||
response.__exit__ = Mock(return_value=False)
|
||||
response.status = 200
|
||||
response.read.return_value = json.dumps({'code': 40100}).encode()
|
||||
with patch.dict(os.environ, {'XC_TOKEN': 'synthetic-secret'}), patch('urllib.request.urlopen', return_value=response):
|
||||
with patch.dict(os.environ, {'XC_TOKEN': 'synthetic-secret', 'LIVE_PRECHECK': '0'}), patch('urllib.request.urlopen', return_value=response):
|
||||
run_cycle([candidate('a')], self.client)
|
||||
self.client.submit.assert_not_called()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user