""" Copyright 2007 Pallets Copyright 2021 Salad Dais Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import pickle from copy import copy from copy import deepcopy import pytest from hippolyzer.lib.base.multidict import MultiDict, OrderedMultiDict class TestNativeItermethods: def test_basic(self): class StupidDict: def keys(self, multi=1): return iter(["a", "b", "c"] * multi) def values(self, multi=1): return iter([1, 2, 3] * multi) def items(self, multi=1): return iter( zip(iter(self.keys(multi=multi)), iter(self.values(multi=multi))) ) d = StupidDict() expected_keys = ["a", "b", "c"] expected_values = [1, 2, 3] expected_items = list(zip(expected_keys, expected_values)) assert list(d.keys()) == expected_keys assert list(d.values()) == expected_values assert list(d.items()) == expected_items assert list(d.keys(2)) == expected_keys * 2 assert list(d.values(2)) == expected_values * 2 assert list(d.items(2)) == expected_items * 2 class _MutableMultiDictTests: storage_class = None def test_pickle(self): cls = self.storage_class def create_instance(module=None): if module is None: d_inst = cls() else: old = cls.__module__ cls.__module__ = module d_inst = cls() cls.__module__ = old d_inst.setlist(b"foo", [1, 2, 3, 4]) d_inst.setlist(b"bar", b"foo bar baz".split()) return d_inst for protocol in range(pickle.HIGHEST_PROTOCOL + 1): d = create_instance() s = pickle.dumps(d, protocol) ud = pickle.loads(s) assert type(ud) == type(d) assert ud == d alternative = pickle.dumps(create_instance("werkzeug"), protocol) assert pickle.loads(alternative) == d ud[b"newkey"] = b"bla" assert ud != d def test_basic_interface(self): md = self.storage_class() assert isinstance(md, dict) mapping = [ ("a", 1), ("b", 2), ("a", 2), ("d", 3), ("a", 1), ("a", 3), ("d", 4), ("c", 3), ] md = self.storage_class(mapping) # simple getitem gives the first value assert md["a"] == 1 assert md["c"] == 3 with pytest.raises(KeyError): md["e"] # noqa assert md.get("a") == 1 # list getitem assert md.getlist("a") == [1, 2, 1, 3] assert md.getlist("d") == [3, 4] # do not raise if key not found assert md.getlist("x") == [] # simple setitem overwrites all values md["a"] = 42 assert md.getlist("a") == [42] # list setitem md.setlist("a", [1, 2, 3]) assert md["a"] == 1 assert md.getlist("a") == [1, 2, 3] # verify that it does not change original lists l1 = [1, 2, 3] md.setlist("a", l1) del l1[:] assert md["a"] == 1 # setdefault, setlistdefault assert md.setdefault("u", 23) == 23 assert md.getlist("u") == [23] del md["u"] md.setlist("u", [-1, -2]) # delitem del md["u"] with pytest.raises(KeyError): md["u"] # noqa del md["d"] assert md.getlist("d") == [] # keys, values, items, lists assert list(sorted(md.keys())) == ["a", "b", "c"] assert list(sorted(md.keys())) == ["a", "b", "c"] # Changed from werkzeug. IMO this is less wrong than only # returning the first item for each. assert list(sorted(md.values())) == [1, 2, 2, 3, 3] assert list(sorted(md.values())) == [1, 2, 2, 3, 3] assert list(sorted(md.items(multi=False))) == [("a", 1), ("b", 2), ("c", 3)] assert list(sorted(md.items())) == [ ("a", 1), ("a", 2), ("a", 3), ("b", 2), ("c", 3), ] assert list(sorted(md.items(multi=False))) == [("a", 1), ("b", 2), ("c", 3)] assert list(sorted(md.items())) == [ ("a", 1), ("a", 2), ("a", 3), ("b", 2), ("c", 3), ] assert list(sorted(md.lists())) == [("a", [1, 2, 3]), ("b", [2]), ("c", [3])] assert list(sorted(md.lists())) == [("a", [1, 2, 3]), ("b", [2]), ("c", [3])] # copy method c = md.copy() assert c["a"] == 1 assert c.getlist("a") == [1, 2, 3] # copy method 2 c = copy(md) assert c["a"] == 1 assert c.getlist("a") == [1, 2, 3] # deepcopy method c = md.deepcopy() assert c["a"] == 1 assert c.getlist("a") == [1, 2, 3] # deepcopy method 2 c = deepcopy(md) assert c["a"] == 1 assert c.getlist("a") == [1, 2, 3] # update with a multidict od = self.storage_class([("a", 4), ("a", 5), ("y", 0)]) md.update(od) assert md.getlist("a") == [1, 2, 3, 4, 5] assert md.getlist("y") == [0] # update with a regular dict md = c od = {"a": 4, "y": 0} md.update(od) assert md.getlist("a") == [1, 2, 3, 4] assert md.getlist("y") == [0] # pop, poplist, popitem, popitemlist assert md.pop("y") == 0 assert "y" not in md assert md.poplist("a") == [1, 2, 3, 4] assert "a" not in md assert md.poplist("missing") == [] # remaining: b=2, c=3 popped = md.popitem() assert popped in [("b", 2), ("c", 3)] popped = md.popitemlist() assert popped in [("b", [2]), ("c", [3])] # repr md = self.storage_class([("a", 1), ("a", 2), ("b", 3)]) assert "('a', 1)" in repr(md) assert "('a', 2)" in repr(md) assert "('b', 3)" in repr(md) # add and getlist md.add("c", "42") md.add("c", "23") assert md.getlist("c") == ["42", "23"] md.add("c", "blah") assert md.getlist("c", elem_type=int) == [42, 23] # setdefault md = self.storage_class() md.setdefault("x", []).append(42) md.setdefault("x", []).append(23) assert md["x"] == [42, 23] # to dict md = self.storage_class() md["foo"] = 42 md.add("bar", 1) md.add("bar", 2) assert md.to_dict() == {"foo": 42, "bar": 1} assert md.to_dict(flat=False) == {"foo": [42], "bar": [1, 2]} # popitem from empty dict with pytest.raises(KeyError): self.storage_class().popitem() with pytest.raises(KeyError): self.storage_class().popitemlist() # key errors are of a special type with pytest.raises(KeyError): self.storage_class()[42] # noqa # setlist works md = self.storage_class() md["foo"] = 42 md.setlist("foo", [1, 2]) assert md.getlist("foo") == [1, 2] class TestMultiDict(_MutableMultiDictTests): storage_class = MultiDict # type: ignore def test_multidict_pop(self): def make_d(): return self.storage_class({"foo": [1, 2, 3, 4]}) d = make_d() assert d.pop("foo") == 1 assert not d d = make_d() assert d.pop("foo", 32) == 1 assert not d d = make_d() assert d.pop("foos", 32) == 32 assert d with pytest.raises(KeyError): d.pop("foos") def test_multidict_pop_raise_keyerror_for_empty_list_value(self): mapping = [("a", "b"), ("a", "c")] md = self.storage_class(mapping) md.setlistdefault("empty", []) with pytest.raises(KeyError): md.pop("empty") def test_multidict_popitem_raise_keyerror_for_empty_list_value(self): mapping = [] md = self.storage_class(mapping) md.setlistdefault("empty", []) with pytest.raises(KeyError): md.popitem() def test_setlistdefault(self): md = self.storage_class() assert md.setlistdefault("u", [-1, -2]) == [-1, -2] assert md.getlist("u") == [-1, -2] assert md["u"] == -1 def test_iter_interfaces(self): mapping = [ ("a", 1), ("b", 2), ("a", 2), ("d", 3), ("a", 1), ("a", 3), ("d", 4), ("c", 3), ] md = self.storage_class(mapping) assert list(zip(md.keys(), md.listvalues())) == list(md.lists()) assert list(zip(md, md.listvalues())) == list(md.lists()) assert list(zip(md.keys(), md.listvalues())) == list(md.lists()) def test_getitem_raise_keyerror_for_empty_list_value(self): mapping = [("a", "b"), ("a", "c")] md = self.storage_class(mapping) md.setlistdefault("empty", []) with pytest.raises(KeyError): md["empty"] # noqa class TestOrderedMultiDict(_MutableMultiDictTests): storage_class = OrderedMultiDict # type: ignore def test_ordered_interface(self): cls = self.storage_class d = cls() assert not d d.add("foo", "bar") assert len(d) == 1 d.add("foo", "baz") assert len(d) == 2 assert list(d.items(multi=False)) == [("foo", "bar")] assert list(d) == ["foo"] assert list(d.items()) == [("foo", "bar"), ("foo", "baz")] del d["foo"] assert not d assert len(d) == 0 assert list(d) == [] d.update([("foo", 1), ("foo", 2), ("bar", 42)]) d.add("foo", 3) assert d.getlist("foo") == [1, 2, 3] assert d.getlist("bar") == [42] assert list(d.items(multi=False)) == [("foo", 1), ("bar", 42)] expected = ["foo", "bar"] assert list(d.keys()) == expected assert list(d) == expected assert list(d.keys()) == expected assert list(d.items()) == [ ("foo", 1), ("foo", 2), ("bar", 42), ("foo", 3), ] assert len(d) == 4 assert d.pop("foo") == 1 assert d.pop("blafasel", None) is None assert d.pop("blafasel", 42) == 42 assert len(d) == 1 assert d.poplist("bar") == [42] assert not d assert d.get("missingkey") is None d.add("foo", 42) d.add("foo", 23) d.add("bar", 2) d.add("foo", 42) assert d == MultiDict(d) storage_id = self.storage_class(d) assert d == storage_id d.add("foo", 2) assert d != storage_id d.update({"blah": [1, 2, 3]}) assert d["blah"] == 1 assert d.getlist("blah") == [1, 2, 3] # setlist works d = self.storage_class() d["foo"] = 42 d.setlist("foo", [1, 2]) assert d.getlist("foo") == [1, 2] with pytest.raises(KeyError): d.pop("missing") with pytest.raises(KeyError): d["missing"] # noqa # popping d = self.storage_class() d.add("foo", 23) d.add("foo", 42) d.add("foo", 1) assert d.popitem() == ("foo", 23) with pytest.raises(KeyError): d.popitem() assert not d d.add("foo", 23) d.add("foo", 42) d.add("foo", 1) assert d.popitemlist() == ("foo", [23, 42, 1]) with pytest.raises(KeyError): d.popitemlist() # Unhashable d = self.storage_class() d.add("foo", 23) pytest.raises(TypeError, hash, d) def test_clear_clears_values(self): foo = self.storage_class() foo.add("bar", 1) foo.add("bar", 2) foo.add("foo", 3) foo.clear() assert list(foo.keys()) == [] assert list(foo.values()) == [] foo.add("bar", 1) assert dict(foo.items()) == {"bar": 1}