Add a fake clipboard for tests

There are a lot of problems and flakiness with using a real clipboard.

Instead we now have a :debug-set-fake-clipboard command to set a text, and use
logging when getting the contents.

Fixes #1285.
This commit is contained in:
Florian Bruhin 2016-02-03 20:27:11 +01:00
parent 7fe818f9c8
commit 79f83a033d
14 changed files with 166 additions and 128 deletions

View File

@ -1236,6 +1236,7 @@ These commands are mainly intended for debugging. They are hidden if qutebrowser
|<<debug-crash,debug-crash>>|Crash for debugging purposes. |<<debug-crash,debug-crash>>|Crash for debugging purposes.
|<<debug-dump-page,debug-dump-page>>|Dump the current page's content to a file. |<<debug-dump-page,debug-dump-page>>|Dump the current page's content to a file.
|<<debug-pyeval,debug-pyeval>>|Evaluate a python string and display the results as a web page. |<<debug-pyeval,debug-pyeval>>|Evaluate a python string and display the results as a web page.
|<<debug-set-fake-clipboard,debug-set-fake-clipboard>>|Put data into the fake clipboard and enable logging, used for tests.
|<<debug-trace,debug-trace>>|Trace executed code via hunter. |<<debug-trace,debug-trace>>|Trace executed code via hunter.
|<<debug-webaction,debug-webaction>>|Execute a webaction. |<<debug-webaction,debug-webaction>>|Execute a webaction.
|============== |==============
@ -1292,6 +1293,15 @@ Evaluate a python string and display the results as a web page.
* This command does not split arguments after the last argument and handles quotes literally. * This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command. * With this command, +;;+ is interpreted literally instead of splitting off a second command.
[[debug-set-fake-clipboard]]
=== debug-set-fake-clipboard
Syntax: +:debug-set-fake-clipboard ['s']+
Put data into the fake clipboard and enable logging, used for tests.
==== positional arguments
* +'s'+: The text to put into the fake clipboard, or unset to enable logging.
[[debug-trace]] [[debug-trace]]
=== debug-trace === debug-trace
Syntax: +:debug-trace ['expr']+ Syntax: +:debug-trace ['expr']+

View File

@ -260,7 +260,7 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
if cmd.startswith(':'): if cmd.startswith(':'):
if win_id is None: if win_id is None:
win_id = mainwindow.get_window(via_ipc, force_tab=True) win_id = mainwindow.get_window(via_ipc, force_tab=True)
log.init.debug("Startup cmd {}".format(cmd)) log.init.debug("Startup cmd {!r}".format(cmd))
commandrunner = runners.CommandRunner(win_id) commandrunner = runners.CommandRunner(win_id)
commandrunner.run_safely_init(cmd[1:]) commandrunner.run_safely_init(cmd[1:])
elif not cmd: elif not cmd:

View File

