import tempfile import time import unittest from pathlib import Path from unittest.mock import Mock,patch from video import Video from inference import Scheduler,InferenceError class VideoTests(unittest.TestCase): def make(self,d): scheduler=Scheduler(Mock());profiles=Mock();profiles.status.return_value={'profiles':[dict(id='v',name='test',kind='video')]} return Video(Path(d),profiles,Mock(),scheduler,Mock()) def wait(self,v): v.thread.join(3);self.assertFalse(v.thread.is_alive()) def test_exclusive_mode_stops_owned_work_and_restores_llm(self): with tempfile.TemporaryDirectory() as d: v=self.make(d);v.select('v');v.blockers=Mock(return_value=[]);v.load=Mock() v.switch('video');self.wait(v) self.assertEqual(v.mode,'video');v.stop_owned.assert_called_once();v.load.assert_called_once() with self.assertRaises(InferenceError): with v.scheduler.lease(('chat',)):pass with self.assertRaises(ValueError):v.select('v') v.switch('llm');self.wait(v);self.assertEqual(v.scheduler.gpu_mode,'llm') with v.scheduler.lease(('chat',)):pass def test_failed_load_does_not_leave_gpu_gate_locked(self): with tempfile.TemporaryDirectory() as d: v=self.make(d);v.select('v');v.blockers=Mock(return_value=[]);v.load=Mock(side_effect=ValueError('busy')) v.switch('video');self.wait(v) self.assertEqual(v.status()['error'],'busy');self.assertEqual(v.scheduler.gpu_mode,'llm') def test_foreign_process_prevents_spawn(self): with tempfile.TemporaryDirectory() as d,patch('video.probe',return_value=[{'processes':1}]),patch('video.subprocess.Popen') as spawn: v=self.make(d) with self.assertRaises(ValueError):v.load({}) spawn.assert_not_called() def test_request_parameters_override_defaults_and_validate(self): with tempfile.TemporaryDirectory() as d: v=self.make(d);v.mode='video';v.state='ready';v.profile={'id':'v','name':'test','parameters':dict(width=512,height=320,fps=24,frames=9,seed=42)} with patch('video.threading.Thread') as thread,patch('video.shutil.disk_usage') as disk: disk.return_value.free=100*1024**3 result=v.generate({'prompt':'synthetic','width':640}) self.assertEqual(result['parameters']['width'],640);self.assertEqual(result['parameters']['fps'],24) self.assertNotIn('prompt',str(v.status())) with self.assertRaises(ValueError):v.generate({'prompt':'synthetic'}) v.job=None for extra in [{'frames':10},{'fps':float('nan')},{'width':True},{'model':'wrong'},{'steps':100}]: with self.assertRaises(ValueError):v.generate(dict(prompt='synthetic',**extra)) def test_multiple_worker_messages_in_one_read(self): import subprocess,sys with tempfile.TemporaryDirectory() as d: v=self.make(d);v.job={'id':'test'} p=subprocess.Popen([sys.executable,'-c','import sys,time;sys.stdout.write(\'{"state":"progress","phase":"test"}\\n{"state":"complete"}\\n\');sys.stdout.flush();time.sleep(3)'],stdout=subprocess.PIPE,bufsize=0) v.process=p try: self.assertEqual(v.read(p,1)['state'],'complete');self.assertEqual(v.job['phase'],'test') finally:p.terminate();p.wait();p.stdout.close() def test_missing_components_leave_current_mode_untouched(self): with tempfile.TemporaryDirectory() as d: v=self.make(d);v.select('v');v.blockers=Mock(return_value=['missing']) with self.assertRaises(ValueError):v.switch('video') self.assertEqual(v.scheduler.gpu_mode,'llm');v.stop_owned.assert_not_called()