Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1 | # |
| 2 | # BitBake Tests for Copy-on-Write (cow.py) |
| 3 | # |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 4 | # SPDX-License-Identifier: GPL-2.0-only |
| 5 | # |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 6 | # Copyright 2006 Holger Freyther <freyther@handhelds.org> |
Andrew Geissler | c9f7865 | 2020-09-18 14:11:35 -0500 | [diff] [blame] | 7 | # Copyright (C) 2020 Agilent Technologies, Inc. |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 8 | # |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 9 | |
Andrew Geissler | c9f7865 | 2020-09-18 14:11:35 -0500 | [diff] [blame] | 10 | import io |
| 11 | import re |
| 12 | import sys |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 13 | import unittest |
Andrew Geissler | c9f7865 | 2020-09-18 14:11:35 -0500 | [diff] [blame] | 14 | import contextlib |
| 15 | import collections |
| 16 | |
| 17 | from bb.COW import COWDictBase, COWSetBase, COWDictMeta, COWSetMeta |
Andrew Geissler | 82c905d | 2020-04-13 13:39:40 -0500 | [diff] [blame] | 18 | |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 19 | |
| 20 | class COWTestCase(unittest.TestCase): |
| 21 | """ |
| 22 | Test case for the COW module from mithro |
| 23 | """ |
| 24 | |
Andrew Geissler | c9f7865 | 2020-09-18 14:11:35 -0500 | [diff] [blame] | 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 | |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 76 | def testGetSet(self): |
| 77 | """ |
| 78 | Test and set |
| 79 | """ |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 80 | a = COWDictBase.copy() |
| 81 | |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 82 | self.assertEqual(False, 'a' in a) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 83 | |
| 84 | a['a'] = 'a' |
| 85 | a['b'] = 'b' |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 86 | self.assertEqual(True, 'a' in a) |
| 87 | self.assertEqual(True, 'b' in a) |
Andrew Geissler | c9f7865 | 2020-09-18 14:11:35 -0500 | [diff] [blame] | 88 | self.assertEqual('a', a['a']) |
| 89 | self.assertEqual('b', a['b']) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 90 | |
| 91 | def testCopyCopy(self): |
| 92 | """ |
| 93 | Test the copy of copies |
| 94 | """ |
| 95 | |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 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 |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 106 | self.assertEqual(False, 'c' in c) |
| 107 | self.assertEqual(30, c['a']) |
| 108 | self.assertEqual(10, b['a']) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 109 | |
| 110 | # test copy |
| 111 | b_2 = b.copy() |
| 112 | c_2 = c.copy() |
| 113 | |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 114 | self.assertEqual(False, 'c' in c_2) |
| 115 | self.assertEqual(10, b_2['a']) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 116 | |
| 117 | b_2['d'] = 40 |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 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) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 123 | |
| 124 | c_2['d'] = 30 |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 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) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 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 |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 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) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 143 | |
| 144 | b_3['e'] = 'viel' |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 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) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 151 | |
| 152 | def testCow(self): |
Andrew Geissler | c9f7865 | 2020-09-18 14:11:35 -0500 | [diff] [blame] | 153 | self.trackWarnings() |
| 154 | |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 155 | c = COWDictBase.copy() |
| 156 | c['123'] = 1027 |
| 157 | c['other'] = 4711 |
Andrew Geissler | c9f7865 | 2020-09-18 14:11:35 -0500 | [diff] [blame] | 158 | c['d'] = {'abc': 10, 'bcd': 20} |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 159 | |
| 160 | copy = c.copy() |
| 161 | |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 162 | self.assertEqual(1027, c['123']) |
| 163 | self.assertEqual(4711, c['other']) |
Andrew Geissler | c9f7865 | 2020-09-18 14:11:35 -0500 | [diff] [blame] | 164 | self.assertEqual({'abc': 10, 'bcd': 20}, c['d']) |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 165 | self.assertEqual(1027, copy['123']) |
| 166 | self.assertEqual(4711, copy['other']) |
Andrew Geissler | c9f7865 | 2020-09-18 14:11:35 -0500 | [diff] [blame] | 167 | with self.checkReportsWarning("Warning: Doing a copy because d is a mutable type."): |
| 168 | self.assertEqual({'abc': 10, 'bcd': 20}, copy['d']) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 169 | |
| 170 | # cow it now |
| 171 | copy['123'] = 1028 |
| 172 | copy['other'] = 4712 |
| 173 | copy['d']['abc'] = 20 |
| 174 | |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 175 | self.assertEqual(1027, c['123']) |
| 176 | self.assertEqual(4711, c['other']) |
Andrew Geissler | c9f7865 | 2020-09-18 14:11:35 -0500 | [diff] [blame] | 177 | self.assertEqual({'abc': 10, 'bcd': 20}, c['d']) |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 178 | self.assertEqual(1028, copy['123']) |
| 179 | self.assertEqual(4712, copy['other']) |
Andrew Geissler | c9f7865 | 2020-09-18 14:11:35 -0500 | [diff] [blame] | 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()) |