@ -30,7 +30,7 @@ import xml.etree.ElementTree
from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtWidgets import QApplication, QTabBar from PyQt5.QtWidgets import QApplication, QTabBar
from PyQt5.QtCore import Qt, QUrl, QEvent from PyQt5.QtCore import Qt, QUrl, QEvent
from PyQt5.QtGui import QClipboard, QKeyEvent from PyQt5.QtGui import QKeyEvent
from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog
from PyQt5.QtWebKitWidgets import QWebPage from PyQt5.QtWebKitWidgets import QWebPage
import pygments import pygments
@ -659,7 +659,6 @@ class CommandDispatcher:
title: Yank the title instead of the URL. title: Yank the title instead of the URL.
domain: Yank only the scheme, domain, and port number. domain: Yank only the scheme, domain, and port number.
""" """
clipboard = QApplication.clipboard()
if title: if title:
s = self._tabbed_browser.page_title(self._current_index()) s = self._tabbed_browser.page_title(self._current_index())
what = 'title' what = 'title'
@ -673,14 +672,14 @@ class CommandDispatcher:
s = self._current_url().toString( s = self._current_url().toString(
QUrl.FullyEncoded | QUrl.RemovePassword) QUrl.FullyEncoded | QUrl.RemovePassword)
what = 'URL' what = 'URL'
if sel and clipboard.supportsSelection():
mode = QClipboard.Selection if sel and QApplication.clipboard().supportsSelection():
target = "primary selection" target = "primary selection"
else: else:
mode = QClipboard.Clipboard sel = False
target = "clipboard" target = "clipboard"
log.misc.debug("Yanking to {}: '{}'".format(target, s))
clipboard.setText(s, mode) utils.set_clipboard(s, selection=sel)
message.info(self._win_id, "Yanked {} to {}: {}".format( message.info(self._win_id, "Yanked {} to {}: {}".format(
what, target, s)) what, target, s))
@ -811,14 +810,12 @@ class CommandDispatcher:
bg: Open in a background tab. bg: Open in a background tab.
window: Open in new window. window: Open in new window.
""" """
clipboard = QApplication.clipboard() if sel and QApplication.clipboard().supportsSelection():
if sel and clipboard.supportsSelection():
mode = QClipboard.Selection
target = "Primary selection" target = "Primary selection"
else: else:
mode = QClipboard.Clipboard sel = False
target = "Clipboard" target = "Clipboard"
text = clipboard.text(mode) text = utils.get_clipboard(selection=sel)
if not text.strip(): if not text.strip():
raise cmdexc.CommandError("{} is empty.".format(target)) raise cmdexc.CommandError("{} is empty.".format(target))
log.misc.debug("{} contained: '{}'".format(target, log.misc.debug("{} contained: '{}'".format(target,
@ -1313,17 +1310,19 @@ class CommandDispatcher:
if not elem.is_editable(strict=True): if not elem.is_editable(strict=True):
raise cmdexc.CommandError("Focused element is not editable!") raise cmdexc.CommandError("Focused element is not editable!")
clipboard = QApplication.clipboard() try:
if clipboard.supportsSelection(): sel = utils.get_clipboard(selection=True)
sel = clipboard.text(QClipboard.Selection) except utils.SelectionUnsupportedError:
log.misc.debug("Pasting primary selection into element {}".format( return
elem.debug_text()))
elem.evaluateJavaScript(""" log.misc.debug("Pasting primary selection into element {}".format(
var sel = '{}'; elem.debug_text()))
var event = document.createEvent('TextEvent'); elem.evaluateJavaScript("""
event.initTextEvent('textInput', true, true, null, sel); var sel = '{}';
this.dispatchEvent(event); var event = document.createEvent('TextEvent');
""".format(webelem.javascript_escape(sel))) event.initTextEvent('textInput', true, true, null, sel);
this.dispatchEvent(event);
""".format(webelem.javascript_escape(sel)))
def _clear_search(self, view, text): def _clear_search(self, view, text):
"""Clear search string/highlights for the given view. """Clear search string/highlights for the given view.
@ -1667,14 +1666,12 @@ class CommandDispatcher:
message.info(self._win_id, "Nothing to yank") message.info(self._win_id, "Nothing to yank")
return return
clipboard = QApplication.clipboard() if sel and QApplication.clipboard().supportsSelection():
if sel and clipboard.supportsSelection():
mode = QClipboard.Selection
target = "primary selection" target = "primary selection"
else: else:
mode = QClipboard.Clipboard sel = False
target = "clipboard" target = "clipboard"
clipboard.setText(s, mode) utils.set_clipboard(s, sel)
message.info(self._win_id, "{} {} yanked to {}".format( message.info(self._win_id, "{} {} yanked to {}".format(
len(s), "char" if len(s) == 1 else "chars", target)) len(s), "char" if len(s) == 1 else "chars", target))
if not keep: if not keep:

View File

@ -27,8 +27,7 @@ import string
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QObject, QEvent, Qt, QUrl, from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QObject, QEvent, Qt, QUrl,
QTimer) QTimer)
from PyQt5.QtGui import QMouseEvent, QClipboard from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebKit import QWebElement from PyQt5.QtWebKit import QWebElement
from PyQt5.QtWebKitWidgets import QWebPage from PyQt5.QtWebKitWidgets import QWebPage
@ -36,8 +35,7 @@ from qutebrowser.config import config
from qutebrowser.keyinput import modeman, modeparsers from qutebrowser.keyinput import modeman, modeparsers
from qutebrowser.browser import webelem from qutebrowser.browser import webelem
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
from qutebrowser.utils import (usertypes, log, qtutils, message, from qutebrowser.utils import usertypes, log, qtutils, message, objreg, utils
objreg)
from qutebrowser.misc import guiprocess from qutebrowser.misc import guiprocess
@ -481,9 +479,9 @@ class HintManager(QObject):
context: The HintContext to use. context: The HintContext to use.
""" """
sel = context.target == Target.yank_primary sel = context.target == Target.yank_primary
mode = QClipboard.Selection if sel else QClipboard.Clipboard
urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword) urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
QApplication.clipboard().setText(urlstr, mode) utils.set_clipboard(urlstr, selection=sel)
msg = "Yanked URL to {}: {}".format( msg = "Yanked URL to {}: {}".format(
"primary selection" if sel else "clipboard", "primary selection" if sel else "clipboard",
urlstr) urlstr)

View File

@ -20,9 +20,9 @@
"""Misc. widgets used at different places.""" """Misc. widgets used at different places."""
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QSize from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QSize
from PyQt5.QtWidgets import (QLineEdit, QApplication, QWidget, QHBoxLayout, from PyQt5.QtWidgets import (QLineEdit, QWidget, QHBoxLayout, QLabel,
QLabel, QStyleOption, QStyle) QStyleOption, QStyle)
from PyQt5.QtGui import QValidator, QClipboard, QPainter from PyQt5.QtGui import QValidator, QPainter
from qutebrowser.utils import utils from qutebrowser.utils import utils
from qutebrowser.misc import cmdhistory from qutebrowser.misc import cmdhistory
@ -101,10 +101,12 @@ class CommandLineEdit(QLineEdit):
def keyPressEvent(self, e): def keyPressEvent(self, e):
"""Override keyPressEvent to paste primary selection on Shift + Ins.""" """Override keyPressEvent to paste primary selection on Shift + Ins."""
if e.key() == Qt.Key_Insert and e.modifiers() == Qt.ShiftModifier: if e.key() == Qt.Key_Insert and e.modifiers() == Qt.ShiftModifier:
clipboard = QApplication.clipboard() try:
if clipboard.supportsSelection(): text = utils.get_clipboard(selection=True)
except utils.SelectionUnsupportedError:
pass
else:
e.accept() e.accept()
text = clipboard.text(QClipboard.Selection)
self.insert(text) self.insert(text)
return return
super().keyPressEvent(e) super().keyPressEvent(e)

View File

@ -29,7 +29,7 @@ except ImportError:
hunter = None hunter = None
from qutebrowser.browser.network import qutescheme from qutebrowser.browser.network import qutescheme
from qutebrowser.utils import log, objreg, usertypes, message, debug from qutebrowser.utils import log, objreg, usertypes, message, debug, utils
from qutebrowser.commands import cmdutils, runners, cmdexc from qutebrowser.commands import cmdutils, runners, cmdexc
from qutebrowser.config import style from qutebrowser.config import style
from qutebrowser.misc import consolewidget from qutebrowser.misc import consolewidget
@ -198,3 +198,16 @@ def debug_pyeval(s, quiet=False):
tabbed_browser = objreg.get('tabbed-browser', scope='window', tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='last-focused') window='last-focused')
tabbed_browser.openurl(QUrl('qute:pyeval'), newtab=True) tabbed_browser.openurl(QUrl('qute:pyeval'), newtab=True)
@cmdutils.register(debug=True)
def debug_set_fake_clipboard(s=None):
"""Put data into the fake clipboard and enable logging, used for tests.
Args:
s: The text to put into the fake clipboard, or unset to enable logging.
"""
if s is None:
utils.log_clipboard = True
else:
utils.fake_clipboard = s

