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