xref: /openbmc/openbmc/poky/bitbake/lib/bb/tests/cow.py (revision c9f7865a)
1#
2# BitBake Tests for Copy-on-Write (cow.py)
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6# Copyright 2006 Holger Freyther <freyther@handhelds.org>
7# Copyright (C) 2020  Agilent Technologies, Inc.
8#
9
10import io
11import re
12import sys
13import unittest
14import contextlib
15import collections
16
17from bb.COW import COWDictBase, COWSetBase, COWDictMeta, COWSetMeta
18
19
20class COWTestCase(unittest.TestCase):
21    """
22    Test case for the COW module from mithro
23    """
24
25    def setUp(self):
26        self._track_warnings = False
27        self._warning_file = io.StringIO()
28        self._unhandled_warnings = collections.deque()
29        COWDictBase.__warn__ = self._warning_file
30
31    def tearDown(self):
32        COWDictBase.__warn__ = sys.stderr
33        if self._track_warnings:
34            self._checkAllWarningsRead()
35
36    def trackWarnings(self):
37        self._track_warnings = True
38
39    def _collectWarnings(self):
40        self._warning_file.seek(0)
41        for warning in self._warning_file:
42            self._unhandled_warnings.append(warning.rstrip("\n"))
43        self._warning_file.truncate(0)
44        self._warning_file.seek(0)
45
46    def _checkAllWarningsRead(self):
47        self._collectWarnings()
48        self.assertSequenceEqual(self._unhandled_warnings, [])
49
50    @contextlib.contextmanager
51    def checkReportsWarning(self, expected_warning):
52        self._checkAllWarningsRead()
53        yield
54        self._collectWarnings()
55        warning = self._unhandled_warnings.popleft()
56        self.assertEqual(warning, expected_warning)
57
58    def checkStrOutput(self, obj, expected_levels, expected_keys):
59        if obj.__class__ is COWDictMeta:
60            expected_class_name = "COWDict"
61        elif obj.__class__ is COWSetMeta:
62            expected_class_name = "COWSet"
63        else:
64            self.fail("obj is of unknown type {0}".format(type(obj)))
65        s = str(obj)
66        regex = re.compile(r"<(\w+) Level: (\d+) Current Keys: (\d+)>")
67        match = regex.match(s)
68        self.assertIsNotNone(match, "bad str output: '{0}'".format(s))
69        class_name = match.group(1)
70        self.assertEqual(class_name, expected_class_name)
71        levels = int(match.group(2))
72        self.assertEqual(levels, expected_levels, "wrong # levels in str: '{0}'".format(s))
73        keys = int(match.group(3))
74        self.assertEqual(keys, expected_keys, "wrong # keys in str: '{0}'".format(s))
75
76    def testGetSet(self):
77        """
78        Test and set
79        """
80        a = COWDictBase.copy()
81
82        self.assertEqual(False, 'a' in a)
83
84        a['a'] = 'a'
85        a['b'] = 'b'
86        self.assertEqual(True, 'a' in a)
87        self.assertEqual(True, 'b' in a)
88        self.assertEqual('a', a['a'])
89        self.assertEqual('b', a['b'])
90
91    def testCopyCopy(self):
92        """
93        Test the copy of copies
94        """
95
96        # create two COW dict 'instances'
97        b = COWDictBase.copy()
98        c = COWDictBase.copy()
99
100        # assign some keys to one instance, some keys to another
101        b['a'] = 10
102        b['c'] = 20
103        c['a'] = 30
104
105        # test separation of the two instances
106        self.assertEqual(False, 'c' in c)
107        self.assertEqual(30, c['a'])
108        self.assertEqual(10, b['a'])
109
110        # test copy
111        b_2 = b.copy()
112        c_2 = c.copy()
113
114        self.assertEqual(False, 'c' in c_2)
115        self.assertEqual(10, b_2['a'])
116
117        b_2['d'] = 40
118        self.assertEqual(False, 'd' in c_2)
119        self.assertEqual(True, 'd' in b_2)
120        self.assertEqual(40, b_2['d'])
121        self.assertEqual(False, 'd' in b)
122        self.assertEqual(False, 'd' in c)
123
124        c_2['d'] = 30
125        self.assertEqual(True, 'd' in c_2)
126        self.assertEqual(True, 'd' in b_2)
127        self.assertEqual(30, c_2['d'])
128        self.assertEqual(40, b_2['d'])
129        self.assertEqual(False, 'd' in b)
130        self.assertEqual(False, 'd' in c)
131
132        # test copy of the copy
133        c_3 = c_2.copy()
134        b_3 = b_2.copy()
135        b_3_2 = b_2.copy()
136
137        c_3['e'] = 4711
138        self.assertEqual(4711, c_3['e'])
139        self.assertEqual(False, 'e' in c_2)
140        self.assertEqual(False, 'e' in b_3)
141        self.assertEqual(False, 'e' in b_3_2)
142        self.assertEqual(False, 'e' in b_2)
143
144        b_3['e'] = 'viel'
145        self.assertEqual('viel', b_3['e'])
146        self.assertEqual(4711, c_3['e'])
147        self.assertEqual(False, 'e' in c_2)
148        self.assertEqual(True, 'e' in b_3)
149        self.assertEqual(False, 'e' in b_3_2)
150        self.assertEqual(False, 'e' in b_2)
151
152    def testCow(self):
153        self.trackWarnings()
154
155        c = COWDictBase.copy()
156        c['123'] = 1027
157        c['other'] = 4711
158        c['d'] = {'abc': 10, 'bcd': 20}
159
160        copy = c.copy()
161
162        self.assertEqual(1027, c['123'])
163        self.assertEqual(4711, c['other'])
164        self.assertEqual({'abc': 10, 'bcd': 20}, c['d'])
165        self.assertEqual(1027, copy['123'])
166        self.assertEqual(4711, copy['other'])
167        with self.checkReportsWarning("Warning: Doing a copy because d is a mutable type."):
168            self.assertEqual({'abc': 10, 'bcd': 20}, copy['d'])
169
170        # cow it now
171        copy['123'] = 1028
172        copy['other'] = 4712
173        copy['d']['abc'] = 20
174
175        self.assertEqual(1027, c['123'])
176        self.assertEqual(4711, c['other'])
177        self.assertEqual({'abc': 10, 'bcd': 20}, c['d'])
178        self.assertEqual(1028, copy['123'])
179        self.assertEqual(4712, copy['other'])
180        self.assertEqual({'abc': 20, 'bcd': 20}, copy['d'])
181
182    def testOriginalTestSuite(self):
183        # This test suite is a port of the original one from COW.py
184        self.trackWarnings()
185
186        a = COWDictBase.copy()
187        self.checkStrOutput(a, 1, 0)
188
189        a['a'] = 'a'
190        a['b'] = 'b'
191        a['dict'] = {}
192        self.checkStrOutput(a, 1, 4)  # 4th member is dict__mutable__
193
194        b = a.copy()
195        self.checkStrOutput(b, 2, 0)
196        b['c'] = 'b'
197        self.checkStrOutput(b, 2, 1)
198
199        with self.checkReportsWarning("Warning: If you aren't going to change any of the values call with True."):
200            self.assertListEqual(list(a.iteritems()),
201                                 [('a', 'a'),
202                                  ('b', 'b'),
203                                  ('dict', {})
204                                  ])
205
206        with self.checkReportsWarning("Warning: If you aren't going to change any of the values call with True."):
207            b_gen = b.iteritems()
208        self.assertTupleEqual(next(b_gen), ('a', 'a'))
209        self.assertTupleEqual(next(b_gen), ('b', 'b'))
210        self.assertTupleEqual(next(b_gen), ('c', 'b'))
211        with self.checkReportsWarning("Warning: Doing a copy because dict is a mutable type."):
212            self.assertTupleEqual(next(b_gen), ('dict', {}))
213        with self.assertRaises(StopIteration):
214            next(b_gen)
215
216        b['dict']['a'] = 'b'
217        b['a'] = 'c'
218
219        self.checkStrOutput(a, 1, 4)
220        self.checkStrOutput(b, 2, 3)
221
222        with self.checkReportsWarning("Warning: If you aren't going to change any of the values call with True."):
223            self.assertListEqual(list(a.iteritems()),
224                                 [('a', 'a'),
225                                  ('b', 'b'),
226                                  ('dict', {})
227                                  ])
228
229        with self.checkReportsWarning("Warning: If you aren't going to change any of the values call with True."):
230            b_gen = b.iteritems()
231        self.assertTupleEqual(next(b_gen), ('a', 'c'))
232        self.assertTupleEqual(next(b_gen), ('b', 'b'))
233        self.assertTupleEqual(next(b_gen), ('c', 'b'))
234        self.assertTupleEqual(next(b_gen), ('dict', {'a': 'b'}))
235        with self.assertRaises(StopIteration):
236            next(b_gen)
237
238        with self.assertRaises(KeyError):
239            print(b["dict2"])
240
241        a['set'] = COWSetBase()
242        a['set'].add("o1")
243        a['set'].add("o1")
244        a['set'].add("o2")
245        self.assertSetEqual(set(a['set'].itervalues()), {"o1", "o2"})
246        self.assertSetEqual(set(b['set'].itervalues()), {"o1", "o2"})
247
248        b['set'].add('o3')
249        self.assertSetEqual(set(a['set'].itervalues()), {"o1", "o2"})
250        self.assertSetEqual(set(b['set'].itervalues()), {"o1", "o2", "o3"})
251
252        a['set2'] = set()
253        a['set2'].add("o1")
254        a['set2'].add("o1")
255        a['set2'].add("o2")
256
257        # We don't expect 'a' to change anymore
258        def check_a():
259            with self.checkReportsWarning("Warning: If you aren't going to change any of the values call with True."):
260                a_gen = a.iteritems()
261                self.assertTupleEqual(next(a_gen), ('a', 'a'))
262            self.assertTupleEqual(next(a_gen), ('b', 'b'))
263            self.assertTupleEqual(next(a_gen), ('dict', {}))
264            self.assertTupleEqual(next(a_gen), ('set2', {'o1', 'o2'}))
265            a_sub_set = next(a_gen)
266            self.assertEqual(a_sub_set[0], 'set')
267            self.checkStrOutput(a_sub_set[1], 1, 2)
268            self.assertSetEqual(set(a_sub_set[1].itervalues()), {'o1', 'o2'})
269
270        check_a()
271
272        b_gen = b.iteritems(readonly=True)
273        self.assertTupleEqual(next(b_gen), ('a', 'c'))
274        self.assertTupleEqual(next(b_gen), ('b', 'b'))
275        self.assertTupleEqual(next(b_gen), ('c', 'b'))
276        self.assertTupleEqual(next(b_gen), ('dict', {'a': 'b'}))
277        self.assertTupleEqual(next(b_gen), ('set2', {'o1', 'o2'}))
278        b_sub_set = next(b_gen)
279        self.assertEqual(b_sub_set[0], 'set')
280        self.checkStrOutput(b_sub_set[1], 2, 1)
281        self.assertSetEqual(set(b_sub_set[1].itervalues()), {'o1', 'o2', 'o3'})
282
283        del b['b']
284        with self.assertRaises(KeyError):
285            print(b['b'])
286        self.assertFalse('b' in b)
287
288        check_a()
289
290        b.__revertitem__('b')
291        check_a()
292        self.assertEqual(b['b'], 'b')
293        self.assertTrue('b' in b)
294
295        b.__revertitem__('dict')
296        check_a()
297
298        b_gen = b.iteritems(readonly=True)
299        self.assertTupleEqual(next(b_gen), ('a', 'c'))
300        self.assertTupleEqual(next(b_gen), ('b', 'b'))
301        self.assertTupleEqual(next(b_gen), ('c', 'b'))
302        self.assertTupleEqual(next(b_gen), ('dict', {}))
303        self.assertTupleEqual(next(b_gen), ('set2', {'o1', 'o2'}))
304        b_sub_set = next(b_gen)
305        self.assertEqual(b_sub_set[0], 'set')
306        self.checkStrOutput(b_sub_set[1], 2, 1)
307        self.assertSetEqual(set(b_sub_set[1].itervalues()), {'o1', 'o2', 'o3'})
308
309        self.checkStrOutput(a, 1, 6)
310        self.checkStrOutput(b, 2, 3)
311
312    def testSetMethods(self):
313        s = COWSetBase()
314        with self.assertRaises(TypeError):
315            print(s.iteritems())
316        with self.assertRaises(TypeError):
317            print(s.iterkeys())
318