View File

@ -29,13 +29,23 @@ import contextlib
import itertools import itertools
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QKeySequence, QColor from PyQt5.QtGui import QKeySequence, QColor, QClipboard
from PyQt5.QtWidgets import QApplication
import pkg_resources import pkg_resources
import qutebrowser import qutebrowser
from qutebrowser.utils import qtutils, log from qutebrowser.utils import qtutils, log
fake_clipboard = None
log_clipboard = False
class SelectionUnsupportedError(Exception):
"""Raised if [gs]et_clipboard is used and selection=True is unsupported."""
def elide(text, length): def elide(text, length):
"""Elide text so it uses a maximum of length chars.""" """Elide text so it uses a maximum of length chars."""
if length < 1: if length < 1:
@ -743,3 +753,33 @@ def newest_slice(iterable, count):
return iterable return iterable
else: else:
return itertools.islice(iterable, len(iterable) - count, len(iterable)) return itertools.islice(iterable, len(iterable) - count, len(iterable))
def set_clipboard(data, selection=False):
"""Set the clipboard to some given data."""
clipboard = QApplication.clipboard()
if selection and not clipboard.supportsSelection():
raise SelectionUnsupportedError
if log_clipboard:
what = 'primary selection' if selection else 'clipboard'
log.misc.debug("Setting fake {}: {!r}".format(what, data))
else:
mode = QClipboard.Selection if selection else QClipboard.Clipboard
clipboard.setText(data, mode=mode)
def get_clipboard(selection=False):
"""Get data from the clipboard."""
global fake_clipboard
clipboard = QApplication.clipboard()
if selection and not clipboard.supportsSelection():
raise SelectionUnsupportedError
if fake_clipboard is not None:
data = fake_clipboard
fake_clipboard = None
else:
mode = QClipboard.Selection if selection else QClipboard.Clipboard
data = clipboard.text(mode=mode)
return data

