diff options
Diffstat (limited to 'tests/integration')
20 files changed, 525 insertions, 53 deletions
diff --git a/tests/integration/data/downloads/ä-issue908.bin b/tests/integration/data/downloads/ä-issue908.bin Binary files differnew file mode 100644 index 000000000..f76dd238a --- /dev/null +++ b/tests/integration/data/downloads/ä-issue908.bin diff --git a/tests/integration/data/marks.html b/tests/integration/data/marks.html new file mode 100644 index 000000000..574e2cdd1 --- /dev/null +++ b/tests/integration/data/marks.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Marks I</title> + </head> + <body> + <h1 id="top">Top</h1> + <a href="#top">Top</a> + <a href="#bottom">Bottom</a> + <div style="height: 3000px; width: 3000px;">Holy Grail</div> + <div style="height: 3000px; width: 3000px;">Waldo</div> + <div style="height: 3000px; width: 3000px;">Holy Grail</div> + <h1 id="bottom">Bottom</h1> + </body> +</html> diff --git a/tests/integration/data/title with spaces.html b/tests/integration/data/title with spaces.html new file mode 100644 index 000000000..d5e2ab9da --- /dev/null +++ b/tests/integration/data/title with spaces.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Test title</title> + </head> + <body> + foo + </body> +</html> diff --git a/tests/integration/features/conftest.py b/tests/integration/features/conftest.py index a099a1887..863cc1a25 100644 --- a/tests/integration/features/conftest.py +++ b/tests/integration/features/conftest.py @@ -28,7 +28,6 @@ import collections import textwrap import pytest -import yaml import pytest_bdd as bdd from helpers import utils @@ -180,6 +179,7 @@ def wait_in_log(quteproc, is_regex, pattern, do_skip): r'"(?P<message>.*)"')) def wait_for_message(quteproc, httpbin, category, message): """Wait for a given statusbar message/error/warning.""" + quteproc.log_summary('Waiting for {} "{}"'.format(category, message)) expect_message(quteproc, httpbin, category, message) @@ -275,10 +275,12 @@ def expect_message(quteproc, httpbin, category, message): @bdd.then(bdd.parsers.re(r'(?P<is_regex>regex )?"(?P<pattern>[^"]+)" should ' r'be logged')) -def should_be_logged(quteproc, is_regex, pattern): +def should_be_logged(quteproc, httpbin, is_regex, pattern): """Expect the given pattern on regex in the log.""" if is_regex: pattern = re.compile(pattern) + else: + pattern = pattern.replace('(port)', str(httpbin.port)) line = quteproc.wait_for(message=pattern) line.expected = True @@ -312,21 +314,15 @@ def compare_session(quteproc, expected): partial_compare is used, which means only the keys/values listed will be compared. """ - # Translate ... to ellipsis in YAML. - loader = yaml.SafeLoader(expected) - loader.add_constructor('!ellipsis', lambda loader, node: ...) - loader.add_implicit_resolver('!ellipsis', re.compile(r'\.\.\.'), None) - - data = quteproc.get_session() - expected = loader.get_data() - assert utils.partial_compare(data, expected) + quteproc.compare_session(expected) @bdd.then("no crash should happen") def no_crash(): """Don't do anything. - This is actually a NOP as a crash is already checked in the log.""" + This is actually a NOP as a crash is already checked in the log. + """ pass diff --git a/tests/integration/features/downloads.feature b/tests/integration/features/downloads.feature index 8097711a5..fc401fe76 100644 --- a/tests/integration/features/downloads.feature +++ b/tests/integration/features/downloads.feature @@ -162,6 +162,26 @@ Feature: Downloading things from a website. And I run :download-open with count 1 Then the error "Download 1 is not done!" should be shown + ## completion -> download-path-suggestion + + Scenario: completion -> download-path-suggestion = path + When I set storage -> prompt-download-directory to true + And I set completion -> download-path-suggestion to path + And I open data/downloads/download.bin + Then the download prompt should be shown with "{downloaddir}/" + + Scenario: completion -> download-path-suggestion = filename + When I set storage -> prompt-download-directory to true + And I set completion -> download-path-suggestion to filename + And I open data/downloads/download.bin + Then the download prompt should be shown with "download.bin" + + Scenario: completion -> download-path-suggestion = both + When I set storage -> prompt-download-directory to true + And I set completion -> download-path-suggestion to both + And I open data/downloads/download.bin + Then the download prompt should be shown with "{downloaddir}/download.bin" + ## https://github.com/The-Compiler/qutebrowser/issues/1242 Scenario: Closing window with remove-finished-downloads timeout diff --git a/tests/integration/features/editor.feature b/tests/integration/features/editor.feature new file mode 100644 index 000000000..ef3798564 --- /dev/null +++ b/tests/integration/features/editor.feature @@ -0,0 +1,72 @@ +Feature: Opening external editors + + ## :edit-url + + Scenario: Editing an URL + When I open data/numbers/1.txt + And I set up a fake editor replacing "1.txt" by "2.txt" + And I run :edit-url + Then data/numbers/2.txt should be loaded + + Scenario: Editing an URL with -t + When I run :tab-only + And I open data/numbers/1.txt + And I set up a fake editor replacing "1.txt" by "2.txt" + And I run :edit-url -t + Then data/numbers/2.txt should be loaded + And the following tabs should be open: + - data/numbers/1.txt + - data/numbers/2.txt (active) + + Scenario: Editing an URL with -b + When I run :tab-only + And I open data/numbers/1.txt + And I set up a fake editor replacing "1.txt" by "2.txt" + And I run :edit-url -b + Then data/numbers/2.txt should be loaded + And the following tabs should be open: + - data/numbers/1.txt (active) + - data/numbers/2.txt + + Scenario: Editing an URL with -w + When I open data/numbers/1.txt in a new tab + And I run :tab-only + And I set up a fake editor replacing "1.txt" by "2.txt" + And I run :edit-url -w + Then data/numbers/2.txt should be loaded + And the session should look like: + windows: + - tabs: + - active: true + history: + - active: true + url: http://localhost:*/data/numbers/1.txt + - tabs: + - active: true + history: + - active: true + url: http://localhost:*/data/numbers/2.txt + + Scenario: Editing an URL with count + Given I have a fresh instance + When I open data/numbers/1.txt + And I run :tab-only + And I open about:blank in a new tab + And I run :tab-focus 1 + And I set up a fake editor replacing "1.txt" by "2.txt" + And I run :edit-url with count 2 + Then data/numbers/2.txt should be loaded + And the following tabs should be open: + - data/numbers/1.txt (active) + - data/numbers/2.txt + + Scenario: Editing an URL with -t and -b + When I run :edit-url -t -b + Then the error "Only one of -t/-b/-w can be given!" should be shown + + Scenario: Editing an URL with invalid URL + When I set general -> auto-search to false + And I open data/hello.txt + And I set up a fake editor replacing "http://localhost:(port)/data/hello.txt" by "foo!" + And I run :edit-url + Then the error "Invalid URL" should be shown diff --git a/tests/integration/features/hints.feature b/tests/integration/features/hints.feature index 3f6fab924..062de293c 100644 --- a/tests/integration/features/hints.feature +++ b/tests/integration/features/hints.feature @@ -44,3 +44,10 @@ Feature: Using hints And I wait until data/hello.txt is loaded Then the following tabs should be open: - data/hello.txt (active) + + @xfail + Scenario: Using :hint spawn with flags (issue 797) + When I open data/hints/link.html + And I run :hint all spawn -v echo + And I run :follow-hint a + Then the message "Command exited successfully" should be shown diff --git a/tests/integration/features/marks.feature b/tests/integration/features/marks.feature new file mode 100644 index 000000000..3a35791b8 --- /dev/null +++ b/tests/integration/features/marks.feature @@ -0,0 +1,91 @@ +Feature: Setting positional marks + + Background: + Given I open data/marks.html + And I run :tab-only + + ## :set-mark, :jump-mark + + Scenario: Setting and jumping to a local mark + When I run :scroll-px 5 10 + And I run :set-mark a + And I run :scroll-px 0 20 + And I run :jump-mark a + Then the page should be scrolled to 5 10 + + Scenario: Jumping back after jumping to a particular percentage + When I run :scroll-px 10 20 + And I run :scroll-perc 100 + And I run :jump-mark "'" + Then the page should be scrolled to 10 20 + + Scenario: Setting the same local mark on another page + When I run :scroll-px 5 10 + And I run :set-mark a + And I open data/marks.html + And I run :scroll-px 0 20 + And I run :set-mark a + And I run :jump-mark a + Then the page should be scrolled to 0 20 + + Scenario: Jumping to a local mark after returning to a page + When I run :scroll-px 5 10 + And I run :set-mark a + And I open data/numbers/1.txt + And I run :set-mark a + And I open data/marks.html + And I run :jump-mark a + Then the page should be scrolled to 5 10 + + Scenario: Setting and jumping to a global mark + When I run :scroll-px 5 20 + And I run :set-mark A + And I open data/numbers/1.txt + And I run :jump-mark A + Then data/marks.html should be loaded + And the page should be scrolled to 5 20 + + Scenario: Jumping to an unset mark + When I run :jump-mark b + Then the error "Mark b is not set" should be shown + + Scenario: Jumping to a local mark that was set on another page + When I run :set-mark b + And I open data/numbers/1.txt + And I run :jump-mark b + Then the error "Mark b is not set" should be shown + + Scenario: Jumping to a local mark after changing fragments + When I open data/marks.html#top + And I run :scroll 'top' + And I run :scroll-px 10 10 + And I run :set-mark a + When I open data/marks.html#bottom + And I run :jump-mark a + Then the page should be scrolled to 10 10 + + Scenario: Jumping back after following a link + When I run :hint links normal + And I run :follow-hint s + And I run :jump-mark "'" + Then the page should be scrolled to 0 0 + + Scenario: Jumping back after searching + When I run :scroll-px 20 15 + And I run :search Waldo + And I run :jump-mark "'" + Then the page should be scrolled to 20 15 + + Scenario: Jumping back after search-next + When I run :search Grail + And I run :search-next + And I run :jump-mark "'" + Then the page should be scrolled to 0 0 + + Scenario: Hovering a hint does not set the ' mark + When I run :scroll-px 30 20 + And I run :scroll-perc 0 + And I run :hint links hover + And I run :follow-hint s + And I run :jump-mark "'" + Then the page should be scrolled to 30 20 diff --git a/tests/integration/features/misc.feature b/tests/integration/features/misc.feature index 8f0ab8014..991560b7a 100644 --- a/tests/integration/features/misc.feature +++ b/tests/integration/features/misc.feature @@ -362,3 +362,10 @@ Feature: Various utility commands. And I press the key "<Tab>" And I press the key "<Ctrl-C>" Then no crash should happen + + ## Custom headers + + Scenario: Setting a custom header + When I set network -> custom-headers to {"X-Qute-Test": "testvalue"} + And I open headers + Then the header X-Qute-Test should be set to testvalue diff --git a/tests/integration/features/spawn.feature b/tests/integration/features/spawn.feature index 03506c18b..861091f91 100644 --- a/tests/integration/features/spawn.feature +++ b/tests/integration/features/spawn.feature @@ -16,9 +16,20 @@ Feature: :spawn When I run :spawn echo {url} Then "Executing echo with args ['about:blank'], userscript=False" should be logged + Scenario: Running :spawn with url variable in fully encoded format + When I open data/title with spaces.html + And I run :spawn echo {url} + Then "Executing echo with args ['http://localhost:(port)/data/title%20with%20spaces.html'], userscript=False" should be logged + + Scenario: Running :spawn with url variable in pretty decoded format + When I open data/title with spaces.html + And I run :spawn echo {url:pretty} + Then "Executing echo with args ['http://localhost:(port)/data/title with spaces.html'], userscript=False" should be logged + @posix Scenario: Running :spawn with userscript - When I execute the userscript open_current_url + When I open about:blank + And I execute the userscript open_current_url And I wait until about:blank is loaded Then the following tabs should be open: - about:blank diff --git a/tests/integration/features/tabs.feature b/tests/integration/features/tabs.feature index 630fa6279..cb7b54da8 100644 --- a/tests/integration/features/tabs.feature +++ b/tests/integration/features/tabs.feature @@ -228,6 +228,34 @@ Feature: Tab management - data/numbers/2.txt - data/numbers/3.txt + Scenario: :tab-focus with -1 + When I open data/numbers/1.txt + And I open data/numbers/2.txt in a new tab + And I open data/numbers/3.txt in a new tab + And I run :tab-focus 1 + And I run :tab-focus -1 + Then the following tabs should be open: + - data/numbers/1.txt + - data/numbers/2.txt + - data/numbers/3.txt (active) + + Scenario: :tab-focus negative index + When I open data/numbers/1.txt + And I open data/numbers/2.txt in a new tab + And I open data/numbers/3.txt in a new tab + And I run :tab-focus -2 + Then the following tabs should be open: + - data/numbers/1.txt + - data/numbers/2.txt (active) + - data/numbers/3.txt + + Scenario: :tab-focus with invalid negative index + When I open data/numbers/1.txt + And I open data/numbers/2.txt in a new tab + And I open data/numbers/3.txt in a new tab + And I run :tab-focus -5 + Then the error "There's no tab with index -1!" should be shown + Scenario: :tab-focus last with no last focused tab Given I have a fresh instance And I run :tab-focus last @@ -712,12 +740,12 @@ Feature: Tab management # :buffer - Scenario: buffer without args + Scenario: :buffer without args Given I have a fresh instance When I run :buffer Then the error "buffer: The following arguments are required: index" should be shown - Scenario: buffer one window title present + Scenario: :buffer with a matching title When I open data/title.html And I open data/search.html in a new tab And I open data/scroll.html in a new tab @@ -727,11 +755,11 @@ Feature: Tab management - data/search.html (active) - data/scroll.html - Scenario: buffer one window title not present + Scenario: :buffer with no matching title When I run :buffer "invalid title" Then the error "No matching tab for: invalid title" should be shown - Scenario: buffer two window title present + Scenario: :buffer with matching title and two windows When I open data/title.html And I open data/search.html in a new tab And I open data/scroll.html in a new tab @@ -757,17 +785,17 @@ Feature: Tab management history: - url: http://localhost:*/data/paste_primary.html - Scenario: buffer one window index not present + Scenario: :buffer with no matching index When I open data/title.html And I run :buffer "666" Then the error "There's no tab with index 666!" should be shown - Scenario: buffer one window win not present + Scenario: :buffer with no matching window index When I open data/title.html And I run :buffer "2/1" Then the error "There's no window with id 2!" should be shown - Scenario: buffer two window index present + Scenario: :buffer with matching window index Given I have a fresh instance When I open data/title.html And I open data/search.html in a new tab @@ -776,6 +804,7 @@ Feature: Tab management And I open data/paste_primary.html in a new tab And I wait until data/caret.html is loaded And I run :buffer "0/2" + And I wait for "Current tab changed, *" in the log Then the session should look like: windows: - active: true @@ -795,30 +824,44 @@ Feature: Tab management history: - url: http://localhost:*/data/paste_primary.html - Scenario: buffer troubling args 01 + Scenario: :buffer with wrong argument (-1) Given I have a fresh instance When I open data/title.html And I run :buffer "-1" Then the error "There's no tab with index -1!" should be shown - Scenario: buffer troubling args 02 + Scenario: :buffer with wrong argument (/) When I open data/title.html And I run :buffer "/" Then the following tabs should be open: - data/title.html (active) - Scenario: buffer troubling args 03 + Scenario: :buffer with wrong argument (//) When I open data/title.html And I run :buffer "//" Then the following tabs should be open: - data/title.html (active) - Scenario: buffer troubling args 04 + Scenario: :buffer with wrong argument (0/x) When I open data/title.html And I run :buffer "0/x" Then the error "No matching tab for: 0/x" should be shown - Scenario: buffer troubling args 05 + Scenario: :buffer with wrong argument (1/2/3) When I open data/title.html And I run :buffer "1/2/3" Then the error "No matching tab for: 1/2/3" should be shown + + Scenario: Using :tab-next after closing last tab (#1448) + When I set tabs -> last-close to close + And I run :tab-only + And I run :tab-close ;; :tab-next + Then qutebrowser should quit + And no crash should happen + + Scenario: Using :tab-prev after closing last tab (#1448) + When I set tabs -> last-close to close + And I run :tab-only + And I run :tab-close ;; :tab-prev + Then qutebrowser should quit + And no crash should happen diff --git a/tests/integration/features/test_downloads.py b/tests/integration/features/test_downloads.py index 51613dc61..36e32a758 100644 --- a/tests/integration/features/test_downloads.py +++ b/tests/integration/features/test_downloads.py @@ -17,6 +17,8 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. +import os + import pytest_bdd as bdd bdd.scenarios('downloads.feature') @@ -49,3 +51,14 @@ def download_should_not_exist(filename, tmpdir): def download_should_exist(filename, tmpdir): path = tmpdir / filename assert path.check() + + +@bdd.then(bdd.parsers.parse('The download prompt should be shown with ' + '"{path}"')) +def download_prompt(tmpdir, quteproc, path): + full_path = path.replace('{downloaddir}', str(tmpdir)).replace('/', os.sep) + msg = ("Asking question <qutebrowser.utils.usertypes.Question " + "default={full_path!r} mode=<PromptMode.text: 2> " + "text='Save file to:'>, *".format(full_path=full_path)) + quteproc.wait_for(message=msg) + quteproc.send_cmd(':leave-mode') diff --git a/tests/integration/features/test_editor.py b/tests/integration/features/test_editor.py new file mode 100644 index 000000000..7b400705f --- /dev/null +++ b/tests/integration/features/test_editor.py @@ -0,0 +1,45 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org> +# +# 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/>. + +import sys +import textwrap + +import pytest_bdd as bdd +bdd.scenarios('editor.feature') + + +@bdd.when(bdd.parsers.parse('I set up a fake editor replacing "{text}" by ' + '"{replacement}"')) +def set_up_editor_replacement(quteproc, httpbin, tmpdir, text, replacement): + """Set up general->editor to a small python script doing a replacement.""" + text = text.replace('(port)', str(httpbin.port)) + script = tmpdir / 'script.py' + script.write(textwrap.dedent(""" + import sys + + with open(sys.argv[1], encoding='utf-8') as f: + data = f.read() + + data = data.replace("{text}", "{replacement}") + + with open(sys.argv[1], 'w', encoding='utf-8') as f: + f.write(data) + """.format(text=text, replacement=replacement))) + editor = '"{}" "{}" {{}}'.format(sys.executable, script) + quteproc.set_setting('general', 'editor', editor) diff --git a/tests/integration/features/test_marks.py b/tests/integration/features/test_marks.py new file mode 100644 index 000000000..b2777cbe4 --- /dev/null +++ b/tests/integration/features/test_marks.py @@ -0,0 +1,29 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2016 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net> +# +# 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/>. + +import pytest_bdd as bdd +bdd.scenarios('marks.feature') + + +@bdd.then(bdd.parsers.parse("the page should be scrolled to {x} {y}")) +def check_y(quteproc, x, y): + data = quteproc.get_session() + pos = data['windows'][0]['tabs'][0]['history'][-1]['scroll-pos'] + assert int(x) == pos['x'] + assert int(y) == pos['y'] diff --git a/tests/integration/features/yankpaste.feature b/tests/integration/features/yankpaste.feature index 8f6c6552f..58ae304b0 100644 --- a/tests/integration/features/yankpaste.feature +++ b/tests/integration/features/yankpaste.feature @@ -33,6 +33,18 @@ Feature: Yanking and pasting. Then the message "Yanked domain to clipboard: http://localhost:(port)" should be shown And the clipboard should contain "http://localhost:(port)" + Scenario: Yanking fully encoded URL + When I open data/title with spaces.html + And I run :yank + Then the message "Yanked URL to clipboard: http://localhost:(port)/data/title%20with%20spaces.html" should be shown + And the clipboard should contain "http://localhost:(port)/data/title%20with%20spaces.html" + + Scenario: Yanking pretty decoded URL + When I open data/title with spaces.html + And I run :yank --pretty + Then the message "Yanked URL to clipboard: http://localhost:(port)/data/title with spaces.html" should be shown + And the clipboard should contain "http://localhost:(port)/data/title with spaces.html" + #### :paste Scenario: Pasting an URL diff --git a/tests/integration/quteprocess.py b/tests/integration/quteprocess.py index 6154cfa5f..c7fa66732 100644 --- a/tests/integration/quteprocess.py +++ b/tests/integration/quteprocess.py @@ -320,6 +320,11 @@ class QuteProc(testprocess.Process): def send_cmd(self, command, count=None): """Send a command to the running qutebrowser instance.""" + summary = command + if count is not None: + summary += ' (count {})'.format(count) + self.log_summary(summary) + assert self._ipc_socket is not None time.sleep(self._delay / 1000) @@ -340,6 +345,8 @@ class QuteProc(testprocess.Process): return msg.message.split(' = ')[1] def set_setting(self, sect, opt, value): + # " in a value should be treated literally, so escape it + value = value.replace('"', '\\"') self.send_cmd(':set "{}" "{}" "{}"'.format(sect, opt, value)) self.wait_for(category='config', message='Config option changed: *') @@ -378,6 +385,8 @@ class QuteProc(testprocess.Process): def wait_for_load_finished(self, path, *, port=None, https=False, timeout=None, load_status='success'): """Wait until any tab has finished loading.""" + __tracebackhide__ = True + if timeout is None: if 'CI' in os.environ: timeout = 15000 @@ -393,7 +402,12 @@ class QuteProc(testprocess.Process): r"tab_id=\d+ url='{url}/?'>: LoadStatus\.{load_status}|fetch: " r"PyQt5\.QtCore\.QUrl\('{url}'\) -> .*)".format( load_status=re.escape(load_status), url=re.escape(url))) - self.wait_for(message=pattern, timeout=timeout) + + try: + self.wait_for(message=pattern, timeout=timeout) + except testprocess.WaitForTimeout: + raise testprocess.WaitForTimeout("Timed out while waiting for {} " + "to be loaded".format(url)) def get_session(self): """Save the session and get the parsed session data.""" @@ -405,7 +419,7 @@ class QuteProc(testprocess.Process): with open(session, encoding='utf-8') as f: data = f.read() - self._log(data) + self._log('\nCurrent session data:\n' + data) return yaml.load(data) def get_content(self, plain=True): @@ -450,6 +464,26 @@ class QuteProc(testprocess.Process): raise ValueError('Invalid response from qutebrowser: {}' .format(message)) + def compare_session(self, expected): + """Compare the current sessions against the given template. + + partial_compare is used, which means only the keys/values listed will + be compared. + """ + __tracebackhide__ = True + # Translate ... to ellipsis in YAML. + loader = yaml.SafeLoader(expected) + loader.add_constructor('!ellipsis', lambda loader, node: ...) + loader.add_implicit_resolver('!ellipsis', re.compile(r'\.\.\.'), None) + + data = self.get_session() + expected = loader.get_data() + outcome = testutils.partial_compare(data, expected) + if not outcome: + msg = "Session comparison failed: {}".format(outcome.error) + msg += '\nsee stdout for details' + pytest.fail(msg) + def _xpath_escape(text): """Escape a string to be used in an XPath expression. diff --git a/tests/integration/test_cmdline_args.py b/tests/integration/test_invocations.py index c88462207..a13c6ac25 100644 --- a/tests/integration/test_cmdline_args.py +++ b/tests/integration/test_invocations.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. -"""Test starting qutebrowser with various commandline arguments.""" +"""Test starting qutebrowser with special arguments/environments.""" import pytest @@ -51,3 +51,23 @@ def test_no_config(tmpdir, quteproc_new): quteproc_new.start(args, env=env) quteproc_new.send_cmd(':quit') quteproc_new.wait_for_quit() + + +@pytest.mark.linux +def test_ascii_locale(httpbin, tmpdir, quteproc_new): + """Test downloads with LC_ALL=C set. + + https://github.com/The-Compiler/qutebrowser/issues/908 + """ + args = ['--debug', '--no-err-windows', '--temp-basedir', 'about:blank'] + quteproc_new.start(args, env={'LC_ALL': 'C'}) + quteproc_new.set_setting('storage', 'download-directory', str(tmpdir)) + quteproc_new.set_setting('storage', 'prompt-download-directory', 'false') + url = 'http://localhost:{port}/data/downloads/ä-issue908.bin'.format( + port=httpbin.port) + quteproc_new.send_cmd(':download {}'.format(url)) + quteproc_new.send_cmd(':quit') + quteproc_new.wait_for_quit() + + assert len(tmpdir.listdir()) == 1 + assert (tmpdir / '?-issue908.bin').exists() diff --git a/tests/integration/test_quteprocess.py b/tests/integration/test_quteprocess.py index 7698e1a07..5110324ef 100644 --- a/tests/integration/test_quteprocess.py +++ b/tests/integration/test_quteprocess.py @@ -195,3 +195,13 @@ class TestClickElement: ]) def test_xpath_escape(string, expected): assert quteprocess._xpath_escape(string) == expected + + +@pytest.mark.parametrize('value', [ + 'foo', + 'foo"bar', # Make sure a " is preserved +]) +def test_set(quteproc, value): + quteproc.set_setting('network', 'accept-language', value) + read_back = quteproc.get_setting('network', 'accept-language') + assert read_back == value diff --git a/tests/integration/test_smoke.py b/tests/integration/test_smoke.py index 4d4f23b3f..cb4b600dc 100644 --- a/tests/integration/test_smoke.py +++ b/tests/integration/test_smoke.py @@ -19,10 +19,12 @@ """Test which simply runs qutebrowser to check if it starts properly.""" - import sys import os.path import subprocess +import signal + +import pytest def test_smoke(): @@ -32,7 +34,15 @@ def test_smoke(): argv = [sys.executable, '-m', 'qutebrowser'] argv += ['--debug', '--no-err-windows', '--nowindow', '--temp-basedir', 'about:blank', ':later 500 quit'] - subprocess.check_call(argv) + try: + subprocess.check_call(argv) + except subprocess.CalledProcessError as e: + if e.returncode == -signal.SIGSEGV: + # pylint: disable=no-member + # https://github.com/The-Compiler/qutebrowser/issues/1387 + pytest.xfail("Ignoring segfault on exit...") + else: + raise def test_smoke_quteproc(quteproc): diff --git a/tests/integration/testprocess.py b/tests/integration/testprocess.py index 5c50121e5..1898a24ba 100644 --- a/tests/integration/testprocess.py +++ b/tests/integration/testprocess.py @@ -31,6 +31,8 @@ from PyQt5.QtTest import QSignalSpy from helpers import utils +from qutebrowser.utils import utils as quteutils + class InvalidLine(Exception): @@ -76,7 +78,11 @@ class Line: def _render_log(data, threshold=100): """Shorten the given log without -v and convert to a string.""" # pylint: disable=no-member - if len(data) > threshold and not pytest.config.getoption('--verbose'): + is_exception = any('Traceback (most recent call last):' in line + for line in data) + if (len(data) > threshold and + not pytest.config.getoption('--verbose') and + not is_exception): msg = '[{} lines suppressed, use -v to show]'.format( len(data) - threshold) data = [msg] + data[-threshold:] @@ -151,6 +157,11 @@ class Process(QObject): print(line) self.captured_log.append(line) + def log_summary(self, text): + """Log the given line as summary/title.""" + text = '\n{line} {text} {line}\n'.format(line='='*30, text=text) + self._log(text) + def _parse_line(self, line): """Parse the given line from the log. @@ -276,7 +287,7 @@ class Process(QObject): # Exit the process to make sure we're in a defined state again self.terminate() self.clear_data() - raise InvalidLine(self._invalid) + raise InvalidLine self.clear_data() if not self.is_running() and not self.exit_expected: @@ -327,6 +338,7 @@ class Process(QObject): Return: either the found line or None. """ + __tracebackhide__ = True for line in self._data: matches = [] @@ -342,6 +354,39 @@ class Process(QObject): return line return None + def _wait_for_new(self, timeout, do_skip, **kwargs): + """Wait for a log message which doesn't exist yet. + + Called via wait_for. + """ + __tracebackhide__ = True + message = kwargs.get('message', None) + if message is not None: + elided = quteutils.elide(repr(message), 50) + self._log("\n----> Waiting for {} in the log".format(elided)) + + spy = QSignalSpy(self.new_data) + elapsed_timer = QElapsedTimer() + elapsed_timer.start() + + while True: + # Skip if there are pending messages causing a skip + self._maybe_skip() + got_signal = spy.wait(timeout) + if not got_signal or elapsed_timer.hasExpired(timeout): + msg = "Timed out after {}ms waiting for {!r}.".format( + timeout, kwargs) + if do_skip: + pytest.skip(msg) + else: + raise WaitForTimeout(msg) + + match = self._wait_for_match(spy, kwargs) + if match is not None: + if message is not None: + self._log("----> found it") + return match + def _wait_for_match(self, spy, kwargs): """Try matching the kwargs with the given QSignalSpy.""" for args in spy: @@ -406,31 +451,12 @@ class Process(QObject): for key in kwargs: assert key in self.KEYS - # Search existing messages existing = self._wait_for_existing(override_waited_for, **kwargs) if existing is not None: return existing - - # If there is none, wait for the message - spy = QSignalSpy(self.new_data) - elapsed_timer = QElapsedTimer() - elapsed_timer.start() - - while True: - # Skip if there are pending messages causing a skip - self._maybe_skip() - got_signal = spy.wait(timeout) - if not got_signal or elapsed_timer.hasExpired(timeout): - msg = "Timed out after {}ms waiting for {!r}.".format( - timeout, kwargs) - if do_skip: - pytest.skip(msg) - else: - raise WaitForTimeout(msg) - - match = self._wait_for_match(spy, kwargs) - if match is not None: - return match + else: + return self._wait_for_new(timeout=timeout, do_skip=do_skip, + **kwargs) def ensure_not_logged(self, delay=500, **kwargs): """Make sure the data matching the given arguments is not logged. |