test_files.py (46053B)
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 5 import buildconfig 6 7 from mozbuild.dirutils import ensureParentDir 8 from mozbuild.nodeutil import find_node_executable 9 from mozbuild.util import ensure_bytes 10 from mozpack.errors import ErrorMessage 11 from mozpack.files import ( 12 AbsoluteSymlinkFile, 13 ComposedFinder, 14 DeflatedFile, 15 Dest, 16 ExistingFile, 17 ExtractedTarFile, 18 File, 19 FileFinder, 20 GeneratedFile, 21 HardlinkFile, 22 JarFinder, 23 ManifestFile, 24 MercurialFile, 25 MercurialRevisionFinder, 26 MinifiedCommentStripped, 27 MinifiedJavaScript, 28 PreprocessedFile, 29 TarFinder, 30 ) 31 32 # We don't have hglib installed everywhere. 33 try: 34 import hglib 35 except ImportError: 36 hglib = None 37 38 import os 39 import platform 40 import random 41 import sys 42 import tarfile 43 import unittest 44 from io import BytesIO 45 from tempfile import mkdtemp 46 47 import mozfile 48 import mozunit 49 50 import mozpack.path as mozpath 51 from mozpack.chrome.manifest import ( 52 ManifestContent, 53 ManifestLocale, 54 ManifestOverride, 55 ManifestResource, 56 ) 57 from mozpack.mozjar import JarReader, JarWriter 58 59 60 class TestWithTmpDir(unittest.TestCase): 61 def setUp(self): 62 self.tmpdir = mkdtemp() 63 64 self.symlink_supported = False 65 self.hardlink_supported = False 66 67 # See comment in mozpack.files.AbsoluteSymlinkFile 68 if hasattr(os, "symlink") and platform.system() != "Windows": 69 dummy_path = self.tmppath("dummy_file") 70 with open(dummy_path, "a"): 71 pass 72 73 try: 74 os.symlink(dummy_path, self.tmppath("dummy_symlink")) 75 os.remove(self.tmppath("dummy_symlink")) 76 except OSError: 77 pass 78 finally: 79 os.remove(dummy_path) 80 81 self.symlink_supported = True 82 83 if hasattr(os, "link"): 84 dummy_path = self.tmppath("dummy_file") 85 with open(dummy_path, "a"): 86 pass 87 88 try: 89 os.link(dummy_path, self.tmppath("dummy_hardlink")) 90 os.remove(self.tmppath("dummy_hardlink")) 91 except OSError: 92 pass 93 finally: 94 os.remove(dummy_path) 95 96 self.hardlink_supported = True 97 98 def tearDown(self): 99 mozfile.rmtree(self.tmpdir) 100 101 def tmppath(self, relpath): 102 return os.path.normpath(os.path.join(self.tmpdir, relpath)) 103 104 105 class MockDest(BytesIO, Dest): 106 def __init__(self): 107 BytesIO.__init__(self) 108 self.mode = None 109 110 def read(self, length=-1): 111 if self.mode != "r": 112 self.seek(0) 113 self.mode = "r" 114 return BytesIO.read(self, length) 115 116 def write(self, data): 117 if self.mode != "w": 118 self.seek(0) 119 self.truncate(0) 120 self.mode = "w" 121 return BytesIO.write(self, data) 122 123 def exists(self): 124 return True 125 126 def close(self): 127 if self.mode: 128 self.mode = None 129 130 131 class DestNoWrite(Dest): 132 def write(self, data): 133 raise RuntimeError 134 135 136 class TestDest(TestWithTmpDir): 137 def test_dest(self): 138 dest = Dest(self.tmppath("dest")) 139 self.assertFalse(dest.exists()) 140 dest.write(b"foo") 141 self.assertTrue(dest.exists()) 142 dest.write(b"foo") 143 self.assertEqual(dest.read(4), b"foof") 144 self.assertEqual(dest.read(), b"oo") 145 self.assertEqual(dest.read(), b"") 146 dest.write(b"bar") 147 self.assertEqual(dest.read(4), b"bar") 148 dest.close() 149 self.assertEqual(dest.read(), b"bar") 150 dest.write(b"foo") 151 dest.close() 152 dest.write(b"qux") 153 self.assertEqual(dest.read(), b"qux") 154 155 156 rand = bytes( 157 random.choice(b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 158 for i in range(131597) 159 ) 160 samples = [ 161 b"", 162 b"test", 163 b"fooo", 164 b"same", 165 b"same", 166 b"Different and longer", 167 rand, 168 rand, 169 rand[:-1] + b"_", 170 b"test", 171 ] 172 173 174 class TestFile(TestWithTmpDir): 175 def test_file(self): 176 """ 177 Check that File.copy yields the proper content in the destination file 178 in all situations that trigger different code paths: 179 - different content 180 - different content of the same size 181 - same content 182 - long content 183 """ 184 src = self.tmppath("src") 185 dest = self.tmppath("dest") 186 187 for content in samples: 188 with open(src, "wb") as tmp: 189 tmp.write(content) 190 # Ensure the destination file, when it exists, is older than the 191 # source 192 if os.path.exists(dest): 193 time = os.path.getmtime(src) - 1 194 os.utime(dest, (time, time)) 195 f = File(src) 196 f.copy(dest) 197 self.assertEqual(content, open(dest, "rb").read()) 198 self.assertEqual(content, f.open().read()) 199 self.assertEqual(content, f.open().read()) 200 201 def test_file_dest(self): 202 """ 203 Similar to test_file, but for a destination object instead of 204 a destination file. This ensures the destination object is being 205 used properly by File.copy, ensuring that other subclasses of Dest 206 will work. 207 """ 208 src = self.tmppath("src") 209 dest = MockDest() 210 211 for content in samples: 212 with open(src, "wb") as tmp: 213 tmp.write(content) 214 f = File(src) 215 f.copy(dest) 216 self.assertEqual(content, dest.getvalue()) 217 218 def test_file_open(self): 219 """ 220 Test whether File.open returns an appropriately reset file object. 221 """ 222 src = self.tmppath("src") 223 content = b"".join(samples) 224 with open(src, "wb") as tmp: 225 tmp.write(content) 226 227 f = File(src) 228 self.assertEqual(content[:42], f.open().read(42)) 229 self.assertEqual(content, f.open().read()) 230 231 def test_file_no_write(self): 232 """ 233 Test various conditions where File.copy is expected not to write 234 in the destination file. 235 """ 236 src = self.tmppath("src") 237 dest = self.tmppath("dest") 238 239 with open(src, "wb") as tmp: 240 tmp.write(b"test") 241 242 # Initial copy 243 f = File(src) 244 f.copy(dest) 245 246 # Ensure subsequent copies won't trigger writes 247 f.copy(DestNoWrite(dest)) 248 self.assertEqual(b"test", open(dest, "rb").read()) 249 250 # When the source file is newer, but with the same content, no copy 251 # should occur 252 time = os.path.getmtime(src) - 1 253 os.utime(dest, (time, time)) 254 f.copy(DestNoWrite(dest)) 255 self.assertEqual(b"test", open(dest, "rb").read()) 256 257 # When the source file is older than the destination file, even with 258 # different content, no copy should occur. 259 with open(src, "wb") as tmp: 260 tmp.write(b"fooo") 261 time = os.path.getmtime(dest) - 1 262 os.utime(src, (time, time)) 263 f.copy(DestNoWrite(dest)) 264 self.assertEqual(b"test", open(dest, "rb").read()) 265 266 # Double check that under conditions where a copy occurs, we would get 267 # an exception. 268 time = os.path.getmtime(src) - 1 269 os.utime(dest, (time, time)) 270 self.assertRaises(RuntimeError, f.copy, DestNoWrite(dest)) 271 272 # skip_if_older=False is expected to force a copy in this situation. 273 f.copy(dest, skip_if_older=False) 274 self.assertEqual(b"fooo", open(dest, "rb").read()) 275 276 277 class TestAbsoluteSymlinkFile(TestWithTmpDir): 278 def test_absolute_relative(self): 279 AbsoluteSymlinkFile("/foo") 280 281 with self.assertRaisesRegex(ValueError, "Symlink target not absolute"): 282 AbsoluteSymlinkFile("./foo") 283 284 def test_symlink_file(self): 285 source = self.tmppath("test_path") 286 with open(source, "w") as fh: 287 fh.write("Hello world") 288 289 s = AbsoluteSymlinkFile(source) 290 dest = self.tmppath("symlink") 291 self.assertTrue(s.copy(dest)) 292 293 if self.symlink_supported: 294 self.assertTrue(os.path.islink(dest)) 295 link = os.readlink(dest) 296 self.assertEqual(link, source) 297 else: 298 self.assertTrue(os.path.isfile(dest)) 299 content = open(dest).read() 300 self.assertEqual(content, "Hello world") 301 302 def test_replace_file_with_symlink(self): 303 # If symlinks are supported, an existing file should be replaced by a 304 # symlink. 305 source = self.tmppath("test_path") 306 with open(source, "w") as fh: 307 fh.write("source") 308 309 dest = self.tmppath("dest") 310 with open(dest, "a"): 311 pass 312 313 s = AbsoluteSymlinkFile(source) 314 s.copy(dest, skip_if_older=False) 315 316 if self.symlink_supported: 317 self.assertTrue(os.path.islink(dest)) 318 link = os.readlink(dest) 319 self.assertEqual(link, source) 320 else: 321 self.assertTrue(os.path.isfile(dest)) 322 content = open(dest).read() 323 self.assertEqual(content, "source") 324 325 def test_replace_symlink(self): 326 if not self.symlink_supported: 327 return 328 329 source = self.tmppath("source") 330 with open(source, "a"): 331 pass 332 333 dest = self.tmppath("dest") 334 335 os.symlink(self.tmppath("bad"), dest) 336 self.assertTrue(os.path.islink(dest)) 337 338 s = AbsoluteSymlinkFile(source) 339 self.assertTrue(s.copy(dest)) 340 341 self.assertTrue(os.path.islink(dest)) 342 link = os.readlink(dest) 343 self.assertEqual(link, source) 344 345 def test_noop(self): 346 if not hasattr(os, "symlink") or sys.platform == "win32": 347 return 348 349 source = self.tmppath("source") 350 dest = self.tmppath("dest") 351 352 with open(source, "a"): 353 pass 354 355 os.symlink(source, dest) 356 link = os.readlink(dest) 357 self.assertEqual(link, source) 358 359 s = AbsoluteSymlinkFile(source) 360 self.assertFalse(s.copy(dest)) 361 362 link = os.readlink(dest) 363 self.assertEqual(link, source) 364 365 366 class TestHardlinkFile(TestWithTmpDir): 367 def test_absolute_relative(self): 368 HardlinkFile("/foo") 369 HardlinkFile("./foo") 370 371 def test_hardlink_file(self): 372 source = self.tmppath("test_path") 373 with open(source, "w") as fh: 374 fh.write("Hello world") 375 376 s = HardlinkFile(source) 377 dest = self.tmppath("hardlink") 378 self.assertTrue(s.copy(dest)) 379 380 if self.hardlink_supported: 381 source_stat = os.stat(source) 382 dest_stat = os.stat(dest) 383 self.assertEqual(source_stat.st_dev, dest_stat.st_dev) 384 self.assertEqual(source_stat.st_ino, dest_stat.st_ino) 385 else: 386 self.assertTrue(os.path.isfile(dest)) 387 with open(dest) as f: 388 content = f.read() 389 self.assertEqual(content, "Hello world") 390 391 def test_replace_file_with_hardlink(self): 392 # If hardlink are supported, an existing file should be replaced by a 393 # symlink. 394 source = self.tmppath("test_path") 395 with open(source, "w") as fh: 396 fh.write("source") 397 398 dest = self.tmppath("dest") 399 with open(dest, "a"): 400 pass 401 402 s = HardlinkFile(source) 403 s.copy(dest, skip_if_older=False) 404 405 if self.hardlink_supported: 406 source_stat = os.stat(source) 407 dest_stat = os.stat(dest) 408 self.assertEqual(source_stat.st_dev, dest_stat.st_dev) 409 self.assertEqual(source_stat.st_ino, dest_stat.st_ino) 410 else: 411 self.assertTrue(os.path.isfile(dest)) 412 with open(dest) as f: 413 content = f.read() 414 self.assertEqual(content, "source") 415 416 def test_replace_hardlink(self): 417 if not self.hardlink_supported: 418 raise unittest.SkipTest("hardlink not supported") 419 420 source = self.tmppath("source") 421 with open(source, "a"): 422 pass 423 424 dest = self.tmppath("dest") 425 426 os.link(source, dest) 427 428 s = HardlinkFile(source) 429 self.assertFalse(s.copy(dest)) 430 431 source_stat = os.lstat(source) 432 dest_stat = os.lstat(dest) 433 self.assertEqual(source_stat.st_dev, dest_stat.st_dev) 434 self.assertEqual(source_stat.st_ino, dest_stat.st_ino) 435 436 def test_noop(self): 437 if not self.hardlink_supported: 438 raise unittest.SkipTest("hardlink not supported") 439 440 source = self.tmppath("source") 441 dest = self.tmppath("dest") 442 443 with open(source, "a"): 444 pass 445 446 os.link(source, dest) 447 448 s = HardlinkFile(source) 449 self.assertFalse(s.copy(dest)) 450 451 source_stat = os.lstat(source) 452 dest_stat = os.lstat(dest) 453 self.assertEqual(source_stat.st_dev, dest_stat.st_dev) 454 self.assertEqual(source_stat.st_ino, dest_stat.st_ino) 455 456 457 class TestPreprocessedFile(TestWithTmpDir): 458 def test_preprocess(self): 459 """ 460 Test that copying the file invokes the preprocessor 461 """ 462 src = self.tmppath("src") 463 dest = self.tmppath("dest") 464 465 with open(src, "wb") as tmp: 466 tmp.write(b"#ifdef FOO\ntest\n#endif") 467 468 f = PreprocessedFile(src, depfile_path=None, marker="#", defines={"FOO": True}) 469 self.assertTrue(f.copy(dest)) 470 471 self.assertEqual(b"test\n", open(dest, "rb").read()) 472 473 def test_preprocess_file_no_write(self): 474 """ 475 Test various conditions where PreprocessedFile.copy is expected not to 476 write in the destination file. 477 """ 478 src = self.tmppath("src") 479 dest = self.tmppath("dest") 480 depfile = self.tmppath("depfile") 481 482 with open(src, "wb") as tmp: 483 tmp.write(b"#ifdef FOO\ntest\n#endif") 484 485 # Initial copy 486 f = PreprocessedFile( 487 src, depfile_path=depfile, marker="#", defines={"FOO": True} 488 ) 489 self.assertTrue(f.copy(dest)) 490 491 # Ensure subsequent copies won't trigger writes 492 self.assertFalse(f.copy(DestNoWrite(dest))) 493 self.assertEqual(b"test\n", open(dest, "rb").read()) 494 495 # When the source file is older than the destination file, even with 496 # different content, no copy should occur. 497 with open(src, "wb") as tmp: 498 tmp.write(b"#ifdef FOO\nfooo\n#endif") 499 time = os.path.getmtime(dest) - 1 500 os.utime(src, (time, time)) 501 self.assertFalse(f.copy(DestNoWrite(dest))) 502 self.assertEqual(b"test\n", open(dest, "rb").read()) 503 504 # skip_if_older=False is expected to force a copy in this situation. 505 self.assertTrue(f.copy(dest, skip_if_older=False)) 506 self.assertEqual(b"fooo\n", open(dest, "rb").read()) 507 508 def test_preprocess_file_dependencies(self): 509 """ 510 Test that the preprocess runs if the dependencies of the source change 511 """ 512 src = self.tmppath("src") 513 dest = self.tmppath("dest") 514 incl = self.tmppath("incl") 515 deps = self.tmppath("src.pp") 516 517 with open(src, "wb") as tmp: 518 tmp.write(b"#ifdef FOO\ntest\n#endif") 519 520 with open(incl, "wb") as tmp: 521 tmp.write(b"foo bar") 522 523 # Initial copy 524 f = PreprocessedFile(src, depfile_path=deps, marker="#", defines={"FOO": True}) 525 self.assertTrue(f.copy(dest)) 526 527 # Update the source so it #includes the include file. 528 with open(src, "wb") as tmp: 529 tmp.write(b"#include incl\n") 530 time = os.path.getmtime(dest) + 1 531 os.utime(src, (time, time)) 532 self.assertTrue(f.copy(dest)) 533 self.assertEqual(b"foo bar", open(dest, "rb").read()) 534 535 # If one of the dependencies changes, the file should be updated. The 536 # mtime of the dependency is set after the destination file, to avoid 537 # both files having the same time. 538 with open(incl, "wb") as tmp: 539 tmp.write(b"quux") 540 time = os.path.getmtime(dest) + 1 541 os.utime(incl, (time, time)) 542 self.assertTrue(f.copy(dest)) 543 self.assertEqual(b"quux", open(dest, "rb").read()) 544 545 # Perform one final copy to confirm that we don't run the preprocessor 546 # again. We update the mtime of the destination so it's newer than the 547 # input files. This would "just work" if we weren't changing 548 time = os.path.getmtime(incl) + 1 549 os.utime(dest, (time, time)) 550 self.assertFalse(f.copy(DestNoWrite(dest))) 551 552 def test_replace_symlink(self): 553 """ 554 Test that if the destination exists, and is a symlink, the target of 555 the symlink is not overwritten by the preprocessor output. 556 """ 557 if not self.symlink_supported: 558 return 559 560 source = self.tmppath("source") 561 dest = self.tmppath("dest") 562 pp_source = self.tmppath("pp_in") 563 deps = self.tmppath("deps") 564 565 with open(source, "a"): 566 pass 567 568 os.symlink(source, dest) 569 self.assertTrue(os.path.islink(dest)) 570 571 with open(pp_source, "wb") as tmp: 572 tmp.write(b"#define FOO\nPREPROCESSED") 573 574 f = PreprocessedFile( 575 pp_source, depfile_path=deps, marker="#", defines={"FOO": True} 576 ) 577 self.assertTrue(f.copy(dest)) 578 579 self.assertEqual(b"PREPROCESSED", open(dest, "rb").read()) 580 self.assertFalse(os.path.islink(dest)) 581 self.assertEqual(b"", open(source, "rb").read()) 582 583 584 class TestExistingFile(TestWithTmpDir): 585 def test_required_missing_dest(self): 586 with self.assertRaisesRegex(ErrorMessage, "Required existing file"): 587 f = ExistingFile(required=True) 588 f.copy(self.tmppath("dest")) 589 590 def test_required_existing_dest(self): 591 p = self.tmppath("dest") 592 with open(p, "a"): 593 pass 594 595 f = ExistingFile(required=True) 596 f.copy(p) 597 598 def test_optional_missing_dest(self): 599 f = ExistingFile(required=False) 600 f.copy(self.tmppath("dest")) 601 602 def test_optional_existing_dest(self): 603 p = self.tmppath("dest") 604 with open(p, "a"): 605 pass 606 607 f = ExistingFile(required=False) 608 f.copy(p) 609 610 611 class TestGeneratedFile(TestWithTmpDir): 612 def test_generated_file(self): 613 """ 614 Check that GeneratedFile.copy yields the proper content in the 615 destination file in all situations that trigger different code paths 616 (see TestFile.test_file) 617 """ 618 dest = self.tmppath("dest") 619 620 for content in samples: 621 f = GeneratedFile(content) 622 f.copy(dest) 623 self.assertEqual(content, open(dest, "rb").read()) 624 625 def test_generated_file_open(self): 626 """ 627 Test whether GeneratedFile.open returns an appropriately reset file 628 object. 629 """ 630 content = b"".join(samples) 631 f = GeneratedFile(content) 632 self.assertEqual(content[:42], f.open().read(42)) 633 self.assertEqual(content, f.open().read()) 634 635 def test_generated_file_no_write(self): 636 """ 637 Test various conditions where GeneratedFile.copy is expected not to 638 write in the destination file. 639 """ 640 dest = self.tmppath("dest") 641 642 # Initial copy 643 f = GeneratedFile(b"test") 644 f.copy(dest) 645 646 # Ensure subsequent copies won't trigger writes 647 f.copy(DestNoWrite(dest)) 648 self.assertEqual(b"test", open(dest, "rb").read()) 649 650 # When using a new instance with the same content, no copy should occur 651 f = GeneratedFile(b"test") 652 f.copy(DestNoWrite(dest)) 653 self.assertEqual(b"test", open(dest, "rb").read()) 654 655 # Double check that under conditions where a copy occurs, we would get 656 # an exception. 657 f = GeneratedFile(b"fooo") 658 self.assertRaises(RuntimeError, f.copy, DestNoWrite(dest)) 659 660 def test_generated_file_function(self): 661 """ 662 Test GeneratedFile behavior with functions. 663 """ 664 dest = self.tmppath("dest") 665 data = { 666 "num_calls": 0, 667 } 668 669 def content(): 670 data["num_calls"] += 1 671 return b"content" 672 673 f = GeneratedFile(content) 674 self.assertEqual(data["num_calls"], 0) 675 f.copy(dest) 676 self.assertEqual(data["num_calls"], 1) 677 self.assertEqual(b"content", open(dest, "rb").read()) 678 self.assertEqual(b"content", f.open().read()) 679 self.assertEqual(b"content", f.read()) 680 self.assertEqual(len(b"content"), f.size()) 681 self.assertEqual(data["num_calls"], 1) 682 683 f.content = b"modified" 684 f.copy(dest) 685 self.assertEqual(data["num_calls"], 1) 686 self.assertEqual(b"modified", open(dest, "rb").read()) 687 self.assertEqual(b"modified", f.open().read()) 688 self.assertEqual(b"modified", f.read()) 689 self.assertEqual(len(b"modified"), f.size()) 690 691 f.content = content 692 self.assertEqual(data["num_calls"], 1) 693 self.assertEqual(b"content", f.read()) 694 self.assertEqual(data["num_calls"], 2) 695 696 697 class TestDeflatedFile(TestWithTmpDir): 698 def test_deflated_file(self): 699 """ 700 Check that DeflatedFile.copy yields the proper content in the 701 destination file in all situations that trigger different code paths 702 (see TestFile.test_file) 703 """ 704 src = self.tmppath("src.jar") 705 dest = self.tmppath("dest") 706 707 contents = {} 708 with JarWriter(src) as jar: 709 for content in samples: 710 name = "".join( 711 random.choice( 712 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 713 ) 714 for i in range(8) 715 ) 716 jar.add(name, content, compress=True) 717 contents[name] = content 718 719 for j in JarReader(src): 720 f = DeflatedFile(j) 721 f.copy(dest) 722 self.assertEqual(contents[j.filename], open(dest, "rb").read()) 723 724 def test_deflated_file_open(self): 725 """ 726 Test whether DeflatedFile.open returns an appropriately reset file 727 object. 728 """ 729 src = self.tmppath("src.jar") 730 content = b"".join(samples) 731 with JarWriter(src) as jar: 732 jar.add("content", content) 733 734 f = DeflatedFile(JarReader(src)["content"]) 735 self.assertEqual(content[:42], f.open().read(42)) 736 self.assertEqual(content, f.open().read()) 737 738 def test_deflated_file_no_write(self): 739 """ 740 Test various conditions where DeflatedFile.copy is expected not to 741 write in the destination file. 742 """ 743 src = self.tmppath("src.jar") 744 dest = self.tmppath("dest") 745 746 with JarWriter(src) as jar: 747 jar.add("test", b"test") 748 jar.add("test2", b"test") 749 jar.add("fooo", b"fooo") 750 751 jar = JarReader(src) 752 # Initial copy 753 f = DeflatedFile(jar["test"]) 754 f.copy(dest) 755 756 # Ensure subsequent copies won't trigger writes 757 f.copy(DestNoWrite(dest)) 758 self.assertEqual(b"test", open(dest, "rb").read()) 759 760 # When using a different file with the same content, no copy should 761 # occur 762 f = DeflatedFile(jar["test2"]) 763 f.copy(DestNoWrite(dest)) 764 self.assertEqual(b"test", open(dest, "rb").read()) 765 766 # Double check that under conditions where a copy occurs, we would get 767 # an exception. 768 f = DeflatedFile(jar["fooo"]) 769 self.assertRaises(RuntimeError, f.copy, DestNoWrite(dest)) 770 771 772 class TestManifestFile(TestWithTmpDir): 773 def test_manifest_file(self): 774 f = ManifestFile("chrome") 775 f.add(ManifestContent("chrome", "global", "toolkit/content/global/")) 776 f.add(ManifestResource("chrome", "gre-resources", "toolkit/res/")) 777 f.add(ManifestResource("chrome/pdfjs", "pdfjs", "./")) 778 f.add(ManifestContent("chrome/pdfjs", "pdfjs", "pdfjs")) 779 f.add(ManifestLocale("chrome", "browser", "en-US", "en-US/locale/browser/")) 780 781 f.copy(self.tmppath("chrome.manifest")) 782 self.assertEqual( 783 open(self.tmppath("chrome.manifest")).readlines(), 784 [ 785 "content global toolkit/content/global/\n", 786 "resource gre-resources toolkit/res/\n", 787 "resource pdfjs pdfjs/\n", 788 "content pdfjs pdfjs/pdfjs\n", 789 "locale browser en-US en-US/locale/browser/\n", 790 ], 791 ) 792 793 self.assertRaises( 794 ValueError, 795 f.remove, 796 ManifestContent("", "global", "toolkit/content/global/"), 797 ) 798 self.assertRaises( 799 ValueError, 800 f.remove, 801 ManifestOverride( 802 "chrome", 803 "chrome://global/locale/netError.dtd", 804 "chrome://browser/locale/netError.dtd", 805 ), 806 ) 807 808 f.remove(ManifestContent("chrome", "global", "toolkit/content/global/")) 809 self.assertRaises( 810 ValueError, 811 f.remove, 812 ManifestContent("chrome", "global", "toolkit/content/global/"), 813 ) 814 815 f.copy(self.tmppath("chrome.manifest")) 816 content = open(self.tmppath("chrome.manifest"), "rb").read() 817 self.assertEqual(content[:42], f.open().read(42)) 818 self.assertEqual(content, f.open().read()) 819 820 821 # Compiled typelib for the following IDL: 822 # interface foo; 823 # [scriptable, uuid(5f70da76-519c-4858-b71e-e3c92333e2d6)] 824 # interface bar { 825 # void bar(in foo f); 826 # }; 827 # We need to make this [scriptable] so it doesn't get deleted from the 828 # typelib. We don't need to make the foo interfaces below [scriptable], 829 # because they will be automatically included by virtue of being an 830 # argument to a method of |bar|. 831 bar_xpt = GeneratedFile( 832 b"\x58\x50\x43\x4f\x4d\x0a\x54\x79\x70\x65\x4c\x69\x62\x0d\x0a\x1a" 833 + b"\x01\x02\x00\x02\x00\x00\x00\x7b\x00\x00\x00\x24\x00\x00\x00\x5c" 834 + b"\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 835 + b"\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x5f" 836 + b"\x70\xda\x76\x51\x9c\x48\x58\xb7\x1e\xe3\xc9\x23\x33\xe2\xd6\x00" 837 + b"\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x66\x6f\x6f\x00" 838 + b"\x62\x61\x72\x00\x62\x61\x72\x00\x00\x00\x00\x01\x00\x00\x00\x00" 839 + b"\x09\x01\x80\x92\x00\x01\x80\x06\x00\x00\x80" 840 ) 841 842 # Compiled typelib for the following IDL: 843 # [uuid(3271bebc-927e-4bef-935e-44e0aaf3c1e5)] 844 # interface foo { 845 # void foo(); 846 # }; 847 foo_xpt = GeneratedFile( 848 b"\x58\x50\x43\x4f\x4d\x0a\x54\x79\x70\x65\x4c\x69\x62\x0d\x0a\x1a" 849 + b"\x01\x02\x00\x01\x00\x00\x00\x57\x00\x00\x00\x24\x00\x00\x00\x40" 850 + b"\x80\x00\x00\x32\x71\xbe\xbc\x92\x7e\x4b\xef\x93\x5e\x44\xe0\xaa" 851 + b"\xf3\xc1\xe5\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x09\x00" 852 + b"\x66\x6f\x6f\x00\x66\x6f\x6f\x00\x00\x00\x00\x01\x00\x00\x00\x00" 853 + b"\x05\x00\x80\x06\x00\x00\x00" 854 ) 855 856 # Compiled typelib for the following IDL: 857 # [uuid(7057f2aa-fdc2-4559-abde-08d939f7e80d)] 858 # interface foo { 859 # void foo(); 860 # }; 861 foo2_xpt = GeneratedFile( 862 b"\x58\x50\x43\x4f\x4d\x0a\x54\x79\x70\x65\x4c\x69\x62\x0d\x0a\x1a" 863 + b"\x01\x02\x00\x01\x00\x00\x00\x57\x00\x00\x00\x24\x00\x00\x00\x40" 864 + b"\x80\x00\x00\x70\x57\xf2\xaa\xfd\xc2\x45\x59\xab\xde\x08\xd9\x39" 865 + b"\xf7\xe8\x0d\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x09\x00" 866 + b"\x66\x6f\x6f\x00\x66\x6f\x6f\x00\x00\x00\x00\x01\x00\x00\x00\x00" 867 + b"\x05\x00\x80\x06\x00\x00\x00" 868 ) 869 870 871 class TestMinifiedCommentStripped(TestWithTmpDir): 872 def test_minified_comment_stripped(self): 873 propLines = [ 874 "# Comments are removed", 875 "foo = bar", 876 "", 877 "# Another comment", 878 ] 879 prop = GeneratedFile("\n".join(propLines)) 880 self.assertEqual( 881 MinifiedCommentStripped(prop).open().readlines(), [b"foo = bar\n", b"\n"] 882 ) 883 open(self.tmppath("prop"), "w").write("\n".join(propLines)) 884 MinifiedCommentStripped(File(self.tmppath("prop"))).copy(self.tmppath("prop2")) 885 self.assertEqual(open(self.tmppath("prop2")).readlines(), ["foo = bar\n", "\n"]) 886 887 888 class TestMinifiedJavaScript(TestWithTmpDir): 889 orig_lines = [ 890 "// Comment line", 891 'let foo = "bar";', 892 "var bar = true;", 893 "", 894 "// Another comment", 895 ] 896 897 def setUp(self): 898 super().setUp() 899 if not buildconfig.substs.get("NODEJS"): 900 node_exe, _ = find_node_executable() 901 if node_exe: 902 buildconfig.substs["NODEJS"] = node_exe 903 904 def test_minified_javascript(self): 905 """Test that MinifiedJavaScript minifies JavaScript content.""" 906 orig_f = GeneratedFile("\n".join(self.orig_lines).encode()) 907 min_f = MinifiedJavaScript(orig_f, "test.js") 908 909 mini_content = min_f.open().read() 910 orig_content = orig_f.open().read() 911 912 # Verify minification occurred (content should be smaller) 913 self.assertTrue(len(mini_content) < len(orig_content)) 914 # Verify content is not empty 915 self.assertTrue(len(mini_content) > 0) 916 917 def test_minified_javascript_open(self): 918 """Test that MinifiedJavaScript.open returns appropriately reset file object.""" 919 orig_f = GeneratedFile("\n".join(self.orig_lines).encode()) 920 min_f = MinifiedJavaScript(orig_f, "test.js") 921 922 # Test reading partial content 923 first_read = min_f.open().read(10) 924 self.assertTrue(len(first_read) <= 10) 925 926 # Test reading full content multiple times 927 full_content = min_f.open().read() 928 second_read = min_f.open().read() 929 self.assertEqual(full_content, second_read) 930 931 def test_preserves_functionality(self): 932 """Test that Terser preserves JavaScript functionality.""" 933 # More complex JavaScript with functions and objects 934 complex_js = """ 935 // This is a test function 936 function testFunction(param) { 937 let result = { 938 value: param * 2, 939 toString: function() { 940 return "Result: " + this.value; 941 } 942 }; 943 return result; 944 } 945 946 // Export for testing 947 var exported = testFunction; 948 """ 949 950 orig_f = GeneratedFile(complex_js.encode()) 951 min_f = MinifiedJavaScript(orig_f, "complex.js") 952 953 minified_content = min_f.open().read().decode() 954 955 # Verify it's minified 956 self.assertTrue(len(minified_content) < len(complex_js)) 957 # Verify functions are still present) 958 self.assertIn("function", minified_content) 959 960 def test_handles_empty_file(self): 961 """Test that MinifiedJavaScript handles empty files gracefully.""" 962 empty_f = GeneratedFile(b"") 963 min_f = MinifiedJavaScript(empty_f, "empty.js") 964 965 # Should handle empty content gracefully 966 result = min_f.open().read() 967 self.assertEqual(result, b"") 968 969 def test_handles_syntax_errors(self): 970 """Test that MinifiedJavaScript raises an error for syntax errors.""" 971 # JavaScript with syntax error 972 broken_js = b"function broken( { return 'missing parenthesis'; }" 973 974 orig_f = GeneratedFile(broken_js) 975 min_f = MinifiedJavaScript(orig_f, "broken.js") 976 977 # Should raise an ErrorMessage when minification fails 978 from mozpack.errors import ErrorMessage 979 980 with self.assertRaises(ErrorMessage): 981 min_f.open().read() 982 983 984 class MatchTestTemplate: 985 def prepare_match_test(self, with_dotfiles=False): 986 self.add("bar") 987 self.add("foo/bar") 988 self.add("foo/baz") 989 self.add("foo/qux/1") 990 self.add("foo/qux/bar") 991 self.add("foo/qux/2/test") 992 self.add("foo/qux/2/test2") 993 if with_dotfiles: 994 self.add("foo/.foo") 995 self.add("foo/.bar/foo") 996 997 def do_match_test(self): 998 self.do_check( 999 "", 1000 [ 1001 "bar", 1002 "foo/bar", 1003 "foo/baz", 1004 "foo/qux/1", 1005 "foo/qux/bar", 1006 "foo/qux/2/test", 1007 "foo/qux/2/test2", 1008 ], 1009 ) 1010 self.do_check( 1011 "*", 1012 [ 1013 "bar", 1014 "foo/bar", 1015 "foo/baz", 1016 "foo/qux/1", 1017 "foo/qux/bar", 1018 "foo/qux/2/test", 1019 "foo/qux/2/test2", 1020 ], 1021 ) 1022 self.do_check( 1023 "foo/qux", ["foo/qux/1", "foo/qux/bar", "foo/qux/2/test", "foo/qux/2/test2"] 1024 ) 1025 self.do_check("foo/b*", ["foo/bar", "foo/baz"]) 1026 self.do_check("baz", []) 1027 self.do_check("foo/foo", []) 1028 self.do_check("foo/*ar", ["foo/bar"]) 1029 self.do_check("*ar", ["bar"]) 1030 self.do_check("*/bar", ["foo/bar"]) 1031 self.do_check( 1032 "foo/*ux", ["foo/qux/1", "foo/qux/bar", "foo/qux/2/test", "foo/qux/2/test2"] 1033 ) 1034 self.do_check( 1035 "foo/q*ux", 1036 ["foo/qux/1", "foo/qux/bar", "foo/qux/2/test", "foo/qux/2/test2"], 1037 ) 1038 self.do_check("foo/*/2/test*", ["foo/qux/2/test", "foo/qux/2/test2"]) 1039 self.do_check("**/bar", ["bar", "foo/bar", "foo/qux/bar"]) 1040 self.do_check("foo/**/test", ["foo/qux/2/test"]) 1041 self.do_check( 1042 "foo", 1043 [ 1044 "foo/bar", 1045 "foo/baz", 1046 "foo/qux/1", 1047 "foo/qux/bar", 1048 "foo/qux/2/test", 1049 "foo/qux/2/test2", 1050 ], 1051 ) 1052 self.do_check( 1053 "foo/**", 1054 [ 1055 "foo/bar", 1056 "foo/baz", 1057 "foo/qux/1", 1058 "foo/qux/bar", 1059 "foo/qux/2/test", 1060 "foo/qux/2/test2", 1061 ], 1062 ) 1063 self.do_check("**/2/test*", ["foo/qux/2/test", "foo/qux/2/test2"]) 1064 self.do_check( 1065 "**/foo", 1066 [ 1067 "foo/bar", 1068 "foo/baz", 1069 "foo/qux/1", 1070 "foo/qux/bar", 1071 "foo/qux/2/test", 1072 "foo/qux/2/test2", 1073 ], 1074 ) 1075 self.do_check("**/barbaz", []) 1076 self.do_check("f**/bar", ["foo/bar"]) 1077 1078 def do_finder_test(self, finder): 1079 self.assertTrue(finder.contains("foo/.foo")) 1080 self.assertTrue(finder.contains("foo/.bar")) 1081 self.assertTrue("foo/.foo" in [f for f, c in finder.find("foo/.foo")]) 1082 self.assertTrue("foo/.bar/foo" in [f for f, c in finder.find("foo/.bar")]) 1083 self.assertEqual( 1084 sorted([f for f, c in finder.find("foo/.*")]), ["foo/.bar/foo", "foo/.foo"] 1085 ) 1086 for pattern in ["foo", "**", "**/*", "**/foo", "foo/*"]: 1087 self.assertFalse("foo/.foo" in [f for f, c in finder.find(pattern)]) 1088 self.assertFalse("foo/.bar/foo" in [f for f, c in finder.find(pattern)]) 1089 self.assertEqual( 1090 sorted([f for f, c in finder.find(pattern)]), 1091 sorted([f for f, c in finder if mozpath.match(f, pattern)]), 1092 ) 1093 1094 1095 def do_check(test, finder, pattern, result): 1096 if result: 1097 test.assertTrue(finder.contains(pattern)) 1098 else: 1099 test.assertFalse(finder.contains(pattern)) 1100 test.assertEqual(sorted(list(f for f, c in finder.find(pattern))), sorted(result)) 1101 1102 1103 class TestFileFinder(MatchTestTemplate, TestWithTmpDir): 1104 def add(self, path): 1105 ensureParentDir(self.tmppath(path)) 1106 open(self.tmppath(path), "wb").write(path.encode()) 1107 1108 def do_check(self, pattern, result): 1109 do_check(self, self.finder, pattern, result) 1110 1111 def test_file_finder(self): 1112 self.prepare_match_test(with_dotfiles=True) 1113 self.finder = FileFinder(self.tmpdir) 1114 self.do_match_test() 1115 self.do_finder_test(self.finder) 1116 1117 def test_get(self): 1118 self.prepare_match_test() 1119 finder = FileFinder(self.tmpdir) 1120 1121 self.assertIsNone(finder.get("does-not-exist")) 1122 res = finder.get("bar") 1123 self.assertIsInstance(res, File) 1124 self.assertEqual(mozpath.normpath(res.path), mozpath.join(self.tmpdir, "bar")) 1125 1126 def test_ignored_dirs(self): 1127 """Ignored directories should not have results returned.""" 1128 self.prepare_match_test() 1129 self.add("fooz") 1130 1131 # Present to ensure prefix matching doesn't exclude. 1132 self.add("foo/quxz") 1133 1134 self.finder = FileFinder(self.tmpdir, ignore=["foo/qux"]) 1135 1136 self.do_check("**", ["bar", "foo/bar", "foo/baz", "foo/quxz", "fooz"]) 1137 self.do_check("foo/*", ["foo/bar", "foo/baz", "foo/quxz"]) 1138 self.do_check("foo/**", ["foo/bar", "foo/baz", "foo/quxz"]) 1139 self.do_check("foo/qux/**", []) 1140 self.do_check("foo/qux/*", []) 1141 self.do_check("foo/qux/bar", []) 1142 self.do_check("foo/quxz", ["foo/quxz"]) 1143 self.do_check("fooz", ["fooz"]) 1144 1145 def test_ignored_files(self): 1146 """Ignored files should not have results returned.""" 1147 self.prepare_match_test() 1148 1149 # Be sure prefix match doesn't get ignored. 1150 self.add("barz") 1151 1152 self.finder = FileFinder(self.tmpdir, ignore=["foo/bar", "bar"]) 1153 self.do_check( 1154 "**", 1155 [ 1156 "barz", 1157 "foo/baz", 1158 "foo/qux/1", 1159 "foo/qux/2/test", 1160 "foo/qux/2/test2", 1161 "foo/qux/bar", 1162 ], 1163 ) 1164 self.do_check( 1165 "foo/**", 1166 [ 1167 "foo/baz", 1168 "foo/qux/1", 1169 "foo/qux/2/test", 1170 "foo/qux/2/test2", 1171 "foo/qux/bar", 1172 ], 1173 ) 1174 1175 def test_ignored_patterns(self): 1176 """Ignore entries with patterns should be honored.""" 1177 self.prepare_match_test() 1178 1179 self.add("foo/quxz") 1180 1181 self.finder = FileFinder(self.tmpdir, ignore=["foo/qux/*"]) 1182 self.do_check("**", ["foo/bar", "foo/baz", "foo/quxz", "bar"]) 1183 self.do_check("foo/**", ["foo/bar", "foo/baz", "foo/quxz"]) 1184 1185 def test_dotfiles(self): 1186 """Finder can find files beginning with . is configured.""" 1187 self.prepare_match_test(with_dotfiles=True) 1188 self.finder = FileFinder(self.tmpdir, find_dotfiles=True) 1189 self.do_check( 1190 "**", 1191 [ 1192 "bar", 1193 "foo/.foo", 1194 "foo/.bar/foo", 1195 "foo/bar", 1196 "foo/baz", 1197 "foo/qux/1", 1198 "foo/qux/bar", 1199 "foo/qux/2/test", 1200 "foo/qux/2/test2", 1201 ], 1202 ) 1203 1204 def test_dotfiles_plus_ignore(self): 1205 self.prepare_match_test(with_dotfiles=True) 1206 self.finder = FileFinder( 1207 self.tmpdir, find_dotfiles=True, ignore=["foo/.bar/**"] 1208 ) 1209 self.do_check( 1210 "foo/**", 1211 [ 1212 "foo/.foo", 1213 "foo/bar", 1214 "foo/baz", 1215 "foo/qux/1", 1216 "foo/qux/bar", 1217 "foo/qux/2/test", 1218 "foo/qux/2/test2", 1219 ], 1220 ) 1221 1222 1223 class TestJarFinder(MatchTestTemplate, TestWithTmpDir): 1224 def add(self, path): 1225 self.jar.add(path, ensure_bytes(path), compress=True) 1226 1227 def do_check(self, pattern, result): 1228 do_check(self, self.finder, pattern, result) 1229 1230 def test_jar_finder(self): 1231 self.jar = JarWriter(file=self.tmppath("test.jar")) 1232 self.prepare_match_test() 1233 self.jar.finish() 1234 reader = JarReader(file=self.tmppath("test.jar")) 1235 self.finder = JarFinder(self.tmppath("test.jar"), reader) 1236 self.do_match_test() 1237 1238 self.assertIsNone(self.finder.get("does-not-exist")) 1239 self.assertIsInstance(self.finder.get("bar"), DeflatedFile) 1240 1241 1242 class TestTarFinder(MatchTestTemplate, TestWithTmpDir): 1243 def add(self, path): 1244 self.tar.addfile(tarfile.TarInfo(name=path)) 1245 1246 def do_check(self, pattern, result): 1247 do_check(self, self.finder, pattern, result) 1248 1249 def test_tar_finder(self): 1250 self.tar = tarfile.open(name=self.tmppath("test.tar.bz2"), mode="w:bz2") 1251 self.prepare_match_test() 1252 self.tar.close() 1253 with tarfile.open(name=self.tmppath("test.tar.bz2"), mode="r:bz2") as tarreader: 1254 self.finder = TarFinder(self.tmppath("test.tar.bz2"), tarreader) 1255 self.do_match_test() 1256 1257 self.assertIsNone(self.finder.get("does-not-exist")) 1258 self.assertIsInstance(self.finder.get("bar"), ExtractedTarFile) 1259 1260 1261 class TestComposedFinder(MatchTestTemplate, TestWithTmpDir): 1262 def add(self, path, content=None): 1263 # Put foo/qux files under $tmp/b. 1264 if path.startswith("foo/qux/"): 1265 real_path = mozpath.join("b", path[8:]) 1266 else: 1267 real_path = mozpath.join("a", path) 1268 ensureParentDir(self.tmppath(real_path)) 1269 if not content: 1270 content = path.encode() 1271 open(self.tmppath(real_path), "wb").write(content) 1272 1273 def do_check(self, pattern, result): 1274 if "*" in pattern: 1275 return 1276 do_check(self, self.finder, pattern, result) 1277 1278 def test_composed_finder(self): 1279 self.prepare_match_test() 1280 # Also add files in $tmp/a/foo/qux because ComposedFinder is 1281 # expected to mask foo/qux entirely with content from $tmp/b. 1282 ensureParentDir(self.tmppath("a/foo/qux/hoge")) 1283 open(self.tmppath("a/foo/qux/hoge"), "wb").write(b"hoge") 1284 open(self.tmppath("a/foo/qux/bar"), "wb").write(b"not the right content") 1285 self.finder = ComposedFinder({ 1286 "": FileFinder(self.tmppath("a")), 1287 "foo/qux": FileFinder(self.tmppath("b")), 1288 }) 1289 self.do_match_test() 1290 1291 self.assertIsNone(self.finder.get("does-not-exist")) 1292 self.assertIsInstance(self.finder.get("bar"), File) 1293 1294 1295 @unittest.skipUnless(hglib, "hglib not available") 1296 @unittest.skipIf(os.name == "nt", "Does not currently work in Python3 on Windows") 1297 class TestMercurialRevisionFinder(MatchTestTemplate, TestWithTmpDir): 1298 def setUp(self): 1299 super().setUp() 1300 hglib.init(self.tmpdir) 1301 self._clients = [] 1302 1303 def tearDown(self): 1304 # Ensure the hg client process is closed. Otherwise, Windows 1305 # may have trouble removing the repo directory because the process 1306 # has an open handle on it. 1307 for client in getattr(self, "_clients", []): 1308 if client.server: 1309 client.close() 1310 1311 self._clients[:] = [] 1312 1313 super().tearDown() 1314 1315 def _client(self): 1316 configs = ( 1317 # b'' because py2 needs !unicode 1318 b'ui.username="Dummy User <dummy@example.com>"', 1319 ) 1320 client = hglib.open( 1321 self.tmpdir.encode(), 1322 encoding=b"UTF-8", # b'' because py2 needs !unicode 1323 configs=configs, 1324 ) 1325 self._clients.append(client) 1326 return client 1327 1328 def add(self, path): 1329 with self._client() as c: 1330 ensureParentDir(self.tmppath(path)) 1331 with open(self.tmppath(path), "wb") as fh: 1332 fh.write(path.encode()) 1333 c.add(self.tmppath(path).encode()) 1334 1335 def do_check(self, pattern, result): 1336 do_check(self, self.finder, pattern, result) 1337 1338 def _get_finder(self, *args, **kwargs): 1339 f = MercurialRevisionFinder(*args, **kwargs) 1340 self._clients.append(f._client) 1341 return f 1342 1343 def test_default_revision(self): 1344 self.prepare_match_test() 1345 with self._client() as c: 1346 c.commit("initial commit") 1347 1348 self.finder = self._get_finder(self.tmpdir) 1349 self.do_match_test() 1350 1351 self.assertIsNone(self.finder.get("does-not-exist")) 1352 self.assertIsInstance(self.finder.get("bar"), MercurialFile) 1353 1354 def test_old_revision(self): 1355 with self._client() as c: 1356 with open(self.tmppath("foo"), "wb") as fh: 1357 fh.write(b"foo initial") 1358 c.add(self.tmppath("foo").encode()) 1359 c.commit("initial") 1360 1361 with open(self.tmppath("foo"), "wb") as fh: 1362 fh.write(b"foo second") 1363 with open(self.tmppath("bar"), "wb") as fh: 1364 fh.write(b"bar second") 1365 c.add(self.tmppath("bar").encode()) 1366 c.commit("second") 1367 # This wipes out the working directory, ensuring the finder isn't 1368 # finding anything from the filesystem. 1369 c.rawcommand([b"update", b"null"]) 1370 1371 finder = self._get_finder(self.tmpdir, "0") 1372 f = finder.get("foo") 1373 self.assertEqual(f.read(), b"foo initial") 1374 self.assertEqual(f.read(), b"foo initial", "read again for good measure") 1375 self.assertIsNone(finder.get("bar")) 1376 1377 finder = self._get_finder(self.tmpdir, rev="1") 1378 f = finder.get("foo") 1379 self.assertEqual(f.read(), b"foo second") 1380 f = finder.get("bar") 1381 self.assertEqual(f.read(), b"bar second") 1382 f = None 1383 1384 def test_recognize_repo_paths(self): 1385 with self._client() as c: 1386 with open(self.tmppath("foo"), "wb") as fh: 1387 fh.write(b"initial") 1388 c.add(self.tmppath("foo").encode()) 1389 c.commit("initial") 1390 c.rawcommand([b"update", b"null"]) 1391 1392 finder = self._get_finder(self.tmpdir, "0", recognize_repo_paths=True) 1393 with self.assertRaises(NotImplementedError): 1394 list(finder.find("")) 1395 1396 with self.assertRaises(ValueError): 1397 finder.get("foo") 1398 with self.assertRaises(ValueError): 1399 finder.get("") 1400 1401 f = finder.get(self.tmppath("foo")) 1402 self.assertIsInstance(f, MercurialFile) 1403 self.assertEqual(f.read(), b"initial") 1404 f = None 1405 1406 1407 if __name__ == "__main__": 1408 mozunit.main()