Merge remote-tracking branch 'TheCompiler/master' into relax-editor-templating

This commit is contained in:
Oliver Caldwell 2016-01-31 22:43:58 +00:00
commit 54ff2aa46c
26 changed files with 234 additions and 58 deletions

13
.pydocstylerc Normal file
View File

@ -0,0 +1,13 @@
[pydocstyle]
# Disabled checks:
# D102: Missing docstring in public method (will be handled by others)
# D103: Missing docstring in public function (will be handled by others)
# D104: Missing docstring in public package (will be handled by others)
# D105: Missing docstring in magic method (will be handled by others)
# D209: Blank line before closing """ (removed from PEP257)
# D211: No blank lines allowed before class docstring
# (PEP257 got changed, but let's stick to the old standard)
# D402: First line should not be function's signature (false-positives)
ignore = D102,D103,D104,D105,D209,D211,D402
match = (?!resources|test_*).*\.py
inherit = false

View File

@ -16,7 +16,7 @@ env:
- TESTENV=unittests-nodisp - TESTENV=unittests-nodisp
- TESTENV=misc - TESTENV=misc
- TESTENV=vulture - TESTENV=vulture
- TESTENV=pep257 - TESTENV=pydocstyle
- TESTENV=flake8 - TESTENV=flake8
- TESTENV=pyroma - TESTENV=pyroma
- TESTENV=check-manifest - TESTENV=check-manifest
@ -53,7 +53,7 @@ matrix:
- os: osx - os: osx
env: TESTENV=vulture env: TESTENV=vulture
- os: osx - os: osx
env: TESTENV=pep257 env: TESTENV=pydocstyle
- os: osx - os: osx
env: TESTENV=flake8 env: TESTENV=flake8
- os: osx - os: osx

View File

@ -23,6 +23,9 @@ Added
- New `--quiet` argument for the `:debug-pyeval` command to not open a tab with - New `--quiet` argument for the `:debug-pyeval` command to not open a tab with
the results. Note `:debug-pyeval` is still only intended for debugging. the results. Note `:debug-pyeval` is still only intended for debugging.
- The completion now matches each entered word separately. - The completion now matches each entered word separately.
- A new command `:paste-primary` got added to paste the primary selection, and
`<Shift-Insert>` got added as a binding so it pastes primary rather than
clipboard.
Changed Changed
~~~~~~~ ~~~~~~~
@ -36,6 +39,10 @@ Fixed
- Fixed starting with -c "". - Fixed starting with -c "".
- Fixed crash when a tab is closed twice via javascript (e.g. Dropbox - Fixed crash when a tab is closed twice via javascript (e.g. Dropbox
authentication dialogs) authentication dialogs)
- Fixed crash when a notification/geolocation prompt is answered after closing
the tab it belongs to.
- Fixed crash when downloading a file without any path information (e.g a
magnet link).
v0.5.1 v0.5.1
------ ------

View File

@ -143,5 +143,5 @@ My issue is not listed.::
https://github.com/The-Compiler/qutebrowser/issues[the issue tracker] or https://github.com/The-Compiler/qutebrowser/issues[the issue tracker] or
using the `:report` command. using the `:report` command.
If you are reporting a segfault, make sure you read the If you are reporting a segfault, make sure you read the
https://github.com/The-Compiler/qutebrowser/blob/master/doc/stacktrace.asciidoc[guide] link:doc/stacktrace.asciidoc[guide] on how to report them with all needed
on how to report them with all needed information. information.

View File

@ -32,6 +32,7 @@ exclude .eslintignore
exclude doc/help exclude doc/help
exclude .appveyor.yml exclude .appveyor.yml
exclude .travis.yml exclude .travis.yml
exclude .pydocstylerc
exclude misc/appveyor_install.py exclude misc/appveyor_install.py
global-exclude __pycache__ *.pyc *.pyo global-exclude __pycache__ *.pyc *.pyo

View File

@ -165,6 +165,7 @@ Contributors, sorted by the number of commits in descending order:
* Jonas Schürmann * Jonas Schürmann
* Panagiotis Ktistakis * Panagiotis Ktistakis
* Jimmy * Jimmy
* Jakub Klinkovský
* skinnay * skinnay
* error800 * error800
* Zach-Button * Zach-Button

View File

