|
1 # This Source Code Form is subject to the terms of the Mozilla Public |
|
2 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
4 |
|
5 from mozbuild.util import ensureParentDir |
|
6 |
|
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 |
|
44 |
|
45 |
|
46 class TestWithTmpDir(unittest.TestCase): |
|
47 def setUp(self): |
|
48 self.tmpdir = mkdtemp() |
|
49 |
|
50 self.symlink_supported = False |
|
51 |
|
52 if not hasattr(os, 'symlink'): |
|
53 return |
|
54 |
|
55 dummy_path = self.tmppath('dummy_file') |
|
56 with open(dummy_path, 'a'): |
|
57 pass |
|
58 |
|
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) |
|
66 |
|
67 self.symlink_supported = True |
|
68 |
|
69 |
|
70 def tearDown(self): |
|
71 mozfile.rmtree(self.tmpdir) |
|
72 |
|
73 def tmppath(self, relpath): |
|
74 return os.path.normpath(os.path.join(self.tmpdir, relpath)) |
|
75 |
|
76 |
|
77 class MockDest(BytesIO, Dest): |
|
78 def __init__(self): |
|
79 BytesIO.__init__(self) |
|
80 self.mode = None |
|
81 |
|
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) |
|
87 |
|
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) |
|
94 |
|
95 def exists(self): |
|
96 return True |
|
97 |
|
98 def close(self): |
|
99 if self.mode: |
|
100 self.mode = None |
|
101 |
|
102 |
|
103 class DestNoWrite(Dest): |
|
104 def write(self, data): |
|
105 raise RuntimeError |
|
106 |
|
107 |
|
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') |
|
126 |
|
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 ] |
|
140 |
|
141 |
|
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') |
|
154 |
|
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()) |
|
168 |
|
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() |
|
178 |
|
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()) |
|
185 |
|
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) |
|
194 |
|
195 f = File(src) |
|
196 self.assertEqual(content[:42], f.open().read(42)) |
|
197 self.assertEqual(content, f.open().read()) |
|
198 |
|
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') |
|
206 |
|
207 with open(src, 'wb') as tmp: |
|
208 tmp.write('test') |
|
209 |
|
210 # Initial copy |
|
211 f = File(src) |
|
212 f.copy(dest) |
|
213 |
|
214 # Ensure subsequent copies won't trigger writes |
|
215 f.copy(DestNoWrite(dest)) |
|
216 self.assertEqual('test', open(dest, 'rb').read()) |
|
217 |
|
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()) |
|
224 |
|
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()) |
|
233 |
|
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)) |
|
239 |
|
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()) |
|
243 |
|
244 |
|
245 class TestAbsoluteSymlinkFile(TestWithTmpDir): |
|
246 def test_absolute_relative(self): |
|
247 AbsoluteSymlinkFile('/foo') |
|
248 |
|
249 with self.assertRaisesRegexp(ValueError, 'Symlink target not absolute'): |
|
250 AbsoluteSymlinkFile('./foo') |
|
251 |
|
252 def test_symlink_file(self): |
|
253 source = self.tmppath('test_path') |
|
254 with open(source, 'wt') as fh: |
|
255 fh.write('Hello world') |
|
256 |
|
257 s = AbsoluteSymlinkFile(source) |
|
258 dest = self.tmppath('symlink') |
|
259 self.assertTrue(s.copy(dest)) |
|
260 |
|
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') |
|
269 |
|
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') |
|
276 |
|
277 dest = self.tmppath('dest') |
|
278 with open(dest, 'a'): |
|
279 pass |
|
280 |
|
281 s = AbsoluteSymlinkFile(source) |
|
282 s.copy(dest, skip_if_older=False) |
|
283 |
|
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') |
|
292 |
|
293 def test_replace_symlink(self): |
|
294 if not self.symlink_supported: |
|
295 return |
|
296 |
|
297 source = self.tmppath('source') |
|
298 with open(source, 'a'): |
|
299 pass |
|
300 |
|
301 dest = self.tmppath('dest') |
|
302 |
|
303 os.symlink(self.tmppath('bad'), dest) |
|
304 self.assertTrue(os.path.islink(dest)) |
|
305 |
|
306 s = AbsoluteSymlinkFile(source) |
|
307 self.assertTrue(s.copy(dest)) |
|
308 |
|
309 self.assertTrue(os.path.islink(dest)) |
|
310 link = os.readlink(dest) |
|
311 self.assertEqual(link, source) |
|
312 |
|
313 def test_noop(self): |
|
314 if not hasattr(os, 'symlink'): |
|
315 return |
|
316 |
|
317 source = self.tmppath('source') |
|
318 dest = self.tmppath('dest') |
|
319 |
|
320 with open(source, 'a'): |
|
321 pass |
|
322 |
|
323 os.symlink(source, dest) |
|
324 link = os.readlink(dest) |
|
325 self.assertEqual(link, source) |
|
326 |
|
327 s = AbsoluteSymlinkFile(source) |
|
328 self.assertFalse(s.copy(dest)) |
|
329 |
|
330 link = os.readlink(dest) |
|
331 self.assertEqual(link, source) |
|
332 |
|
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') |
|
340 |
|
341 with open(src, 'wb') as tmp: |
|
342 tmp.write('#ifdef FOO\ntest\n#endif') |
|
343 |
|
344 f = PreprocessedFile(src, depfile_path=None, marker='#', defines={'FOO': True}) |
|
345 self.assertTrue(f.copy(dest)) |
|
346 |
|
347 self.assertEqual('test\n', open(dest, 'rb').read()) |
|
348 |
|
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') |
|
357 |
|
358 with open(src, 'wb') as tmp: |
|
359 tmp.write('#ifdef FOO\ntest\n#endif') |
|
360 |
|
361 # Initial copy |
|
362 f = PreprocessedFile(src, depfile_path=depfile, marker='#', defines={'FOO': True}) |
|
363 self.assertTrue(f.copy(dest)) |
|
364 |
|
365 # Ensure subsequent copies won't trigger writes |
|
366 self.assertFalse(f.copy(DestNoWrite(dest))) |
|
367 self.assertEqual('test\n', open(dest, 'rb').read()) |
|
368 |
|
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()) |
|
377 |
|
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()) |
|
381 |
|
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') |
|
390 |
|
391 with open(src, 'wb') as tmp: |
|
392 tmp.write('#ifdef FOO\ntest\n#endif') |
|
393 |
|
394 with open(incl, 'wb') as tmp: |
|
395 tmp.write('foo bar') |
|
396 |
|
397 # Initial copy |
|
398 f = PreprocessedFile(src, depfile_path=deps, marker='#', defines={'FOO': True}) |
|
399 self.assertTrue(f.copy(dest)) |
|
400 |
|
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()) |
|
408 |
|
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()) |
|
418 |
|
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))) |
|
425 |
|
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 |
|
433 |
|
434 source = self.tmppath('source') |
|
435 dest = self.tmppath('dest') |
|
436 pp_source = self.tmppath('pp_in') |
|
437 deps = self.tmppath('deps') |
|
438 |
|
439 with open(source, 'a'): |
|
440 pass |
|
441 |
|
442 os.symlink(source, dest) |
|
443 self.assertTrue(os.path.islink(dest)) |
|
444 |
|
445 with open(pp_source, 'wb') as tmp: |
|
446 tmp.write('#define FOO\nPREPROCESSED') |
|
447 |
|
448 f = PreprocessedFile(pp_source, depfile_path=deps, marker='#', |
|
449 defines={'FOO': True}) |
|
450 self.assertTrue(f.copy(dest)) |
|
451 |
|
452 self.assertEqual('PREPROCESSED', open(dest, 'rb').read()) |
|
453 self.assertFalse(os.path.islink(dest)) |
|
454 self.assertEqual('', open(source, 'rb').read()) |
|
455 |
|
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')) |
|
461 |
|
462 def test_required_existing_dest(self): |
|
463 p = self.tmppath('dest') |
|
464 with open(p, 'a'): |
|
465 pass |
|
466 |
|
467 f = ExistingFile(required=True) |
|
468 f.copy(p) |
|
469 |
|
470 def test_optional_missing_dest(self): |
|
471 f = ExistingFile(required=False) |
|
472 f.copy(self.tmppath('dest')) |
|
473 |
|
474 def test_optional_existing_dest(self): |
|
475 p = self.tmppath('dest') |
|
476 with open(p, 'a'): |
|
477 pass |
|
478 |
|
479 f = ExistingFile(required=False) |
|
480 f.copy(p) |
|
481 |
|
482 |
|
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') |
|
491 |
|
492 for content in samples: |
|
493 f = GeneratedFile(content) |
|
494 f.copy(dest) |
|
495 self.assertEqual(content, open(dest, 'rb').read()) |
|
496 |
|
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()) |
|
506 |
|
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') |
|
513 |
|
514 # Initial copy |
|
515 f = GeneratedFile('test') |
|
516 f.copy(dest) |
|
517 |
|
518 # Ensure subsequent copies won't trigger writes |
|
519 f.copy(DestNoWrite(dest)) |
|
520 self.assertEqual('test', open(dest, 'rb').read()) |
|
521 |
|
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()) |
|
526 |
|
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)) |
|
531 |
|
532 |
|
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') |
|
542 |
|
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 |
|
550 |
|
551 for j in JarReader(src): |
|
552 f = DeflatedFile(j) |
|
553 f.copy(dest) |
|
554 self.assertEqual(contents[j.filename], open(dest, 'rb').read()) |
|
555 |
|
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) |
|
565 |
|
566 f = DeflatedFile(JarReader(src)['content']) |
|
567 self.assertEqual(content[:42], f.open().read(42)) |
|
568 self.assertEqual(content, f.open().read()) |
|
569 |
|
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') |
|
577 |
|
578 with JarWriter(src) as jar: |
|
579 jar.add('test', 'test') |
|
580 jar.add('test2', 'test') |
|
581 jar.add('fooo', 'fooo') |
|
582 |
|
583 jar = JarReader(src) |
|
584 # Initial copy |
|
585 f = DeflatedFile(jar['test']) |
|
586 f.copy(dest) |
|
587 |
|
588 # Ensure subsequent copies won't trigger writes |
|
589 f.copy(DestNoWrite(dest)) |
|
590 self.assertEqual('test', open(dest, 'rb').read()) |
|
591 |
|
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()) |
|
597 |
|
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)) |
|
602 |
|
603 |
|
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/')) |
|
613 |
|
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 ]) |
|
622 |
|
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 ) |
|
634 |
|
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 ) |
|
642 |
|
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()) |
|
647 |
|
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 ) |
|
664 |
|
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 ) |
|
678 |
|
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 ) |
|
692 |
|
693 |
|
694 def read_interfaces(file): |
|
695 return dict((i.name, i) for i in Typelib.read(file).interfaces) |
|
696 |
|
697 |
|
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')) |
|
704 |
|
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']) |
|
711 |
|
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']) |
|
717 |
|
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']) |
|
723 |
|
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']) |
|
731 |
|
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')) |
|
738 |
|
739 |
|
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']) |
|
756 |
|
757 |
|
758 class TestMinifiedJavaScript(TestWithTmpDir): |
|
759 orig_lines = [ |
|
760 '// Comment line', |
|
761 'let foo = "bar";', |
|
762 'var bar = true;', |
|
763 '', |
|
764 '// Another comment', |
|
765 ] |
|
766 |
|
767 def test_minified_javascript(self): |
|
768 orig_f = GeneratedFile('\n'.join(self.orig_lines)) |
|
769 min_f = MinifiedJavaScript(orig_f) |
|
770 |
|
771 mini_lines = min_f.open().readlines() |
|
772 self.assertTrue(mini_lines) |
|
773 self.assertTrue(len(mini_lines) < len(self.orig_lines)) |
|
774 |
|
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 ] |
|
782 |
|
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')) |
|
787 |
|
788 mini_lines = min_f.open().readlines() |
|
789 self.assertTrue(mini_lines) |
|
790 self.assertTrue(len(mini_lines) < len(self.orig_lines)) |
|
791 |
|
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')) |
|
796 |
|
797 mini_lines = min_f.open().readlines() |
|
798 self.assertEqual(mini_lines, orig_f.open().readlines()) |
|
799 |
|
800 |
|
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') |
|
813 |
|
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']) |
|
852 |
|
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)])) |
|
870 |
|
871 |
|
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)) |
|
879 |
|
880 |
|
881 class TestFileFinder(MatchTestTemplate, TestWithTmpDir): |
|
882 def add(self, path): |
|
883 ensureParentDir(self.tmppath(path)) |
|
884 open(self.tmppath(path), 'wb').write(path) |
|
885 |
|
886 def do_check(self, pattern, result): |
|
887 do_check(self, self.finder, pattern, result) |
|
888 |
|
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) |
|
894 |
|
895 def test_ignored_dirs(self): |
|
896 """Ignored directories should not have results returned.""" |
|
897 self.prepare_match_test() |
|
898 self.add('fooz') |
|
899 |
|
900 # Present to ensure prefix matching doesn't exclude. |
|
901 self.add('foo/quxz') |
|
902 |
|
903 self.finder = FileFinder(self.tmpdir, ignore=['foo/qux']) |
|
904 |
|
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']) |
|
913 |
|
914 def test_ignored_files(self): |
|
915 """Ignored files should not have results returned.""" |
|
916 self.prepare_match_test() |
|
917 |
|
918 # Be sure prefix match doesn't get ignored. |
|
919 self.add('barz') |
|
920 |
|
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']) |
|
926 |
|
927 def test_ignored_patterns(self): |
|
928 """Ignore entries with patterns should be honored.""" |
|
929 self.prepare_match_test() |
|
930 |
|
931 self.add('foo/quxz') |
|
932 |
|
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']) |
|
936 |
|
937 |
|
938 class TestJarFinder(MatchTestTemplate, TestWithTmpDir): |
|
939 def add(self, path): |
|
940 self.jar.add(path, path, compress=True) |
|
941 |
|
942 def do_check(self, pattern, result): |
|
943 do_check(self, self.finder, pattern, result) |
|
944 |
|
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() |
|
952 |
|
953 |
|
954 if __name__ == '__main__': |
|
955 mozunit.main() |