From 9f9311840a2a46b25288d5ed2f432bec27802a9a Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Fri, 8 Dec 2017 16:44:53 +0000 Subject: [PATCH 01/20] Add --output-to-tab flag for :spawn. This puts the exit status, stdout, and stderr in a new tab. --- doc/help/commands.asciidoc | 3 ++- qutebrowser/browser/commands.py | 6 ++++-- qutebrowser/browser/qutescheme.py | 8 ++++++++ qutebrowser/misc/guiprocess.py | 33 ++++++++++++++++++++++++++++--- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 5d026bfca..2487b8c85 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -1148,7 +1148,7 @@ Set a mark at the current scroll position in the current tab. [[spawn]] === spawn -Syntax: +:spawn [*--userscript*] [*--verbose*] [*--detach*] 'cmdline'+ +Syntax: +:spawn [*--userscript*] [*--verbose*] [*--output-to-tab*] [*--detach*] 'cmdline'+ Spawn a command in a shell. @@ -1163,6 +1163,7 @@ Spawn a command in a shell. - `/usr/share/qutebrowser/userscripts` * +*-v*+, +*--verbose*+: Show notifications when the command started/exited. +* +*-v*+, +*--output-to-tab*+: Show stderr, stdout, and exit status in a new tab. * +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser. ==== note diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index bced4daf4..8c623aa37 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1177,7 +1177,8 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0, no_replace_variables=True) - def spawn(self, cmdline, userscript=False, verbose=False, detach=False): + def spawn(self, cmdline, userscript=False, verbose=False, + output_to_tab=False, detach=False): """Spawn a command in a shell. Args: @@ -1208,7 +1209,8 @@ class CommandDispatcher: else: cmd = os.path.expanduser(cmd) proc = guiprocess.GUIProcess(what='command', verbose=verbose, - parent=self._tabbed_browser) + parent=self._tabbed_browser, + output_to_tab=output_to_tab) if detach: proc.start_detached(cmd, args) else: diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index e6262a007..3bd2c66a4 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -42,6 +42,7 @@ from qutebrowser.misc import objects pyeval_output = ":pyeval was never called" +spawn_output = ":spawn was never called" _HANDLERS = {} @@ -268,6 +269,13 @@ def qute_pyeval(_url): return 'text/html', html +@add_handler('spawn_output') +def qute_spawn_output(_url): + """Handler for qute://spawn_output.""" + html = jinja.render('pre.html', title='spawn output', content=spawn_output) + return 'text/html', html + + @add_handler('version') @add_handler('verizon') def qute_version(_url): diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 1adf6817e..3c2057864 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -22,9 +22,11 @@ import shlex from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QProcess, - QProcessEnvironment) + QProcessEnvironment, QUrl) -from qutebrowser.utils import message, log +from qutebrowser.utils import message, log, objreg + +from qutebrowser.browser import qutescheme # A mapping of QProcess::ErrorCode's to human-readable strings. @@ -62,10 +64,11 @@ class GUIProcess(QObject): started = pyqtSignal() def __init__(self, what, *, verbose=False, additional_env=None, - parent=None): + parent=None, output_to_tab=False): super().__init__(parent) self._what = what self.verbose = verbose + self.output_to_tab = output_to_tab self._started = False self.cmd = None self.args = None @@ -96,6 +99,30 @@ class GUIProcess(QObject): self._started = False log.procs.debug("Process finished with code {}, status {}.".format( code, status)) + + stdout = None + stderr = None + + if self.output_to_tab: + stderr = bytes(self._proc.readAllStandardError()).decode('utf-8') + stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8') + + spawn_log = "" + + spawn_log += "Process finished with code {},status {}.".format( + code, status) + + if stdout: + spawn_log += "\nProcess stdout:\n" + stdout.strip() + if stderr: + spawn_log += "\nProcess stderr:\n" + stderr.strip() + + qutescheme.spawn_output = spawn_log + + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window='last-focused') + tabbed_browser.openurl(QUrl('qute://spawn_output'), newtab=True) + if status == QProcess.CrashExit: message.error("{} crashed!".format(self._what.capitalize())) elif status == QProcess.NormalExit and code == 0: From 9f8dbe95e47e5ac9440b89e31c72edb79857ee0a Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Fri, 8 Dec 2017 19:00:46 +0000 Subject: [PATCH 02/20] Code review changes. This fixes the following problems found in a review: 1. Manual modification of the asciidoc has been undone. 2. --output-to-tab has been renamed to the less verbose --output. 3. spawn_output has been changed to spawn-output in the url. 4. Erroneous newline in imports has been removed. 5. output in guiprocess.py has been marked private. 6. If there is no output for either stderr or stdout, say so. 7. Missing space in a text line was added. 8. Redundant initialising of an empty string removed. --- doc/help/commands.asciidoc | 4 ++-- qutebrowser/browser/commands.py | 5 +++-- qutebrowser/browser/qutescheme.py | 4 ++-- qutebrowser/misc/guiprocess.py | 24 ++++++++++-------------- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 2487b8c85..3da81a391 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -1148,7 +1148,7 @@ Set a mark at the current scroll position in the current tab. [[spawn]] === spawn -Syntax: +:spawn [*--userscript*] [*--verbose*] [*--output-to-tab*] [*--detach*] 'cmdline'+ +Syntax: +:spawn [*--userscript*] [*--verbose*] [*--output*] [*--detach*] 'cmdline'+ Spawn a command in a shell. @@ -1163,7 +1163,7 @@ Spawn a command in a shell. - `/usr/share/qutebrowser/userscripts` * +*-v*+, +*--verbose*+: Show notifications when the command started/exited. -* +*-v*+, +*--output-to-tab*+: Show stderr, stdout, and exit status in a new tab. +* +*-o*+, +*--output*+: Whether the output should be shown in a new tab. * +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser. ==== note diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 8c623aa37..7ad73708a 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1178,7 +1178,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0, no_replace_variables=True) def spawn(self, cmdline, userscript=False, verbose=False, - output_to_tab=False, detach=False): + output=False, detach=False): """Spawn a command in a shell. Args: @@ -1189,6 +1189,7 @@ class CommandDispatcher: (or `$XDG_DATA_DIR`) - `/usr/share/qutebrowser/userscripts` verbose: Show notifications when the command started/exited. + output: Whether the output should be shown in a new tab. detach: Whether the command should be detached from qutebrowser. cmdline: The commandline to execute. """ @@ -1210,7 +1211,7 @@ class CommandDispatcher: cmd = os.path.expanduser(cmd) proc = guiprocess.GUIProcess(what='command', verbose=verbose, parent=self._tabbed_browser, - output_to_tab=output_to_tab) + output=output) if detach: proc.start_detached(cmd, args) else: diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 3bd2c66a4..32bc5806a 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -269,9 +269,9 @@ def qute_pyeval(_url): return 'text/html', html -@add_handler('spawn_output') +@add_handler('spawn-output') def qute_spawn_output(_url): - """Handler for qute://spawn_output.""" + """Handler for qute://spawn-output.""" html = jinja.render('pre.html', title='spawn output', content=spawn_output) return 'text/html', html diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 3c2057864..32a4fbf62 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -25,7 +25,6 @@ from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QProcess, QProcessEnvironment, QUrl) from qutebrowser.utils import message, log, objreg - from qutebrowser.browser import qutescheme # A mapping of QProcess::ErrorCode's to human-readable strings. @@ -50,6 +49,7 @@ class GUIProcess(QObject): cmd: The command which was started. args: A list of arguments which gets passed. verbose: Whether to show more messages. + _output: Whether to show the output in a new tab. _started: Whether the underlying process is started. _proc: The underlying QProcess. _what: What kind of thing is spawned (process/editor/userscript/...). @@ -64,11 +64,11 @@ class GUIProcess(QObject): started = pyqtSignal() def __init__(self, what, *, verbose=False, additional_env=None, - parent=None, output_to_tab=False): + parent=None, output=False): super().__init__(parent) self._what = what self.verbose = verbose - self.output_to_tab = output_to_tab + self._output = output self._started = False self.cmd = None self.args = None @@ -100,28 +100,24 @@ class GUIProcess(QObject): log.procs.debug("Process finished with code {}, status {}.".format( code, status)) - stdout = None - stderr = None - - if self.output_to_tab: + if self._output: stderr = bytes(self._proc.readAllStandardError()).decode('utf-8') stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8') - spawn_log = "" + stderr = stderr or "(No output)" + stdout = stdout or "(No output)" - spawn_log += "Process finished with code {},status {}.".format( + spawn_log = "Process finished with code {}, status {}.".format( code, status) - if stdout: - spawn_log += "\nProcess stdout:\n" + stdout.strip() - if stderr: - spawn_log += "\nProcess stderr:\n" + stderr.strip() + spawn_log += "\nProcess stdout:\n" + stdout.strip() + spawn_log += "\nProcess stderr:\n" + stderr.strip() qutescheme.spawn_output = spawn_log tabbed_browser = objreg.get('tabbed-browser', scope='window', window='last-focused') - tabbed_browser.openurl(QUrl('qute://spawn_output'), newtab=True) + tabbed_browser.openurl(QUrl('qute://spawn-output'), newtab=True) if status == QProcess.CrashExit: message.error("{} crashed!".format(self._what.capitalize())) From 038bb85a67c1115c7091f89c3f83fa55381ea647 Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Sun, 10 Dec 2017 19:12:47 +0000 Subject: [PATCH 03/20] Capture stdout and stderr always for spawn. This change makes it so that stderr and stdout is unconditionally read from for a completed process, and sent to qute://spawn-output. This allows the user to see the results of the previous process, even if they had forgotten to use --output. --- qutebrowser/misc/guiprocess.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 32a4fbf62..9d0b50be6 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -100,21 +100,18 @@ class GUIProcess(QObject): log.procs.debug("Process finished with code {}, status {}.".format( code, status)) + stderr = bytes(self._proc.readAllStandardError()).decode('utf-8') + stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8') + + spawn_log = "Process finished with code {}, status {}.".format( + code, status) + + spawn_log += "\nProcess stdout:\n" + (stdout or "(No output)").strip() + spawn_log += "\nProcess stderr:\n" + (stderr or "(No output)").strip() + + qutescheme.spawn_output = spawn_log + if self._output: - stderr = bytes(self._proc.readAllStandardError()).decode('utf-8') - stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8') - - stderr = stderr or "(No output)" - stdout = stdout or "(No output)" - - spawn_log = "Process finished with code {}, status {}.".format( - code, status) - - spawn_log += "\nProcess stdout:\n" + stdout.strip() - spawn_log += "\nProcess stderr:\n" + stderr.strip() - - qutescheme.spawn_output = spawn_log - tabbed_browser = objreg.get('tabbed-browser', scope='window', window='last-focused') tabbed_browser.openurl(QUrl('qute://spawn-output'), newtab=True) @@ -132,8 +129,6 @@ class GUIProcess(QObject): message.error("{} exited with status {}, see :messages for " "details.".format(self._what.capitalize(), code)) - stderr = bytes(self._proc.readAllStandardError()).decode('utf-8') - stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8') if stdout: log.procs.error("Process stdout:\n" + stdout.strip()) if stderr: From d32a4ea99e2d3ff691c14b6ba8ff8ec6d3d4a35a Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Sun, 10 Dec 2017 23:45:43 +0000 Subject: [PATCH 04/20] Seperate _output from guiprocess and keep window opening in spawn. This removes the extraneous variable, and makes testing easier. --- qutebrowser/browser/commands.py | 8 ++++++-- qutebrowser/misc/guiprocess.py | 33 ++++++++++++++++----------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 7ad73708a..495d48414 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1210,13 +1210,17 @@ class CommandDispatcher: else: cmd = os.path.expanduser(cmd) proc = guiprocess.GUIProcess(what='command', verbose=verbose, - parent=self._tabbed_browser, - output=output) + parent=self._tabbed_browser) if detach: proc.start_detached(cmd, args) else: proc.start(cmd, args) + if output: + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window='last-focused') + tabbed_browser.openurl(QUrl('qute://spawn-output'), newtab=True) + @cmdutils.register(instance='command-dispatcher', scope='window') def home(self): """Open main startpage in current tab.""" diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 9d0b50be6..80803f53f 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -22,9 +22,9 @@ import shlex from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QProcess, - QProcessEnvironment, QUrl) + QProcessEnvironment) -from qutebrowser.utils import message, log, objreg +from qutebrowser.utils import message, log from qutebrowser.browser import qutescheme # A mapping of QProcess::ErrorCode's to human-readable strings. @@ -49,7 +49,6 @@ class GUIProcess(QObject): cmd: The command which was started. args: A list of arguments which gets passed. verbose: Whether to show more messages. - _output: Whether to show the output in a new tab. _started: Whether the underlying process is started. _proc: The underlying QProcess. _what: What kind of thing is spawned (process/editor/userscript/...). @@ -64,11 +63,10 @@ class GUIProcess(QObject): started = pyqtSignal() def __init__(self, what, *, verbose=False, additional_env=None, - parent=None, output=False): + parent=None): super().__init__(parent) self._what = what self.verbose = verbose - self._output = output self._started = False self.cmd = None self.args = None @@ -103,18 +101,8 @@ class GUIProcess(QObject): stderr = bytes(self._proc.readAllStandardError()).decode('utf-8') stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8') - spawn_log = "Process finished with code {}, status {}.".format( - code, status) - - spawn_log += "\nProcess stdout:\n" + (stdout or "(No output)").strip() - spawn_log += "\nProcess stderr:\n" + (stderr or "(No output)").strip() - - qutescheme.spawn_output = spawn_log - - if self._output: - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window='last-focused') - tabbed_browser.openurl(QUrl('qute://spawn-output'), newtab=True) + qutescheme.spawn_output = self.spawn_format(code, status, + stdout, stderr) if status == QProcess.CrashExit: message.error("{} crashed!".format(self._what.capitalize())) @@ -134,6 +122,17 @@ class GUIProcess(QObject): if stderr: log.procs.error("Process stderr:\n" + stderr.strip()) + def spawn_format(self, code=0, status=0, stdout="", stderr=""): + """Produce a formatted string for spawn output.""" + stdout = (stdout or "(No output)").strip() + stderr = (stderr or "(No output)").strip() + + spawn_string = ("Process finished with code {}, status {}\n" + "\nProcess stdout:\n {}" + "\nProcess stderr:\n {}").format(code, status, + stdout, stderr) + return spawn_string + @pyqtSlot() def on_started(self): """Called when the process started successfully.""" From 3b10584749c09b44e85ce45233da7d3fe0e870e9 Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Sun, 10 Dec 2017 23:46:35 +0000 Subject: [PATCH 05/20] Update tests to work with the earlier consumption of stdin etc. Note: this adds an element to vulture's whitelist that vulture mistakenly identified as unused. --- scripts/dev/run_vulture.py | 1 + tests/unit/misc/test_guiprocess.py | 30 +++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/scripts/dev/run_vulture.py b/scripts/dev/run_vulture.py index 9d21ad428..657d4b85e 100755 --- a/scripts/dev/run_vulture.py +++ b/scripts/dev/run_vulture.py @@ -82,6 +82,7 @@ def whitelist_generator(): # noqa yield 'qutebrowser.utils.jinja.Loader.get_source' yield 'qutebrowser.utils.log.QtWarningFilter.filter' yield 'qutebrowser.browser.pdfjs.is_available' + yield 'qutebrowser.misc.guiprocess.spawn_output' yield 'QEvent.posted' yield 'log_stack' # from message.py yield 'propagate' # logging.getLogger('...).propagate = False diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py index 674c250e5..69ce3812f 100644 --- a/tests/unit/misc/test_guiprocess.py +++ b/tests/unit/misc/test_guiprocess.py @@ -19,7 +19,6 @@ """Tests for qutebrowser.misc.guiprocess.""" -import json import logging import pytest @@ -27,6 +26,7 @@ from PyQt5.QtCore import QProcess, QIODevice from qutebrowser.misc import guiprocess from qutebrowser.utils import usertypes +from qutebrowser.browser import qutescheme @pytest.fixture() @@ -60,7 +60,7 @@ def test_start(proc, qtbot, message_mock, py_proc): proc.start(*argv) assert not message_mock.messages - assert bytes(proc._proc.readAll()).rstrip() == b'test' + assert qutescheme.spawn_output == proc.spawn_format(0, 0, stdout="test") def test_start_verbose(proc, qtbot, message_mock, py_proc): @@ -77,7 +77,24 @@ def test_start_verbose(proc, qtbot, message_mock, py_proc): assert msgs[1].level == usertypes.MessageLevel.info assert msgs[0].text.startswith("Executing:") assert msgs[1].text == "Testprocess exited successfully." - assert bytes(proc._proc.readAll()).rstrip() == b'test' + assert qutescheme.spawn_output == proc.spawn_format(0, 0, stdout="test") + + +def test_start_output(proc, qtbot, message_mock, py_proc): + """Test starting a process verbosely.""" + proc.verbose = True + + with qtbot.waitSignals([proc.started, proc.finished], timeout=10000, + order='strict'): + argv = py_proc("import sys; print('test'); sys.exit(0)") + proc.start(*argv) + + msgs = message_mock.messages + assert msgs[0].level == usertypes.MessageLevel.info + assert msgs[1].level == usertypes.MessageLevel.info + assert msgs[0].text.startswith("Executing:") + assert msgs[1].text == "Testprocess exited successfully." + assert qutescheme.spawn_output == proc.spawn_format(0, 0, stdout="test") def test_start_env(monkeypatch, qtbot, py_proc): @@ -99,10 +116,9 @@ def test_start_env(monkeypatch, qtbot, py_proc): order='strict'): proc.start(*argv) - data = bytes(proc._proc.readAll()).decode('utf-8') - ret_env = json.loads(data) - assert 'QUTEBROWSER_TEST_1' in ret_env - assert 'QUTEBROWSER_TEST_2' in ret_env + data = qutescheme.spawn_output + assert 'QUTEBROWSER_TEST_1' in data + assert 'QUTEBROWSER_TEST_2' in data @pytest.mark.qt_log_ignore('QIODevice::read.*: WriteOnly device') From a2bcd68d5628bcc1f0df47dd965bedf302855729 Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Mon, 11 Dec 2017 13:35:39 +0000 Subject: [PATCH 06/20] Code review changes. This fixes whitespace and alignment issues, and removes a stray test. --- qutebrowser/browser/commands.py | 4 ++-- qutebrowser/misc/guiprocess.py | 6 +++--- tests/unit/misc/test_guiprocess.py | 21 ++------------------- 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 495d48414..3075a24da 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1178,7 +1178,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0, no_replace_variables=True) def spawn(self, cmdline, userscript=False, verbose=False, - output=False, detach=False): + output=False, detach=False): """Spawn a command in a shell. Args: @@ -1218,7 +1218,7 @@ class CommandDispatcher: if output: tabbed_browser = objreg.get('tabbed-browser', scope='window', - window='last-focused') + window='last-focused') tabbed_browser.openurl(QUrl('qute://spawn-output'), newtab=True) @cmdutils.register(instance='command-dispatcher', scope='window') diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 80803f53f..4b74a512d 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -101,7 +101,7 @@ class GUIProcess(QObject): stderr = bytes(self._proc.readAllStandardError()).decode('utf-8') stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8') - qutescheme.spawn_output = self.spawn_format(code, status, + qutescheme.spawn_output = self._spawn_format(code, status, stdout, stderr) if status == QProcess.CrashExit: @@ -122,7 +122,7 @@ class GUIProcess(QObject): if stderr: log.procs.error("Process stderr:\n" + stderr.strip()) - def spawn_format(self, code=0, status=0, stdout="", stderr=""): + def _spawn_format(self, code=0, status=0, stdout="", stderr=""): """Produce a formatted string for spawn output.""" stdout = (stdout or "(No output)").strip() stderr = (stderr or "(No output)").strip() @@ -130,7 +130,7 @@ class GUIProcess(QObject): spawn_string = ("Process finished with code {}, status {}\n" "\nProcess stdout:\n {}" "\nProcess stderr:\n {}").format(code, status, - stdout, stderr) + stdout, stderr) return spawn_string @pyqtSlot() diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py index 69ce3812f..25e46476e 100644 --- a/tests/unit/misc/test_guiprocess.py +++ b/tests/unit/misc/test_guiprocess.py @@ -60,7 +60,7 @@ def test_start(proc, qtbot, message_mock, py_proc): proc.start(*argv) assert not message_mock.messages - assert qutescheme.spawn_output == proc.spawn_format(0, 0, stdout="test") + assert qutescheme.spawn_output == proc._spawn_format(stdout="test") def test_start_verbose(proc, qtbot, message_mock, py_proc): @@ -77,24 +77,7 @@ def test_start_verbose(proc, qtbot, message_mock, py_proc): assert msgs[1].level == usertypes.MessageLevel.info assert msgs[0].text.startswith("Executing:") assert msgs[1].text == "Testprocess exited successfully." - assert qutescheme.spawn_output == proc.spawn_format(0, 0, stdout="test") - - -def test_start_output(proc, qtbot, message_mock, py_proc): - """Test starting a process verbosely.""" - proc.verbose = True - - with qtbot.waitSignals([proc.started, proc.finished], timeout=10000, - order='strict'): - argv = py_proc("import sys; print('test'); sys.exit(0)") - proc.start(*argv) - - msgs = message_mock.messages - assert msgs[0].level == usertypes.MessageLevel.info - assert msgs[1].level == usertypes.MessageLevel.info - assert msgs[0].text.startswith("Executing:") - assert msgs[1].text == "Testprocess exited successfully." - assert qutescheme.spawn_output == proc.spawn_format(0, 0, stdout="test") + assert qutescheme.spawn_output == proc._spawn_format(stdout="test") def test_start_env(monkeypatch, qtbot, py_proc): From dd7a082265d4e15567c31bf61d1baec0fb232892 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 11 Dec 2017 16:23:12 +0100 Subject: [PATCH 07/20] Update codecov from 2.0.9 to 2.0.10 --- misc/requirements/requirements-codecov.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index 31c319c39..6601cfb12 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -2,7 +2,7 @@ certifi==2017.11.5 chardet==3.0.4 -codecov==2.0.9 +codecov==2.0.10 coverage==4.4.2 idna==2.6 requests==2.18.4 From 519dc6a7c9f890f2377fc71d716f7d0ddfa4b6ce Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 11 Dec 2017 16:23:13 +0100 Subject: [PATCH 08/20] Update setuptools from 38.2.3 to 38.2.4 --- misc/requirements/requirements-pip.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt index 8ca5a867e..b7914cac5 100644 --- a/misc/requirements/requirements-pip.txt +++ b/misc/requirements/requirements-pip.txt @@ -3,6 +3,6 @@ appdirs==1.4.3 packaging==16.8 pyparsing==2.2.0 -setuptools==38.2.3 +setuptools==38.2.4 six==1.11.0 wheel==0.30.0 From 713a2ef2c169a19db1ab6f7c97e60c2e1d2e2806 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 11 Dec 2017 16:23:15 +0100 Subject: [PATCH 09/20] Update pylint from 1.7.4 to 1.7.5 --- misc/requirements/requirements-pylint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index cab15c497..346fa4227 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -8,7 +8,7 @@ idna==2.6 isort==4.2.15 lazy-object-proxy==1.3.1 mccabe==0.6.1 -pylint==1.7.4 +pylint==1.7.5 ./scripts/dev/pylint_checkers requests==2.18.4 six==1.11.0 From 5d8e3a969f59c0484bb7f45087166e728b4467d1 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 11 Dec 2017 16:23:16 +0100 Subject: [PATCH 10/20] Update cheroot from 5.10.0 to 6.0.0 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 46905f497..62ea92c3c 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -2,7 +2,7 @@ attrs==17.3.0 beautifulsoup4==4.6.0 -cheroot==5.10.0 +cheroot==6.0.0 click==6.7 # colorama==0.3.9 coverage==4.4.2 From 22fe42d38ea76f261aea69e8c8d961517af18b03 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 11 Dec 2017 16:23:18 +0100 Subject: [PATCH 11/20] Update hypothesis from 3.40.1 to 3.42.1 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 62ea92c3c..439267282 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -11,7 +11,7 @@ fields==5.0.0 Flask==0.12.2 glob2==0.6 hunter==2.0.2 -hypothesis==3.40.1 +hypothesis==3.42.1 itsdangerous==0.24 # Jinja2==2.10 Mako==1.0.7 From d57a81a3d347975c7cb16a6194261ae320a9b5ad Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 11 Dec 2017 16:23:19 +0100 Subject: [PATCH 12/20] Update pytest from 3.3.0 to 3.3.1 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 439267282..bfb8a5ea3 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -21,7 +21,7 @@ parse-type==0.4.2 pluggy==0.6.0 py==1.5.2 py-cpuinfo==3.3.0 -pytest==3.3.0 +pytest==3.3.1 pytest-bdd==2.19.0 pytest-benchmark==3.1.1 pytest-cov==2.5.1 From d114deac70d50b843291b9d3cfafe122e7afef34 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 11 Dec 2017 16:23:21 +0100 Subject: [PATCH 13/20] Update werkzeug from 0.12.2 to 0.13 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index bfb8a5ea3..bf80acee7 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -36,4 +36,4 @@ pytest-xvfb==1.0.0 PyVirtualDisplay==0.2.1 six==1.11.0 vulture==0.26 -Werkzeug==0.12.2 +Werkzeug==0.13 From 81bfa814486204ffecaf89325ae57e0e12f300f7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 12 Dec 2017 06:45:47 +0100 Subject: [PATCH 14/20] Don't run end2end tests on macOS anymore They are just too flaky on macOS to be useful, and I have no idea how to make things more stable there --- scripts/dev/ci/travis_run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev/ci/travis_run.sh b/scripts/dev/ci/travis_run.sh index b7d44968e..55ca7c11e 100644 --- a/scripts/dev/ci/travis_run.sh +++ b/scripts/dev/ci/travis_run.sh @@ -26,7 +26,7 @@ elif [[ $TESTENV == shellcheck ]]; then koalaman/shellcheck:latest "${scripts[@]}" else args=() - [[ $TRAVIS_OS_NAME == osx ]] && args=('--qute-bdd-webengine' '--no-xvfb') + [[ $TRAVIS_OS_NAME == osx ]] && args=('--qute-bdd-webengine' '--no-xvfb' 'tests/unit') tox -e "$TESTENV" -- "${args[@]}" fi From 4f2f1a64940253b0eb3b387b43999483584a93ac Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 12 Dec 2017 06:57:39 +0100 Subject: [PATCH 15/20] Make sure editor test doesn't run on Windows --- tests/end2end/features/test_editor_bdd.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/end2end/features/test_editor_bdd.py b/tests/end2end/features/test_editor_bdd.py index 3e2b2ee8d..260197a46 100644 --- a/tests/end2end/features/test_editor_bdd.py +++ b/tests/end2end/features/test_editor_bdd.py @@ -26,6 +26,8 @@ import signal import pytest_bdd as bdd bdd.scenarios('editor.feature') +from qutebrowser.utils import utils + @bdd.when(bdd.parsers.parse('I set up a fake editor replacing "{text}" by ' '"{replacement}"')) @@ -71,6 +73,7 @@ def set_up_editor_empty(quteproc, tmpdir): @bdd.when(bdd.parsers.parse('I set up a fake editor that waits')) def set_up_editor_wait(quteproc, tmpdir): """Set up editor.command to a small python script inserting a text.""" + assert not utils.is_windows pidfile = tmpdir / 'editor_pid' script = tmpdir / 'script.py' script.write(textwrap.dedent(""" From 12f6304659f3ef83a0a206fdb5c0fdb296a2b1a7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 12 Dec 2017 07:03:48 +0100 Subject: [PATCH 16/20] Fix indent --- qutebrowser/misc/guiprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 4b74a512d..4fb887de0 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -102,7 +102,7 @@ class GUIProcess(QObject): stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8') qutescheme.spawn_output = self._spawn_format(code, status, - stdout, stderr) + stdout, stderr) if status == QProcess.CrashExit: message.error("{} crashed!".format(self._what.capitalize())) From 5a97e79099683e59c0ab99d9ba84fbd6e595220a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 12 Dec 2017 07:05:55 +0100 Subject: [PATCH 17/20] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index fddb3f6a5..72dc695dd 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -57,6 +57,7 @@ Added when restoring a session. - New `hist_importer.py` script to import history from Firefox/Chromium. - New `{protocol}` replacement for `tabs.title.format` and friends. +- New `-o` flag for `:spawn` to show stdout/stderr in a new tab. Changed ~~~~~~~ From 6655793e6abdb6e0bbd016cff58236c89715f577 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 12 Dec 2017 09:33:34 +0100 Subject: [PATCH 18/20] Use 'terminate' to clean up webserver subprocess We already have TestProcess.terminate which does exactly the same WebserverProcess.cleanup does. See #3384 --- tests/end2end/fixtures/webserver.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/end2end/fixtures/webserver.py b/tests/end2end/fixtures/webserver.py index 85a6af070..a40c62015 100644 --- a/tests/end2end/fixtures/webserver.py +++ b/tests/end2end/fixtures/webserver.py @@ -172,11 +172,6 @@ class WebserverProcess(testprocess.Process): def _default_args(self): return [str(self.port)] - def cleanup(self): - """Clean up and shut down the process.""" - self.proc.terminate() - self.proc.waitForFinished() - @pytest.fixture(scope='session', autouse=True) def server(qapp): @@ -184,7 +179,7 @@ def server(qapp): server = WebserverProcess('webserver_sub') server.start() yield server - server.cleanup() + server.terminate() @pytest.fixture(autouse=True) @@ -208,4 +203,4 @@ def ssl_server(request, qapp): server.start() yield server server.after_test() - server.cleanup() + server.terminate() From 2e8acf482591958924587976106716000b570bee Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 12 Dec 2017 09:34:57 +0100 Subject: [PATCH 19/20] Improve terminating of test processes There are various small changes here: - If the process is already finished, we don't try to terminate it. - On Windows, we use QProcess::kill instead of QProcess::terminate, as terminate will only work with processes which have a GUI loop. - We assert that quitting the suprocess actually worked. Fixes #3384 --- tests/end2end/fixtures/testprocess.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/end2end/fixtures/testprocess.py b/tests/end2end/fixtures/testprocess.py index bc987043f..570a3125c 100644 --- a/tests/end2end/fixtures/testprocess.py +++ b/tests/end2end/fixtures/testprocess.py @@ -300,8 +300,16 @@ class Process(QObject): def terminate(self): """Clean up and shut down the process.""" - self.proc.terminate() - self.proc.waitForFinished() + if not self.is_running(): + return + + if quteutils.is_windows: + self.proc.kill() + else: + self.proc.terminate() + + ok = self.proc.waitForFinished() + assert ok def is_running(self): """Check if the process is currently running.""" From 5fe91c30cc0eb3b960ef1a256a302cf89996a273 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 12 Dec 2017 11:33:34 +0100 Subject: [PATCH 20/20] Fix :click-element with an ID containing non-alphanumeric characters See #3201 --- doc/changelog.asciidoc | 1 + qutebrowser/browser/webkit/webkittab.py | 5 +++++ tests/end2end/data/click_element.html | 1 + tests/end2end/features/misc.feature | 5 +++++ 4 files changed, 12 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 72dc695dd..8095f1bd8 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -119,6 +119,7 @@ Fixed - Fixed crash when closing the tab an external editor was opened in. - When using `:search-next` before a search is finished, no warning about no results being found is shown anymore. +- Fix :click-element with an ID containing non-alphanumeric characters. Deprecated ~~~~~~~~~~ diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 43bec3456..f16beb7fe 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -19,6 +19,7 @@ """Wrapper over our (QtWebKit) WebView.""" +import re import functools import xml.etree.ElementTree @@ -545,6 +546,10 @@ class WebKitElements(browsertab.AbstractElements): callback(None) else: callback(elems[0]) + + # Escape non-alphanumeric characters in the selector + # https://www.w3.org/TR/CSS2/syndata.html#value-def-identifier + elem_id = re.sub(r'[^a-zA-Z0-9_-]', r'\\\g<0>', elem_id) self.find_css('#' + elem_id, find_id_cb) def find_focused(self, callback): diff --git a/tests/end2end/data/click_element.html b/tests/end2end/data/click_element.html index 55bf8b88c..acf0cf77c 100644 --- a/tests/end2end/data/click_element.html +++ b/tests/end2end/data/click_element.html @@ -9,5 +9,6 @@ Duplicate
link + ID with dot diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 70505c1f8..82ae8cf91 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -449,6 +449,11 @@ Feature: Various utility commands. And I run :click-element id qute-input Then "Entering mode KeyMode.insert (reason: clicking input)" should be logged + Scenario: Clicking an element by ID with dot + When I open data/click_element.html + And I run :click-element id foo.bar + Then the javascript message "id with dot" should be logged + Scenario: Clicking an element with tab target When I open data/click_element.html And I run :tab-only