@ -833,6 +833,7 @@ How many steps to zoom out.
|<<move-to-start-of-next-block,move-to-start-of-next-block>>|Move the cursor or selection to the start of next block. |<<move-to-start-of-next-block,move-to-start-of-next-block>>|Move the cursor or selection to the start of next block.
|<<move-to-start-of-prev-block,move-to-start-of-prev-block>>|Move the cursor or selection to the start of previous block. |<<move-to-start-of-prev-block,move-to-start-of-prev-block>>|Move the cursor or selection to the start of previous block.
|<<open-editor,open-editor>>|Open an external editor with the currently selected form field. |<<open-editor,open-editor>>|Open an external editor with the currently selected form field.
|<<paste-primary,paste-primary>>|Paste the primary selection at cursor position.
|<<prompt-accept,prompt-accept>>|Accept the current prompt. |<<prompt-accept,prompt-accept>>|Accept the current prompt.
|<<prompt-no,prompt-no>>|Answer no to a yes/no prompt. |<<prompt-no,prompt-no>>|Answer no to a yes/no prompt.
|<<prompt-yes,prompt-yes>>|Answer yes to a yes/no prompt. |<<prompt-yes,prompt-yes>>|Answer yes to a yes/no prompt.
@ -1046,6 +1047,10 @@ Open an external editor with the currently selected form field.
The editor which should be launched can be configured via the `general -> editor` config option. The editor which should be launched can be configured via the `general -> editor` config option.
[[paste-primary]]
=== paste-primary
Paste the primary selection at cursor position.
[[prompt-accept]] [[prompt-accept]]
=== prompt-accept === prompt-accept
Accept the current prompt. Accept the current prompt.

View File

