2026-10-16 19:00:15 +08:00
|
|
|
import json
|
|
|
|
|
import re
|
|
|
|
|
import unittest
|
|
|
|
|
import os
|
|
|
|
|
from unittest.mock import patch
|
|
|
|
|
from pathlib import Path
|
2026-10-16 22:00:52 +08:00
|
|
|
from src.config_params import build_config_params, config_params_source, build_config_params_with_meta, GAP
|
2026-10-16 19:00:15 +08:00
|
|
|
from src.build_config_client import BuildConfigError
|
|
|
|
|
from scripts.dry_run import _render
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestConfigParams(unittest.TestCase):
|
|
|
|
|
def setUp(self):
|
|
|
|
|
env = patch.dict(os.environ, {}, clear=True)
|
|
|
|
|
env.start()
|
|
|
|
|
self.addCleanup(env.stop)
|
|
|
|
|
|
|
|
|
|
def test_source_selection_and_failure(self):
|
2026-10-16 22:00:52 +08:00
|
|
|
# No token → sample, no network
|
|
|
|
|
with patch('src.config_params.resolve_token', side_effect=ValueError), patch('src.config_params.build_config') as client:
|
|
|
|
|
self.assertEqual(config_params_source(), 'sample')
|
|
|
|
|
yaml, meta = build_config_params_with_meta('壁砺')
|
|
|
|
|
self.assertEqual(meta['auth'], 'missing')
|
|
|
|
|
self.assertEqual(meta['configParams_source'], 'sample')
|
|
|
|
|
client.assert_not_called()
|
|
|
|
|
# Token alone → prefer build-config (kickoff default)
|
|
|
|
|
for gpu, enum in [('壁砺', 'Biren_166m'), ('壁砺166M', 'Biren_166m'),
|
|
|
|
|
('Biren_166m', 'Biren_166m'), ('曦望', 'Sunrise_pt-200-x1'),
|
|
|
|
|
('曦望 S2', 'Sunrise_pt-200-x1'), ('Sunrise_pt-200-x1', 'Sunrise_pt-200-x1')]:
|
|
|
|
|
with self.subTest(gpu=gpu), patch('src.config_params.resolve_token', return_value='synthetic'), patch('src.config_params.build_config', return_value='api: yaml') as client:
|
|
|
|
|
self.assertEqual(config_params_source(), 'build-config')
|
|
|
|
|
yaml, meta = build_config_params_with_meta(gpu)
|
|
|
|
|
self.assertEqual(yaml, 'api: yaml')
|
|
|
|
|
self.assertEqual(meta['configParams_source'], 'build-config')
|
|
|
|
|
self.assertEqual(meta['auth'], 'ok')
|
|
|
|
|
client.assert_called_once_with(enum, 'vllm', 'text-generation')
|
|
|
|
|
# Failure → sample + GAP reason
|
|
|
|
|
with patch('src.config_params.resolve_token', return_value='synthetic'), patch('src.config_params.build_config', side_effect=BuildConfigError('transport/timeout')):
|
|
|
|
|
yaml, meta = build_config_params_with_meta('壁砺')
|
|
|
|
|
self.assertEqual(meta['configParams_source'], 'sample')
|
|
|
|
|
self.assertEqual(meta['configParams_fallback_reason'], 'GAP: transport/timeout')
|
|
|
|
|
self.assertIn('max_model_len: 4096', yaml)
|
|
|
|
|
# Forced build-config also soft-falls back in with_meta
|
|
|
|
|
with patch.dict(os.environ, {'CONFIG_PARAMS_SOURCE': 'build-config'}), patch('src.config_params.resolve_token', return_value='synthetic'), patch('src.config_params.build_config', side_effect=BuildConfigError('transport/timeout')):
|
|
|
|
|
yaml, meta = build_config_params_with_meta('壁砺')
|
|
|
|
|
self.assertEqual(meta['configParams_fallback_reason'], 'GAP: transport/timeout')
|
|
|
|
|
# Unmapped GPU → no API call
|
|
|
|
|
for gpu in ('天垓150', '智铠 mrv-100', 'MetaX_c-500', '<GPU>', None):
|
|
|
|
|
with patch('src.config_params.resolve_token', return_value='synthetic'), patch('src.config_params.build_config') as client:
|
|
|
|
|
_, meta = build_config_params_with_meta(gpu)
|
|
|
|
|
self.assertIn('GAP: gpu enum unmapped', meta['configParams_fallback_reason'])
|
|
|
|
|
client.assert_not_called()
|
|
|
|
|
# Force sample even with token
|
|
|
|
|
with patch.dict(os.environ, {'CONFIG_PARAMS_SOURCE': 'sample'}), patch('src.config_params.resolve_token', return_value='synthetic'), patch('src.config_params.build_config') as client:
|
|
|
|
|
self.assertEqual(config_params_source(), 'sample')
|
|
|
|
|
build_config_params('壁砺')
|
|
|
|
|
client.assert_not_called()
|
|
|
|
|
|
|
|
|
|
def test_report_api_failure_metadata(self):
|
|
|
|
|
with patch('src.config_params.resolve_token', return_value='synthetic'), patch('src.config_params.build_config', side_effect=BuildConfigError('transport/timeout')):
|
|
|
|
|
report = _render([{'model_address': 'org/a', 'try_next_gpus': ['壁砺'],
|
|
|
|
|
'foothold_class': 'verified_reuse', 'evidence_source': 'pending-foothold',
|
|
|
|
|
'no_download': False}], 'test', 'fixture')
|
|
|
|
|
for value in ('foothold_class=verified_reuse', 'evidence_source=pending-foothold', 'no_download=False',
|
|
|
|
|
'configParams_source=sample', 'configParams_fallback_reason=GAP: transport/timeout'):
|
|
|
|
|
self.assertIn(value, report)
|
2026-10-16 19:00:15 +08:00
|
|
|
|
|
|
|
|
def test_official_sample_exact(self):
|
|
|
|
|
scrape = Path('/workspace/modelhub-adapt/logs/api-docs-scrape-2026-10-16.md')
|
|
|
|
|
if scrape.is_file():
|
|
|
|
|
body = json.loads(re.search(r'```json\n(.*?)\n```', scrape.read_text(), re.S)[1])
|
2026-10-16 22:00:52 +08:00
|
|
|
with patch('src.config_params.resolve_token', side_effect=ValueError):
|
|
|
|
|
self.assertEqual(build_config_params(), body['configParams'])
|
|
|
|
|
with patch('src.config_params.resolve_token', side_effect=ValueError):
|
|
|
|
|
self.assertIn('max_model_len: 4096', build_config_params())
|
|
|
|
|
self.assertIn('framework: vllm', build_config_params())
|
2026-10-16 19:00:15 +08:00
|
|
|
self.assertIn('NOT production-ready', GAP)
|
|
|
|
|
|
|
|
|
|
def test_report_draft_and_gap(self):
|
2026-10-16 22:00:52 +08:00
|
|
|
with patch('src.config_params.resolve_token', side_effect=ValueError):
|
|
|
|
|
report = _render([{'model_address': 'org/a', 'try_next_gpus': ['壁砺']}], 'test', 'fixture')
|
|
|
|
|
for value in ('代提候选 / 需 Victor 确认', 'configParams source', 'configParams_source=sample', 'foothold_class=unknown', 'evidence_source=GAP', 'no_download=False', 'GAP:', 'truncated', 'max_model_len: 4096', 'perf_order=unset', 'order_source=experience'):
|
2026-10-16 19:00:15 +08:00
|
|
|
self.assertIn(value, report)
|