e52ddb7dd4
CodeQL / Analyze (python) (push) Failing after 1s
Linting and testing / build (3.11) (push) Failing after 0s
Package exe with PyInstaller - Windows / build (push) Failing after 2s
Linting and testing / build (3.10) (push) Failing after 1s
Linting and testing / minimal-install (push) Failing after 1s
Update sites rating and statistics / build (push) Failing after 1s
Build docker image and push to DockerHub / docker (push) Failing after 1s
Linting and testing / build (3.12) (push) Failing after 1s
Linting and testing / build (3.14) (push) Failing after 1s
Linting and testing / build (3.13) (push) Failing after 2s
150 lines
5.9 KiB
Python
150 lines
5.9 KiB
Python
"""Unit tests for the PyInstaller Windows wrapper.
|
|
|
|
The wrapper at ``pyinstaller/maigret_standalone.py`` lives outside the
|
|
``maigret`` package, so we load it by path.
|
|
"""
|
|
import importlib.util
|
|
import os
|
|
import sys
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
|
|
|
|
WRAPPER_PATH = os.path.join(
|
|
os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
|
|
"pyinstaller",
|
|
"maigret_standalone.py",
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def wrapper():
|
|
spec = importlib.util.spec_from_file_location(
|
|
"maigret_standalone_under_test", WRAPPER_PATH
|
|
)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def test_returns_false_on_non_windows(wrapper):
|
|
with mock.patch.object(wrapper.platform, "system", return_value="Darwin"):
|
|
assert wrapper._launched_by_double_click() is False
|
|
|
|
|
|
def test_returns_false_when_username_argument_present(wrapper):
|
|
# Extra argv => user invoked from a shell with a username; never a double-click.
|
|
with mock.patch.object(wrapper.platform, "system", return_value="Windows"), \
|
|
mock.patch.object(wrapper.sys, "argv", ["maigret_standalone.exe", "alice"]):
|
|
assert wrapper._launched_by_double_click() is False
|
|
|
|
|
|
def test_double_click_detected_when_only_our_process_attached(wrapper):
|
|
fake_ctypes = mock.MagicMock()
|
|
fake_ctypes.windll.kernel32.GetConsoleProcessList.return_value = 1
|
|
|
|
with mock.patch.object(wrapper.platform, "system", return_value="Windows"), \
|
|
mock.patch.object(wrapper.sys, "argv", ["maigret_standalone.exe"]), \
|
|
mock.patch.dict(sys.modules, {"ctypes": fake_ctypes}):
|
|
assert wrapper._launched_by_double_click() is True
|
|
|
|
|
|
def test_cmd_invocation_detected_when_shell_also_attached(wrapper):
|
|
fake_ctypes = mock.MagicMock()
|
|
fake_ctypes.windll.kernel32.GetConsoleProcessList.return_value = 2
|
|
|
|
with mock.patch.object(wrapper.platform, "system", return_value="Windows"), \
|
|
mock.patch.object(wrapper.sys, "argv", ["maigret_standalone.exe"]), \
|
|
mock.patch.dict(sys.modules, {"ctypes": fake_ctypes}):
|
|
assert wrapper._launched_by_double_click() is False
|
|
|
|
|
|
def test_returns_false_when_console_api_raises(wrapper):
|
|
fake_ctypes = mock.MagicMock()
|
|
fake_ctypes.windll.kernel32.GetConsoleProcessList.side_effect = OSError("boom")
|
|
|
|
with mock.patch.object(wrapper.platform, "system", return_value="Windows"), \
|
|
mock.patch.object(wrapper.sys, "argv", ["maigret_standalone.exe"]), \
|
|
mock.patch.dict(sys.modules, {"ctypes": fake_ctypes}):
|
|
assert wrapper._launched_by_double_click() is False
|
|
|
|
|
|
def test_main_runs_cli_when_not_double_click(wrapper):
|
|
sentinel = object()
|
|
with mock.patch.object(wrapper, "_launched_by_double_click", return_value=False), \
|
|
mock.patch.object(wrapper.asyncio, "run") as run_mock, \
|
|
mock.patch.object(wrapper.maigret, "cli", new=mock.Mock(return_value=sentinel)) as cli_mock:
|
|
wrapper.main()
|
|
|
|
cli_mock.assert_called_once_with()
|
|
run_mock.assert_called_once_with(sentinel)
|
|
|
|
|
|
def test_main_prompts_and_runs_search_on_double_click(wrapper):
|
|
sentinel = object()
|
|
original_argv = ["maigret_standalone.exe"]
|
|
inputs = iter(["alice", ""]) # username prompt, then pause prompt
|
|
|
|
with mock.patch.object(wrapper, "_launched_by_double_click", return_value=True), \
|
|
mock.patch.object(wrapper.sys, "argv", list(original_argv)), \
|
|
mock.patch("builtins.input", side_effect=lambda *_: next(inputs)) as input_mock, \
|
|
mock.patch.object(wrapper.asyncio, "run") as run_mock, \
|
|
mock.patch.object(wrapper.maigret, "cli", new=mock.Mock(return_value=sentinel)):
|
|
wrapper.main()
|
|
# argv was rewritten to feed argparse the entered username.
|
|
assert wrapper.sys.argv == ["maigret_standalone.exe", "alice"]
|
|
|
|
run_mock.assert_called_once_with(sentinel)
|
|
assert input_mock.call_count == 2 # username prompt + final pause
|
|
|
|
|
|
def test_main_skips_search_on_empty_username(wrapper):
|
|
inputs = iter(["", ""]) # empty username, then pause prompt
|
|
|
|
with mock.patch.object(wrapper, "_launched_by_double_click", return_value=True), \
|
|
mock.patch.object(wrapper.sys, "argv", ["maigret_standalone.exe"]), \
|
|
mock.patch("builtins.input", side_effect=lambda *_: next(inputs)) as input_mock, \
|
|
mock.patch.object(wrapper.asyncio, "run") as run_mock, \
|
|
mock.patch.object(wrapper.maigret, "cli") as cli_mock:
|
|
wrapper.main()
|
|
|
|
cli_mock.assert_not_called()
|
|
run_mock.assert_not_called()
|
|
assert input_mock.call_count == 2 # asked for username, then paused
|
|
|
|
|
|
def test_main_pauses_even_when_cli_raises_system_exit(wrapper):
|
|
inputs = iter(["alice", ""])
|
|
|
|
with mock.patch.object(wrapper, "_launched_by_double_click", return_value=True), \
|
|
mock.patch.object(wrapper.sys, "argv", ["maigret_standalone.exe"]), \
|
|
mock.patch("builtins.input", side_effect=lambda *_: next(inputs)) as input_mock, \
|
|
mock.patch.object(wrapper.asyncio, "run", side_effect=SystemExit(2)), \
|
|
mock.patch.object(wrapper.maigret, "cli", new=mock.Mock()):
|
|
with pytest.raises(SystemExit):
|
|
wrapper.main()
|
|
|
|
# Both inputs should have fired: username prompt + final pause via finally.
|
|
assert input_mock.call_count == 2
|
|
|
|
|
|
def test_main_treats_keyboard_interrupt_at_prompt_as_empty(wrapper):
|
|
def fake_input(*_args, **_kwargs):
|
|
if fake_input.calls == 0:
|
|
fake_input.calls += 1
|
|
raise KeyboardInterrupt
|
|
fake_input.calls += 1
|
|
return ""
|
|
fake_input.calls = 0
|
|
|
|
with mock.patch.object(wrapper, "_launched_by_double_click", return_value=True), \
|
|
mock.patch.object(wrapper.sys, "argv", ["maigret_standalone.exe"]), \
|
|
mock.patch("builtins.input", side_effect=fake_input), \
|
|
mock.patch.object(wrapper.asyncio, "run") as run_mock, \
|
|
mock.patch.object(wrapper.maigret, "cli") as cli_mock:
|
|
wrapper.main()
|
|
|
|
cli_mock.assert_not_called()
|
|
run_mock.assert_not_called()
|