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