View File

@ -81,6 +81,8 @@ def whitelist_generator():
yield 'qutebrowser.misc.utilcmds.pyeval_output' yield 'qutebrowser.misc.utilcmds.pyeval_output'
yield 'utils.use_color' yield 'utils.use_color'
yield 'qutebrowser.browser.mhtml.last_used_directory' yield 'qutebrowser.browser.mhtml.last_used_directory'
yield 'qutebrowser.utils.utils.fake_clipboard'
yield 'qutebrowser.utils.utils.log_clipboard'
# Other false-positives # Other false-positives
yield ('qutebrowser.completion.models.sortfilter.CompletionFilterModel().' yield ('qutebrowser.completion.models.sortfilter.CompletionFilterModel().'

View File

@ -30,28 +30,10 @@ import textwrap
import pytest import pytest
import yaml import yaml
import pytest_bdd as bdd import pytest_bdd as bdd
from PyQt5.QtCore import QElapsedTimer
from PyQt5.QtGui import QClipboard
from helpers import utils from helpers import utils
class WaitForClipboardTimeout(Exception):
"""Raised when _wait_for_clipboard didn't get the expected message."""
def _clipboard_mode(qapp, what):
"""Get the QClipboard::Mode to use based on a string."""
if what == 'clipboard':
return QClipboard.Clipboard
elif what == 'primary selection':
assert qapp.clipboard().supportsSelection()
return QClipboard.Selection
else:
raise AssertionError
## Given ## Given
@ -207,21 +189,17 @@ def selection_supported(qapp):
@bdd.when(bdd.parsers.re(r'I put "(?P<content>.*)" into the ' @bdd.when(bdd.parsers.re(r'I put "(?P<content>.*)" into the '
r'(?P<what>primary selection|clipboard)')) r'(?P<what>primary selection|clipboard)'))
def fill_clipboard(qtbot, qapp, httpbin, what, content): def fill_clipboard(quteproc, httpbin, what, content):
mode = _clipboard_mode(qapp, what)
content = content.replace('(port)', str(httpbin.port)) content = content.replace('(port)', str(httpbin.port))
content = content.replace(r'\n', '\n') content = content.replace(r'\n', '\n')
quteproc.send_cmd(':debug-set-fake-clipboard "{}"'.format(content))
clipboard = qapp.clipboard()
clipboard.setText(content, mode)
_wait_for_clipboard(qtbot, qapp.clipboard(), mode, content)
@bdd.when(bdd.parsers.re(r'I put the following lines into the ' @bdd.when(bdd.parsers.re(r'I put the following lines into the '
r'(?P<what>primary selection|clipboard):\n' r'(?P<what>primary selection|clipboard):\n'
r'(?P<content>.+)$', flags=re.DOTALL)) r'(?P<content>.+)$', flags=re.DOTALL))
def fill_clipboard_multiline(qtbot, qapp, httpbin, what, content): def fill_clipboard_multiline(quteproc, httpbin, what, content):
fill_clipboard(qtbot, qapp, httpbin, what, textwrap.dedent(content)) fill_clipboard(quteproc, httpbin, what, textwrap.dedent(content))
## Then ## Then
@ -417,51 +395,18 @@ def check_open_tabs(quteproc, tabs):
assert 'active' not in session_tab assert 'active' not in session_tab
def _wait_for_clipboard(qtbot, clipboard, mode, expected):
timeout = 1000
timer = QElapsedTimer()
timer.start()
while True:
if clipboard.text(mode=mode) == expected:
return
# We need to poll the clipboard, as for some reason it can change with
# emitting changed (?).
with qtbot.waitSignal(clipboard.changed, timeout=100, raising=False):
pass
if timer.hasExpired(timeout):
mode_names = {
QClipboard.Clipboard: 'clipboard',
QClipboard.Selection: 'primary selection',
}
raise WaitForClipboardTimeout(
"Timed out after {timeout}ms waiting for {what}:\n"
" expected: {expected!r}\n"
" clipboard: {clipboard!r}\n"
" primary: {primary!r}.".format(
timeout=timeout, what=mode_names[mode],
expected=expected,
clipboard=clipboard.text(mode=QClipboard.Clipboard),
primary=clipboard.text(mode=QClipboard.Selection))
)
@bdd.then(bdd.parsers.re(r'the (?P<what>primary selection|clipboard) should ' @bdd.then(bdd.parsers.re(r'the (?P<what>primary selection|clipboard) should '
r'contain "(?P<content>.*)"')) r'contain "(?P<content>.*)"'))
def clipboard_contains(qtbot, qapp, httpbin, what, content): def clipboard_contains(quteproc, httpbin, what, content):
mode = _clipboard_mode(qapp, what)
expected = content.replace('(port)', str(httpbin.port)) expected = content.replace('(port)', str(httpbin.port))
expected = expected.replace('\\n', '\n') expected = expected.replace('\\n', '\n')
_wait_for_clipboard(qtbot, qapp.clipboard(), mode, expected) quteproc.wait_for(message='Setting fake {}: {!r}'.format(what, expected))
@bdd.then(bdd.parsers.parse('the clipboard should contain:\n{content}')) @bdd.then(bdd.parsers.parse('the clipboard should contain:\n{content}'))
def clipboard_contains_multiline(qtbot, qapp, content): def clipboard_contains_multiline(quteproc, content):
expected = textwrap.dedent(content) expected = textwrap.dedent(content)
_wait_for_clipboard(qtbot, qapp.clipboard(), QClipboard.Clipboard, quteproc.wait_for(message='Setting fake clipboard: {!r}'.format(expected))
expected)
@bdd.then("qutebrowser should quit") @bdd.then("qutebrowser should quit")

View File

@ -20,7 +20,7 @@
import pytest_bdd as bdd import pytest_bdd as bdd
# pylint: disable=unused-import # pylint: disable=unused-import
from test_yankpaste import skip_with_broken_clipboard from test_yankpaste import init_fake_clipboard
bdd.scenarios('caret.feature') bdd.scenarios('caret.feature')

View File

@ -20,7 +20,7 @@
import pytest_bdd as bdd import pytest_bdd as bdd
# pylint: disable=unused-import # pylint: disable=unused-import
from test_yankpaste import skip_with_broken_clipboard from test_yankpaste import init_fake_clipboard
bdd.scenarios('search.feature') bdd.scenarios('search.feature')

View File

@ -26,21 +26,9 @@ bdd.scenarios('yankpaste.feature')
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def skip_with_broken_clipboard(qtbot, qapp): def init_fake_clipboard(quteproc):
"""The clipboard seems to be broken on some platforms (OS X Yosemite?). """Make sure the fake clipboard will be used."""
quteproc.send_cmd(':debug-set-fake-clipboard')
This skips the tests if this is the case.
"""
clipboard = qapp.clipboard()
with qtbot.waitSignal(clipboard.changed, raising=False):
clipboard.setText("Does this work?")
if clipboard.text() != "Does this work?":
pytest.skip("Clipboard seems to be broken on this platform.")
with qtbot.waitSignal(clipboard.changed):
clipboard.clear()
@bdd.when(bdd.parsers.parse('I set the text field to "{value}"')) @bdd.when(bdd.parsers.parse('I set the text field to "{value}"'))

View File

@ -182,8 +182,6 @@ Feature: Yanking and pasting.
And I run :paste -t And I run :paste -t
Then no crash should happen Then no crash should happen
# https://github.com/The-Compiler/qutebrowser/issues/1285
@xfail
Scenario: Pasting multiple urls with an almost empty one Scenario: Pasting multiple urls with an almost empty one
When I open about:blank When I open about:blank
And I put "http://localhost:(port)/data/hello.txt\n \nhttp://localhost:(port)/data/hello2.txt" into the clipboard And I put "http://localhost:(port)/data/hello.txt\n \nhttp://localhost:(port)/data/hello2.txt" into the clipboard
@ -192,8 +190,6 @@ Feature: Yanking and pasting.
#### :paste-primary #### :paste-primary
# https://github.com/The-Compiler/qutebrowser/issues/1285
@xfail
Scenario: Pasting the primary selection into an empty text field Scenario: Pasting the primary selection into an empty text field
When selection is supported When selection is supported
And I open data/paste_primary.html And I open data/paste_primary.html

View File

@ -29,7 +29,7 @@ import functools
import collections import collections
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor from PyQt5.QtGui import QColor, QClipboard
import pytest import pytest
import qutebrowser import qutebrowser
@ -930,3 +930,50 @@ class TestNewestSlice:
"""Test slices which shouldn't raise an exception.""" """Test slices which shouldn't raise an exception."""
sliced = utils.newest_slice(items, count) sliced = utils.newest_slice(items, count)
assert list(sliced) == list(expected) assert list(sliced) == list(expected)
class TestGetSetClipboard:
@pytest.fixture(autouse=True)
def clipboard_mock(self, mocker):
m = mocker.patch('qutebrowser.utils.utils.QApplication.clipboard',
autospec=True)
clipboard = m()
clipboard.text.return_value = 'mocked clipboard text'
return clipboard
def test_set(self, clipboard_mock, caplog):
utils.set_clipboard('Hello World')
clipboard_mock.setText.assert_called_with('Hello World',
mode=QClipboard.Clipboard)
assert not caplog.records
def test_set_unsupported_selection(self, clipboard_mock):
clipboard_mock.supportsSelection.return_value = False
with pytest.raises(utils.SelectionUnsupportedError):
utils.set_clipboard('foo', selection=True)
@pytest.mark.parametrize('selection, what', [
(True, 'primary selection'),
(False, 'clipboard'),
])
def test_set_logging(self, clipboard_mock, caplog, selection, what):
utils.log_clipboard = True
utils.set_clipboard('fake clipboard text', selection=selection)
assert not clipboard_mock.setText.called
expected = "Setting fake {}: 'fake clipboard text'".format(what)
assert caplog.records[0].message == expected
def test_get(self):
assert utils.get_clipboard() == 'mocked clipboard text'
def test_get_unsupported_selection(self, clipboard_mock):
clipboard_mock.supportsSelection.return_value = False
with pytest.raises(utils.SelectionUnsupportedError):
utils.get_clipboard(selection=True)
@pytest.mark.parametrize('selection', [True, False])
def test_get_fake_clipboard(self, selection):
utils.fake_clipboard = 'fake clipboard text'
utils.get_clipboard(selection=selection)
assert utils.fake_clipboard is None