python/mozbuild/mozpack/test/test_files.py

changeset 0
6474c204b198
     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()

mercurial