@ -1307,6 +1307,31 @@ class CommandDispatcher:
except webelem.IsNullError: except webelem.IsNullError:
raise cmdexc.CommandError("Element vanished while editing!") raise cmdexc.CommandError("Element vanished while editing!")
@cmdutils.register(instance='command-dispatcher',
modes=[KeyMode.insert], hide=True, scope='window',
needs_js=True)
def paste_primary(self):
"""Paste the primary selection at cursor position."""
frame = self._current_widget().page().currentFrame()
try:
elem = webelem.focus_elem(frame)
except webelem.IsNullError:
raise cmdexc.CommandError("No element focused!")
if not elem.is_editable(strict=True):
raise cmdexc.CommandError("Focused element is not editable!")
clipboard = QApplication.clipboard()
if clipboard.supportsSelection():
sel = clipboard.text(QClipboard.Selection)
log.misc.debug("Pasting primary selection into element {}".format(
elem.debug_text()))
elem.evaluateJavaScript("""
var sel = '{}';
var event = document.createEvent('TextEvent');
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.

View File

@ -774,7 +774,10 @@ class DownloadManager(QAbstractListModel):
# https://bugreports.qt.io/browse/QTBUG-42757 # https://bugreports.qt.io/browse/QTBUG-42757
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute, request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
QNetworkRequest.AlwaysNetwork) QNetworkRequest.AlwaysNetwork)
suggested_fn = urlutils.filename_from_url(request.url()) suggested_fn = urlutils.filename_from_url(request.url())
if suggested_fn is None:
suggested_fn = 'qutebrowser-download'
# We won't need a question if a filename or fileobj is already given # We won't need a question if a filename or fileobj is already given
if fileobj is None and filename is None: if fileobj is None and filename is None:

View File

@ -372,6 +372,7 @@ class BrowserPage(QWebPage):
q.answered_no.connect(no_action) q.answered_no.connect(no_action)
q.cancelled.connect(no_action) q.cancelled.connect(no_action)
self.shutting_down.connect(q.abort)
q.completed.connect(q.deleteLater) q.completed.connect(q.deleteLater)
self.featurePermissionRequestCanceled.connect(functools.partial( self.featurePermissionRequestCanceled.connect(functools.partial(

View File

@ -1324,7 +1324,8 @@ KEY_SECTION_DESC = {
"Since normal keypresses are passed through, only special keys are " "Since normal keypresses are passed through, only special keys are "
"supported in this mode.\n" "supported in this mode.\n"
"Useful hidden commands to map in this section:\n\n" "Useful hidden commands to map in this section:\n\n"
" * `open-editor`: Open a texteditor with the focused field."), " * `open-editor`: Open a texteditor with the focused field.\n"
" * `paste-primary`: Paste primary selection at cursor position."),
'hint': ( 'hint': (
"Keybindings for hint mode.\n" "Keybindings for hint mode.\n"
"Since normal keypresses are passed through, only special keys are " "Since normal keypresses are passed through, only special keys are "
@ -1495,6 +1496,7 @@ KEY_DATA = collections.OrderedDict([
('insert', collections.OrderedDict([ ('insert', collections.OrderedDict([
('open-editor', ['<Ctrl-E>']), ('open-editor', ['<Ctrl-E>']),
('paste-primary', ['<Shift-Ins>']),
])), ])),
('hint', collections.OrderedDict([ ('hint', collections.OrderedDict([

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Crash when trying to get filename from custom URL</title>
</head>
<body>
<a href="qute://">download</a>
</body>
</html>

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Paste primary selection</title>
</head>
<body>
<textarea id="qute-textarea"></textarea>
<textarea id="qute-textarea-noedit" readonly></textarea>
</body>
</html>

View File

@ -24,6 +24,16 @@ Feature: Downloading things from a website.
And I wait for the error "Download error: * - server replied: NOT FOUND" And I wait for the error "Download error: * - server replied: NOT FOUND"
Then no crash should happen Then no crash should happen
Scenario: Downloading a link without path information (issue 1243)
When I set completion -> download-path-suggestion to filename
And I set storage -> prompt-download-directory to true
And I open data/downloads/issue1243.html
And I run :hint links download
And I run :follow-hint a
And I wait for "Asking question <qutebrowser.utils.usertypes.Question default='qutebrowser-download' mode=<PromptMode.text: 2> text='Save file to:'>, *" in the log
And I run :leave-mode
Then no crash should happen
Scenario: Retrying a failed download Scenario: Retrying a failed download
When I run :download http://localhost:(port)/does-not-exist When I run :download http://localhost:(port)/does-not-exist
And I wait for the error "Download error: * - server replied: NOT FOUND" And I wait for the error "Download error: * - server replied: NOT FOUND"

View File

@ -178,6 +178,15 @@ Feature: Prompts
And I run :leave-mode And I run :leave-mode
Then the javascript message "notification permission aborted" should be logged Then the javascript message "notification permission aborted" should be logged
Scenario: answering notification after closing tab
When I set content -> notifications to ask
And I open data/prompt/notifications.html in a new tab
And I click the button
And I wait for a prompt
And I run :tab-close
And I wait for "Leaving mode KeyMode.yesno (reason: aborted)" in the log
Then no crash should happen
# Page authentication # Page authentication
Scenario: Successful webpage authentification Scenario: Successful webpage authentification

View File

@ -38,3 +38,16 @@ def skip_with_broken_clipboard(qtbot, qapp):
if clipboard.text() != "Does this work?": if clipboard.text() != "Does this work?":
pytest.skip("Clipboard seems to be broken on this platform.") pytest.skip("Clipboard seems to be broken on this platform.")
@bdd.when(bdd.parsers.parse('I set the text field to "{value}"'))
def set_text_field(quteproc, value):
quteproc.send_cmd(":jseval document.getElementById('qute-textarea').value "
"= '{}';".format(value))
@bdd.then(bdd.parsers.parse('the text field should contain "{value}"'))
def check_text_field(quteproc, value):
quteproc.send_cmd(":jseval console.log('text: ' + "
"document.getElementById('qute-textarea').value);")
quteproc.wait_for_js('text: ' + value)

View File

@ -170,3 +170,67 @@ Feature: Yanking and pasting.
history: history:
- active: true - active: true
url: http://localhost:*/data/hello3.txt url: http://localhost:*/data/hello3.txt
#### :paste-primary
Scenario: Pasting the primary selection into an empty text field
When selection is supported
And I open data/paste_primary.html
And I put "Hello world" into the primary selection
# Click the text field
And I run :hint all
And I run :follow-hint a
And I run :paste-primary
# Compare
Then the text field should contain "Hello world"
Scenario: Pasting the primary selection into a text field at specific position
When selection is supported
And I open data/paste_primary.html
And I set the text field to "one two three four"
And I put " Hello world" into the primary selection
# Click the text field
And I run :hint all
And I run :follow-hint a
# Move to the beginning and two words to the right
And I press the keys "<Home>"
And I press the key "<Ctrl+Right>"
And I press the key "<Ctrl+Right>"
And I run :paste-primary
# Compare
Then the text field should contain "one two Hello world three four"
Scenario: Pasting the primary selection into a text field with undo
When selection is supported
And I open data/paste_primary.html
# Click the text field
And I run :hint all
And I run :follow-hint a
# Paste and undo
And I put "This text should be undone" into the primary selection
And I run :paste-primary
And I press the key "<Ctrl+z>"
# Paste final text
And I put "This text should stay" into the primary selection
And I run :paste-primary
# Compare
Then the text field should contain "This text should stay"
Scenario: Pasting the primary selection without a focused field
When selection is supported
And I open data/paste_primary.html
And I put "test" into the primary selection
And I run :enter-mode insert
And I run :paste-primary
Then the error "No element focused!" should be shown
Scenario: Pasting the primary selection with a read-only field
When selection is supported
And I open data/paste_primary.html
# Click the text field
And I run :hint all
And I run :follow-hint s
And I put "test" into the primary selection
And I run :enter-mode insert
And I run :paste-primary
Then the error "Focused element is not editable!" should be shown

View File

@ -599,7 +599,7 @@ class TestJavascriptEscape:
# http://qutebrowser.org:8010/builders/debian-jessie/builds/765/steps/unittests/ # http://qutebrowser.org:8010/builders/debian-jessie/builds/765/steps/unittests/
# Should that be ignored? # Should that be ignored?
@pytest.mark.parametrize('before, after', TESTS.items(), ids=repr) @pytest.mark.parametrize('before, after', sorted(TESTS.items()), ids=repr)
def test_fake_escape(self, before, after): def test_fake_escape(self, before, after):
"""Test javascript escaping with some expected outcomes.""" """Test javascript escaping with some expected outcomes."""
assert webelem.javascript_escape(before) == after assert webelem.javascript_escape(before) == after
@ -645,7 +645,7 @@ class TestJavascriptEscape:
result = webframe.evaluateJavaScript('"{}";'.format(escaped)) result = webframe.evaluateJavaScript('"{}";'.format(escaped))
assert result == text assert result == text
@pytest.mark.parametrize('text', TESTS, ids=repr) @pytest.mark.parametrize('text', sorted(TESTS), ids=repr)
def test_real_escape(self, webframe, qtbot, text): def test_real_escape(self, webframe, qtbot, text):
"""Test javascript escaping with a real QWebPage.""" """Test javascript escaping with a real QWebPage."""
self._test_escape(text, qtbot, webframe) self._test_escape(text, qtbot, webframe)

View File

@ -189,7 +189,7 @@ class TestConfigParser:
assert new_sect in configdata.DATA assert new_sect in configdata.DATA
@pytest.mark.parametrize('old_tuple, new_option', @pytest.mark.parametrize('old_tuple, new_option',
config.ConfigManager.RENAMED_OPTIONS.items()) sorted(config.ConfigManager.RENAMED_OPTIONS.items()))
def test_renamed_options(self, old_tuple, new_option): def test_renamed_options(self, old_tuple, new_option):
"""Make sure renamed options exist under the new name.""" """Make sure renamed options exist under the new name."""
section, old_option = old_tuple section, old_option = old_tuple

View File

@ -254,7 +254,7 @@ class TestMappingType:
def klass(self): def klass(self):
return MappingSubclass return MappingSubclass
@pytest.mark.parametrize('val', TESTS.keys()) @pytest.mark.parametrize('val', sorted(TESTS.keys()))
def test_validate_valid(self, klass, val): def test_validate_valid(self, klass, val):
klass(none_ok=True).validate(val) klass(none_ok=True).validate(val)
@ -263,7 +263,7 @@ class TestMappingType:
with pytest.raises(configexc.ValidationError): with pytest.raises(configexc.ValidationError):
klass().validate(val) klass().validate(val)
@pytest.mark.parametrize('val, expected', TESTS.items()) @pytest.mark.parametrize('val, expected', sorted(TESTS.items()))
def test_transform(self, klass, val, expected): def test_transform(self, klass, val, expected):
assert klass().transform(val) == expected assert klass().transform(val) == expected
@ -488,11 +488,11 @@ class TestBool:
def klass(self): def klass(self):
return configtypes.Bool return configtypes.Bool
@pytest.mark.parametrize('val, expected', TESTS.items()) @pytest.mark.parametrize('val, expected', sorted(TESTS.items()))
def test_transform(self, klass, val, expected): def test_transform(self, klass, val, expected):
assert klass().transform(val) == expected assert klass().transform(val) == expected
@pytest.mark.parametrize('val', TESTS) @pytest.mark.parametrize('val', sorted(TESTS))
def test_validate_valid(self, klass, val): def test_validate_valid(self, klass, val):
klass(none_ok=True).validate(val) klass(none_ok=True).validate(val)
@ -518,11 +518,11 @@ class TestBoolAsk:
def klass(self): def klass(self):
return configtypes.BoolAsk return configtypes.BoolAsk
@pytest.mark.parametrize('val, expected', TESTS.items()) @pytest.mark.parametrize('val, expected', sorted(TESTS.items()))
def test_transform(self, klass, val, expected): def test_transform(self, klass, val, expected):
assert klass().transform(val) == expected assert klass().transform(val) == expected
@pytest.mark.parametrize('val', TESTS) @pytest.mark.parametrize('val', sorted(TESTS))
def test_validate_valid(self, klass, val): def test_validate_valid(self, klass, val):
klass(none_ok=True).validate(val) klass(none_ok=True).validate(val)
@ -871,7 +871,7 @@ class TestColorSystem:
def klass(self): def klass(self):
return configtypes.ColorSystem return configtypes.ColorSystem
@pytest.mark.parametrize('val', TESTS) @pytest.mark.parametrize('val', sorted(TESTS))
def test_validate_valid(self, klass, val): def test_validate_valid(self, klass, val):
klass(none_ok=True).validate(val) klass(none_ok=True).validate(val)
@ -880,7 +880,7 @@ class TestColorSystem:
with pytest.raises(configexc.ValidationError): with pytest.raises(configexc.ValidationError):
klass().validate(val) klass().validate(val)
@pytest.mark.parametrize('val, expected', TESTS.items()) @pytest.mark.parametrize('val, expected', sorted(TESTS.items()))
def test_transform(self, klass, val, expected): def test_transform(self, klass, val, expected):
assert klass().transform(val) == expected assert klass().transform(val) == expected
@ -1048,7 +1048,7 @@ class TestFont:
def qtfont_class(self): def qtfont_class(self):
return configtypes.QtFont return configtypes.QtFont
@pytest.mark.parametrize('val', list(TESTS) + ['']) @pytest.mark.parametrize('val', sorted(list(TESTS)) + [''])
def test_validate_valid(self, klass, val): def test_validate_valid(self, klass, val):
klass(none_ok=True).validate(val) klass(none_ok=True).validate(val)
@ -1077,11 +1077,11 @@ class TestFont:
with pytest.raises(configexc.ValidationError): with pytest.raises(configexc.ValidationError):
klass().validate('') klass().validate('')
@pytest.mark.parametrize('string', TESTS) @pytest.mark.parametrize('string', sorted(TESTS))
def test_transform_font(self, font_class, string): def test_transform_font(self, font_class, string):
assert font_class().transform(string) == string assert font_class().transform(string) == string
@pytest.mark.parametrize('string, desc', TESTS.items()) @pytest.mark.parametrize('string, desc', sorted(TESTS.items()))
def test_transform_qtfont(self, qtfont_class, string, desc): def test_transform_qtfont(self, qtfont_class, string, desc):
assert Font(qtfont_class().transform(string)) == Font.fromdesc(desc) assert Font(qtfont_class().transform(string)) == Font.fromdesc(desc)
@ -1828,7 +1828,7 @@ class TestAutoSearch:
def klass(self): def klass(self):
return configtypes.AutoSearch return configtypes.AutoSearch
@pytest.mark.parametrize('val', TESTS) @pytest.mark.parametrize('val', sorted(TESTS))
def test_validate_valid(self, klass, val): def test_validate_valid(self, klass, val):
klass(none_ok=True).validate(val) klass(none_ok=True).validate(val)
@ -1837,7 +1837,7 @@ class TestAutoSearch:
with pytest.raises(configexc.ValidationError): with pytest.raises(configexc.ValidationError):
klass().validate(val) klass().validate(val)
@pytest.mark.parametrize('val, expected', TESTS.items()) @pytest.mark.parametrize('val, expected', sorted(TESTS.items()))
def test_transform(self, klass, val, expected): def test_transform(self, klass, val, expected):
assert klass().transform(val) == expected assert klass().transform(val) == expected
@ -1858,7 +1858,7 @@ class TestIgnoreCase:
def klass(self): def klass(self):
return configtypes.IgnoreCase return configtypes.IgnoreCase
@pytest.mark.parametrize('val', TESTS) @pytest.mark.parametrize('val', sorted(TESTS))
def test_validate_valid(self, klass, val): def test_validate_valid(self, klass, val):
klass(none_ok=True).validate(val) klass(none_ok=True).validate(val)
@ -1867,7 +1867,7 @@ class TestIgnoreCase:
with pytest.raises(configexc.ValidationError): with pytest.raises(configexc.ValidationError):
klass().validate(val) klass().validate(val)
@pytest.mark.parametrize('val, expected', TESTS.items()) @pytest.mark.parametrize('val, expected', sorted(TESTS.items()))
def test_transform(self, klass, val, expected): def test_transform(self, klass, val, expected):
assert klass().transform(val) == expected assert klass().transform(val) == expected
@ -1909,7 +1909,7 @@ class TestUrlList:
def klass(self): def klass(self):
return configtypes.UrlList return configtypes.UrlList
@pytest.mark.parametrize('val', TESTS) @pytest.mark.parametrize('val', sorted(TESTS))
def test_validate_valid(self, klass, val): def test_validate_valid(self, klass, val):
klass(none_ok=True).validate(val) klass(none_ok=True).validate(val)
@ -1927,7 +1927,7 @@ class TestUrlList:
with pytest.raises(configexc.ValidationError): with pytest.raises(configexc.ValidationError):
klass().validate('foo,,bar') klass().validate('foo,,bar')
@pytest.mark.parametrize('val, expected', TESTS.items()) @pytest.mark.parametrize('val, expected', sorted(TESTS.items()))
def test_transform_single(self, klass, val, expected): def test_transform_single(self, klass, val, expected):
assert klass().transform(val) == expected assert klass().transform(val) == expected
@ -1967,7 +1967,7 @@ class TestConfirmQuit:
def klass(self): def klass(self):
return configtypes.ConfirmQuit return configtypes.ConfirmQuit
@pytest.mark.parametrize('val', TESTS.keys()) @pytest.mark.parametrize('val', sorted(TESTS.keys()))
def test_validate_valid(self, klass, val): def test_validate_valid(self, klass, val):
klass(none_ok=True).validate(val) klass(none_ok=True).validate(val)
@ -1984,7 +1984,7 @@ class TestConfirmQuit:
with pytest.raises(configexc.ValidationError): with pytest.raises(configexc.ValidationError):
klass().validate(val) klass().validate(val)
@pytest.mark.parametrize('val, expected', TESTS.items()) @pytest.mark.parametrize('val, expected', sorted(TESTS.items()))
def test_transform(self, klass, val, expected): def test_transform(self, klass, val, expected):
assert klass().transform(val) == expected assert klass().transform(val) == expected

View File

@ -94,7 +94,7 @@ class TestParseFatalStacktrace:
"QT_IM_MODULE = fcitx" "QT_IM_MODULE = fcitx"
), ),
({'LANGUAGE': 'foo', 'LANG': 'en_US.UTF-8'}, "LANG = en_US.UTF-8"), ({'LANGUAGE': 'foo', 'LANG': 'en_US.UTF-8'}, "LANG = en_US.UTF-8"),
], ids=repr) ], ids=lambda e: e[1])
def test_get_environment_vars(monkeypatch, env, expected): def test_get_environment_vars(monkeypatch, env, expected):
"""Test for crashdialog._get_environment_vars.""" """Test for crashdialog._get_environment_vars."""
for key in os.environ.copy(): for key in os.environ.copy():

View File

@ -126,7 +126,8 @@ class TestSplit:
"""Test split.""" """Test split."""
@pytest.fixture(params=_parse_split_test_data_str(), ids=lambda e: e.input) @pytest.fixture(params=list(_parse_split_test_data_str()),
ids=lambda e: e.input)
def split_test_case(self, request): def split_test_case(self, request):
"""Fixture to automatically parametrize all depending tests. """Fixture to automatically parametrize all depending tests.
@ -163,7 +164,7 @@ class TestSimpleSplit:
'foo\nbar': ['foo', '\nbar'], 'foo\nbar': ['foo', '\nbar'],
} }
@pytest.mark.parametrize('test', TESTS, ids=repr) @pytest.mark.parametrize('test', sorted(TESTS), ids=repr)
def test_str_split(self, test): def test_str_split(self, test):
"""Test if the behavior matches str.split.""" """Test if the behavior matches str.split."""
assert split.simple_split(test) == test.rstrip().split() assert split.simple_split(test) == test.rstrip().split()
@ -177,7 +178,7 @@ class TestSimpleSplit:
expected = s.rstrip().split(maxsplit=maxsplit) expected = s.rstrip().split(maxsplit=maxsplit)
assert actual == expected assert actual == expected
@pytest.mark.parametrize('test, expected', TESTS.items(), ids=repr) @pytest.mark.parametrize('test, expected', sorted(TESTS.items()), ids=repr)
def test_split_keep(self, test, expected): def test_split_keep(self, test, expected):
"""Test splitting with keep=True.""" """Test splitting with keep=True."""
assert split.simple_split(test, keep=True) == expected assert split.simple_split(test, keep=True) == expected

View File

@ -49,12 +49,20 @@ BAD_VALUES = {
} }
def good_values():
return list(iter_good_values())
def bad_values():
return list(iter_bad_values())
def iter_good_values(): def iter_good_values():
"""Yield "good" (C data type, value) tuples. """Yield "good" (C data type, value) tuples.
Those should pass overflow checking. Those should pass overflow checking.
""" """
for ctype, values in GOOD_VALUES.items(): for ctype, values in sorted(GOOD_VALUES.items()):
for value in values: for value in values:
yield ctype, value yield ctype, value
@ -65,6 +73,6 @@ def iter_bad_values():
These should not pass overflow checking. The third value is the value they These should not pass overflow checking. The third value is the value they
should be replaced with if overflow checking should not be fatal. should be replaced with if overflow checking should not be fatal.
""" """
for ctype, values in BAD_VALUES.items(): for ctype, values in sorted(BAD_VALUES.items()):
for value, repl in values: for value, repl in values:
yield ctype, value, repl yield ctype, value, repl

View File

@ -69,21 +69,21 @@ class TestCheckOverflow:
"""Test check_overflow.""" """Test check_overflow."""
@pytest.mark.parametrize('ctype, val', @pytest.mark.parametrize('ctype, val',
overflow_test_cases.iter_good_values()) overflow_test_cases.good_values())
def test_good_values(self, ctype, val): def test_good_values(self, ctype, val):
"""Test values which are inside bounds.""" """Test values which are inside bounds."""
qtutils.check_overflow(val, ctype) qtutils.check_overflow(val, ctype)
@pytest.mark.parametrize('ctype, val', @pytest.mark.parametrize('ctype, val',
[(ctype, val) for (ctype, val, _) in [(ctype, val) for (ctype, val, _) in
overflow_test_cases.iter_bad_values()]) overflow_test_cases.bad_values()])
def test_bad_values_fatal(self, ctype, val): def test_bad_values_fatal(self, ctype, val):
"""Test values which are outside bounds with fatal=True.""" """Test values which are outside bounds with fatal=True."""
with pytest.raises(OverflowError): with pytest.raises(OverflowError):
qtutils.check_overflow(val, ctype) qtutils.check_overflow(val, ctype)
@pytest.mark.parametrize('ctype, val, repl', @pytest.mark.parametrize('ctype, val, repl',
overflow_test_cases.iter_bad_values()) overflow_test_cases.bad_values())
def test_bad_values_nonfatal(self, ctype, val, repl): def test_bad_values_nonfatal(self, ctype, val, repl):
"""Test values which are outside bounds with fatal=False.""" """Test values which are outside bounds with fatal=False."""
newval = qtutils.check_overflow(val, ctype, fatal=False) newval = qtutils.check_overflow(val, ctype, fatal=False)

View File

@ -805,7 +805,8 @@ QUALNAME_OBJ = QualnameObj()
(qutebrowser, 'qutebrowser'), # module (qutebrowser, 'qutebrowser'), # module
(qutebrowser.utils, 'qutebrowser.utils'), # submodule (qutebrowser.utils, 'qutebrowser.utils'), # submodule
(utils, 'qutebrowser.utils.utils'), # submodule (from-import) (utils, 'qutebrowser.utils.utils'), # submodule (from-import)
]) ], ids=['instance', 'class', 'unbound-method', 'bound-method', 'function',
'partial', 'module', 'submodule', 'from-import'])
def test_qualname(obj, expected): def test_qualname(obj, expected):
assert utils.qualname(obj) == expected assert utils.qualname(obj) == expected

33
tox.ini
View File

@ -4,7 +4,7 @@
# and then run "tox" from this directory. # and then run "tox" from this directory.
[tox] [tox]
envlist = py34,py35-cov,misc,vulture,pep257,flake8,pylint,pyroma,check-manifest envlist = py34,py35-cov,misc,vulture,pydocstyle,flake8,pylint,pyroma,check-manifest
[testenv] [testenv]
# https://bitbucket.org/hpk42/tox/issue/246/ - only needed for Windows though # https://bitbucket.org/hpk42/tox/issue/246/ - only needed for Windows though
@ -19,7 +19,7 @@ deps =
decorator==4.0.6 decorator==4.0.6
Flask==0.10.1 Flask==0.10.1
glob2==0.4.1 glob2==0.4.1
httpbin==0.4.0 httpbin==0.4.1
hypothesis==2.0.0 hypothesis==2.0.0
itsdangerous==0.24 itsdangerous==0.24
Mako==1.0.3 Mako==1.0.3
@ -29,10 +29,10 @@ deps =
pytest==2.8.7 pytest==2.8.7
pytest-bdd==2.16.0 pytest-bdd==2.16.0
pytest-catchlog==1.2.2 pytest-catchlog==1.2.2
pytest-cov==2.2.0 pytest-cov==2.2.1
pytest-faulthandler==1.3.0 pytest-faulthandler==1.3.0
pytest-html==1.7 pytest-html==1.7
pytest-mock==0.9.0 pytest-mock==0.10.1
pytest-qt==1.11.0 pytest-qt==1.11.0
pytest-instafail==0.3.0 pytest-instafail==0.3.0
pytest-travis-fold==1.2.0 pytest-travis-fold==1.2.0
@ -42,7 +42,7 @@ deps =
vulture==0.8.1 vulture==0.8.1
Werkzeug==0.11.3 Werkzeug==0.11.3
wheel==0.26.0 wheel==0.26.0
xvfbwrapper==0.2.7 xvfbwrapper==0.2.8
cherrypy==4.0.0 cherrypy==4.0.0
commands = commands =
{envpython} scripts/link_pyqt.py --tox {envdir} {envpython} scripts/link_pyqt.py --tox {envdir}
@ -150,34 +150,25 @@ commands =
{envpython} -m pylint scripts qutebrowser --output-format=colorized --reports=no {envpython} -m pylint scripts qutebrowser --output-format=colorized --reports=no
{envpython} scripts/dev/run_pylint_on_tests.py --output-format=colorized --reports=no {envpython} scripts/dev/run_pylint_on_tests.py --output-format=colorized --reports=no
[testenv:pep257] [testenv:pydocstyle]
basepython = python3 basepython = python3
skip_install = true skip_install = true
passenv = PYTHON LANG passenv = PYTHON LANG
deps = pep257==0.7.0 deps = pydocstyle==1.0.0
# Disabled checks: commands = {envpython} -m pydocstyle scripts tests qutebrowser
# D102: Missing docstring in public method (will be handled by others)
# D103: Missing docstring in public function (will be handled by others)
# D104: Missing docstring in public package (will be handled by others)
# D105: Missing docstring in magic method (will be handled by others)
# D209: Blank line before closing """ (removed from PEP257)
# D211: Now b lank lines allowed before class docstring
# (PEP257 got changed, but let's stick to the old standard)
# D402: First line should not be function's signature (false-positives)
commands = {envpython} -m pep257 scripts tests qutebrowser --ignore=D102,D103,D104,D105,D209,D211,D402 '--match=(?!resources|test_*).*\.py'
[testenv:flake8] [testenv:flake8]
basepython = python3 basepython = python3
passenv = passenv =
deps = deps =
-r{toxinidir}/requirements.txt -r{toxinidir}/requirements.txt
flake8==2.5.1 flake8==2.5.2
flake8-debugger==1.4.0 flake8-debugger==1.4.0
pep8-naming==0.3.3 pep8-naming==0.3.3
flake8-putty==0.2.0 flake8-putty==0.2.0
ebb-lint==0.4.3 ebb-lint==0.4.4
flake8-copyright==0.1 flake8-copyright==0.1
mccabe==0.3.1 mccabe==0.4.0
pep8==1.7.0 pep8==1.7.0
pyflakes==1.0.0 pyflakes==1.0.0
flake8-string-format==0.2.1 flake8-string-format==0.2.1
@ -202,7 +193,7 @@ basepython = python3
skip_install = true skip_install = true
passenv = passenv =
deps = deps =
check-manifest==0.30 check-manifest==0.31
commands = commands =
{envdir}/bin/check-manifest --ignore 'qutebrowser/git-commit-id,qutebrowser/html/doc,qutebrowser/html/doc/*,*/__pycache__' {envdir}/bin/check-manifest --ignore 'qutebrowser/git-commit-id,qutebrowser/html/doc,qutebrowser/html/doc/*,*/__pycache__'