1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5from __future__ import absolute_import, print_function, unicode_literals 6 7import os 8import six 9import unittest 10 11from mozunit import main 12 13from mozbuild.frontend.context import ( 14 AbsolutePath, 15 Context, 16 ContextDerivedTypedHierarchicalStringList, 17 ContextDerivedTypedList, 18 ContextDerivedTypedListWithItems, 19 ContextDerivedTypedRecord, 20 Files, 21 FUNCTIONS, 22 ObjDirPath, 23 Path, 24 SourcePath, 25 SPECIAL_VARIABLES, 26 SUBCONTEXTS, 27 VARIABLES, 28) 29 30from mozbuild.util import StrictOrderingOnAppendListWithFlagsFactory 31from mozpack import path as mozpath 32 33 34class TestContext(unittest.TestCase): 35 def test_defaults(self): 36 test = Context({ 37 'foo': (int, int, ''), 38 'bar': (bool, bool, ''), 39 'baz': (dict, dict, ''), 40 }) 41 42 self.assertEqual(list(test), []) 43 44 self.assertEqual(test['foo'], 0) 45 46 self.assertEqual(set(test.keys()), {'foo'}) 47 48 self.assertEqual(test['bar'], False) 49 50 self.assertEqual(set(test.keys()), {'foo', 'bar'}) 51 52 self.assertEqual(test['baz'], {}) 53 54 self.assertEqual(set(test.keys()), {'foo', 'bar', 'baz'}) 55 56 with self.assertRaises(KeyError): 57 test['qux'] 58 59 self.assertEqual(set(test.keys()), {'foo', 'bar', 'baz'}) 60 61 def test_type_check(self): 62 test = Context({ 63 'foo': (int, int, ''), 64 'baz': (dict, list, ''), 65 }) 66 67 test['foo'] = 5 68 69 self.assertEqual(test['foo'], 5) 70 71 with self.assertRaises(ValueError): 72 test['foo'] = {} 73 74 self.assertEqual(test['foo'], 5) 75 76 with self.assertRaises(KeyError): 77 test['bar'] = True 78 79 test['baz'] = [('a', 1), ('b', 2)] 80 81 self.assertEqual(test['baz'], {'a': 1, 'b': 2}) 82 83 def test_update(self): 84 test = Context({ 85 'foo': (int, int, ''), 86 'bar': (bool, bool, ''), 87 'baz': (dict, list, ''), 88 }) 89 90 self.assertEqual(list(test), []) 91 92 with self.assertRaises(ValueError): 93 test.update(bar=True, foo={}) 94 95 self.assertEqual(list(test), []) 96 97 test.update(bar=True, foo=1) 98 99 self.assertEqual(set(test.keys()), {'foo', 'bar'}) 100 self.assertEqual(test['foo'], 1) 101 self.assertEqual(test['bar'], True) 102 103 test.update([('bar', False), ('foo', 2)]) 104 self.assertEqual(test['foo'], 2) 105 self.assertEqual(test['bar'], False) 106 107 test.update([('foo', 0), ('baz', {'a': 1, 'b': 2})]) 108 self.assertEqual(test['foo'], 0) 109 self.assertEqual(test['baz'], {'a': 1, 'b': 2}) 110 111 test.update([('foo', 42), ('baz', [('c', 3), ('d', 4)])]) 112 self.assertEqual(test['foo'], 42) 113 self.assertEqual(test['baz'], {'c': 3, 'd': 4}) 114 115 def test_context_paths(self): 116 test = Context() 117 118 # Newly created context has no paths. 119 self.assertIsNone(test.main_path) 120 self.assertIsNone(test.current_path) 121 self.assertEqual(test.all_paths, set()) 122 self.assertEqual(test.source_stack, []) 123 124 foo = os.path.abspath('foo') 125 test.add_source(foo) 126 127 # Adding the first source makes it the main and current path. 128 self.assertEqual(test.main_path, foo) 129 self.assertEqual(test.current_path, foo) 130 self.assertEqual(test.all_paths, set([foo])) 131 self.assertEqual(test.source_stack, [foo]) 132 133 bar = os.path.abspath('bar') 134 test.add_source(bar) 135 136 # Adding the second source makes leaves main and current paths alone. 137 self.assertEqual(test.main_path, foo) 138 self.assertEqual(test.current_path, foo) 139 self.assertEqual(test.all_paths, set([bar, foo])) 140 self.assertEqual(test.source_stack, [foo]) 141 142 qux = os.path.abspath('qux') 143 test.push_source(qux) 144 145 # Pushing a source makes it the current path 146 self.assertEqual(test.main_path, foo) 147 self.assertEqual(test.current_path, qux) 148 self.assertEqual(test.all_paths, set([bar, foo, qux])) 149 self.assertEqual(test.source_stack, [foo, qux]) 150 151 hoge = os.path.abspath('hoge') 152 test.push_source(hoge) 153 self.assertEqual(test.main_path, foo) 154 self.assertEqual(test.current_path, hoge) 155 self.assertEqual(test.all_paths, set([bar, foo, hoge, qux])) 156 self.assertEqual(test.source_stack, [foo, qux, hoge]) 157 158 fuga = os.path.abspath('fuga') 159 160 # Adding a source after pushing doesn't change the source stack 161 test.add_source(fuga) 162 self.assertEqual(test.main_path, foo) 163 self.assertEqual(test.current_path, hoge) 164 self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux])) 165 self.assertEqual(test.source_stack, [foo, qux, hoge]) 166 167 # Adding a source twice doesn't change anything 168 test.add_source(qux) 169 self.assertEqual(test.main_path, foo) 170 self.assertEqual(test.current_path, hoge) 171 self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux])) 172 self.assertEqual(test.source_stack, [foo, qux, hoge]) 173 174 last = test.pop_source() 175 176 # Popping a source returns the last pushed one, not the last added one. 177 self.assertEqual(last, hoge) 178 self.assertEqual(test.main_path, foo) 179 self.assertEqual(test.current_path, qux) 180 self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux])) 181 self.assertEqual(test.source_stack, [foo, qux]) 182 183 last = test.pop_source() 184 self.assertEqual(last, qux) 185 self.assertEqual(test.main_path, foo) 186 self.assertEqual(test.current_path, foo) 187 self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux])) 188 self.assertEqual(test.source_stack, [foo]) 189 190 # Popping the main path is allowed. 191 last = test.pop_source() 192 self.assertEqual(last, foo) 193 self.assertEqual(test.main_path, foo) 194 self.assertIsNone(test.current_path) 195 self.assertEqual(test.all_paths, set([bar, foo, fuga, hoge, qux])) 196 self.assertEqual(test.source_stack, []) 197 198 # Popping past the main path asserts. 199 with self.assertRaises(AssertionError): 200 test.pop_source() 201 202 # Pushing after the main path was popped asserts. 203 with self.assertRaises(AssertionError): 204 test.push_source(foo) 205 206 test = Context() 207 test.push_source(foo) 208 test.push_source(bar) 209 210 # Pushing the same file twice is allowed. 211 test.push_source(bar) 212 test.push_source(foo) 213 self.assertEqual(last, foo) 214 self.assertEqual(test.main_path, foo) 215 self.assertEqual(test.current_path, foo) 216 self.assertEqual(test.all_paths, set([bar, foo])) 217 self.assertEqual(test.source_stack, [foo, bar, bar, foo]) 218 219 def test_context_dirs(self): 220 class Config(object): 221 pass 222 config = Config() 223 config.topsrcdir = mozpath.abspath(os.curdir) 224 config.topobjdir = mozpath.abspath('obj') 225 test = Context(config=config) 226 foo = mozpath.abspath('foo') 227 test.push_source(foo) 228 229 self.assertEqual(test.srcdir, config.topsrcdir) 230 self.assertEqual(test.relsrcdir, '') 231 self.assertEqual(test.objdir, config.topobjdir) 232 self.assertEqual(test.relobjdir, '') 233 234 foobar = os.path.abspath('foo/bar') 235 test.push_source(foobar) 236 self.assertEqual(test.srcdir, mozpath.join(config.topsrcdir, 'foo')) 237 self.assertEqual(test.relsrcdir, 'foo') 238 self.assertEqual(test.objdir, config.topobjdir) 239 self.assertEqual(test.relobjdir, '') 240 241 242class TestSymbols(unittest.TestCase): 243 def _verify_doc(self, doc): 244 # Documentation should be of the format: 245 # """SUMMARY LINE 246 # 247 # EXTRA PARAGRAPHS 248 # """ 249 250 self.assertNotIn('\r', doc) 251 252 lines = doc.split('\n') 253 254 # No trailing whitespace. 255 for line in lines[0:-1]: 256 self.assertEqual(line, line.rstrip()) 257 258 self.assertGreater(len(lines), 0) 259 self.assertGreater(len(lines[0].strip()), 0) 260 261 # Last line should be empty. 262 self.assertEqual(lines[-1].strip(), '') 263 264 def test_documentation_formatting(self): 265 for typ, inp, doc in VARIABLES.values(): 266 self._verify_doc(doc) 267 268 for attr, args, doc in FUNCTIONS.values(): 269 self._verify_doc(doc) 270 271 for func, typ, doc in SPECIAL_VARIABLES.values(): 272 self._verify_doc(doc) 273 274 for name, cls in SUBCONTEXTS.items(): 275 self._verify_doc(cls.__doc__) 276 277 for name, v in cls.VARIABLES.items(): 278 self._verify_doc(v[2]) 279 280 281class TestPaths(unittest.TestCase): 282 @classmethod 283 def setUpClass(cls): 284 class Config(object): 285 pass 286 cls.config = config = Config() 287 config.topsrcdir = mozpath.abspath(os.curdir) 288 config.topobjdir = mozpath.abspath('obj') 289 config.external_source_dir = None 290 291 def test_path(self): 292 config = self.config 293 ctxt1 = Context(config=config) 294 ctxt1.push_source(mozpath.join(config.topsrcdir, 'foo', 'moz.build')) 295 ctxt2 = Context(config=config) 296 ctxt2.push_source(mozpath.join(config.topsrcdir, 'bar', 'moz.build')) 297 298 path1 = Path(ctxt1, 'qux') 299 self.assertIsInstance(path1, SourcePath) 300 self.assertEqual(path1, 'qux') 301 self.assertEqual(path1.full_path, 302 mozpath.join(config.topsrcdir, 'foo', 'qux')) 303 304 path2 = Path(ctxt2, '../foo/qux') 305 self.assertIsInstance(path2, SourcePath) 306 self.assertEqual(path2, '../foo/qux') 307 self.assertEqual(path2.full_path, 308 mozpath.join(config.topsrcdir, 'foo', 'qux')) 309 310 self.assertEqual(path1, path2) 311 312 self.assertEqual(path1.join('../../bar/qux').full_path, 313 mozpath.join(config.topsrcdir, 'bar', 'qux')) 314 315 path1 = Path(ctxt1, '/qux/qux') 316 self.assertIsInstance(path1, SourcePath) 317 self.assertEqual(path1, '/qux/qux') 318 self.assertEqual(path1.full_path, 319 mozpath.join(config.topsrcdir, 'qux', 'qux')) 320 321 path2 = Path(ctxt2, '/qux/qux') 322 self.assertIsInstance(path2, SourcePath) 323 self.assertEqual(path2, '/qux/qux') 324 self.assertEqual(path2.full_path, 325 mozpath.join(config.topsrcdir, 'qux', 'qux')) 326 327 self.assertEqual(path1, path2) 328 329 path1 = Path(ctxt1, '!qux') 330 self.assertIsInstance(path1, ObjDirPath) 331 self.assertEqual(path1, '!qux') 332 self.assertEqual(path1.full_path, 333 mozpath.join(config.topobjdir, 'foo', 'qux')) 334 335 path2 = Path(ctxt2, '!../foo/qux') 336 self.assertIsInstance(path2, ObjDirPath) 337 self.assertEqual(path2, '!../foo/qux') 338 self.assertEqual(path2.full_path, 339 mozpath.join(config.topobjdir, 'foo', 'qux')) 340 341 self.assertEqual(path1, path2) 342 343 path1 = Path(ctxt1, '!/qux/qux') 344 self.assertIsInstance(path1, ObjDirPath) 345 self.assertEqual(path1, '!/qux/qux') 346 self.assertEqual(path1.full_path, 347 mozpath.join(config.topobjdir, 'qux', 'qux')) 348 349 path2 = Path(ctxt2, '!/qux/qux') 350 self.assertIsInstance(path2, ObjDirPath) 351 self.assertEqual(path2, '!/qux/qux') 352 self.assertEqual(path2.full_path, 353 mozpath.join(config.topobjdir, 'qux', 'qux')) 354 355 self.assertEqual(path1, path2) 356 357 path1 = Path(ctxt1, path1) 358 self.assertIsInstance(path1, ObjDirPath) 359 self.assertEqual(path1, '!/qux/qux') 360 self.assertEqual(path1.full_path, 361 mozpath.join(config.topobjdir, 'qux', 'qux')) 362 363 path2 = Path(ctxt2, path2) 364 self.assertIsInstance(path2, ObjDirPath) 365 self.assertEqual(path2, '!/qux/qux') 366 self.assertEqual(path2.full_path, 367 mozpath.join(config.topobjdir, 'qux', 'qux')) 368 369 self.assertEqual(path1, path2) 370 371 path1 = Path(path1) 372 self.assertIsInstance(path1, ObjDirPath) 373 self.assertEqual(path1, '!/qux/qux') 374 self.assertEqual(path1.full_path, 375 mozpath.join(config.topobjdir, 'qux', 'qux')) 376 377 self.assertEqual(path1, path2) 378 379 path2 = Path(path2) 380 self.assertIsInstance(path2, ObjDirPath) 381 self.assertEqual(path2, '!/qux/qux') 382 self.assertEqual(path2.full_path, 383 mozpath.join(config.topobjdir, 'qux', 'qux')) 384 385 self.assertEqual(path1, path2) 386 387 def test_source_path(self): 388 config = self.config 389 ctxt = Context(config=config) 390 ctxt.push_source(mozpath.join(config.topsrcdir, 'foo', 'moz.build')) 391 392 path = SourcePath(ctxt, 'qux') 393 self.assertEqual(path, 'qux') 394 self.assertEqual(path.full_path, 395 mozpath.join(config.topsrcdir, 'foo', 'qux')) 396 self.assertEqual(path.translated, 397 mozpath.join(config.topobjdir, 'foo', 'qux')) 398 399 path = SourcePath(ctxt, '../bar/qux') 400 self.assertEqual(path, '../bar/qux') 401 self.assertEqual(path.full_path, 402 mozpath.join(config.topsrcdir, 'bar', 'qux')) 403 self.assertEqual(path.translated, 404 mozpath.join(config.topobjdir, 'bar', 'qux')) 405 406 path = SourcePath(ctxt, '/qux/qux') 407 self.assertEqual(path, '/qux/qux') 408 self.assertEqual(path.full_path, 409 mozpath.join(config.topsrcdir, 'qux', 'qux')) 410 self.assertEqual(path.translated, 411 mozpath.join(config.topobjdir, 'qux', 'qux')) 412 413 with self.assertRaises(ValueError): 414 SourcePath(ctxt, '!../bar/qux') 415 416 with self.assertRaises(ValueError): 417 SourcePath(ctxt, '!/qux/qux') 418 419 path = SourcePath(path) 420 self.assertIsInstance(path, SourcePath) 421 self.assertEqual(path, '/qux/qux') 422 self.assertEqual(path.full_path, 423 mozpath.join(config.topsrcdir, 'qux', 'qux')) 424 self.assertEqual(path.translated, 425 mozpath.join(config.topobjdir, 'qux', 'qux')) 426 427 path = Path(path) 428 self.assertIsInstance(path, SourcePath) 429 430 def test_objdir_path(self): 431 config = self.config 432 ctxt = Context(config=config) 433 ctxt.push_source(mozpath.join(config.topsrcdir, 'foo', 'moz.build')) 434 435 path = ObjDirPath(ctxt, '!qux') 436 self.assertEqual(path, '!qux') 437 self.assertEqual(path.full_path, 438 mozpath.join(config.topobjdir, 'foo', 'qux')) 439 440 path = ObjDirPath(ctxt, '!../bar/qux') 441 self.assertEqual(path, '!../bar/qux') 442 self.assertEqual(path.full_path, 443 mozpath.join(config.topobjdir, 'bar', 'qux')) 444 445 path = ObjDirPath(ctxt, '!/qux/qux') 446 self.assertEqual(path, '!/qux/qux') 447 self.assertEqual(path.full_path, 448 mozpath.join(config.topobjdir, 'qux', 'qux')) 449 450 with self.assertRaises(ValueError): 451 path = ObjDirPath(ctxt, '../bar/qux') 452 453 with self.assertRaises(ValueError): 454 path = ObjDirPath(ctxt, '/qux/qux') 455 456 path = ObjDirPath(path) 457 self.assertIsInstance(path, ObjDirPath) 458 self.assertEqual(path, '!/qux/qux') 459 self.assertEqual(path.full_path, 460 mozpath.join(config.topobjdir, 'qux', 'qux')) 461 462 path = Path(path) 463 self.assertIsInstance(path, ObjDirPath) 464 465 def test_absolute_path(self): 466 config = self.config 467 ctxt = Context(config=config) 468 ctxt.push_source(mozpath.join(config.topsrcdir, 'foo', 'moz.build')) 469 470 path = AbsolutePath(ctxt, '%/qux') 471 self.assertEqual(path, '%/qux') 472 self.assertEqual(path.full_path, '/qux') 473 474 with self.assertRaises(ValueError): 475 path = AbsolutePath(ctxt, '%qux') 476 477 def test_path_with_mixed_contexts(self): 478 config = self.config 479 ctxt1 = Context(config=config) 480 ctxt1.push_source(mozpath.join(config.topsrcdir, 'foo', 'moz.build')) 481 ctxt2 = Context(config=config) 482 ctxt2.push_source(mozpath.join(config.topsrcdir, 'bar', 'moz.build')) 483 484 path1 = Path(ctxt1, 'qux') 485 path2 = Path(ctxt2, path1) 486 self.assertEqual(path2, path1) 487 self.assertEqual(path2, 'qux') 488 self.assertEqual(path2.context, ctxt1) 489 self.assertEqual(path2.full_path, 490 mozpath.join(config.topsrcdir, 'foo', 'qux')) 491 492 path1 = Path(ctxt1, '../bar/qux') 493 path2 = Path(ctxt2, path1) 494 self.assertEqual(path2, path1) 495 self.assertEqual(path2, '../bar/qux') 496 self.assertEqual(path2.context, ctxt1) 497 self.assertEqual(path2.full_path, 498 mozpath.join(config.topsrcdir, 'bar', 'qux')) 499 500 path1 = Path(ctxt1, '/qux/qux') 501 path2 = Path(ctxt2, path1) 502 self.assertEqual(path2, path1) 503 self.assertEqual(path2, '/qux/qux') 504 self.assertEqual(path2.context, ctxt1) 505 self.assertEqual(path2.full_path, 506 mozpath.join(config.topsrcdir, 'qux', 'qux')) 507 508 path1 = Path(ctxt1, '!qux') 509 path2 = Path(ctxt2, path1) 510 self.assertEqual(path2, path1) 511 self.assertEqual(path2, '!qux') 512 self.assertEqual(path2.context, ctxt1) 513 self.assertEqual(path2.full_path, 514 mozpath.join(config.topobjdir, 'foo', 'qux')) 515 516 path1 = Path(ctxt1, '!../bar/qux') 517 path2 = Path(ctxt2, path1) 518 self.assertEqual(path2, path1) 519 self.assertEqual(path2, '!../bar/qux') 520 self.assertEqual(path2.context, ctxt1) 521 self.assertEqual(path2.full_path, 522 mozpath.join(config.topobjdir, 'bar', 'qux')) 523 524 path1 = Path(ctxt1, '!/qux/qux') 525 path2 = Path(ctxt2, path1) 526 self.assertEqual(path2, path1) 527 self.assertEqual(path2, '!/qux/qux') 528 self.assertEqual(path2.context, ctxt1) 529 self.assertEqual(path2.full_path, 530 mozpath.join(config.topobjdir, 'qux', 'qux')) 531 532 def test_path_typed_list(self): 533 config = self.config 534 ctxt1 = Context(config=config) 535 ctxt1.push_source(mozpath.join(config.topsrcdir, 'foo', 'moz.build')) 536 ctxt2 = Context(config=config) 537 ctxt2.push_source(mozpath.join(config.topsrcdir, 'bar', 'moz.build')) 538 539 paths = [ 540 '!../bar/qux', 541 '!/qux/qux', 542 '!qux', 543 '../bar/qux', 544 '/qux/qux', 545 'qux', 546 ] 547 548 MyList = ContextDerivedTypedList(Path) 549 l = MyList(ctxt1) 550 l += paths 551 552 for p_str, p_path in zip(paths, l): 553 self.assertEqual(p_str, p_path) 554 self.assertEqual(p_path, Path(ctxt1, p_str)) 555 self.assertEqual(p_path.join('foo'), 556 Path(ctxt1, mozpath.join(p_str, 'foo'))) 557 558 l2 = MyList(ctxt2) 559 l2 += paths 560 561 for p_str, p_path in zip(paths, l2): 562 self.assertEqual(p_str, p_path) 563 self.assertEqual(p_path, Path(ctxt2, p_str)) 564 565 # Assigning with Paths from another context doesn't rebase them 566 l2 = MyList(ctxt2) 567 l2 += l 568 569 for p_str, p_path in zip(paths, l2): 570 self.assertEqual(p_str, p_path) 571 self.assertEqual(p_path, Path(ctxt1, p_str)) 572 573 MyListWithFlags = ContextDerivedTypedListWithItems( 574 Path, StrictOrderingOnAppendListWithFlagsFactory({ 575 'foo': bool, 576 })) 577 l = MyListWithFlags(ctxt1) 578 l += paths 579 580 for p in paths: 581 l[p].foo = True 582 583 for p_str, p_path in zip(paths, l): 584 self.assertEqual(p_str, p_path) 585 self.assertEqual(p_path, Path(ctxt1, p_str)) 586 self.assertEqual(l[p_str].foo, True) 587 self.assertEqual(l[p_path].foo, True) 588 589 def test_path_typed_hierarchy_list(self): 590 config = self.config 591 ctxt1 = Context(config=config) 592 ctxt1.push_source(mozpath.join(config.topsrcdir, 'foo', 'moz.build')) 593 ctxt2 = Context(config=config) 594 ctxt2.push_source(mozpath.join(config.topsrcdir, 'bar', 'moz.build')) 595 596 paths = [ 597 '!../bar/qux', 598 '!/qux/qux', 599 '!qux', 600 '../bar/qux', 601 '/qux/qux', 602 'qux', 603 ] 604 605 MyList = ContextDerivedTypedHierarchicalStringList(Path) 606 l = MyList(ctxt1) 607 l += paths 608 l.subdir += paths 609 610 for _, files in l.walk(): 611 for p_str, p_path in zip(paths, files): 612 self.assertEqual(p_str, p_path) 613 self.assertEqual(p_path, Path(ctxt1, p_str)) 614 self.assertEqual(p_path.join('foo'), 615 Path(ctxt1, mozpath.join(p_str, 'foo'))) 616 617 l2 = MyList(ctxt2) 618 l2 += paths 619 l2.subdir += paths 620 621 for _, files in l2.walk(): 622 for p_str, p_path in zip(paths, files): 623 self.assertEqual(p_str, p_path) 624 self.assertEqual(p_path, Path(ctxt2, p_str)) 625 626 # Assigning with Paths from another context doesn't rebase them 627 l2 = MyList(ctxt2) 628 l2 += l 629 630 for _, files in l2.walk(): 631 for p_str, p_path in zip(paths, files): 632 self.assertEqual(p_str, p_path) 633 self.assertEqual(p_path, Path(ctxt1, p_str)) 634 635 636class TestTypedRecord(unittest.TestCase): 637 638 def test_fields(self): 639 T = ContextDerivedTypedRecord(('field1', six.text_type), 640 ('field2', list)) 641 inst = T(None) 642 self.assertEqual(inst.field1, '') 643 self.assertEqual(inst.field2, []) 644 645 inst.field1 = 'foo' 646 inst.field2 += ['bar'] 647 648 self.assertEqual(inst.field1, 'foo') 649 self.assertEqual(inst.field2, ['bar']) 650 651 with self.assertRaises(AttributeError): 652 inst.field3 = [] 653 654 def test_coercion(self): 655 T = ContextDerivedTypedRecord(('field1', six.text_type), 656 ('field2', list)) 657 inst = T(None) 658 inst.field1 = 3 659 inst.field2 += ('bar',) 660 self.assertEqual(inst.field1, '3') 661 self.assertEqual(inst.field2, ['bar']) 662 663 with self.assertRaises(TypeError): 664 inst.field2 = object() 665 666 667class TestFiles(unittest.TestCase): 668 def test_aggregate_empty(self): 669 c = Context({}) 670 671 files = {'moz.build': Files(c, '**')} 672 673 self.assertEqual(Files.aggregate(files), { 674 'bug_component_counts': [], 675 'recommended_bug_component': None, 676 }) 677 678 def test_single_bug_component(self): 679 c = Context({}) 680 f = Files(c, '**') 681 f['BUG_COMPONENT'] = (u'Product1', u'Component1') 682 683 files = {'moz.build': f} 684 self.assertEqual(Files.aggregate(files), { 685 'bug_component_counts': [((u'Product1', u'Component1'), 1)], 686 'recommended_bug_component': (u'Product1', u'Component1'), 687 }) 688 689 def test_multiple_bug_components(self): 690 c = Context({}) 691 f1 = Files(c, '**') 692 f1['BUG_COMPONENT'] = (u'Product1', u'Component1') 693 694 f2 = Files(c, '**') 695 f2['BUG_COMPONENT'] = (u'Product2', u'Component2') 696 697 files = {'a': f1, 'b': f2, 'c': f1} 698 self.assertEqual(Files.aggregate(files), { 699 'bug_component_counts': [ 700 ((u'Product1', u'Component1'), 2), 701 ((u'Product2', u'Component2'), 1), 702 ], 703 'recommended_bug_component': (u'Product1', u'Component1'), 704 }) 705 706 def test_no_recommended_bug_component(self): 707 """If there is no clear count winner, we don't recommend a bug component.""" 708 c = Context({}) 709 f1 = Files(c, '**') 710 f1['BUG_COMPONENT'] = (u'Product1', u'Component1') 711 712 f2 = Files(c, '**') 713 f2['BUG_COMPONENT'] = (u'Product2', u'Component2') 714 715 files = {'a': f1, 'b': f2} 716 self.assertEqual(Files.aggregate(files), { 717 'bug_component_counts': [ 718 ((u'Product1', u'Component1'), 1), 719 ((u'Product2', u'Component2'), 1), 720 ], 721 'recommended_bug_component': None, 722 }) 723 724 def test_multiple_patterns(self): 725 c = Context({}) 726 f1 = Files(c, 'a/**') 727 f1['BUG_COMPONENT'] = (u'Product1', u'Component1') 728 f2 = Files(c, 'b/**', 'a/bar') 729 f2['BUG_COMPONENT'] = (u'Product2', u'Component2') 730 731 files = {'a/foo': f1, 'a/bar': f2, 'b/foo': f2} 732 self.assertEqual(Files.aggregate(files), { 733 'bug_component_counts': [ 734 ((u'Product2', u'Component2'), 2), 735 ((u'Product1', u'Component1'), 1), 736 ], 737 'recommended_bug_component': (u'Product2', u'Component2'), 738 }) 739 740 741if __name__ == '__main__': 742 main() 743