qutebrowser/tests/unit/misc/test_ipc.py

806 lines
27 KiB
Python
Raw Normal View History

2015-09-02 07:56:03 +02:00
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
2017-05-09 21:37:03 +02:00
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
2015-09-02 07:56:03 +02:00
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Tests for qutebrowser.misc.ipc."""
import sys
import os
2015-09-02 07:56:03 +02:00
import getpass
import logging
2015-09-06 18:06:23 +02:00
import json
2015-09-09 08:36:33 +02:00
import hashlib
2015-09-02 07:56:03 +02:00
from unittest import mock
2017-09-19 22:18:02 +02:00
import attr
2015-09-02 07:56:03 +02:00
import pytest
from PyQt5.QtCore import pyqtSignal, QObject
2015-09-04 06:57:08 +02:00
from PyQt5.QtNetwork import QLocalServer, QLocalSocket, QAbstractSocket
2015-09-02 07:56:03 +02:00
from PyQt5.QtTest import QSignalSpy
2015-09-06 18:06:23 +02:00
import qutebrowser
2015-09-02 07:56:03 +02:00
from qutebrowser.misc import ipc
2017-09-18 09:10:32 +02:00
from qutebrowser.utils import objreg, standarddir
from helpers import stubs
2015-09-02 07:56:03 +02:00
2015-09-28 21:50:55 +02:00
pytestmark = pytest.mark.usefixtures('qapp')
@pytest.fixture(autouse=True)
def shutdown_server():
"""If ipc.send_or_listen was called, make sure to shut server down."""
yield
try:
server = objreg.get('ipc-server')
except KeyError:
pass
else:
server.shutdown()
@pytest.fixture
2015-09-02 07:56:03 +02:00
def ipc_server(qapp, qtbot):
server = ipc.IPCServer('qute-test')
2015-09-02 07:56:03 +02:00
yield server
if (server._socket is not None and
server._socket.state() != QLocalSocket.UnconnectedState):
with qtbot.waitSignal(server._socket.disconnected, raising=False):
server._socket.abort()
try:
server.shutdown()
except ipc.Error:
pass
@pytest.fixture
2015-09-02 07:56:03 +02:00
def qlocalserver(qapp):
server = QLocalServer()
yield server
server.close()
server.deleteLater()
@pytest.fixture
def qlocalsocket(qapp):
2015-09-02 07:56:03 +02:00
socket = QLocalSocket()
yield socket
socket.disconnectFromServer()
if socket.state() != QLocalSocket.UnconnectedState:
2015-09-03 08:02:06 +02:00
socket.waitForDisconnected(1000)
2015-09-02 07:56:03 +02:00
2015-09-06 19:50:03 +02:00
@pytest.fixture(autouse=True)
2015-09-09 11:52:28 +02:00
def fake_runtime_dir(monkeypatch, short_tmpdir):
monkeypatch.setenv('XDG_RUNTIME_DIR', str(short_tmpdir))
standarddir._init_dirs()
2015-09-09 11:52:28 +02:00
return short_tmpdir
2015-09-06 19:50:03 +02:00
2015-09-02 21:46:20 +02:00
class FakeSocket(QObject):
2015-09-02 20:40:41 +02:00
"""A stub for a QLocalSocket.
Args:
_can_read_line_val: The value returned for canReadLine().
_error_val: The value returned for error().
_state_val: The value returned for state().
2015-09-04 06:57:08 +02:00
_connect_successful: The value returned for waitForConnected().
2015-09-02 20:40:41 +02:00
"""
2015-09-02 21:46:20 +02:00
readyRead = pyqtSignal()
disconnected = pyqtSignal()
2015-09-02 20:40:41 +02:00
def __init__(self, *, error=QLocalSocket.UnknownSocketError, state=None,
2015-09-04 06:57:08 +02:00
data=None, connect_successful=True, parent=None):
2015-09-02 21:46:20 +02:00
super().__init__(parent)
2015-09-02 20:40:41 +02:00
self._error_val = error
self._state_val = state
self._data = data
2015-09-04 06:57:08 +02:00
self._connect_successful = connect_successful
2015-09-02 20:40:41 +02:00
self.error = stubs.FakeSignal('error', func=self._error)
def _error(self):
return self._error_val
def state(self):
return self._state_val
def canReadLine(self):
return bool(self._data)
def readLine(self):
firstline, mid, rest = self._data.partition(b'\n')
self._data = rest
return firstline + mid
def errorString(self):
return "Error string"
def abort(self):
2015-09-02 21:46:20 +02:00
self.disconnected.emit()
2015-09-02 20:40:41 +02:00
2015-09-04 06:57:08 +02:00
def disconnectFromServer(self):
pass
def connectToServer(self, _name):
pass
def waitForConnected(self, _time):
return self._connect_successful
def writeData(self, _data):
pass
def waitForBytesWritten(self, _time):
pass
def waitForDisconnected(self, _time):
pass
2015-09-02 20:40:41 +02:00
class FakeServer:
def __init__(self, socket):
self._socket = socket
def nextPendingConnection(self):
socket = self._socket
self._socket = None
return socket
def close(self):
pass
def deleteLater(self):
pass
2015-09-02 07:56:03 +02:00
def test_getpass_getuser():
"""Make sure getpass.getuser() returns something sensible."""
assert getpass.getuser()
2015-09-09 08:36:33 +02:00
def md5(inp):
return hashlib.md5(inp.encode('utf-8')).hexdigest()
2015-09-02 07:56:03 +02:00
2015-09-09 08:36:33 +02:00
class TestSocketName:
POSIX_TESTS = [
2015-09-09 08:36:33 +02:00
(None, 'ipc-{}'.format(md5('testusername'))),
('/x', 'ipc-{}'.format(md5('testusername-/x'))),
]
2017-07-09 22:09:31 +02:00
WINDOWS_TESTS = [
(None, 'qutebrowser-testusername'),
('/x', 'qutebrowser-testusername-{}'.format(md5('/x'))),
]
2015-09-09 08:36:33 +02:00
@pytest.fixture(autouse=True)
def patch_user(self, monkeypatch):
monkeypatch.setattr(ipc.getpass, 'getuser', lambda: 'testusername')
2015-09-09 08:36:33 +02:00
2017-07-09 22:09:31 +02:00
@pytest.mark.parametrize('basedir, expected', WINDOWS_TESTS)
@pytest.mark.windows
def test_windows(self, basedir, expected):
socketname = ipc._get_socketname(basedir)
assert socketname == expected
2017-07-09 22:09:31 +02:00
@pytest.mark.parametrize('basedir, expected', WINDOWS_TESTS)
def test_windows_on_posix(self, basedir, expected):
socketname = ipc._get_socketname_windows(basedir)
assert socketname == expected
2017-07-08 11:12:43 +02:00
@pytest.mark.mac
@pytest.mark.parametrize('basedir, expected', POSIX_TESTS)
2017-07-08 11:12:43 +02:00
def test_mac(self, basedir, expected):
socketname = ipc._get_socketname(basedir)
parts = socketname.split(os.sep)
assert parts[-2] == 'qutebrowser'
assert parts[-1] == expected
@pytest.mark.linux
@pytest.mark.parametrize('basedir, expected', POSIX_TESTS)
def test_linux(self, basedir, fake_runtime_dir, expected):
socketname = ipc._get_socketname(basedir)
expected_path = str(fake_runtime_dir / 'qutebrowser' / expected)
assert socketname == expected_path
def test_other_unix(self):
2017-07-08 11:12:43 +02:00
"""Fake test for POSIX systems which aren't Linux/macOS.
We probably would adjust the code first to make it work on that
platform.
"""
if os.name == 'nt':
pass
elif sys.platform == 'darwin':
pass
elif sys.platform.startswith('linux'):
pass
else:
raise Exception("Unexpected platform!")
2015-09-02 07:56:03 +02:00
class TestExceptions:
2015-09-02 07:56:03 +02:00
def test_listen_error(self, qlocalserver):
2015-09-02 07:56:03 +02:00
qlocalserver.listen(None)
exc = ipc.ListenError(qlocalserver)
assert exc.code == 2
assert exc.message == "QLocalServer::listen: Name error"
2015-09-02 20:40:41 +02:00
msg = ("Error while listening to IPC server: QLocalServer::listen: "
"Name error (error 2)")
2015-09-02 07:56:03 +02:00
assert str(exc) == msg
with pytest.raises(ipc.Error):
raise exc
def test_socket_error(self, qlocalserver):
socket = FakeSocket(error=QLocalSocket.ConnectionRefusedError)
exc = ipc.SocketError("testing", socket)
assert exc.code == QLocalSocket.ConnectionRefusedError
assert exc.message == "Error string"
assert str(exc) == "Error while testing: Error string (error 0)"
with pytest.raises(ipc.Error):
raise exc
class TestListen:
2015-09-03 07:04:34 +02:00
@pytest.mark.posix
2015-09-02 07:56:03 +02:00
def test_remove_error(self, ipc_server, monkeypatch):
"""Simulate an error in _remove_server."""
monkeypatch.setattr(ipc_server, '_socketname', None)
2017-05-23 09:36:00 +02:00
with pytest.raises(ipc.Error,
match="Error while removing server None!"):
2015-09-02 07:56:03 +02:00
ipc_server.listen()
def test_error(self, ipc_server, monkeypatch):
"""Simulate an error while listening."""
monkeypatch.setattr(ipc.QLocalServer, 'removeServer',
2015-09-02 07:56:03 +02:00
lambda self: True)
monkeypatch.setattr(ipc_server, '_socketname', None)
with pytest.raises(ipc.ListenError):
ipc_server.listen()
2015-09-03 07:04:34 +02:00
@pytest.mark.posix
2015-09-02 07:56:03 +02:00
def test_in_use(self, qlocalserver, ipc_server, monkeypatch):
monkeypatch.setattr(ipc.QLocalServer, 'removeServer',
2015-09-02 07:56:03 +02:00
lambda self: True)
qlocalserver.listen('qute-test')
2015-09-02 07:56:03 +02:00
with pytest.raises(ipc.AddressInUseError):
ipc_server.listen()
def test_successful(self, ipc_server):
ipc_server.listen()
2015-09-08 21:08:49 +02:00
@pytest.mark.windows
def test_permissions_windows(self, ipc_server):
opts = ipc_server._server.socketOptions()
assert opts == QLocalServer.UserAccessOption
@pytest.mark.posix
def test_permissions_posix(self, ipc_server):
# pylint: disable=no-member,useless-suppression
2015-09-08 21:08:49 +02:00
ipc_server.listen()
sockfile = ipc_server._server.fullServerName()
sockdir = os.path.dirname(sockfile)
file_stat = os.stat(sockfile)
dir_stat = os.stat(sockdir)
file_owner_ok = file_stat.st_uid == os.getuid()
dir_owner_ok = dir_stat.st_uid == os.getuid()
file_mode_ok = file_stat.st_mode & 0o777 == 0o700
dir_mode_ok = dir_stat.st_mode & 0o777 == 0o700
print('sockdir: {} / owner {} / mode {:o}'.format(
sockdir, dir_stat.st_uid, dir_stat.st_mode))
print('sockfile: {} / owner {} / mode {:o}'.format(
sockfile, file_stat.st_uid, file_stat.st_mode))
2015-09-08 21:08:49 +02:00
assert file_owner_ok or dir_owner_ok
assert file_mode_ok or dir_mode_ok
@pytest.mark.posix
def test_atime_update(self, qtbot, ipc_server):
ipc_server._atime_timer.setInterval(500) # We don't want to wait 6h
ipc_server.listen()
old_atime = os.stat(ipc_server._server.fullServerName()).st_atime_ns
with qtbot.waitSignal(ipc_server._atime_timer.timeout, timeout=2000):
pass
# Make sure the timer is not singleShot
with qtbot.waitSignal(ipc_server._atime_timer.timeout, timeout=2000):
pass
new_atime = os.stat(ipc_server._server.fullServerName()).st_atime_ns
assert old_atime != new_atime
@pytest.mark.posix
def test_atime_update_no_name(self, qtbot, caplog, ipc_server):
with caplog.at_level(logging.ERROR):
ipc_server.update_atime()
assert caplog.records[-1].msg == "In update_atime with no server path!"
@pytest.mark.posix
def test_atime_shutdown_typeerror(self, qtbot, ipc_server):
"""This should never happen, but let's handle it gracefully."""
ipc_server._atime_timer.timeout.disconnect(ipc_server.update_atime)
ipc_server.shutdown()
2015-09-02 07:56:03 +02:00
class TestOnError:
def test_closed(self, ipc_server):
ipc_server._socket = QLocalSocket()
ipc_server._timer.timeout.disconnect()
ipc_server._timer.start()
ipc_server.on_error(QLocalSocket.PeerClosedError)
assert not ipc_server._timer.isActive()
def test_other_error(self, ipc_server, monkeypatch):
socket = QLocalSocket()
ipc_server._socket = socket
monkeypatch.setattr(socket, 'error',
lambda: QLocalSocket.ConnectionRefusedError)
monkeypatch.setattr(socket, 'errorString',
lambda: "Connection refused")
socket.setErrorString("Connection refused.")
2017-05-23 09:36:00 +02:00
with pytest.raises(ipc.Error, match=r"Error while handling IPC "
r"connection: Connection refused \(error 0\)"):
2015-09-02 07:56:03 +02:00
ipc_server.on_error(QLocalSocket.ConnectionRefusedError)
class TestHandleConnection:
def test_ignored(self, ipc_server, monkeypatch):
m = mock.Mock(spec=[])
monkeypatch.setattr(ipc_server._server, 'nextPendingConnection', m)
ipc_server.ignored = True
ipc_server.handle_connection()
assert not m.called
def test_no_connection(self, ipc_server, caplog):
ipc_server.handle_connection()
assert caplog.records[-1].message == "No new connection to handle."
2015-09-02 07:56:03 +02:00
def test_double_connection(self, qlocalsocket, ipc_server, caplog):
ipc_server._socket = qlocalsocket
ipc_server.handle_connection()
2016-01-08 12:02:36 +01:00
msg = ("Got new connection but ignoring it because we're still "
"handling another one")
assert any(rec.message.startswith(msg) for rec in caplog.records)
2015-09-02 20:40:41 +02:00
def test_disconnected_immediately(self, ipc_server, caplog):
socket = FakeSocket(state=QLocalSocket.UnconnectedState)
ipc_server._server = FakeServer(socket)
ipc_server.handle_connection()
msg = "Socket was disconnected immediately."
all_msgs = [r.message for r in caplog.records]
2015-09-02 20:40:41 +02:00
assert msg in all_msgs
def test_error_immediately(self, ipc_server, caplog):
socket = FakeSocket(error=QLocalSocket.ConnectionError)
ipc_server._server = FakeServer(socket)
2017-05-23 09:36:00 +02:00
with pytest.raises(ipc.Error, match=r"Error while handling IPC "
r"connection: Error string \(error 7\)"):
2015-09-02 20:40:41 +02:00
ipc_server.handle_connection()
msg = "We got an error immediately."
all_msgs = [r.message for r in caplog.records]
2015-09-02 20:40:41 +02:00
assert msg in all_msgs
def test_read_line_immediately(self, qtbot, ipc_server, caplog):
2015-10-06 22:59:49 +02:00
data = ('{{"args": ["foo"], "target_arg": "tab", '
'"protocol_version": {}}}\n'.format(ipc.PROTOCOL_VERSION))
socket = FakeSocket(data=data.encode('utf-8'))
2015-09-02 20:40:41 +02:00
ipc_server._server = FakeServer(socket)
with qtbot.waitSignal(ipc_server.got_args) as blocker:
2015-09-02 20:40:41 +02:00
ipc_server.handle_connection()
assert blocker.args == [['foo'], 'tab', '']
all_msgs = [r.message for r in caplog.records]
2015-09-02 20:40:41 +02:00
assert "We can read a line immediately." in all_msgs
2015-09-02 07:56:03 +02:00
@pytest.fixture
2015-09-02 21:46:56 +02:00
def connected_socket(qtbot, qlocalsocket, ipc_server):
if sys.platform == 'darwin':
pytest.skip("Skipping connected_socket test - "
2017-02-05 00:13:11 +01:00
"https://github.com/qutebrowser/qutebrowser/issues/1045")
2015-09-02 21:46:56 +02:00
ipc_server.listen()
with qtbot.waitSignal(ipc_server._server.newConnection):
qlocalsocket.connectToServer('qute-test')
2015-09-02 21:46:56 +02:00
yield qlocalsocket
qlocalsocket.disconnectFromServer()
def test_disconnected_without_data(qtbot, connected_socket,
ipc_server, caplog):
"""Disconnect without sending data.
2015-09-02 07:56:03 +02:00
2015-09-02 21:46:56 +02:00
This means self._socket will be None on on_disconnected.
"""
connected_socket.disconnectFromServer()
2015-09-02 20:40:41 +02:00
2015-09-02 07:56:03 +02:00
2015-09-02 21:46:56 +02:00
def test_partial_line(connected_socket):
connected_socket.write(b'foo')
2015-09-02 07:56:03 +02:00
OLD_VERSION = str(ipc.PROTOCOL_VERSION - 1).encode('utf-8')
NEW_VERSION = str(ipc.PROTOCOL_VERSION + 1).encode('utf-8')
@pytest.mark.parametrize('data, msg', [
(b'\x80\n', 'invalid utf-8'),
(b'\n', 'invalid json'),
(b'{"is this invalid json?": true\n', 'invalid json'),
2015-10-06 22:59:49 +02:00
(b'{"valid json without args": true}\n', 'Missing args'),
(b'{"args": []}\n', 'Missing target_arg'),
(b'{"args": [], "target_arg": null, "protocol_version": ' + OLD_VERSION +
b'}\n', 'incompatible version'),
(b'{"args": [], "target_arg": null, "protocol_version": ' + NEW_VERSION +
b'}\n', 'incompatible version'),
(b'{"args": [], "target_arg": null, "protocol_version": "foo"}\n',
'invalid version'),
(b'{"args": [], "target_arg": null}\n', 'invalid version'),
2015-09-02 21:46:56 +02:00
])
def test_invalid_data(qtbot, ipc_server, connected_socket, caplog, data, msg):
2015-09-02 21:46:56 +02:00
signals = [ipc_server.got_invalid_data, connected_socket.disconnected]
with caplog.at_level(logging.ERROR):
with qtbot.assertNotEmitted(ipc_server.got_args):
with qtbot.waitSignals(signals, order='strict'):
connected_socket.write(data)
messages = [r.message for r in caplog.records]
2016-01-08 12:02:36 +01:00
assert messages[-1].startswith('Ignoring invalid IPC data from socket ')
assert messages[-2].startswith(msg)
2015-09-02 07:56:03 +02:00
2015-09-02 21:46:56 +02:00
def test_multiline(qtbot, ipc_server, connected_socket):
spy = QSignalSpy(ipc_server.got_args)
data = ('{{"args": ["one"], "target_arg": "tab",'
' "protocol_version": {version}}}\n'
'{{"args": ["two"], "target_arg": null,'
' "protocol_version": {version}}}\n'.format(
version=ipc.PROTOCOL_VERSION))
with qtbot.assertNotEmitted(ipc_server.got_invalid_data):
with qtbot.waitSignals([ipc_server.got_args, ipc_server.got_args],
order='strict'):
connected_socket.write(data.encode('utf-8'))
2015-09-02 21:46:56 +02:00
assert len(spy) == 2
assert spy[0] == [['one'], 'tab', '']
assert spy[1] == [['two'], '', '']
2015-09-02 21:46:56 +02:00
2015-09-04 06:57:08 +02:00
class TestSendToRunningInstance:
def test_no_server(self, caplog):
sent = ipc.send_to_running_instance('qute-test', [], None)
2015-09-04 06:57:08 +02:00
assert not sent
msg = caplog.records[-1].message
2015-09-04 06:57:08 +02:00
assert msg == "No existing instance present (error 2)"
@pytest.mark.parametrize('has_cwd', [True, False])
2017-07-08 11:12:43 +02:00
@pytest.mark.linux(reason="Causes random trouble on Windows and macOS")
2015-09-04 06:57:08 +02:00
def test_normal(self, qtbot, tmpdir, ipc_server, mocker, has_cwd):
ipc_server.listen()
with qtbot.assertNotEmitted(ipc_server.got_invalid_data):
with qtbot.waitSignal(ipc_server.got_args,
timeout=5000) as blocker:
with qtbot.waitSignal(ipc_server.got_raw,
timeout=5000) as raw_blocker:
with tmpdir.as_cwd():
if not has_cwd:
m = mocker.patch('qutebrowser.misc.ipc.os')
m.getcwd.side_effect = OSError
sent = ipc.send_to_running_instance(
'qute-test', ['foo'], None)
2015-09-04 06:57:08 +02:00
assert sent
2015-09-06 18:06:23 +02:00
2015-09-04 06:57:08 +02:00
expected_cwd = str(tmpdir) if has_cwd else ''
2015-09-06 18:06:23 +02:00
assert blocker.args == [['foo'], '', expected_cwd]
2015-09-04 06:57:08 +02:00
raw_expected = {'args': ['foo'], 'target_arg': None,
'version': qutebrowser.__version__,
'protocol_version': ipc.PROTOCOL_VERSION}
2015-09-06 18:06:23 +02:00
if has_cwd:
raw_expected['cwd'] = str(tmpdir)
assert len(raw_blocker.args) == 1
parsed = json.loads(raw_blocker.args[0].decode('utf-8'))
2015-09-06 18:06:23 +02:00
assert parsed == raw_expected
2015-09-04 06:57:08 +02:00
def test_socket_error(self):
socket = FakeSocket(error=QLocalSocket.ConnectionError)
2017-05-23 09:36:00 +02:00
with pytest.raises(ipc.Error, match=r"Error while writing to running "
r"instance: Error string \(error 7\)"):
ipc.send_to_running_instance('qute-test', [], None, socket=socket)
2015-09-04 06:57:08 +02:00
def test_not_disconnected_immediately(self):
socket = FakeSocket()
ipc.send_to_running_instance('qute-test', [], None, socket=socket)
2015-09-04 06:57:08 +02:00
def test_socket_error_no_server(self):
socket = FakeSocket(error=QLocalSocket.ConnectionError,
connect_successful=False)
2017-05-23 09:36:00 +02:00
with pytest.raises(ipc.Error, match=r"Error while connecting to "
r"running instance: Error string \(error 7\)"):
ipc.send_to_running_instance('qute-test', [], None, socket=socket)
2015-09-04 06:57:08 +02:00
2015-09-02 21:46:56 +02:00
2017-07-08 11:12:43 +02:00
@pytest.mark.not_mac(reason="https://github.com/qutebrowser/qutebrowser/"
"issues/975")
2015-09-02 21:46:56 +02:00
def test_timeout(qtbot, caplog, qlocalsocket, ipc_server):
ipc_server._timer.setInterval(100)
ipc_server.listen()
2015-09-02 07:56:03 +02:00
with qtbot.waitSignal(ipc_server._server.newConnection):
qlocalsocket.connectToServer('qute-test')
2015-09-02 07:56:03 +02:00
with caplog.at_level(logging.ERROR):
with qtbot.waitSignal(qlocalsocket.disconnected, timeout=5000):
2015-09-02 21:46:56 +02:00
pass
2015-09-02 07:56:03 +02:00
2016-01-08 12:02:36 +01:00
assert caplog.records[-1].message.startswith("IPC connection timed out")
def test_ipcserver_socket_none_readyread(ipc_server, caplog):
assert ipc_server._socket is None
assert ipc_server._old_socket is None
with caplog.at_level(logging.WARNING):
ipc_server.on_ready_read()
msg = "In on_ready_read with None socket and old_socket!"
assert msg in [r.message for r in caplog.records]
@pytest.mark.posix
def test_ipcserver_socket_none_error(ipc_server, caplog):
assert ipc_server._socket is None
ipc_server.on_error(0)
msg = "In on_error with None socket!"
assert msg in [r.message for r in caplog.records]
2015-09-04 06:57:08 +02:00
class TestSendOrListen:
2017-09-19 22:18:02 +02:00
@attr.s
class Args:
no_err_windows = attr.ib()
basedir = attr.ib()
command = attr.ib()
target = attr.ib()
2015-09-04 06:57:08 +02:00
@pytest.fixture
def args(self):
return self.Args(no_err_windows=True, basedir='/basedir/for/testing',
command=['test'], target=None)
2015-09-04 06:57:08 +02:00
@pytest.fixture(autouse=True)
def cleanup(self):
try:
objreg.delete('ipc-server')
except KeyError:
pass
@pytest.fixture
def qlocalserver_mock(self, mocker):
m = mocker.patch('qutebrowser.misc.ipc.QLocalServer', autospec=True)
m().errorString.return_value = "Error string"
m().newConnection = stubs.FakeSignal()
return m
@pytest.fixture
def qlocalsocket_mock(self, mocker):
m = mocker.patch('qutebrowser.misc.ipc.QLocalSocket', autospec=True)
m().errorString.return_value = "Error string"
2017-09-19 22:18:02 +02:00
for name in ['UnknownSocketError', 'UnconnectedState',
2015-09-04 06:57:08 +02:00
'ConnectionRefusedError', 'ServerNotFoundError',
'PeerClosedError']:
2017-09-19 22:18:02 +02:00
setattr(m, name, getattr(QLocalSocket, name))
2015-09-04 06:57:08 +02:00
return m
2017-07-08 11:12:43 +02:00
@pytest.mark.linux(reason="Flaky on Windows and macOS")
2015-09-04 06:57:08 +02:00
def test_normal_connection(self, caplog, qtbot, args):
ret_server = ipc.send_or_listen(args)
assert isinstance(ret_server, ipc.IPCServer)
msgs = [e.message for e in caplog.records]
2015-09-04 06:57:08 +02:00
assert "Starting IPC server..." in msgs
objreg_server = objreg.get('ipc-server')
assert objreg_server is ret_server
with qtbot.waitSignal(ret_server.got_args):
2015-09-04 06:57:08 +02:00
ret_client = ipc.send_or_listen(args)
assert ret_client is None
@pytest.mark.posix(reason="Unneeded on Windows")
def test_correct_socket_name(self, args):
server = ipc.send_or_listen(args)
expected_dir = ipc._get_socketname(args.basedir)
assert '/' in expected_dir
assert server._socketname == expected_dir
2015-09-04 06:57:08 +02:00
def test_address_in_use_ok(self, qlocalserver_mock, qlocalsocket_mock,
stubs, caplog, args):
"""Test the following scenario.
2015-09-04 06:57:08 +02:00
- First call to send_to_running_instance:
-> could not connect (server not found)
- Trying to set up a server and listen
-> AddressInUseError
- Second call to send_to_running_instance:
-> success
"""
qlocalserver_mock().listen.return_value = False
err = QAbstractSocket.AddressInUseError
qlocalserver_mock().serverError.return_value = err
qlocalsocket_mock().waitForConnected.side_effect = [False, True]
qlocalsocket_mock().error.side_effect = [
QLocalSocket.ServerNotFoundError,
QLocalSocket.UnknownSocketError,
QLocalSocket.UnknownSocketError, # error() gets called twice
]
ret = ipc.send_or_listen(args)
assert ret is None
msgs = [e.message for e in caplog.records]
2015-09-04 06:57:08 +02:00
assert "Got AddressInUseError, trying again." in msgs
@pytest.mark.parametrize('has_error, exc_name, exc_msg', [
(True, 'SocketError',
'Error while writing to running instance: Error string (error 0)'),
(False, 'AddressInUseError',
'Error while listening to IPC server: Error string (error 8)'),
2015-09-04 06:57:08 +02:00
])
def test_address_in_use_error(self, qlocalserver_mock, qlocalsocket_mock,
stubs, caplog, args, has_error, exc_name,
exc_msg):
"""Test the following scenario.
2015-09-04 06:57:08 +02:00
- First call to send_to_running_instance:
-> could not connect (server not found)
- Trying to set up a server and listen
-> AddressInUseError
- Second call to send_to_running_instance:
-> not sent / error
"""
qlocalserver_mock().listen.return_value = False
err = QAbstractSocket.AddressInUseError
qlocalserver_mock().serverError.return_value = err
# If the second connection succeeds, we will have an error later.
# If it fails, that's the "not sent" case above.
qlocalsocket_mock().waitForConnected.side_effect = [False, has_error]
qlocalsocket_mock().error.side_effect = [
QLocalSocket.ServerNotFoundError,
QLocalSocket.ServerNotFoundError,
QLocalSocket.ConnectionRefusedError,
QLocalSocket.ConnectionRefusedError, # error() gets called twice
]
with caplog.at_level(logging.ERROR):
2015-09-04 06:57:08 +02:00
with pytest.raises(ipc.Error):
ipc.send_or_listen(args)
assert len(caplog.records) == 1
2015-09-04 06:57:08 +02:00
error_msgs = [
'Handling fatal misc.ipc.{} with --no-err-windows!'.format(
exc_name),
'',
2015-09-04 06:57:08 +02:00
'title: Error while connecting to running instance!',
'pre_text: ',
'post_text: Maybe another instance is running but frozen?',
'exception text: {}'.format(exc_msg),
2015-09-04 06:57:08 +02:00
]
assert caplog.records[0].msg == '\n'.join(error_msgs)
2015-09-04 06:57:08 +02:00
@pytest.mark.posix(reason="Flaky on Windows")
2015-09-06 16:42:44 +02:00
def test_error_while_listening(self, qlocalserver_mock, caplog, args):
2015-09-04 06:57:08 +02:00
"""Test an error with the first listen call."""
qlocalserver_mock().listen.return_value = False
err = QAbstractSocket.SocketResourceError
qlocalserver_mock().serverError.return_value = err
with caplog.at_level(logging.ERROR):
2015-09-04 06:57:08 +02:00
with pytest.raises(ipc.Error):
ipc.send_or_listen(args)
assert len(caplog.records) == 1
2015-09-04 06:57:08 +02:00
error_msgs = [
'Handling fatal misc.ipc.ListenError with --no-err-windows!',
'',
2015-09-04 06:57:08 +02:00
'title: Error while connecting to running instance!',
'pre_text: ',
'post_text: Maybe another instance is running but frozen?',
('exception text: Error while listening to IPC server: Error '
'string (error 4)'),
2015-09-04 06:57:08 +02:00
]
assert caplog.records[0].msg == '\n'.join(error_msgs)
2015-09-06 19:50:03 +02:00
@pytest.mark.windows
2017-07-08 11:12:43 +02:00
@pytest.mark.mac
def test_long_username(monkeypatch):
2017-02-05 00:13:11 +01:00
"""See https://github.com/qutebrowser/qutebrowser/issues/888."""
username = 'alexandercogneau'
2015-09-09 08:36:33 +02:00
basedir = '/this_is_a_long_basedir'
2017-03-01 12:12:40 +01:00
monkeypatch.setattr('getpass.getuser', lambda: username)
2015-09-09 08:36:33 +02:00
name = ipc._get_socketname(basedir=basedir)
2015-09-06 19:50:03 +02:00
server = ipc.IPCServer(name)
2015-09-09 08:36:33 +02:00
expected_md5 = md5('{}-{}'.format(username, basedir))
assert expected_md5 in server._socketname
2015-09-06 19:50:03 +02:00
try:
server.listen()
finally:
server.shutdown()
2015-10-04 15:41:42 +02:00
def test_connect_inexistent(qlocalsocket):
"""Make sure connecting to an inexistent server fails immediately.
If this test fails, our connection logic checking for the old naming scheme
would not work properly.
"""
qlocalsocket.connectToServer('qute-test-inexistent')
assert qlocalsocket.error() == QLocalSocket.ServerNotFoundError
@pytest.mark.posix
def test_socket_options_address_in_use_problem(qlocalserver, short_tmpdir):
"""Qt seems to ignore AddressInUseError when using socketOptions.
With this test we verify this bug still exists. If it fails, we can
probably start using setSocketOptions again.
"""
servername = str(short_tmpdir / 'x')
s1 = QLocalServer()
ok = s1.listen(servername)
assert ok
s2 = QLocalServer()
s2.setSocketOptions(QLocalServer.UserAccessOption)
ok = s2.listen(servername)
print(s2.errorString())
# We actually would expect ok == False here - but we want the test to fail
# when the Qt bug is fixed.
assert ok