Add tests and fix error handling for stream callbacks
This commit is contained in:
parent
f9a655e7ca
commit
7343604f10
2 changed files with 128 additions and 9 deletions
28
mpv.py
28
mpv.py
|
|
@ -898,10 +898,13 @@ class MPV(object):
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
try:
|
for fut in self._exception_futures:
|
||||||
fut = next(iter(self._exception_futures))
|
try:
|
||||||
fut.set_exception(e)
|
fut.set_exception(e)
|
||||||
except StopIteration:
|
break
|
||||||
|
except InvalidStateError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
warn(f'Unhandled exception on python-mpv event loop: {e}\n{traceback.format_exc()}', RuntimeWarning)
|
warn(f'Unhandled exception on python-mpv event loop: {e}\n{traceback.format_exc()}', RuntimeWarning)
|
||||||
|
|
||||||
def _loop(self):
|
def _loop(self):
|
||||||
|
|
@ -1087,7 +1090,6 @@ class MPV(object):
|
||||||
|
|
||||||
@self.event_callback(*event_types)
|
@self.event_callback(*event_types)
|
||||||
def target_handler(evt):
|
def target_handler(evt):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rv = cond(evt)
|
rv = cond(evt)
|
||||||
if rv:
|
if rv:
|
||||||
|
|
@ -1801,12 +1803,17 @@ class MPV(object):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return ErrorCode.LOADING_FAILED
|
return ErrorCode.LOADING_FAILED
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
try:
|
for fut in self._exception_futures:
|
||||||
fut = next(iter(self._exception_futures))
|
try:
|
||||||
fut.set_exception(e)
|
fut.set_exception(e)
|
||||||
except StopIteration:
|
break
|
||||||
|
except InvalidStateError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
warnings.warn(f'Unhandled exception {e} inside stream open callback for URI {uri}\n{traceback.format_exc()}')
|
warnings.warn(f'Unhandled exception {e} inside stream open callback for URI {uri}\n{traceback.format_exc()}')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return ErrorCode.LOADING_FAILED
|
return ErrorCode.LOADING_FAILED
|
||||||
|
|
||||||
cb_info.contents.cookie = None
|
cb_info.contents.cookie = None
|
||||||
|
|
@ -1817,6 +1824,7 @@ class MPV(object):
|
||||||
for i in range(len(data)):
|
for i in range(len(data)):
|
||||||
buf[i] = data[i]
|
buf[i] = data[i]
|
||||||
return len(data)
|
return len(data)
|
||||||
|
return -1
|
||||||
read = cb_info.contents.read = StreamReadFn(read_backend)
|
read = cb_info.contents.read = StreamReadFn(read_backend)
|
||||||
|
|
||||||
def close_backend(_userdata):
|
def close_backend(_userdata):
|
||||||
|
|
@ -1832,12 +1840,14 @@ class MPV(object):
|
||||||
def seek_backend(_userdata, offx):
|
def seek_backend(_userdata, offx):
|
||||||
with self._enqueue_exceptions():
|
with self._enqueue_exceptions():
|
||||||
return frontend.seek(offx)
|
return frontend.seek(offx)
|
||||||
|
return ErrorCode.GENERIC
|
||||||
seek = cb_info.contents.seek = StreamSeekFn(seek_backend)
|
seek = cb_info.contents.seek = StreamSeekFn(seek_backend)
|
||||||
|
|
||||||
if hasattr(frontend, 'size') and frontend.size is not None:
|
if hasattr(frontend, 'size') and frontend.size is not None:
|
||||||
def size_backend(_userdata):
|
def size_backend(_userdata):
|
||||||
with self._enqueue_exceptions():
|
with self._enqueue_exceptions():
|
||||||
return frontend.size
|
return frontend.size
|
||||||
|
return 0
|
||||||
size = cb_info.contents.size = StreamSizeFn(size_backend)
|
size = cb_info.contents.size = StreamSizeFn(size_backend)
|
||||||
|
|
||||||
if hasattr(frontend, 'cancel'):
|
if hasattr(frontend, 'cancel'):
|
||||||
|
|
|
||||||
|
|
@ -535,6 +535,115 @@ class TestStreams(unittest.TestCase):
|
||||||
m.terminate()
|
m.terminate()
|
||||||
disp.stop()
|
disp.stop()
|
||||||
|
|
||||||
|
def test_stream_open_exception(self):
|
||||||
|
disp = Xvfb()
|
||||||
|
disp.start()
|
||||||
|
m = mpv.MPV(vo=testvo, video=False)
|
||||||
|
|
||||||
|
@m.register_stream_protocol('raiseerror')
|
||||||
|
def open_fn(uri):
|
||||||
|
raise SystemError()
|
||||||
|
|
||||||
|
waiting = threading.Semaphore()
|
||||||
|
result = Future()
|
||||||
|
def run():
|
||||||
|
result.set_running_or_notify_cancel()
|
||||||
|
try:
|
||||||
|
waiting.release()
|
||||||
|
m.wait_for_playback()
|
||||||
|
result.set_result(False)
|
||||||
|
except SystemError:
|
||||||
|
result.set_result(True)
|
||||||
|
except Exception:
|
||||||
|
result.set_result(False)
|
||||||
|
|
||||||
|
t = threading.Thread(target=run, daemon=True)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
with waiting:
|
||||||
|
time.sleep(0.2)
|
||||||
|
m.play('raiseerror://foo')
|
||||||
|
|
||||||
|
m.wait_for_playback(catch_errors=False)
|
||||||
|
try:
|
||||||
|
assert result.result()
|
||||||
|
finally:
|
||||||
|
m.terminate()
|
||||||
|
disp.stop()
|
||||||
|
|
||||||
|
def test_python_stream_exception(self):
|
||||||
|
disp = Xvfb()
|
||||||
|
disp.start()
|
||||||
|
m = mpv.MPV(vo=testvo)
|
||||||
|
|
||||||
|
@m.python_stream('foo')
|
||||||
|
def foo_gen():
|
||||||
|
with open(TESTVID, 'rb') as f:
|
||||||
|
yield f.read(100)
|
||||||
|
raise SystemError()
|
||||||
|
|
||||||
|
waiting = threading.Semaphore()
|
||||||
|
result = Future()
|
||||||
|
def run():
|
||||||
|
result.set_running_or_notify_cancel()
|
||||||
|
try:
|
||||||
|
waiting.release()
|
||||||
|
m.wait_for_playback()
|
||||||
|
result.set_result(False)
|
||||||
|
except SystemError:
|
||||||
|
result.set_result(True)
|
||||||
|
except Exception:
|
||||||
|
result.set_result(False)
|
||||||
|
|
||||||
|
t = threading.Thread(target=run, daemon=True)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
with waiting:
|
||||||
|
time.sleep(0.2)
|
||||||
|
m.play('python://foo')
|
||||||
|
|
||||||
|
m.wait_for_playback(catch_errors=False)
|
||||||
|
try:
|
||||||
|
assert result.result()
|
||||||
|
finally:
|
||||||
|
m.terminate()
|
||||||
|
disp.stop()
|
||||||
|
|
||||||
|
def test_stream_open_forward(self):
|
||||||
|
disp = Xvfb()
|
||||||
|
disp.start()
|
||||||
|
m = mpv.MPV(vo=testvo, video=False)
|
||||||
|
|
||||||
|
@m.register_stream_protocol('raiseerror')
|
||||||
|
def open_fn(uri):
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
waiting = threading.Semaphore()
|
||||||
|
result = Future()
|
||||||
|
def run():
|
||||||
|
result.set_running_or_notify_cancel()
|
||||||
|
try:
|
||||||
|
waiting.release()
|
||||||
|
m.wait_for_playback()
|
||||||
|
result.set_result(True)
|
||||||
|
except Exception:
|
||||||
|
result.set_result(False)
|
||||||
|
|
||||||
|
t = threading.Thread(target=run, daemon=True)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
with waiting:
|
||||||
|
time.sleep(0.2)
|
||||||
|
m.play('raiseerror://foo')
|
||||||
|
|
||||||
|
m.wait_for_playback(catch_errors=False)
|
||||||
|
try:
|
||||||
|
assert result.result()
|
||||||
|
finally:
|
||||||
|
m.terminate()
|
||||||
|
disp.stop()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestLifecycle(unittest.TestCase):
|
class TestLifecycle(unittest.TestCase):
|
||||||
def test_create_destroy(self):
|
def test_create_destroy(self):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue