1# -*- coding: utf-8 -*- 2# Copyright (c) 2018–2019, Sviatoslav Sydorenko <webknjaz@redhat.com> 3# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) 4"""Test low-level utility functions from ``module_utils.common.collections``.""" 5 6from __future__ import absolute_import, division, print_function 7__metaclass__ = type 8 9import pytest 10 11from ansible.module_utils.six import Iterator 12from ansible.module_utils.common._collections_compat import Sequence 13from ansible.module_utils.common.collections import ImmutableDict, is_iterable, is_sequence 14 15 16class SeqStub: 17 """Stub emulating a sequence type. 18 19 >>> from collections.abc import Sequence 20 >>> assert issubclass(SeqStub, Sequence) 21 >>> assert isinstance(SeqStub(), Sequence) 22 """ 23 24 25Sequence.register(SeqStub) 26 27 28class IteratorStub(Iterator): 29 def __next__(self): 30 raise StopIteration 31 32 33class IterableStub: 34 def __iter__(self): 35 return IteratorStub() 36 37 38TEST_STRINGS = u'he', u'Україна', u'Česká republika' 39TEST_STRINGS = TEST_STRINGS + tuple(s.encode('utf-8') for s in TEST_STRINGS) 40 41TEST_ITEMS_NON_SEQUENCES = ( 42 {}, object(), frozenset(), 43 4, 0., 44) + TEST_STRINGS 45 46TEST_ITEMS_SEQUENCES = ( 47 [], (), 48 SeqStub(), 49) 50TEST_ITEMS_SEQUENCES = TEST_ITEMS_SEQUENCES + ( 51 # Iterable effectively containing nested random data: 52 TEST_ITEMS_NON_SEQUENCES, 53) 54 55 56@pytest.mark.parametrize('sequence_input', TEST_ITEMS_SEQUENCES) 57def test_sequence_positive(sequence_input): 58 """Test that non-string item sequences are identified correctly.""" 59 assert is_sequence(sequence_input) 60 assert is_sequence(sequence_input, include_strings=False) 61 62 63@pytest.mark.parametrize('non_sequence_input', TEST_ITEMS_NON_SEQUENCES) 64def test_sequence_negative(non_sequence_input): 65 """Test that non-sequences are identified correctly.""" 66 assert not is_sequence(non_sequence_input) 67 68 69@pytest.mark.parametrize('string_input', TEST_STRINGS) 70def test_sequence_string_types_with_strings(string_input): 71 """Test that ``is_sequence`` can separate string and non-string.""" 72 assert is_sequence(string_input, include_strings=True) 73 74 75@pytest.mark.parametrize('string_input', TEST_STRINGS) 76def test_sequence_string_types_without_strings(string_input): 77 """Test that ``is_sequence`` can separate string and non-string.""" 78 assert not is_sequence(string_input, include_strings=False) 79 80 81@pytest.mark.parametrize( 82 'seq', 83 ([], (), {}, set(), frozenset(), IterableStub()), 84) 85def test_iterable_positive(seq): 86 assert is_iterable(seq) 87 88 89@pytest.mark.parametrize( 90 'seq', (IteratorStub(), object(), 5, 9.) 91) 92def test_iterable_negative(seq): 93 assert not is_iterable(seq) 94 95 96@pytest.mark.parametrize('string_input', TEST_STRINGS) 97def test_iterable_including_strings(string_input): 98 assert is_iterable(string_input, include_strings=True) 99 100 101@pytest.mark.parametrize('string_input', TEST_STRINGS) 102def test_iterable_excluding_strings(string_input): 103 assert not is_iterable(string_input, include_strings=False) 104 105 106class TestImmutableDict: 107 def test_scalar(self): 108 imdict = ImmutableDict({1: 2}) 109 assert imdict[1] == 2 110 111 def test_string(self): 112 imdict = ImmutableDict({u'café': u'くらとみ'}) 113 assert imdict[u'café'] == u'くらとみ' 114 115 def test_container(self): 116 imdict = ImmutableDict({(1, 2): ['1', '2']}) 117 assert imdict[(1, 2)] == ['1', '2'] 118 119 def test_from_tuples(self): 120 imdict = ImmutableDict((('a', 1), ('b', 2))) 121 assert frozenset(imdict.items()) == frozenset((('a', 1), ('b', 2))) 122 123 def test_from_kwargs(self): 124 imdict = ImmutableDict(a=1, b=2) 125 assert frozenset(imdict.items()) == frozenset((('a', 1), ('b', 2))) 126 127 def test_immutable(self): 128 imdict = ImmutableDict({1: 2}) 129 130 expected_reason = r"^'ImmutableDict' object does not support item assignment$" 131 132 with pytest.raises(TypeError, match=expected_reason): 133 imdict[1] = 3 134 135 with pytest.raises(TypeError, match=expected_reason): 136 imdict[5] = 3 137 138 def test_hashable(self): 139 # ImmutableDict is hashable when all of its values are hashable 140 imdict = ImmutableDict({u'café': u'くらとみ'}) 141 assert hash(imdict) 142 143 def test_nonhashable(self): 144 # ImmutableDict is unhashable when one of its values is unhashable 145 imdict = ImmutableDict({u'café': u'くらとみ', 1: [1, 2]}) 146 147 expected_reason = r"^unhashable type: 'list'$" 148 149 with pytest.raises(TypeError, match=expected_reason): 150 hash(imdict) 151 152 def test_len(self): 153 imdict = ImmutableDict({1: 2, 'a': 'b'}) 154 assert len(imdict) == 2 155 156 def test_repr(self): 157 initial_data = {1: 2, 'a': 'b'} 158 initial_data_repr = repr(initial_data) 159 imdict = ImmutableDict(initial_data) 160 actual_repr = repr(imdict) 161 expected_repr = "ImmutableDict({0})".format(initial_data_repr) 162 assert actual_repr == expected_repr 163