Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
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/.
5 from mozbuild.util import ensureParentDir
7 from mozpack.errors import ErrorMessage
8 from mozpack.files import (
9 AbsoluteSymlinkFile,
10 DeflatedFile,
11 Dest,
12 ExistingFile,
13 FileFinder,
14 File,
15 GeneratedFile,
16 JarFinder,
17 ManifestFile,
18 MinifiedJavaScript,
19 MinifiedProperties,
20 PreprocessedFile,
21 XPTFile,
22 )
23 from mozpack.mozjar import (
24 JarReader,
25 JarWriter,
26 )
27 from mozpack.chrome.manifest import (
28 ManifestContent,
29 ManifestResource,
30 ManifestLocale,
31 ManifestOverride,
32 )
33 import unittest
34 import mozfile
35 import mozunit
36 import os
37 import random
38 import string
39 import sys
40 import mozpack.path
41 from tempfile import mkdtemp
42 from io import BytesIO
43 from xpt import Typelib
46 class TestWithTmpDir(unittest.TestCase):
47 def setUp(self):
48 self.tmpdir = mkdtemp()
50 self.symlink_supported = False
52 if not hasattr(os, 'symlink'):
53 return
55 dummy_path = self.tmppath('dummy_file')
56 with open(dummy_path, 'a'):
57 pass
59 try:
60 os.symlink(dummy_path, self.tmppath('dummy_symlink'))
61 os.remove(self.tmppath('dummy_symlink'))
62 except EnvironmentError:
63 pass
64 finally:
65 os.remove(dummy_path)
67 self.symlink_supported = True
70 def tearDown(self):
71 mozfile.rmtree(self.tmpdir)
73 def tmppath(self, relpath):
74 return os.path.normpath(os.path.join(self.tmpdir, relpath))
77 class MockDest(BytesIO, Dest):
78 def __init__(self):
79 BytesIO.__init__(self)
80 self.mode = None
82 def read(self, length=-1):
83 if self.mode != 'r':
84 self.seek(0)
85 self.mode = 'r'
86 return BytesIO.read(self, length)
88 def write(self, data):
89 if self.mode != 'w':
90 self.seek(0)
91 self.truncate(0)
92 self.mode = 'w'
93 return BytesIO.write(self, data)
95 def exists(self):
96 return True
98 def close(self):
99 if self.mode:
100 self.mode = None
103 class DestNoWrite(Dest):
104 def write(self, data):
105 raise RuntimeError
108 class TestDest(TestWithTmpDir):
109 def test_dest(self):
110 dest = Dest(self.tmppath('dest'))
111 self.assertFalse(dest.exists())
112 dest.write('foo')
113 self.assertTrue(dest.exists())
114 dest.write('foo')
115 self.assertEqual(dest.read(4), 'foof')
116 self.assertEqual(dest.read(), 'oo')
117 self.assertEqual(dest.read(), '')
118 dest.write('bar')
119 self.assertEqual(dest.read(4), 'bar')
120 dest.close()
121 self.assertEqual(dest.read(), 'bar')
122 dest.write('foo')
123 dest.close()
124 dest.write('qux')
125 self.assertEqual(dest.read(), 'qux')
127 rand = ''.join(random.choice(string.letters) for i in xrange(131597))
128 samples = [
129 '',
130 'test',
131 'fooo',
132 'same',
133 'same',
134 'Different and longer',
135 rand,
136 rand,
137 rand[:-1] + '_',
138 'test'
139 ]
142 class TestFile(TestWithTmpDir):
143 def test_file(self):
144 '''
145 Check that File.copy yields the proper content in the destination file
146 in all situations that trigger different code paths:
147 - different content
148 - different content of the same size
149 - same content
150 - long content
151 '''
152 src = self.tmppath('src')
153 dest = self.tmppath('dest')
155 for content in samples:
156 with open(src, 'wb') as tmp:
157 tmp.write(content)
158 # Ensure the destination file, when it exists, is older than the
159 # source
160 if os.path.exists(dest):
161 time = os.path.getmtime(src) - 1
162 os.utime(dest, (time, time))
163 f = File(src)
164 f.copy(dest)
165 self.assertEqual(content, open(dest, 'rb').read())
166 self.assertEqual(content, f.open().read())
167 self.assertEqual(content, f.open().read())
169 def test_file_dest(self):
170 '''
171 Similar to test_file, but for a destination object instead of
172 a destination file. This ensures the destination object is being
173 used properly by File.copy, ensuring that other subclasses of Dest
174 will work.
175 '''
176 src = self.tmppath('src')
177 dest = MockDest()
179 for content in samples:
180 with open(src, 'wb') as tmp:
181 tmp.write(content)
182 f = File(src)
183 f.copy(dest)
184 self.assertEqual(content, dest.getvalue())
186 def test_file_open(self):
187 '''
188 Test whether File.open returns an appropriately reset file object.
189 '''
190 src = self.tmppath('src')
191 content = ''.join(samples)
192 with open(src, 'wb') as tmp:
193 tmp.write(content)
195 f = File(src)
196 self.assertEqual(content[:42], f.open().read(42))
197 self.assertEqual(content, f.open().read())
199 def test_file_no_write(self):
200 '''
201 Test various conditions where File.copy is expected not to write
202 in the destination file.
203 '''
204 src = self.tmppath('src')
205 dest = self.tmppath('dest')
207 with open(src, 'wb') as tmp:
208 tmp.write('test')
210 # Initial copy
211 f = File(src)
212 f.copy(dest)
214 # Ensure subsequent copies won't trigger writes
215 f.copy(DestNoWrite(dest))
216 self.assertEqual('test', open(dest, 'rb').read())
218 # When the source file is newer, but with the same content, no copy
219 # should occur
220 time = os.path.getmtime(src) - 1
221 os.utime(dest, (time, time))
222 f.copy(DestNoWrite(dest))
223 self.assertEqual('test', open(dest, 'rb').read())
225 # When the source file is older than the destination file, even with
226 # different content, no copy should occur.
227 with open(src, 'wb') as tmp:
228 tmp.write('fooo')
229 time = os.path.getmtime(dest) - 1
230 os.utime(src, (time, time))
231 f.copy(DestNoWrite(dest))
232 self.assertEqual('test', open(dest, 'rb').read())
234 # Double check that under conditions where a copy occurs, we would get
235 # an exception.
236 time = os.path.getmtime(src) - 1
237 os.utime(dest, (time, time))
238 self.assertRaises(RuntimeError, f.copy, DestNoWrite(dest))
240 # skip_if_older=False is expected to force a copy in this situation.
241 f.copy(dest, skip_if_older=False)
242 self.assertEqual('fooo', open(dest, 'rb').read())
245 class TestAbsoluteSymlinkFile(TestWithTmpDir):
246 def test_absolute_relative(self):
247 AbsoluteSymlinkFile('/foo')
249 with self.assertRaisesRegexp(ValueError, 'Symlink target not absolute'):
250 AbsoluteSymlinkFile('./foo')
252 def test_symlink_file(self):
253 source = self.tmppath('test_path')
254 with open(source, 'wt') as fh:
255 fh.write('Hello world')
257 s = AbsoluteSymlinkFile(source)
258 dest = self.tmppath('symlink')
259 self.assertTrue(s.copy(dest))
261 if self.symlink_supported:
262 self.assertTrue(os.path.islink(dest))
263 link = os.readlink(dest)
264 self.assertEqual(link, source)
265 else:
266 self.assertTrue(os.path.isfile(dest))
267 content = open(dest).read()
268 self.assertEqual(content, 'Hello world')
270 def test_replace_file_with_symlink(self):
271 # If symlinks are supported, an existing file should be replaced by a
272 # symlink.
273 source = self.tmppath('test_path')
274 with open(source, 'wt') as fh:
275 fh.write('source')
277 dest = self.tmppath('dest')
278 with open(dest, 'a'):
279 pass
281 s = AbsoluteSymlinkFile(source)
282 s.copy(dest, skip_if_older=False)
284 if self.symlink_supported:
285 self.assertTrue(os.path.islink(dest))
286 link = os.readlink(dest)
287 self.assertEqual(link, source)
288 else:
289 self.assertTrue(os.path.isfile(dest))
290 content = open(dest).read()
291 self.assertEqual(content, 'source')
293 def test_replace_symlink(self):
294 if not self.symlink_supported:
295 return
297 source = self.tmppath('source')
298 with open(source, 'a'):
299 pass
301 dest = self.tmppath('dest')
303 os.symlink(self.tmppath('bad'), dest)
304 self.assertTrue(os.path.islink(dest))
306 s = AbsoluteSymlinkFile(source)
307 self.assertTrue(s.copy(dest))
309 self.assertTrue(os.path.islink(dest))
310 link = os.readlink(dest)
311 self.assertEqual(link, source)
313 def test_noop(self):
314 if not hasattr(os, 'symlink'):
315 return
317 source = self.tmppath('source')
318 dest = self.tmppath('dest')
320 with open(source, 'a'):
321 pass
323 os.symlink(source, dest)
324 link = os.readlink(dest)
325 self.assertEqual(link, source)
327 s = AbsoluteSymlinkFile(source)
328 self.assertFalse(s.copy(dest))
330 link = os.readlink(dest)
331 self.assertEqual(link, source)
333 class TestPreprocessedFile(TestWithTmpDir):
334 def test_preprocess(self):
335 '''
336 Test that copying the file invokes the preprocessor
337 '''
338 src = self.tmppath('src')
339 dest = self.tmppath('dest')
341 with open(src, 'wb') as tmp:
342 tmp.write('#ifdef FOO\ntest\n#endif')
344 f = PreprocessedFile(src, depfile_path=None, marker='#', defines={'FOO': True})
345 self.assertTrue(f.copy(dest))
347 self.assertEqual('test\n', open(dest, 'rb').read())
349 def test_preprocess_file_no_write(self):
350 '''
351 Test various conditions where PreprocessedFile.copy is expected not to
352 write in the destination file.
353 '''
354 src = self.tmppath('src')
355 dest = self.tmppath('dest')
356 depfile = self.tmppath('depfile')
358 with open(src, 'wb') as tmp:
359 tmp.write('#ifdef FOO\ntest\n#endif')
361 # Initial copy
362 f = PreprocessedFile(src, depfile_path=depfile, marker='#', defines={'FOO': True})
363 self.assertTrue(f.copy(dest))
365 # Ensure subsequent copies won't trigger writes
366 self.assertFalse(f.copy(DestNoWrite(dest)))
367 self.assertEqual('test\n', open(dest, 'rb').read())
369 # When the source file is older than the destination file, even with
370 # different content, no copy should occur.
371 with open(src, 'wb') as tmp:
372 tmp.write('#ifdef FOO\nfooo\n#endif')
373 time = os.path.getmtime(dest) - 1
374 os.utime(src, (time, time))
375 self.assertFalse(f.copy(DestNoWrite(dest)))
376 self.assertEqual('test\n', open(dest, 'rb').read())
378 # skip_if_older=False is expected to force a copy in this situation.
379 self.assertTrue(f.copy(dest, skip_if_older=False))
380 self.assertEqual('fooo\n', open(dest, 'rb').read())
382 def test_preprocess_file_dependencies(self):
383 '''
384 Test that the preprocess runs if the dependencies of the source change
385 '''
386 src = self.tmppath('src')
387 dest = self.tmppath('dest')
388 incl = self.tmppath('incl')
389 deps = self.tmppath('src.pp')
391 with open(src, 'wb') as tmp:
392 tmp.write('#ifdef FOO\ntest\n#endif')
394 with open(incl, 'wb') as tmp:
395 tmp.write('foo bar')
397 # Initial copy
398 f = PreprocessedFile(src, depfile_path=deps, marker='#', defines={'FOO': True})
399 self.assertTrue(f.copy(dest))
401 # Update the source so it #includes the include file.
402 with open(src, 'wb') as tmp:
403 tmp.write('#include incl\n')
404 time = os.path.getmtime(dest) + 1
405 os.utime(src, (time, time))
406 self.assertTrue(f.copy(dest))
407 self.assertEqual('foo bar', open(dest, 'rb').read())
409 # If one of the dependencies changes, the file should be updated. The
410 # mtime of the dependency is set after the destination file, to avoid
411 # both files having the same time.
412 with open(incl, 'wb') as tmp:
413 tmp.write('quux')
414 time = os.path.getmtime(dest) + 1
415 os.utime(incl, (time, time))
416 self.assertTrue(f.copy(dest))
417 self.assertEqual('quux', open(dest, 'rb').read())
419 # Perform one final copy to confirm that we don't run the preprocessor
420 # again. We update the mtime of the destination so it's newer than the
421 # input files. This would "just work" if we weren't changing
422 time = os.path.getmtime(incl) + 1
423 os.utime(dest, (time, time))
424 self.assertFalse(f.copy(DestNoWrite(dest)))
426 def test_replace_symlink(self):
427 '''
428 Test that if the destination exists, and is a symlink, the target of
429 the symlink is not overwritten by the preprocessor output.
430 '''
431 if not self.symlink_supported:
432 return
434 source = self.tmppath('source')
435 dest = self.tmppath('dest')
436 pp_source = self.tmppath('pp_in')
437 deps = self.tmppath('deps')
439 with open(source, 'a'):
440 pass
442 os.symlink(source, dest)
443 self.assertTrue(os.path.islink(dest))
445 with open(pp_source, 'wb') as tmp:
446 tmp.write('#define FOO\nPREPROCESSED')
448 f = PreprocessedFile(pp_source, depfile_path=deps, marker='#',
449 defines={'FOO': True})
450 self.assertTrue(f.copy(dest))
452 self.assertEqual('PREPROCESSED', open(dest, 'rb').read())
453 self.assertFalse(os.path.islink(dest))
454 self.assertEqual('', open(source, 'rb').read())
456 class TestExistingFile(TestWithTmpDir):
457 def test_required_missing_dest(self):
458 with self.assertRaisesRegexp(ErrorMessage, 'Required existing file'):
459 f = ExistingFile(required=True)
460 f.copy(self.tmppath('dest'))
462 def test_required_existing_dest(self):
463 p = self.tmppath('dest')
464 with open(p, 'a'):
465 pass
467 f = ExistingFile(required=True)
468 f.copy(p)
470 def test_optional_missing_dest(self):
471 f = ExistingFile(required=False)
472 f.copy(self.tmppath('dest'))
474 def test_optional_existing_dest(self):
475 p = self.tmppath('dest')
476 with open(p, 'a'):
477 pass
479 f = ExistingFile(required=False)
480 f.copy(p)
483 class TestGeneratedFile(TestWithTmpDir):
484 def test_generated_file(self):
485 '''
486 Check that GeneratedFile.copy yields the proper content in the
487 destination file in all situations that trigger different code paths
488 (see TestFile.test_file)
489 '''
490 dest = self.tmppath('dest')
492 for content in samples:
493 f = GeneratedFile(content)
494 f.copy(dest)
495 self.assertEqual(content, open(dest, 'rb').read())
497 def test_generated_file_open(self):
498 '''
499 Test whether GeneratedFile.open returns an appropriately reset file
500 object.
501 '''
502 content = ''.join(samples)
503 f = GeneratedFile(content)
504 self.assertEqual(content[:42], f.open().read(42))
505 self.assertEqual(content, f.open().read())
507 def test_generated_file_no_write(self):
508 '''
509 Test various conditions where GeneratedFile.copy is expected not to
510 write in the destination file.
511 '''
512 dest = self.tmppath('dest')
514 # Initial copy
515 f = GeneratedFile('test')
516 f.copy(dest)
518 # Ensure subsequent copies won't trigger writes
519 f.copy(DestNoWrite(dest))
520 self.assertEqual('test', open(dest, 'rb').read())
522 # When using a new instance with the same content, no copy should occur
523 f = GeneratedFile('test')
524 f.copy(DestNoWrite(dest))
525 self.assertEqual('test', open(dest, 'rb').read())
527 # Double check that under conditions where a copy occurs, we would get
528 # an exception.
529 f = GeneratedFile('fooo')
530 self.assertRaises(RuntimeError, f.copy, DestNoWrite(dest))
533 class TestDeflatedFile(TestWithTmpDir):
534 def test_deflated_file(self):
535 '''
536 Check that DeflatedFile.copy yields the proper content in the
537 destination file in all situations that trigger different code paths
538 (see TestFile.test_file)
539 '''
540 src = self.tmppath('src.jar')
541 dest = self.tmppath('dest')
543 contents = {}
544 with JarWriter(src) as jar:
545 for content in samples:
546 name = ''.join(random.choice(string.letters)
547 for i in xrange(8))
548 jar.add(name, content, compress=True)
549 contents[name] = content
551 for j in JarReader(src):
552 f = DeflatedFile(j)
553 f.copy(dest)
554 self.assertEqual(contents[j.filename], open(dest, 'rb').read())
556 def test_deflated_file_open(self):
557 '''
558 Test whether DeflatedFile.open returns an appropriately reset file
559 object.
560 '''
561 src = self.tmppath('src.jar')
562 content = ''.join(samples)
563 with JarWriter(src) as jar:
564 jar.add('content', content)
566 f = DeflatedFile(JarReader(src)['content'])
567 self.assertEqual(content[:42], f.open().read(42))
568 self.assertEqual(content, f.open().read())
570 def test_deflated_file_no_write(self):
571 '''
572 Test various conditions where DeflatedFile.copy is expected not to
573 write in the destination file.
574 '''
575 src = self.tmppath('src.jar')
576 dest = self.tmppath('dest')
578 with JarWriter(src) as jar:
579 jar.add('test', 'test')
580 jar.add('test2', 'test')
581 jar.add('fooo', 'fooo')
583 jar = JarReader(src)
584 # Initial copy
585 f = DeflatedFile(jar['test'])
586 f.copy(dest)
588 # Ensure subsequent copies won't trigger writes
589 f.copy(DestNoWrite(dest))
590 self.assertEqual('test', open(dest, 'rb').read())
592 # When using a different file with the same content, no copy should
593 # occur
594 f = DeflatedFile(jar['test2'])
595 f.copy(DestNoWrite(dest))
596 self.assertEqual('test', open(dest, 'rb').read())
598 # Double check that under conditions where a copy occurs, we would get
599 # an exception.
600 f = DeflatedFile(jar['fooo'])
601 self.assertRaises(RuntimeError, f.copy, DestNoWrite(dest))
604 class TestManifestFile(TestWithTmpDir):
605 def test_manifest_file(self):
606 f = ManifestFile('chrome')
607 f.add(ManifestContent('chrome', 'global', 'toolkit/content/global/'))
608 f.add(ManifestResource('chrome', 'gre-resources', 'toolkit/res/'))
609 f.add(ManifestResource('chrome/pdfjs', 'pdfjs', './'))
610 f.add(ManifestContent('chrome/pdfjs', 'pdfjs', 'pdfjs'))
611 f.add(ManifestLocale('chrome', 'browser', 'en-US',
612 'en-US/locale/browser/'))
614 f.copy(self.tmppath('chrome.manifest'))
615 self.assertEqual(open(self.tmppath('chrome.manifest')).readlines(), [
616 'content global toolkit/content/global/\n',
617 'resource gre-resources toolkit/res/\n',
618 'resource pdfjs pdfjs/\n',
619 'content pdfjs pdfjs/pdfjs\n',
620 'locale browser en-US en-US/locale/browser/\n',
621 ])
623 self.assertRaises(
624 ValueError,
625 f.remove,
626 ManifestContent('', 'global', 'toolkit/content/global/')
627 )
628 self.assertRaises(
629 ValueError,
630 f.remove,
631 ManifestOverride('chrome', 'chrome://global/locale/netError.dtd',
632 'chrome://browser/locale/netError.dtd')
633 )
635 f.remove(ManifestContent('chrome', 'global',
636 'toolkit/content/global/'))
637 self.assertRaises(
638 ValueError,
639 f.remove,
640 ManifestContent('chrome', 'global', 'toolkit/content/global/')
641 )
643 f.copy(self.tmppath('chrome.manifest'))
644 content = open(self.tmppath('chrome.manifest')).read()
645 self.assertEqual(content[:42], f.open().read(42))
646 self.assertEqual(content, f.open().read())
648 # Compiled typelib for the following IDL:
649 # interface foo;
650 # [uuid(5f70da76-519c-4858-b71e-e3c92333e2d6)]
651 # interface bar {
652 # void bar(in foo f);
653 # };
654 bar_xpt = GeneratedFile(
655 b'\x58\x50\x43\x4F\x4D\x0A\x54\x79\x70\x65\x4C\x69\x62\x0D\x0A\x1A' +
656 b'\x01\x02\x00\x02\x00\x00\x00\x7B\x00\x00\x00\x24\x00\x00\x00\x5C' +
657 b'\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +
658 b'\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x5F' +
659 b'\x70\xDA\x76\x51\x9C\x48\x58\xB7\x1E\xE3\xC9\x23\x33\xE2\xD6\x00' +
660 b'\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x0D\x00\x66\x6F\x6F\x00' +
661 b'\x62\x61\x72\x00\x62\x61\x72\x00\x00\x00\x00\x01\x00\x00\x00\x00' +
662 b'\x09\x01\x80\x92\x00\x01\x80\x06\x00\x00\x00'
663 )
665 # Compiled typelib for the following IDL:
666 # [uuid(3271bebc-927e-4bef-935e-44e0aaf3c1e5)]
667 # interface foo {
668 # void foo();
669 # };
670 foo_xpt = GeneratedFile(
671 b'\x58\x50\x43\x4F\x4D\x0A\x54\x79\x70\x65\x4C\x69\x62\x0D\x0A\x1A' +
672 b'\x01\x02\x00\x01\x00\x00\x00\x57\x00\x00\x00\x24\x00\x00\x00\x40' +
673 b'\x80\x00\x00\x32\x71\xBE\xBC\x92\x7E\x4B\xEF\x93\x5E\x44\xE0\xAA' +
674 b'\xF3\xC1\xE5\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x09\x00' +
675 b'\x66\x6F\x6F\x00\x66\x6F\x6F\x00\x00\x00\x00\x01\x00\x00\x00\x00' +
676 b'\x05\x00\x80\x06\x00\x00\x00'
677 )
679 # Compiled typelib for the following IDL:
680 # [uuid(7057f2aa-fdc2-4559-abde-08d939f7e80d)]
681 # interface foo {
682 # void foo();
683 # };
684 foo2_xpt = GeneratedFile(
685 b'\x58\x50\x43\x4F\x4D\x0A\x54\x79\x70\x65\x4C\x69\x62\x0D\x0A\x1A' +
686 b'\x01\x02\x00\x01\x00\x00\x00\x57\x00\x00\x00\x24\x00\x00\x00\x40' +
687 b'\x80\x00\x00\x70\x57\xF2\xAA\xFD\xC2\x45\x59\xAB\xDE\x08\xD9\x39' +
688 b'\xF7\xE8\x0D\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x09\x00' +
689 b'\x66\x6F\x6F\x00\x66\x6F\x6F\x00\x00\x00\x00\x01\x00\x00\x00\x00' +
690 b'\x05\x00\x80\x06\x00\x00\x00'
691 )
694 def read_interfaces(file):
695 return dict((i.name, i) for i in Typelib.read(file).interfaces)
698 class TestXPTFile(TestWithTmpDir):
699 def test_xpt_file(self):
700 x = XPTFile()
701 x.add(foo_xpt)
702 x.add(bar_xpt)
703 x.copy(self.tmppath('interfaces.xpt'))
705 foo = read_interfaces(foo_xpt.open())
706 foo2 = read_interfaces(foo2_xpt.open())
707 bar = read_interfaces(bar_xpt.open())
708 linked = read_interfaces(self.tmppath('interfaces.xpt'))
709 self.assertEqual(foo['foo'], linked['foo'])
710 self.assertEqual(bar['bar'], linked['bar'])
712 x.remove(foo_xpt)
713 x.copy(self.tmppath('interfaces2.xpt'))
714 linked = read_interfaces(self.tmppath('interfaces2.xpt'))
715 self.assertEqual(bar['foo'], linked['foo'])
716 self.assertEqual(bar['bar'], linked['bar'])
718 x.add(foo_xpt)
719 x.copy(DestNoWrite(self.tmppath('interfaces.xpt')))
720 linked = read_interfaces(self.tmppath('interfaces.xpt'))
721 self.assertEqual(foo['foo'], linked['foo'])
722 self.assertEqual(bar['bar'], linked['bar'])
724 x = XPTFile()
725 x.add(foo2_xpt)
726 x.add(bar_xpt)
727 x.copy(self.tmppath('interfaces.xpt'))
728 linked = read_interfaces(self.tmppath('interfaces.xpt'))
729 self.assertEqual(foo2['foo'], linked['foo'])
730 self.assertEqual(bar['bar'], linked['bar'])
732 x = XPTFile()
733 x.add(foo_xpt)
734 x.add(foo2_xpt)
735 x.add(bar_xpt)
736 from xpt import DataError
737 self.assertRaises(DataError, x.copy, self.tmppath('interfaces.xpt'))
740 class TestMinifiedProperties(TestWithTmpDir):
741 def test_minified_properties(self):
742 propLines = [
743 '# Comments are removed',
744 'foo = bar',
745 '',
746 '# Another comment',
747 ]
748 prop = GeneratedFile('\n'.join(propLines))
749 self.assertEqual(MinifiedProperties(prop).open().readlines(),
750 ['foo = bar\n', '\n'])
751 open(self.tmppath('prop'), 'wb').write('\n'.join(propLines))
752 MinifiedProperties(File(self.tmppath('prop'))) \
753 .copy(self.tmppath('prop2'))
754 self.assertEqual(open(self.tmppath('prop2')).readlines(),
755 ['foo = bar\n', '\n'])
758 class TestMinifiedJavaScript(TestWithTmpDir):
759 orig_lines = [
760 '// Comment line',
761 'let foo = "bar";',
762 'var bar = true;',
763 '',
764 '// Another comment',
765 ]
767 def test_minified_javascript(self):
768 orig_f = GeneratedFile('\n'.join(self.orig_lines))
769 min_f = MinifiedJavaScript(orig_f)
771 mini_lines = min_f.open().readlines()
772 self.assertTrue(mini_lines)
773 self.assertTrue(len(mini_lines) < len(self.orig_lines))
775 def _verify_command(self, code):
776 our_dir = os.path.abspath(os.path.dirname(__file__))
777 return [
778 sys.executable,
779 os.path.join(our_dir, 'support', 'minify_js_verify.py'),
780 code,
781 ]
783 def test_minified_verify_success(self):
784 orig_f = GeneratedFile('\n'.join(self.orig_lines))
785 min_f = MinifiedJavaScript(orig_f,
786 verify_command=self._verify_command('0'))
788 mini_lines = min_f.open().readlines()
789 self.assertTrue(mini_lines)
790 self.assertTrue(len(mini_lines) < len(self.orig_lines))
792 def test_minified_verify_failure(self):
793 orig_f = GeneratedFile('\n'.join(self.orig_lines))
794 min_f = MinifiedJavaScript(orig_f,
795 verify_command=self._verify_command('1'))
797 mini_lines = min_f.open().readlines()
798 self.assertEqual(mini_lines, orig_f.open().readlines())
801 class MatchTestTemplate(object):
802 def prepare_match_test(self, with_dotfiles=False):
803 self.add('bar')
804 self.add('foo/bar')
805 self.add('foo/baz')
806 self.add('foo/qux/1')
807 self.add('foo/qux/bar')
808 self.add('foo/qux/2/test')
809 self.add('foo/qux/2/test2')
810 if with_dotfiles:
811 self.add('foo/.foo')
812 self.add('foo/.bar/foo')
814 def do_match_test(self):
815 self.do_check('', [
816 'bar', 'foo/bar', 'foo/baz', 'foo/qux/1', 'foo/qux/bar',
817 'foo/qux/2/test', 'foo/qux/2/test2'
818 ])
819 self.do_check('*', [
820 'bar', 'foo/bar', 'foo/baz', 'foo/qux/1', 'foo/qux/bar',
821 'foo/qux/2/test', 'foo/qux/2/test2'
822 ])
823 self.do_check('foo/qux', [
824 'foo/qux/1', 'foo/qux/bar', 'foo/qux/2/test', 'foo/qux/2/test2'
825 ])
826 self.do_check('foo/b*', ['foo/bar', 'foo/baz'])
827 self.do_check('baz', [])
828 self.do_check('foo/foo', [])
829 self.do_check('foo/*ar', ['foo/bar'])
830 self.do_check('*ar', ['bar'])
831 self.do_check('*/bar', ['foo/bar'])
832 self.do_check('foo/*ux', [
833 'foo/qux/1', 'foo/qux/bar', 'foo/qux/2/test', 'foo/qux/2/test2'
834 ])
835 self.do_check('foo/q*ux', [
836 'foo/qux/1', 'foo/qux/bar', 'foo/qux/2/test', 'foo/qux/2/test2'
837 ])
838 self.do_check('foo/*/2/test*', ['foo/qux/2/test', 'foo/qux/2/test2'])
839 self.do_check('**/bar', ['bar', 'foo/bar', 'foo/qux/bar'])
840 self.do_check('foo/**/test', ['foo/qux/2/test'])
841 self.do_check('foo/**', [
842 'foo/bar', 'foo/baz', 'foo/qux/1', 'foo/qux/bar',
843 'foo/qux/2/test', 'foo/qux/2/test2'
844 ])
845 self.do_check('**/2/test*', ['foo/qux/2/test', 'foo/qux/2/test2'])
846 self.do_check('**/foo', [
847 'foo/bar', 'foo/baz', 'foo/qux/1', 'foo/qux/bar',
848 'foo/qux/2/test', 'foo/qux/2/test2'
849 ])
850 self.do_check('**/barbaz', [])
851 self.do_check('f**/bar', ['foo/bar'])
853 def do_finder_test(self, finder):
854 self.assertTrue(finder.contains('foo/.foo'))
855 self.assertTrue(finder.contains('foo/.bar'))
856 self.assertTrue('foo/.foo' in [f for f, c in
857 finder.find('foo/.foo')])
858 self.assertTrue('foo/.bar/foo' in [f for f, c in
859 finder.find('foo/.bar')])
860 self.assertEqual(sorted([f for f, c in finder.find('foo/.*')]),
861 ['foo/.bar/foo', 'foo/.foo'])
862 for pattern in ['foo', '**', '**/*', '**/foo', 'foo/*']:
863 self.assertFalse('foo/.foo' in [f for f, c in
864 finder.find(pattern)])
865 self.assertFalse('foo/.bar/foo' in [f for f, c in
866 finder.find(pattern)])
867 self.assertEqual(sorted([f for f, c in finder.find(pattern)]),
868 sorted([f for f, c in finder
869 if mozpack.path.match(f, pattern)]))
872 def do_check(test, finder, pattern, result):
873 if result:
874 test.assertTrue(finder.contains(pattern))
875 else:
876 test.assertFalse(finder.contains(pattern))
877 test.assertEqual(sorted(list(f for f, c in finder.find(pattern))),
878 sorted(result))
881 class TestFileFinder(MatchTestTemplate, TestWithTmpDir):
882 def add(self, path):
883 ensureParentDir(self.tmppath(path))
884 open(self.tmppath(path), 'wb').write(path)
886 def do_check(self, pattern, result):
887 do_check(self, self.finder, pattern, result)
889 def test_file_finder(self):
890 self.prepare_match_test(with_dotfiles=True)
891 self.finder = FileFinder(self.tmpdir)
892 self.do_match_test()
893 self.do_finder_test(self.finder)
895 def test_ignored_dirs(self):
896 """Ignored directories should not have results returned."""
897 self.prepare_match_test()
898 self.add('fooz')
900 # Present to ensure prefix matching doesn't exclude.
901 self.add('foo/quxz')
903 self.finder = FileFinder(self.tmpdir, ignore=['foo/qux'])
905 self.do_check('**', ['bar', 'foo/bar', 'foo/baz', 'foo/quxz', 'fooz'])
906 self.do_check('foo/*', ['foo/bar', 'foo/baz', 'foo/quxz'])
907 self.do_check('foo/**', ['foo/bar', 'foo/baz', 'foo/quxz'])
908 self.do_check('foo/qux/**', [])
909 self.do_check('foo/qux/*', [])
910 self.do_check('foo/qux/bar', [])
911 self.do_check('foo/quxz', ['foo/quxz'])
912 self.do_check('fooz', ['fooz'])
914 def test_ignored_files(self):
915 """Ignored files should not have results returned."""
916 self.prepare_match_test()
918 # Be sure prefix match doesn't get ignored.
919 self.add('barz')
921 self.finder = FileFinder(self.tmpdir, ignore=['foo/bar', 'bar'])
922 self.do_check('**', ['barz', 'foo/baz', 'foo/qux/1', 'foo/qux/2/test',
923 'foo/qux/2/test2', 'foo/qux/bar'])
924 self.do_check('foo/**', ['foo/baz', 'foo/qux/1', 'foo/qux/2/test',
925 'foo/qux/2/test2', 'foo/qux/bar'])
927 def test_ignored_patterns(self):
928 """Ignore entries with patterns should be honored."""
929 self.prepare_match_test()
931 self.add('foo/quxz')
933 self.finder = FileFinder(self.tmpdir, ignore=['foo/qux/*'])
934 self.do_check('**', ['foo/bar', 'foo/baz', 'foo/quxz', 'bar'])
935 self.do_check('foo/**', ['foo/bar', 'foo/baz', 'foo/quxz'])
938 class TestJarFinder(MatchTestTemplate, TestWithTmpDir):
939 def add(self, path):
940 self.jar.add(path, path, compress=True)
942 def do_check(self, pattern, result):
943 do_check(self, self.finder, pattern, result)
945 def test_jar_finder(self):
946 self.jar = JarWriter(file=self.tmppath('test.jar'))
947 self.prepare_match_test()
948 self.jar.finish()
949 reader = JarReader(file=self.tmppath('test.jar'))
950 self.finder = JarFinder(self.tmppath('test.jar'), reader)
951 self.do_match_test()
954 if __name__ == '__main__':
955 mozunit.main()