1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/python/mozbuild/mozpack/test/test_files.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,955 @@ 1.4 +# This Source Code Form is subject to the terms of the Mozilla Public 1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.7 + 1.8 +from mozbuild.util import ensureParentDir 1.9 + 1.10 +from mozpack.errors import ErrorMessage 1.11 +from mozpack.files import ( 1.12 + AbsoluteSymlinkFile, 1.13 + DeflatedFile, 1.14 + Dest, 1.15 + ExistingFile, 1.16 + FileFinder, 1.17 + File, 1.18 + GeneratedFile, 1.19 + JarFinder, 1.20 + ManifestFile, 1.21 + MinifiedJavaScript, 1.22 + MinifiedProperties, 1.23 + PreprocessedFile, 1.24 + XPTFile, 1.25 +) 1.26 +from mozpack.mozjar import ( 1.27 + JarReader, 1.28 + JarWriter, 1.29 +) 1.30 +from mozpack.chrome.manifest import ( 1.31 + ManifestContent, 1.32 + ManifestResource, 1.33 + ManifestLocale, 1.34 + ManifestOverride, 1.35 +) 1.36 +import unittest 1.37 +import mozfile 1.38 +import mozunit 1.39 +import os 1.40 +import random 1.41 +import string 1.42 +import sys 1.43 +import mozpack.path 1.44 +from tempfile import mkdtemp 1.45 +from io import BytesIO 1.46 +from xpt import Typelib 1.47 + 1.48 + 1.49 +class TestWithTmpDir(unittest.TestCase): 1.50 + def setUp(self): 1.51 + self.tmpdir = mkdtemp() 1.52 + 1.53 + self.symlink_supported = False 1.54 + 1.55 + if not hasattr(os, 'symlink'): 1.56 + return 1.57 + 1.58 + dummy_path = self.tmppath('dummy_file') 1.59 + with open(dummy_path, 'a'): 1.60 + pass 1.61 + 1.62 + try: 1.63 + os.symlink(dummy_path, self.tmppath('dummy_symlink')) 1.64 + os.remove(self.tmppath('dummy_symlink')) 1.65 + except EnvironmentError: 1.66 + pass 1.67 + finally: 1.68 + os.remove(dummy_path) 1.69 + 1.70 + self.symlink_supported = True 1.71 + 1.72 + 1.73 + def tearDown(self): 1.74 + mozfile.rmtree(self.tmpdir) 1.75 + 1.76 + def tmppath(self, relpath): 1.77 + return os.path.normpath(os.path.join(self.tmpdir, relpath)) 1.78 + 1.79 + 1.80 +class MockDest(BytesIO, Dest): 1.81 + def __init__(self): 1.82 + BytesIO.__init__(self) 1.83 + self.mode = None 1.84 + 1.85 + def read(self, length=-1): 1.86 + if self.mode != 'r': 1.87 + self.seek(0) 1.88 + self.mode = 'r' 1.89 + return BytesIO.read(self, length) 1.90 + 1.91 + def write(self, data): 1.92 + if self.mode != 'w': 1.93 + self.seek(0) 1.94 + self.truncate(0) 1.95 + self.mode = 'w' 1.96 + return BytesIO.write(self, data) 1.97 + 1.98 + def exists(self): 1.99 + return True 1.100 + 1.101 + def close(self): 1.102 + if self.mode: 1.103 + self.mode = None 1.104 + 1.105 + 1.106 +class DestNoWrite(Dest): 1.107 + def write(self, data): 1.108 + raise RuntimeError 1.109 + 1.110 + 1.111 +class TestDest(TestWithTmpDir): 1.112 + def test_dest(self): 1.113 + dest = Dest(self.tmppath('dest')) 1.114 + self.assertFalse(dest.exists()) 1.115 + dest.write('foo') 1.116 + self.assertTrue(dest.exists()) 1.117 + dest.write('foo') 1.118 + self.assertEqual(dest.read(4), 'foof') 1.119 + self.assertEqual(dest.read(), 'oo') 1.120 + self.assertEqual(dest.read(), '') 1.121 + dest.write('bar') 1.122 + self.assertEqual(dest.read(4), 'bar') 1.123 + dest.close() 1.124 + self.assertEqual(dest.read(), 'bar') 1.125 + dest.write('foo') 1.126 + dest.close() 1.127 + dest.write('qux') 1.128 + self.assertEqual(dest.read(), 'qux') 1.129 + 1.130 +rand = ''.join(random.choice(string.letters) for i in xrange(131597)) 1.131 +samples = [ 1.132 + '', 1.133 + 'test', 1.134 + 'fooo', 1.135 + 'same', 1.136 + 'same', 1.137 + 'Different and longer', 1.138 + rand, 1.139 + rand, 1.140 + rand[:-1] + '_', 1.141 + 'test' 1.142 +] 1.143 + 1.144 + 1.145 +class TestFile(TestWithTmpDir): 1.146 + def test_file(self): 1.147 + ''' 1.148 + Check that File.copy yields the proper content in the destination file 1.149 + in all situations that trigger different code paths: 1.150 + - different content 1.151 + - different content of the same size 1.152 + - same content 1.153 + - long content 1.154 + ''' 1.155 + src = self.tmppath('src') 1.156 + dest = self.tmppath('dest') 1.157 + 1.158 + for content in samples: 1.159 + with open(src, 'wb') as tmp: 1.160 + tmp.write(content) 1.161 + # Ensure the destination file, when it exists, is older than the 1.162 + # source 1.163 + if os.path.exists(dest): 1.164 + time = os.path.getmtime(src) - 1 1.165 + os.utime(dest, (time, time)) 1.166 + f = File(src) 1.167 + f.copy(dest) 1.168 + self.assertEqual(content, open(dest, 'rb').read()) 1.169 + self.assertEqual(content, f.open().read()) 1.170 + self.assertEqual(content, f.open().read()) 1.171 + 1.172 + def test_file_dest(self): 1.173 + ''' 1.174 + Similar to test_file, but for a destination object instead of 1.175 + a destination file. This ensures the destination object is being 1.176 + used properly by File.copy, ensuring that other subclasses of Dest 1.177 + will work. 1.178 + ''' 1.179 + src = self.tmppath('src') 1.180 + dest = MockDest() 1.181 + 1.182 + for content in samples: 1.183 + with open(src, 'wb') as tmp: 1.184 + tmp.write(content) 1.185 + f = File(src) 1.186 + f.copy(dest) 1.187 + self.assertEqual(content, dest.getvalue()) 1.188 + 1.189 + def test_file_open(self): 1.190 + ''' 1.191 + Test whether File.open returns an appropriately reset file object. 1.192 + ''' 1.193 + src = self.tmppath('src') 1.194 + content = ''.join(samples) 1.195 + with open(src, 'wb') as tmp: 1.196 + tmp.write(content) 1.197 + 1.198 + f = File(src) 1.199 + self.assertEqual(content[:42], f.open().read(42)) 1.200 + self.assertEqual(content, f.open().read()) 1.201 + 1.202 + def test_file_no_write(self): 1.203 + ''' 1.204 + Test various conditions where File.copy is expected not to write 1.205 + in the destination file. 1.206 + ''' 1.207 + src = self.tmppath('src') 1.208 + dest = self.tmppath('dest') 1.209 + 1.210 + with open(src, 'wb') as tmp: 1.211 + tmp.write('test') 1.212 + 1.213 + # Initial copy 1.214 + f = File(src) 1.215 + f.copy(dest) 1.216 + 1.217 + # Ensure subsequent copies won't trigger writes 1.218 + f.copy(DestNoWrite(dest)) 1.219 + self.assertEqual('test', open(dest, 'rb').read()) 1.220 + 1.221 + # When the source file is newer, but with the same content, no copy 1.222 + # should occur 1.223 + time = os.path.getmtime(src) - 1 1.224 + os.utime(dest, (time, time)) 1.225 + f.copy(DestNoWrite(dest)) 1.226 + self.assertEqual('test', open(dest, 'rb').read()) 1.227 + 1.228 + # When the source file is older than the destination file, even with 1.229 + # different content, no copy should occur. 1.230 + with open(src, 'wb') as tmp: 1.231 + tmp.write('fooo') 1.232 + time = os.path.getmtime(dest) - 1 1.233 + os.utime(src, (time, time)) 1.234 + f.copy(DestNoWrite(dest)) 1.235 + self.assertEqual('test', open(dest, 'rb').read()) 1.236 + 1.237 + # Double check that under conditions where a copy occurs, we would get 1.238 + # an exception. 1.239 + time = os.path.getmtime(src) - 1 1.240 + os.utime(dest, (time, time)) 1.241 + self.assertRaises(RuntimeError, f.copy, DestNoWrite(dest)) 1.242 + 1.243 + # skip_if_older=False is expected to force a copy in this situation. 1.244 + f.copy(dest, skip_if_older=False) 1.245 + self.assertEqual('fooo', open(dest, 'rb').read()) 1.246 + 1.247 + 1.248 +class TestAbsoluteSymlinkFile(TestWithTmpDir): 1.249 + def test_absolute_relative(self): 1.250 + AbsoluteSymlinkFile('/foo') 1.251 + 1.252 + with self.assertRaisesRegexp(ValueError, 'Symlink target not absolute'): 1.253 + AbsoluteSymlinkFile('./foo') 1.254 + 1.255 + def test_symlink_file(self): 1.256 + source = self.tmppath('test_path') 1.257 + with open(source, 'wt') as fh: 1.258 + fh.write('Hello world') 1.259 + 1.260 + s = AbsoluteSymlinkFile(source) 1.261 + dest = self.tmppath('symlink') 1.262 + self.assertTrue(s.copy(dest)) 1.263 + 1.264 + if self.symlink_supported: 1.265 + self.assertTrue(os.path.islink(dest)) 1.266 + link = os.readlink(dest) 1.267 + self.assertEqual(link, source) 1.268 + else: 1.269 + self.assertTrue(os.path.isfile(dest)) 1.270 + content = open(dest).read() 1.271 + self.assertEqual(content, 'Hello world') 1.272 + 1.273 + def test_replace_file_with_symlink(self): 1.274 + # If symlinks are supported, an existing file should be replaced by a 1.275 + # symlink. 1.276 + source = self.tmppath('test_path') 1.277 + with open(source, 'wt') as fh: 1.278 + fh.write('source') 1.279 + 1.280 + dest = self.tmppath('dest') 1.281 + with open(dest, 'a'): 1.282 + pass 1.283 + 1.284 + s = AbsoluteSymlinkFile(source) 1.285 + s.copy(dest, skip_if_older=False) 1.286 + 1.287 + if self.symlink_supported: 1.288 + self.assertTrue(os.path.islink(dest)) 1.289 + link = os.readlink(dest) 1.290 + self.assertEqual(link, source) 1.291 + else: 1.292 + self.assertTrue(os.path.isfile(dest)) 1.293 + content = open(dest).read() 1.294 + self.assertEqual(content, 'source') 1.295 + 1.296 + def test_replace_symlink(self): 1.297 + if not self.symlink_supported: 1.298 + return 1.299 + 1.300 + source = self.tmppath('source') 1.301 + with open(source, 'a'): 1.302 + pass 1.303 + 1.304 + dest = self.tmppath('dest') 1.305 + 1.306 + os.symlink(self.tmppath('bad'), dest) 1.307 + self.assertTrue(os.path.islink(dest)) 1.308 + 1.309 + s = AbsoluteSymlinkFile(source) 1.310 + self.assertTrue(s.copy(dest)) 1.311 + 1.312 + self.assertTrue(os.path.islink(dest)) 1.313 + link = os.readlink(dest) 1.314 + self.assertEqual(link, source) 1.315 + 1.316 + def test_noop(self): 1.317 + if not hasattr(os, 'symlink'): 1.318 + return 1.319 + 1.320 + source = self.tmppath('source') 1.321 + dest = self.tmppath('dest') 1.322 + 1.323 + with open(source, 'a'): 1.324 + pass 1.325 + 1.326 + os.symlink(source, dest) 1.327 + link = os.readlink(dest) 1.328 + self.assertEqual(link, source) 1.329 + 1.330 + s = AbsoluteSymlinkFile(source) 1.331 + self.assertFalse(s.copy(dest)) 1.332 + 1.333 + link = os.readlink(dest) 1.334 + self.assertEqual(link, source) 1.335 + 1.336 +class TestPreprocessedFile(TestWithTmpDir): 1.337 + def test_preprocess(self): 1.338 + ''' 1.339 + Test that copying the file invokes the preprocessor 1.340 + ''' 1.341 + src = self.tmppath('src') 1.342 + dest = self.tmppath('dest') 1.343 + 1.344 + with open(src, 'wb') as tmp: 1.345 + tmp.write('#ifdef FOO\ntest\n#endif') 1.346 + 1.347 + f = PreprocessedFile(src, depfile_path=None, marker='#', defines={'FOO': True}) 1.348 + self.assertTrue(f.copy(dest)) 1.349 + 1.350 + self.assertEqual('test\n', open(dest, 'rb').read()) 1.351 + 1.352 + def test_preprocess_file_no_write(self): 1.353 + ''' 1.354 + Test various conditions where PreprocessedFile.copy is expected not to 1.355 + write in the destination file. 1.356 + ''' 1.357 + src = self.tmppath('src') 1.358 + dest = self.tmppath('dest') 1.359 + depfile = self.tmppath('depfile') 1.360 + 1.361 + with open(src, 'wb') as tmp: 1.362 + tmp.write('#ifdef FOO\ntest\n#endif') 1.363 + 1.364 + # Initial copy 1.365 + f = PreprocessedFile(src, depfile_path=depfile, marker='#', defines={'FOO': True}) 1.366 + self.assertTrue(f.copy(dest)) 1.367 + 1.368 + # Ensure subsequent copies won't trigger writes 1.369 + self.assertFalse(f.copy(DestNoWrite(dest))) 1.370 + self.assertEqual('test\n', open(dest, 'rb').read()) 1.371 + 1.372 + # When the source file is older than the destination file, even with 1.373 + # different content, no copy should occur. 1.374 + with open(src, 'wb') as tmp: 1.375 + tmp.write('#ifdef FOO\nfooo\n#endif') 1.376 + time = os.path.getmtime(dest) - 1 1.377 + os.utime(src, (time, time)) 1.378 + self.assertFalse(f.copy(DestNoWrite(dest))) 1.379 + self.assertEqual('test\n', open(dest, 'rb').read()) 1.380 + 1.381 + # skip_if_older=False is expected to force a copy in this situation. 1.382 + self.assertTrue(f.copy(dest, skip_if_older=False)) 1.383 + self.assertEqual('fooo\n', open(dest, 'rb').read()) 1.384 + 1.385 + def test_preprocess_file_dependencies(self): 1.386 + ''' 1.387 + Test that the preprocess runs if the dependencies of the source change 1.388 + ''' 1.389 + src = self.tmppath('src') 1.390 + dest = self.tmppath('dest') 1.391 + incl = self.tmppath('incl') 1.392 + deps = self.tmppath('src.pp') 1.393 + 1.394 + with open(src, 'wb') as tmp: 1.395 + tmp.write('#ifdef FOO\ntest\n#endif') 1.396 + 1.397 + with open(incl, 'wb') as tmp: 1.398 + tmp.write('foo bar') 1.399 + 1.400 + # Initial copy 1.401 + f = PreprocessedFile(src, depfile_path=deps, marker='#', defines={'FOO': True}) 1.402 + self.assertTrue(f.copy(dest)) 1.403 + 1.404 + # Update the source so it #includes the include file. 1.405 + with open(src, 'wb') as tmp: 1.406 + tmp.write('#include incl\n') 1.407 + time = os.path.getmtime(dest) + 1 1.408 + os.utime(src, (time, time)) 1.409 + self.assertTrue(f.copy(dest)) 1.410 + self.assertEqual('foo bar', open(dest, 'rb').read()) 1.411 + 1.412 + # If one of the dependencies changes, the file should be updated. The 1.413 + # mtime of the dependency is set after the destination file, to avoid 1.414 + # both files having the same time. 1.415 + with open(incl, 'wb') as tmp: 1.416 + tmp.write('quux') 1.417 + time = os.path.getmtime(dest) + 1 1.418 + os.utime(incl, (time, time)) 1.419 + self.assertTrue(f.copy(dest)) 1.420 + self.assertEqual('quux', open(dest, 'rb').read()) 1.421 + 1.422 + # Perform one final copy to confirm that we don't run the preprocessor 1.423 + # again. We update the mtime of the destination so it's newer than the 1.424 + # input files. This would "just work" if we weren't changing 1.425 + time = os.path.getmtime(incl) + 1 1.426 + os.utime(dest, (time, time)) 1.427 + self.assertFalse(f.copy(DestNoWrite(dest))) 1.428 + 1.429 + def test_replace_symlink(self): 1.430 + ''' 1.431 + Test that if the destination exists, and is a symlink, the target of 1.432 + the symlink is not overwritten by the preprocessor output. 1.433 + ''' 1.434 + if not self.symlink_supported: 1.435 + return 1.436 + 1.437 + source = self.tmppath('source') 1.438 + dest = self.tmppath('dest') 1.439 + pp_source = self.tmppath('pp_in') 1.440 + deps = self.tmppath('deps') 1.441 + 1.442 + with open(source, 'a'): 1.443 + pass 1.444 + 1.445 + os.symlink(source, dest) 1.446 + self.assertTrue(os.path.islink(dest)) 1.447 + 1.448 + with open(pp_source, 'wb') as tmp: 1.449 + tmp.write('#define FOO\nPREPROCESSED') 1.450 + 1.451 + f = PreprocessedFile(pp_source, depfile_path=deps, marker='#', 1.452 + defines={'FOO': True}) 1.453 + self.assertTrue(f.copy(dest)) 1.454 + 1.455 + self.assertEqual('PREPROCESSED', open(dest, 'rb').read()) 1.456 + self.assertFalse(os.path.islink(dest)) 1.457 + self.assertEqual('', open(source, 'rb').read()) 1.458 + 1.459 +class TestExistingFile(TestWithTmpDir): 1.460 + def test_required_missing_dest(self): 1.461 + with self.assertRaisesRegexp(ErrorMessage, 'Required existing file'): 1.462 + f = ExistingFile(required=True) 1.463 + f.copy(self.tmppath('dest')) 1.464 + 1.465 + def test_required_existing_dest(self): 1.466 + p = self.tmppath('dest') 1.467 + with open(p, 'a'): 1.468 + pass 1.469 + 1.470 + f = ExistingFile(required=True) 1.471 + f.copy(p) 1.472 + 1.473 + def test_optional_missing_dest(self): 1.474 + f = ExistingFile(required=False) 1.475 + f.copy(self.tmppath('dest')) 1.476 + 1.477 + def test_optional_existing_dest(self): 1.478 + p = self.tmppath('dest') 1.479 + with open(p, 'a'): 1.480 + pass 1.481 + 1.482 + f = ExistingFile(required=False) 1.483 + f.copy(p) 1.484 + 1.485 + 1.486 +class TestGeneratedFile(TestWithTmpDir): 1.487 + def test_generated_file(self): 1.488 + ''' 1.489 + Check that GeneratedFile.copy yields the proper content in the 1.490 + destination file in all situations that trigger different code paths 1.491 + (see TestFile.test_file) 1.492 + ''' 1.493 + dest = self.tmppath('dest') 1.494 + 1.495 + for content in samples: 1.496 + f = GeneratedFile(content) 1.497 + f.copy(dest) 1.498 + self.assertEqual(content, open(dest, 'rb').read()) 1.499 + 1.500 + def test_generated_file_open(self): 1.501 + ''' 1.502 + Test whether GeneratedFile.open returns an appropriately reset file 1.503 + object. 1.504 + ''' 1.505 + content = ''.join(samples) 1.506 + f = GeneratedFile(content) 1.507 + self.assertEqual(content[:42], f.open().read(42)) 1.508 + self.assertEqual(content, f.open().read()) 1.509 + 1.510 + def test_generated_file_no_write(self): 1.511 + ''' 1.512 + Test various conditions where GeneratedFile.copy is expected not to 1.513 + write in the destination file. 1.514 + ''' 1.515 + dest = self.tmppath('dest') 1.516 + 1.517 + # Initial copy 1.518 + f = GeneratedFile('test') 1.519 + f.copy(dest) 1.520 + 1.521 + # Ensure subsequent copies won't trigger writes 1.522 + f.copy(DestNoWrite(dest)) 1.523 + self.assertEqual('test', open(dest, 'rb').read()) 1.524 + 1.525 + # When using a new instance with the same content, no copy should occur 1.526 + f = GeneratedFile('test') 1.527 + f.copy(DestNoWrite(dest)) 1.528 + self.assertEqual('test', open(dest, 'rb').read()) 1.529 + 1.530 + # Double check that under conditions where a copy occurs, we would get 1.531 + # an exception. 1.532 + f = GeneratedFile('fooo') 1.533 + self.assertRaises(RuntimeError, f.copy, DestNoWrite(dest)) 1.534 + 1.535 + 1.536 +class TestDeflatedFile(TestWithTmpDir): 1.537 + def test_deflated_file(self): 1.538 + ''' 1.539 + Check that DeflatedFile.copy yields the proper content in the 1.540 + destination file in all situations that trigger different code paths 1.541 + (see TestFile.test_file) 1.542 + ''' 1.543 + src = self.tmppath('src.jar') 1.544 + dest = self.tmppath('dest') 1.545 + 1.546 + contents = {} 1.547 + with JarWriter(src) as jar: 1.548 + for content in samples: 1.549 + name = ''.join(random.choice(string.letters) 1.550 + for i in xrange(8)) 1.551 + jar.add(name, content, compress=True) 1.552 + contents[name] = content 1.553 + 1.554 + for j in JarReader(src): 1.555 + f = DeflatedFile(j) 1.556 + f.copy(dest) 1.557 + self.assertEqual(contents[j.filename], open(dest, 'rb').read()) 1.558 + 1.559 + def test_deflated_file_open(self): 1.560 + ''' 1.561 + Test whether DeflatedFile.open returns an appropriately reset file 1.562 + object. 1.563 + ''' 1.564 + src = self.tmppath('src.jar') 1.565 + content = ''.join(samples) 1.566 + with JarWriter(src) as jar: 1.567 + jar.add('content', content) 1.568 + 1.569 + f = DeflatedFile(JarReader(src)['content']) 1.570 + self.assertEqual(content[:42], f.open().read(42)) 1.571 + self.assertEqual(content, f.open().read()) 1.572 + 1.573 + def test_deflated_file_no_write(self): 1.574 + ''' 1.575 + Test various conditions where DeflatedFile.copy is expected not to 1.576 + write in the destination file. 1.577 + ''' 1.578 + src = self.tmppath('src.jar') 1.579 + dest = self.tmppath('dest') 1.580 + 1.581 + with JarWriter(src) as jar: 1.582 + jar.add('test', 'test') 1.583 + jar.add('test2', 'test') 1.584 + jar.add('fooo', 'fooo') 1.585 + 1.586 + jar = JarReader(src) 1.587 + # Initial copy 1.588 + f = DeflatedFile(jar['test']) 1.589 + f.copy(dest) 1.590 + 1.591 + # Ensure subsequent copies won't trigger writes 1.592 + f.copy(DestNoWrite(dest)) 1.593 + self.assertEqual('test', open(dest, 'rb').read()) 1.594 + 1.595 + # When using a different file with the same content, no copy should 1.596 + # occur 1.597 + f = DeflatedFile(jar['test2']) 1.598 + f.copy(DestNoWrite(dest)) 1.599 + self.assertEqual('test', open(dest, 'rb').read()) 1.600 + 1.601 + # Double check that under conditions where a copy occurs, we would get 1.602 + # an exception. 1.603 + f = DeflatedFile(jar['fooo']) 1.604 + self.assertRaises(RuntimeError, f.copy, DestNoWrite(dest)) 1.605 + 1.606 + 1.607 +class TestManifestFile(TestWithTmpDir): 1.608 + def test_manifest_file(self): 1.609 + f = ManifestFile('chrome') 1.610 + f.add(ManifestContent('chrome', 'global', 'toolkit/content/global/')) 1.611 + f.add(ManifestResource('chrome', 'gre-resources', 'toolkit/res/')) 1.612 + f.add(ManifestResource('chrome/pdfjs', 'pdfjs', './')) 1.613 + f.add(ManifestContent('chrome/pdfjs', 'pdfjs', 'pdfjs')) 1.614 + f.add(ManifestLocale('chrome', 'browser', 'en-US', 1.615 + 'en-US/locale/browser/')) 1.616 + 1.617 + f.copy(self.tmppath('chrome.manifest')) 1.618 + self.assertEqual(open(self.tmppath('chrome.manifest')).readlines(), [ 1.619 + 'content global toolkit/content/global/\n', 1.620 + 'resource gre-resources toolkit/res/\n', 1.621 + 'resource pdfjs pdfjs/\n', 1.622 + 'content pdfjs pdfjs/pdfjs\n', 1.623 + 'locale browser en-US en-US/locale/browser/\n', 1.624 + ]) 1.625 + 1.626 + self.assertRaises( 1.627 + ValueError, 1.628 + f.remove, 1.629 + ManifestContent('', 'global', 'toolkit/content/global/') 1.630 + ) 1.631 + self.assertRaises( 1.632 + ValueError, 1.633 + f.remove, 1.634 + ManifestOverride('chrome', 'chrome://global/locale/netError.dtd', 1.635 + 'chrome://browser/locale/netError.dtd') 1.636 + ) 1.637 + 1.638 + f.remove(ManifestContent('chrome', 'global', 1.639 + 'toolkit/content/global/')) 1.640 + self.assertRaises( 1.641 + ValueError, 1.642 + f.remove, 1.643 + ManifestContent('chrome', 'global', 'toolkit/content/global/') 1.644 + ) 1.645 + 1.646 + f.copy(self.tmppath('chrome.manifest')) 1.647 + content = open(self.tmppath('chrome.manifest')).read() 1.648 + self.assertEqual(content[:42], f.open().read(42)) 1.649 + self.assertEqual(content, f.open().read()) 1.650 + 1.651 +# Compiled typelib for the following IDL: 1.652 +# interface foo; 1.653 +# [uuid(5f70da76-519c-4858-b71e-e3c92333e2d6)] 1.654 +# interface bar { 1.655 +# void bar(in foo f); 1.656 +# }; 1.657 +bar_xpt = GeneratedFile( 1.658 + b'\x58\x50\x43\x4F\x4D\x0A\x54\x79\x70\x65\x4C\x69\x62\x0D\x0A\x1A' + 1.659 + b'\x01\x02\x00\x02\x00\x00\x00\x7B\x00\x00\x00\x24\x00\x00\x00\x5C' + 1.660 + b'\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 1.661 + b'\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x5F' + 1.662 + b'\x70\xDA\x76\x51\x9C\x48\x58\xB7\x1E\xE3\xC9\x23\x33\xE2\xD6\x00' + 1.663 + b'\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x0D\x00\x66\x6F\x6F\x00' + 1.664 + b'\x62\x61\x72\x00\x62\x61\x72\x00\x00\x00\x00\x01\x00\x00\x00\x00' + 1.665 + b'\x09\x01\x80\x92\x00\x01\x80\x06\x00\x00\x00' 1.666 +) 1.667 + 1.668 +# Compiled typelib for the following IDL: 1.669 +# [uuid(3271bebc-927e-4bef-935e-44e0aaf3c1e5)] 1.670 +# interface foo { 1.671 +# void foo(); 1.672 +# }; 1.673 +foo_xpt = GeneratedFile( 1.674 + b'\x58\x50\x43\x4F\x4D\x0A\x54\x79\x70\x65\x4C\x69\x62\x0D\x0A\x1A' + 1.675 + b'\x01\x02\x00\x01\x00\x00\x00\x57\x00\x00\x00\x24\x00\x00\x00\x40' + 1.676 + b'\x80\x00\x00\x32\x71\xBE\xBC\x92\x7E\x4B\xEF\x93\x5E\x44\xE0\xAA' + 1.677 + b'\xF3\xC1\xE5\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x09\x00' + 1.678 + b'\x66\x6F\x6F\x00\x66\x6F\x6F\x00\x00\x00\x00\x01\x00\x00\x00\x00' + 1.679 + b'\x05\x00\x80\x06\x00\x00\x00' 1.680 +) 1.681 + 1.682 +# Compiled typelib for the following IDL: 1.683 +# [uuid(7057f2aa-fdc2-4559-abde-08d939f7e80d)] 1.684 +# interface foo { 1.685 +# void foo(); 1.686 +# }; 1.687 +foo2_xpt = GeneratedFile( 1.688 + b'\x58\x50\x43\x4F\x4D\x0A\x54\x79\x70\x65\x4C\x69\x62\x0D\x0A\x1A' + 1.689 + b'\x01\x02\x00\x01\x00\x00\x00\x57\x00\x00\x00\x24\x00\x00\x00\x40' + 1.690 + b'\x80\x00\x00\x70\x57\xF2\xAA\xFD\xC2\x45\x59\xAB\xDE\x08\xD9\x39' + 1.691 + b'\xF7\xE8\x0D\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x09\x00' + 1.692 + b'\x66\x6F\x6F\x00\x66\x6F\x6F\x00\x00\x00\x00\x01\x00\x00\x00\x00' + 1.693 + b'\x05\x00\x80\x06\x00\x00\x00' 1.694 +) 1.695 + 1.696 + 1.697 +def read_interfaces(file): 1.698 + return dict((i.name, i) for i in Typelib.read(file).interfaces) 1.699 + 1.700 + 1.701 +class TestXPTFile(TestWithTmpDir): 1.702 + def test_xpt_file(self): 1.703 + x = XPTFile() 1.704 + x.add(foo_xpt) 1.705 + x.add(bar_xpt) 1.706 + x.copy(self.tmppath('interfaces.xpt')) 1.707 + 1.708 + foo = read_interfaces(foo_xpt.open()) 1.709 + foo2 = read_interfaces(foo2_xpt.open()) 1.710 + bar = read_interfaces(bar_xpt.open()) 1.711 + linked = read_interfaces(self.tmppath('interfaces.xpt')) 1.712 + self.assertEqual(foo['foo'], linked['foo']) 1.713 + self.assertEqual(bar['bar'], linked['bar']) 1.714 + 1.715 + x.remove(foo_xpt) 1.716 + x.copy(self.tmppath('interfaces2.xpt')) 1.717 + linked = read_interfaces(self.tmppath('interfaces2.xpt')) 1.718 + self.assertEqual(bar['foo'], linked['foo']) 1.719 + self.assertEqual(bar['bar'], linked['bar']) 1.720 + 1.721 + x.add(foo_xpt) 1.722 + x.copy(DestNoWrite(self.tmppath('interfaces.xpt'))) 1.723 + linked = read_interfaces(self.tmppath('interfaces.xpt')) 1.724 + self.assertEqual(foo['foo'], linked['foo']) 1.725 + self.assertEqual(bar['bar'], linked['bar']) 1.726 + 1.727 + x = XPTFile() 1.728 + x.add(foo2_xpt) 1.729 + x.add(bar_xpt) 1.730 + x.copy(self.tmppath('interfaces.xpt')) 1.731 + linked = read_interfaces(self.tmppath('interfaces.xpt')) 1.732 + self.assertEqual(foo2['foo'], linked['foo']) 1.733 + self.assertEqual(bar['bar'], linked['bar']) 1.734 + 1.735 + x = XPTFile() 1.736 + x.add(foo_xpt) 1.737 + x.add(foo2_xpt) 1.738 + x.add(bar_xpt) 1.739 + from xpt import DataError 1.740 + self.assertRaises(DataError, x.copy, self.tmppath('interfaces.xpt')) 1.741 + 1.742 + 1.743 +class TestMinifiedProperties(TestWithTmpDir): 1.744 + def test_minified_properties(self): 1.745 + propLines = [ 1.746 + '# Comments are removed', 1.747 + 'foo = bar', 1.748 + '', 1.749 + '# Another comment', 1.750 + ] 1.751 + prop = GeneratedFile('\n'.join(propLines)) 1.752 + self.assertEqual(MinifiedProperties(prop).open().readlines(), 1.753 + ['foo = bar\n', '\n']) 1.754 + open(self.tmppath('prop'), 'wb').write('\n'.join(propLines)) 1.755 + MinifiedProperties(File(self.tmppath('prop'))) \ 1.756 + .copy(self.tmppath('prop2')) 1.757 + self.assertEqual(open(self.tmppath('prop2')).readlines(), 1.758 + ['foo = bar\n', '\n']) 1.759 + 1.760 + 1.761 +class TestMinifiedJavaScript(TestWithTmpDir): 1.762 + orig_lines = [ 1.763 + '// Comment line', 1.764 + 'let foo = "bar";', 1.765 + 'var bar = true;', 1.766 + '', 1.767 + '// Another comment', 1.768 + ] 1.769 + 1.770 + def test_minified_javascript(self): 1.771 + orig_f = GeneratedFile('\n'.join(self.orig_lines)) 1.772 + min_f = MinifiedJavaScript(orig_f) 1.773 + 1.774 + mini_lines = min_f.open().readlines() 1.775 + self.assertTrue(mini_lines) 1.776 + self.assertTrue(len(mini_lines) < len(self.orig_lines)) 1.777 + 1.778 + def _verify_command(self, code): 1.779 + our_dir = os.path.abspath(os.path.dirname(__file__)) 1.780 + return [ 1.781 + sys.executable, 1.782 + os.path.join(our_dir, 'support', 'minify_js_verify.py'), 1.783 + code, 1.784 + ] 1.785 + 1.786 + def test_minified_verify_success(self): 1.787 + orig_f = GeneratedFile('\n'.join(self.orig_lines)) 1.788 + min_f = MinifiedJavaScript(orig_f, 1.789 + verify_command=self._verify_command('0')) 1.790 + 1.791 + mini_lines = min_f.open().readlines() 1.792 + self.assertTrue(mini_lines) 1.793 + self.assertTrue(len(mini_lines) < len(self.orig_lines)) 1.794 + 1.795 + def test_minified_verify_failure(self): 1.796 + orig_f = GeneratedFile('\n'.join(self.orig_lines)) 1.797 + min_f = MinifiedJavaScript(orig_f, 1.798 + verify_command=self._verify_command('1')) 1.799 + 1.800 + mini_lines = min_f.open().readlines() 1.801 + self.assertEqual(mini_lines, orig_f.open().readlines()) 1.802 + 1.803 + 1.804 +class MatchTestTemplate(object): 1.805 + def prepare_match_test(self, with_dotfiles=False): 1.806 + self.add('bar') 1.807 + self.add('foo/bar') 1.808 + self.add('foo/baz') 1.809 + self.add('foo/qux/1') 1.810 + self.add('foo/qux/bar') 1.811 + self.add('foo/qux/2/test') 1.812 + self.add('foo/qux/2/test2') 1.813 + if with_dotfiles: 1.814 + self.add('foo/.foo') 1.815 + self.add('foo/.bar/foo') 1.816 + 1.817 + def do_match_test(self): 1.818 + self.do_check('', [ 1.819 + 'bar', 'foo/bar', 'foo/baz', 'foo/qux/1', 'foo/qux/bar', 1.820 + 'foo/qux/2/test', 'foo/qux/2/test2' 1.821 + ]) 1.822 + self.do_check('*', [ 1.823 + 'bar', 'foo/bar', 'foo/baz', 'foo/qux/1', 'foo/qux/bar', 1.824 + 'foo/qux/2/test', 'foo/qux/2/test2' 1.825 + ]) 1.826 + self.do_check('foo/qux', [ 1.827 + 'foo/qux/1', 'foo/qux/bar', 'foo/qux/2/test', 'foo/qux/2/test2' 1.828 + ]) 1.829 + self.do_check('foo/b*', ['foo/bar', 'foo/baz']) 1.830 + self.do_check('baz', []) 1.831 + self.do_check('foo/foo', []) 1.832 + self.do_check('foo/*ar', ['foo/bar']) 1.833 + self.do_check('*ar', ['bar']) 1.834 + self.do_check('*/bar', ['foo/bar']) 1.835 + self.do_check('foo/*ux', [ 1.836 + 'foo/qux/1', 'foo/qux/bar', 'foo/qux/2/test', 'foo/qux/2/test2' 1.837 + ]) 1.838 + self.do_check('foo/q*ux', [ 1.839 + 'foo/qux/1', 'foo/qux/bar', 'foo/qux/2/test', 'foo/qux/2/test2' 1.840 + ]) 1.841 + self.do_check('foo/*/2/test*', ['foo/qux/2/test', 'foo/qux/2/test2']) 1.842 + self.do_check('**/bar', ['bar', 'foo/bar', 'foo/qux/bar']) 1.843 + self.do_check('foo/**/test', ['foo/qux/2/test']) 1.844 + self.do_check('foo/**', [ 1.845 + 'foo/bar', 'foo/baz', 'foo/qux/1', 'foo/qux/bar', 1.846 + 'foo/qux/2/test', 'foo/qux/2/test2' 1.847 + ]) 1.848 + self.do_check('**/2/test*', ['foo/qux/2/test', 'foo/qux/2/test2']) 1.849 + self.do_check('**/foo', [ 1.850 + 'foo/bar', 'foo/baz', 'foo/qux/1', 'foo/qux/bar', 1.851 + 'foo/qux/2/test', 'foo/qux/2/test2' 1.852 + ]) 1.853 + self.do_check('**/barbaz', []) 1.854 + self.do_check('f**/bar', ['foo/bar']) 1.855 + 1.856 + def do_finder_test(self, finder): 1.857 + self.assertTrue(finder.contains('foo/.foo')) 1.858 + self.assertTrue(finder.contains('foo/.bar')) 1.859 + self.assertTrue('foo/.foo' in [f for f, c in 1.860 + finder.find('foo/.foo')]) 1.861 + self.assertTrue('foo/.bar/foo' in [f for f, c in 1.862 + finder.find('foo/.bar')]) 1.863 + self.assertEqual(sorted([f for f, c in finder.find('foo/.*')]), 1.864 + ['foo/.bar/foo', 'foo/.foo']) 1.865 + for pattern in ['foo', '**', '**/*', '**/foo', 'foo/*']: 1.866 + self.assertFalse('foo/.foo' in [f for f, c in 1.867 + finder.find(pattern)]) 1.868 + self.assertFalse('foo/.bar/foo' in [f for f, c in 1.869 + finder.find(pattern)]) 1.870 + self.assertEqual(sorted([f for f, c in finder.find(pattern)]), 1.871 + sorted([f for f, c in finder 1.872 + if mozpack.path.match(f, pattern)])) 1.873 + 1.874 + 1.875 +def do_check(test, finder, pattern, result): 1.876 + if result: 1.877 + test.assertTrue(finder.contains(pattern)) 1.878 + else: 1.879 + test.assertFalse(finder.contains(pattern)) 1.880 + test.assertEqual(sorted(list(f for f, c in finder.find(pattern))), 1.881 + sorted(result)) 1.882 + 1.883 + 1.884 +class TestFileFinder(MatchTestTemplate, TestWithTmpDir): 1.885 + def add(self, path): 1.886 + ensureParentDir(self.tmppath(path)) 1.887 + open(self.tmppath(path), 'wb').write(path) 1.888 + 1.889 + def do_check(self, pattern, result): 1.890 + do_check(self, self.finder, pattern, result) 1.891 + 1.892 + def test_file_finder(self): 1.893 + self.prepare_match_test(with_dotfiles=True) 1.894 + self.finder = FileFinder(self.tmpdir) 1.895 + self.do_match_test() 1.896 + self.do_finder_test(self.finder) 1.897 + 1.898 + def test_ignored_dirs(self): 1.899 + """Ignored directories should not have results returned.""" 1.900 + self.prepare_match_test() 1.901 + self.add('fooz') 1.902 + 1.903 + # Present to ensure prefix matching doesn't exclude. 1.904 + self.add('foo/quxz') 1.905 + 1.906 + self.finder = FileFinder(self.tmpdir, ignore=['foo/qux']) 1.907 + 1.908 + self.do_check('**', ['bar', 'foo/bar', 'foo/baz', 'foo/quxz', 'fooz']) 1.909 + self.do_check('foo/*', ['foo/bar', 'foo/baz', 'foo/quxz']) 1.910 + self.do_check('foo/**', ['foo/bar', 'foo/baz', 'foo/quxz']) 1.911 + self.do_check('foo/qux/**', []) 1.912 + self.do_check('foo/qux/*', []) 1.913 + self.do_check('foo/qux/bar', []) 1.914 + self.do_check('foo/quxz', ['foo/quxz']) 1.915 + self.do_check('fooz', ['fooz']) 1.916 + 1.917 + def test_ignored_files(self): 1.918 + """Ignored files should not have results returned.""" 1.919 + self.prepare_match_test() 1.920 + 1.921 + # Be sure prefix match doesn't get ignored. 1.922 + self.add('barz') 1.923 + 1.924 + self.finder = FileFinder(self.tmpdir, ignore=['foo/bar', 'bar']) 1.925 + self.do_check('**', ['barz', 'foo/baz', 'foo/qux/1', 'foo/qux/2/test', 1.926 + 'foo/qux/2/test2', 'foo/qux/bar']) 1.927 + self.do_check('foo/**', ['foo/baz', 'foo/qux/1', 'foo/qux/2/test', 1.928 + 'foo/qux/2/test2', 'foo/qux/bar']) 1.929 + 1.930 + def test_ignored_patterns(self): 1.931 + """Ignore entries with patterns should be honored.""" 1.932 + self.prepare_match_test() 1.933 + 1.934 + self.add('foo/quxz') 1.935 + 1.936 + self.finder = FileFinder(self.tmpdir, ignore=['foo/qux/*']) 1.937 + self.do_check('**', ['foo/bar', 'foo/baz', 'foo/quxz', 'bar']) 1.938 + self.do_check('foo/**', ['foo/bar', 'foo/baz', 'foo/quxz']) 1.939 + 1.940 + 1.941 +class TestJarFinder(MatchTestTemplate, TestWithTmpDir): 1.942 + def add(self, path): 1.943 + self.jar.add(path, path, compress=True) 1.944 + 1.945 + def do_check(self, pattern, result): 1.946 + do_check(self, self.finder, pattern, result) 1.947 + 1.948 + def test_jar_finder(self): 1.949 + self.jar = JarWriter(file=self.tmppath('test.jar')) 1.950 + self.prepare_match_test() 1.951 + self.jar.finish() 1.952 + reader = JarReader(file=self.tmppath('test.jar')) 1.953 + self.finder = JarFinder(self.tmppath('test.jar'), reader) 1.954 + self.do_match_test() 1.955 + 1.956 + 1.957 +if __name__ == '__main__': 1.958 + mozunit.main()