From e5e1a0d95ceddb01613ce3fb57ed12b85d675d15 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 3 Feb 2016 21:05:35 +0100 Subject: [PATCH] Reject hints -> chars containing duplicate chars Fixes #1286. --- CHANGELOG.asciidoc | 1 + qutebrowser/config/configdata.py | 2 +- qutebrowser/config/configtypes.py | 15 +++++++++++++++ tests/unit/config/test_configtypes.py | 25 +++++++++++++++---------- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 7286eb547..1998fce15 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -48,6 +48,7 @@ Fixed - Fixed crash when downloading a file without any path information (e.g a magnet link). - Fixed crashes when opening an empty URL (e.g. via pasting). +- Fixed validation of duplicate values in `hints -> chars`. v0.5.1 ------ diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 656452a83..03ca2d78f 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -870,7 +870,7 @@ def data(readonly=False): "Mode to use for hints."), ('chars', - SettingValue(typ.String(minlen=2, completions=[ + SettingValue(typ.UniqueCharString(minlen=2, completions=[ ('asdfghjkl', "Home row"), ('dhtnaoeu', "Home row (Dvorak)"), ('abcdefghijklmnopqrstuvwxyz', "All letters"), diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 77ba97f3b..d79e35df1 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -283,6 +283,21 @@ class String(BaseType): return super().complete() +class UniqueCharString(String): + + """A string which may not contain duplicate chars.""" + + def validate(self, value): + super().validate(value) + if not value: + return + + # Check for duplicate values + if len(set(value)) != len(value): + raise configexc.ValidationError( + value, "String contains duplicate values!") + + class List(BaseType): """Base class for a (string-)list setting.""" diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 0c345a6f5..85f60ce9c 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -278,9 +278,9 @@ class TestString: """Test String.""" - @pytest.fixture - def klass(self): - return configtypes.String + @pytest.fixture(params=[configtypes.String, configtypes.UniqueCharString]) + def klass(self, request): + return request.param @pytest.mark.parametrize('minlen, maxlen', [(1, None), (None, 1)]) def test_lengths_valid(self, klass, minlen, maxlen): @@ -297,16 +297,16 @@ class TestString: @pytest.mark.parametrize('kwargs, val', [ ({'none_ok': True}, ''), # Empty with none_ok - ({}, "Hello World! :-)"), + ({}, "Test! :-)"), # Forbidden chars - ({'forbidden': 'xyz'}, 'foobar'), + ({'forbidden': 'xyz'}, 'fobar'), ({'forbidden': 'xyz'}, 'foXbar'), # Lengths ({'minlen': 2}, 'fo'), ({'minlen': 2, 'maxlen': 3}, 'fo'), - ({'minlen': 2, 'maxlen': 3}, 'foo'), + ({'minlen': 2, 'maxlen': 3}, 'abc'), # valid_values - ({'valid_values': configtypes.ValidValues('fooo')}, 'fooo'), + ({'valid_values': configtypes.ValidValues('abcd')}, 'abcd'), ]) def test_validate_valid(self, klass, kwargs, val): klass(**kwargs).validate(val) @@ -320,16 +320,21 @@ class TestString: ({'minlen': 2}, 'f'), ({'maxlen': 2}, 'fob'), ({'minlen': 2, 'maxlen': 3}, 'f'), - ({'minlen': 2, 'maxlen': 3}, 'fooo'), + ({'minlen': 2, 'maxlen': 3}, 'abcd'), # valid_values - ({'valid_values': configtypes.ValidValues('blah')}, 'fooo'), + ({'valid_values': configtypes.ValidValues('blah')}, 'abcd'), ]) def test_validate_invalid(self, klass, kwargs, val): with pytest.raises(configexc.ValidationError): klass(**kwargs).validate(val) + def test_validate_duplicate_invalid(self): + typ = configtypes.UniqueCharString() + with pytest.raises(configexc.ValidationError): + typ.validate('foobar') + def test_transform(self, klass): - assert klass().transform('foobar') == 'foobar' + assert klass().transform('fobar') == 'fobar' @pytest.mark.parametrize('value', [ None,