|
@@ -13,9 +13,10 @@ from unittest.mock import Mock
|
|
|
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)) + "/..")
|
|
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)) + "/..")
|
|
|
|
|
|
|
|
import asot_player.main as main_mod
|
|
import asot_player.main as main_mod
|
|
|
|
|
+import asot_player.engine as engine_mod
|
|
|
from asot_player.feed import Episode
|
|
from asot_player.feed import Episode
|
|
|
from asot_player.id3 import Meta, parse_id3
|
|
from asot_player.id3 import Meta, parse_id3
|
|
|
-from asot_player.engine import FakeEngine
|
|
|
|
|
|
|
+from asot_player.engine import FakeEngine, MpEngine
|
|
|
from asot_player.main import Player, resolve_start
|
|
from asot_player.main import Player, resolve_start
|
|
|
from asot_player.state import State
|
|
from asot_player.state import State
|
|
|
from asot_player.ui import UI, visible_len
|
|
from asot_player.ui import UI, visible_len
|
|
@@ -348,6 +349,69 @@ class FakeEngineTests(unittest.TestCase):
|
|
|
self.assertIsNone(e.played_url)
|
|
self.assertIsNone(e.played_url)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+# --------------------------------------------------------------------------
|
|
|
|
|
+# MpEngine derived state (no mpv process — pure logic on position progress)
|
|
|
|
|
+# --------------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+class MpDerivedStateTests(unittest.TestCase):
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def eng():
|
|
|
|
|
+ e = MpEngine.__new__(MpEngine) # skip __init__: no mpv spawn needed
|
|
|
|
|
+ e._adv = None
|
|
|
|
|
+ return e
|
|
|
|
|
+
|
|
|
|
|
+ def test_paused_wins(self):
|
|
|
|
|
+ self.assertEqual(self.eng()._derived_state(True, 100.0, 7000.0), "paused")
|
|
|
|
|
+
|
|
|
|
|
+ def test_near_end_is_eof(self):
|
|
|
|
|
+ self.assertEqual(self.eng()._derived_state(False, 6999.0, 7000.0), "eof")
|
|
|
|
|
+
|
|
|
|
|
+ def test_progress_is_playing(self):
|
|
|
|
|
+ e = self.eng()
|
|
|
|
|
+ real = engine_mod.time.time
|
|
|
|
|
+ try:
|
|
|
|
|
+ engine_mod.time.time = lambda: 1_000.0
|
|
|
|
|
+ e._derived_state(False, 3_000.0, 7_000.0) # baseline
|
|
|
|
|
+ engine_mod.time.time = lambda: 1_030.0
|
|
|
|
|
+ self.assertEqual(e._derived_state(False, 3_030.0, 7_000.0), "playing")
|
|
|
|
|
+ finally:
|
|
|
|
|
+ engine_mod.time.time = real
|
|
|
|
|
+
|
|
|
|
|
+ def test_long_stall_is_buffering_not_eof(self):
|
|
|
|
|
+ # Regression: a stall used to become "eof" after 8s, which made a
|
|
|
|
|
+ # resume on an unseekable HTTP stream auto-advance to the next
|
|
|
|
|
+ # episode mid-playback. Stalls must stay "buffering" indefinitely.
|
|
|
|
|
+ e = self.eng()
|
|
|
|
|
+ real = engine_mod.time.time
|
|
|
|
|
+ try:
|
|
|
|
|
+ engine_mod.time.time = lambda: 1_000.0
|
|
|
|
|
+ e._derived_state(False, 3_000.0, 7_000.0) # baseline
|
|
|
|
|
+ engine_mod.time.time = lambda: 1_200.0 # 200 s no progress
|
|
|
|
|
+ self.assertEqual(e._derived_state(False, 3_000.0, 7_000.0), "buffering")
|
|
|
|
|
+ finally:
|
|
|
|
|
+ engine_mod.time.time = real
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# --------------------------------------------------------------------------
|
|
|
|
|
+# Keyboard restore: Linux termios has no TCSU — using it silently skipped the
|
|
|
|
|
+# restore (AttributeError swallowed), leaving the shell in cbreak mode.
|
|
|
|
|
+# --------------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+class KeyboardRestoreTests(unittest.TestCase):
|
|
|
|
|
+ def test_stop_uses_posix_flush_constant(self):
|
|
|
|
|
+ kb = main_mod.Keyboard.__new__(main_mod.Keyboard)
|
|
|
|
|
+ kb.fd, kb.oldattr = 0, Mock()
|
|
|
|
|
+ t = main_mod.termios
|
|
|
|
|
+ calls = []
|
|
|
|
|
+ real = t.tcsetattr
|
|
|
|
|
+ t.tcsetattr = lambda f, when, attr: calls.append(when)
|
|
|
|
|
+ try:
|
|
|
|
|
+ kb.stop()
|
|
|
|
|
+ finally:
|
|
|
|
|
+ t.tcsetattr = real
|
|
|
|
|
+ self.assertEqual(calls, [t.TCSAFLUSH])
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
# --------------------------------------------------------------------------
|
|
# --------------------------------------------------------------------------
|
|
|
# State: round-trip / corrupt file / env dir
|
|
# State: round-trip / corrupt file / env dir
|
|
|
# --------------------------------------------------------------------------
|
|
# --------------------------------------------------------------------------
|