Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(120)

Side by Side Diff: client/libs/arfile/arfile_test.py

Issue 2049523004: luci-py: Tools for working with BSD style ar archives. (Closed) Base URL: https://github.com/luci/luci-py.git@master
Patch Set: Actually fix the unicode problem properly. Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « client/libs/arfile/arfile.py ('k') | client/libs/arfile/cli.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2016 The LUCI Authors. All rights reserved.
3 # Use of this source code is governed under the Apache License, Version 2.0
4 # that can be found in the LICENSE file.
5
6 # pylint: disable=relative-import
7
8 import doctest
9 import io
10 import os
11 import shutil
12 import subprocess
13 import sys
14 import tempfile
15 import unittest
16
17 import arfile
18 import cli
19
20
21 ARFILE_DIR = os.path.dirname(os.path.abspath(__file__))
22 sys.path.insert(0, ARFILE_DIR)
23
24
25 if not hasattr(subprocess, 'DEVNULL'):
26 subprocess.DEVNULL = file(os.devnull, 'wb')
27
28
29 def filesystem_supports_unicode():
30 try:
31 u'\u2603'.encode(sys.getfilesystemencoding())
32 return True
33 except UnicodeEncodeError:
34 return False
35
36
37 class ClosesSaveIOBytes(io.BytesIO):
38
39 def close(self):
40 _value = self.getvalue()
41 self.getvalue = lambda: _value
42 io.BytesIO.close(self)
43
44
45 AR_TEST_SIMPLE1 = (
46 # ar file header
47 '!<arch>\n'
48 # File 1
49 # ----------------------
50 # (16 bytes) simple file
51 'filename1 '
52 # (12 bytes) modification time
53 '123 '
54 # (6 bytes) user id
55 '1000 '
56 # (6 bytes) group id
57 '1000 '
58 # (8 bytes) file mode
59 '100640 '
60 # (10 bytes) data size
61 '6 '
62 # (2 bytes) file magic
63 '\x60\n'
64 # File data
65 'abc123'
66 # Finished
67 '')
68
69 AR_TEST_SIMPLE_UTF = (
70 # ar file header
71 '!<arch>\n'
72 # File 1
73 # ----------------------
74 # (16 bytes) simple file
75 '\xe2\x98\x83 '
76 # (12 bytes) modification time
77 '123 '
78 # (6 bytes) user id
79 '1000 '
80 # (6 bytes) group id
81 '1000 '
82 # (8 bytes) file mode
83 '100640 '
84 # (10 bytes) data size
85 '4 '
86 # (2 bytes) file magic
87 '\x60\n'
88 # (4 bytes) File data
89 '\xf0\x9f\x92\xa9'
90 # Finished
91 '')
92
93 AR_TEST_BSD1 = (
94 # ar file header
95 '!<arch>\n'
96 # File 1
97 # ----------------------
98 # (16 bytes) BSD style filename length
99 '#1/9 '
100 # (12 bytes) modification time
101 '1234 '
102 # (6 bytes) user id
103 '1001 '
104 # (6 bytes) group id
105 '1001 '
106 # (8 bytes) file mode
107 '100644 '
108 # (10 bytes) data size
109 '15 '
110 # (2 bytes) file magic
111 '\x60\n'
112 # BSD style filename
113 'filename1'
114 # File data
115 'abc123'
116 # Padding
117 '\n'
118 # Finished
119 '')
120
121 AR_TEST_BSD2 = (
122 # ar file header
123 '!<arch>\n'
124
125 # File 1
126 # ----------------------
127 # (16 bytes) filename len
128 '#1/5 '
129 # (12 bytes) mtime
130 '1447140471 '
131 # (6 bytes) owner id
132 '1000 '
133 # (6 bytes) group id
134 '1000 '
135 # (8 bytes) file mode
136 '100640 '
137 # (10 bytes) Data size
138 '13 '
139 # (2 bytes) File magic
140 '\x60\n'
141 # (9 bytes) File name
142 'file1'
143 # (6 bytes) File data
144 'contents'
145 # (1 byte) Padding
146 '\n'
147
148 # File 2
149 # ----------------------
150 # (16 bytes) filename len
151 '#1/7 '
152 # (12 bytes) mtime
153 '1447140471 '
154 # (6 bytes) owner id
155 '1000 '
156 # (6 bytes) group id
157 '1000 '
158 # (8 bytes) file mode
159 '100640 '
160 # (10 bytes) Data size
161 '10 '
162 # (2 bytes) File magic
163 '\x60\n'
164 # (9 bytes) File name
165 'fileabc'
166 # (6 bytes) File data
167 '123'
168 # (0 byte) No padding
169 ''
170
171 # File 3
172 # ----------------------
173 # (16 bytes) filename len
174 '#1/10 '
175 # (12 bytes) mtime
176 '1447140471 '
177 # (6 bytes) owner id
178 '1000 '
179 # (6 bytes) group id
180 '1000 '
181 # (8 bytes) file mode
182 '100640 '
183 # (10 bytes) Data size
184 '16 '
185 # (2 bytes) File magic
186 '\x60\n'
187 # (9 bytes) File name
188 'dir1/file1'
189 # (6 bytes) File data
190 '123abc'
191 # (0 byte) No padding
192 ''
193
194 # Finished
195 '')
196
197 AR_TEST_BSD_UTF = (
198 # ar file header
199 '!<arch>\n'
200 # File 1
201 # ----------------------
202 # (16 bytes) BSD style filename length
203 '#1/3 '
204 # (12 bytes) modification time
205 '1234 '
206 # (6 bytes) user id
207 '1001 '
208 # (6 bytes) group id
209 '1001 '
210 # (8 bytes) file mode
211 '100644 '
212 # (10 bytes) data size
213 '7 '
214 # (2 bytes) file magic
215 '\x60\n'
216 # (3 bytes) BSD style filename
217 '\xe2\x98\x83'
218 # (4 bytes) File data
219 '\xf0\x9f\x92\xa9'
220 # Padding
221 '\n'
222 # Finished
223 '')
224
225
226 class TestArFileReader(unittest.TestCase):
227
228 def testSimple1(self):
229 fileobj = io.BytesIO(AR_TEST_SIMPLE1)
230
231 afri = iter(arfile.ArFileReader(fileobj))
232 ai, af = afri.next()
233 self.assertIs(arfile.AR_FORMAT_SIMPLE, ai.format)
234 self.assertEqual('filename1', ai.name)
235 self.assertEqual(6, ai.size)
236 self.assertEqual(123, ai.mtime)
237 self.assertEqual(1000, ai.uid)
238 self.assertEqual(1000, ai.gid)
239 self.assertEqual('0100640', oct(ai.mode))
240 self.assertEqual('abc123', af.read(ai.size))
241
242 def testSimpleUTF(self):
243 fileobj = io.BytesIO(AR_TEST_SIMPLE_UTF)
244
245 afri = iter(arfile.ArFileReader(fileobj))
246 ai, af = afri.next()
247 self.assertIs(arfile.AR_FORMAT_SIMPLE, ai.format)
248 self.assertEqual(u'\u2603', ai.name)
249 self.assertEqual(4, ai.size)
250 self.assertEqual(123, ai.mtime)
251 self.assertEqual(1000, ai.uid)
252 self.assertEqual(1000, ai.gid)
253 self.assertEqual('0100640', oct(ai.mode))
254 self.assertEqual(u'\U0001f4a9', af.read(ai.size).decode('utf-8'))
255
256 def testBSD1(self):
257 fileobj = io.BytesIO(AR_TEST_BSD1)
258
259 afri = iter(arfile.ArFileReader(fileobj))
260 ai, af = afri.next()
261 self.assertIs(arfile.AR_FORMAT_BSD, ai.format)
262 self.assertEqual('filename1', ai.name)
263 self.assertEqual(6, ai.size)
264 self.assertEqual(1234, ai.mtime)
265 self.assertEqual(1001, ai.uid)
266 self.assertEqual(1001, ai.gid)
267 self.assertEqual('0100644', oct(ai.mode))
268 self.assertEqual('abc123', af.read(ai.size))
269
270 def testBSD2(self):
271 fileobj = io.BytesIO(AR_TEST_BSD2)
272
273 afri = iter(arfile.ArFileReader(fileobj))
274 ai, af = afri.next()
275 self.assertIs(arfile.AR_FORMAT_BSD, ai.format)
276 self.assertEqual('file1', ai.name)
277 self.assertEqual(8, ai.size)
278 self.assertEqual(1447140471, ai.mtime)
279 self.assertEqual(1000, ai.uid)
280 self.assertEqual(1000, ai.gid)
281 self.assertEqual('0100640', oct(ai.mode))
282 self.assertEqual('contents', af.read(ai.size))
283
284 ai, af = afri.next()
285 self.assertIs(arfile.AR_FORMAT_BSD, ai.format)
286 self.assertEqual('fileabc', ai.name)
287 self.assertEqual(3, ai.size)
288 self.assertEqual(1447140471, ai.mtime)
289 self.assertEqual(1000, ai.uid)
290 self.assertEqual(1000, ai.gid)
291 self.assertEqual('0100640', oct(ai.mode))
292 self.assertEqual('123', af.read(ai.size))
293
294 ai, af = afri.next()
295 self.assertIs(arfile.AR_FORMAT_BSD, ai.format)
296 self.assertEqual('dir1/file1', ai.name)
297 self.assertEqual(6, ai.size)
298 self.assertEqual(1447140471, ai.mtime)
299 self.assertEqual(1000, ai.uid)
300 self.assertEqual(1000, ai.gid)
301 self.assertEqual('0100640', oct(ai.mode))
302 self.assertEqual('123abc', af.read(ai.size))
303
304 def testBSDUTF(self):
305 fileobj = io.BytesIO(AR_TEST_BSD_UTF)
306
307 afri = iter(arfile.ArFileReader(fileobj))
308 ai, af = afri.next()
309 self.assertIs(arfile.AR_FORMAT_BSD, ai.format)
310 self.assertEqual(u'\u2603', ai.name)
311 self.assertEqual(4, ai.size)
312 self.assertEqual(1234, ai.mtime)
313 self.assertEqual(1001, ai.uid)
314 self.assertEqual(1001, ai.gid)
315 self.assertEqual('0100644', oct(ai.mode))
316 self.assertEqual(u'\U0001f4a9', af.read(ai.size).decode('utf-8'))
317
318
319 class TestArFileWriter(unittest.TestCase):
320
321 def testSimple1(self):
322 fileobj = ClosesSaveIOBytes()
323
324 afw = arfile.ArFileWriter(fileobj)
325 ai = arfile.ArInfo(
326 arfile.AR_FORMAT_SIMPLE, 'filename1', 6, 123, 1000, 1000, 0100640)
327 afw.addfile(ai, io.BytesIO('abc123'))
328 afw.close()
329
330 self.assertMultiLineEqual(AR_TEST_SIMPLE1, fileobj.getvalue())
331
332 def testSimpleUTF(self):
333 fileobj = ClosesSaveIOBytes()
334
335 afw = arfile.ArFileWriter(fileobj)
336 ai = arfile.ArInfo(
337 arfile.AR_FORMAT_SIMPLE, u'\u2603', 4, 123, 1000, 1000, 0100640)
338 afw.addfile(ai, io.BytesIO(u'\U0001f4a9'.encode('utf-8')))
339 afw.close()
340
341 self.assertMultiLineEqual(AR_TEST_SIMPLE_UTF, fileobj.getvalue())
342
343 def testBSD1(self):
344 fileobj = ClosesSaveIOBytes()
345
346 afw = arfile.ArFileWriter(fileobj)
347 ai = arfile.ArInfo(
348 arfile.AR_FORMAT_BSD, 'filename1', 6, 1234, 1001, 1001, 0100644)
349 afw.addfile(ai, io.BytesIO('abc123'))
350 afw.close()
351
352 self.assertMultiLineEqual(AR_TEST_BSD1, fileobj.getvalue())
353
354 def testBSD2(self):
355 fileobj = ClosesSaveIOBytes()
356
357 afw = arfile.ArFileWriter(fileobj)
358 afw.addfile(
359 arfile.ArInfo.fromdefault(
360 'file1', 8, arformat=arfile.AR_FORMAT_BSD),
361 io.BytesIO('contents'))
362 afw.addfile(
363 arfile.ArInfo.fromdefault(
364 'fileabc', 3, arformat=arfile.AR_FORMAT_BSD),
365 io.BytesIO('123'))
366 afw.addfile(
367 arfile.ArInfo.fromdefault(
368 'dir1/file1', 6, arformat=arfile.AR_FORMAT_BSD),
369 io.BytesIO('123abc'))
370 afw.close()
371
372 self.assertMultiLineEqual(AR_TEST_BSD2, fileobj.getvalue())
373
374 def testBSDUTF(self):
375 fileobj = ClosesSaveIOBytes()
376
377 afw = arfile.ArFileWriter(fileobj)
378 ai = arfile.ArInfo(
379 arfile.AR_FORMAT_BSD, u'\u2603', 4, 1234, 1001, 1001, 0100644)
380 afw.addfile(ai, io.BytesIO(u'\U0001f4a9'.encode('utf-8')))
381 afw.close()
382
383 self.assertMultiLineEqual(AR_TEST_BSD_UTF, fileobj.getvalue())
384
385
386 class BaseTestSuite(object):
387
388 def testSimple1(self):
389 self.assertWorking(
390 (
391 arfile.ArInfo(
392 arfile.AR_FORMAT_SIMPLE, 'filename1',
393 6, 123, 1000, 1000, 0100640),
394 'abc123'))
395
396 def testSimpleUTF(self):
397 self.assertWorking(
398 (
399 arfile.ArInfo(
400 arfile.AR_FORMAT_SIMPLE, u'\u2603',
401 4, 123, 1000, 1000, 0100640),
402 u'\U0001f4a9'.encode('utf-8')))
403
404 def testBSD1(self):
405 self.assertWorking(
406 (
407 arfile.ArInfo(
408 arfile.AR_FORMAT_BSD, 'filename1',
409 6, 123, 1000, 1000, 0100640),
410 'abc123'))
411
412 def testBSD2(self):
413 self.assertWorking(
414 (
415 arfile.ArInfo.fromdefault(
416 'file1', 8, arformat=arfile.AR_FORMAT_BSD),
417 'contents'),
418 (
419 arfile.ArInfo.fromdefault(
420 'fileabc', 3, arformat=arfile.AR_FORMAT_BSD),
421 '123'),
422 (
423 arfile.ArInfo.fromdefault(
424 'dir1/file1', 6, arformat=arfile.AR_FORMAT_BSD),
425 '123abc'))
426
427 def testBSDUTF(self):
428 self.assertWorking(
429 (
430 arfile.ArInfo(
431 arfile.AR_FORMAT_BSD, u'\u2603',
432 4, 123, 1000, 1000, 0100640),
433 u'\U0001f4a9'.encode('utf-8')))
434
435 def testMixed(self):
436 self.assertWorking(
437 (arfile.ArInfo.fromdefault('file1', 0), ''),
438 (arfile.ArInfo.fromdefault('f f', 1), 'a'),
439 (arfile.ArInfo.fromdefault('123456789abcedefa', 1), 'a'))
440
441
442 class TestArRoundTrip(BaseTestSuite, unittest.TestCase):
443
444 def assertWorking(self, *initems):
445 outfile = ClosesSaveIOBytes()
446
447 afw = arfile.ArFileWriter(outfile)
448 for ai, data in initems:
449 assert ai.size == len(data)
450 afw.addfile(ai, io.BytesIO(data))
451 afw.close()
452
453 infile = io.BytesIO(outfile.getvalue())
454 afr = arfile.ArFileReader(infile)
455
456 outitems = []
457 for ai, fd in afr:
458 data = fd.read(ai.size)
459 outitems.append((ai, data))
460
461 self.assertSequenceEqual(initems, outitems)
462
463
464 def system_has_ar():
465 retcode = subprocess.call(
466 'ar', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
467 return retcode == 1
468
469
470 @unittest.skipIf(not system_has_ar(), 'no ar binary found.')
471 class TestArExternal(BaseTestSuite, unittest.TestCase):
472
473 def assertWorking(self, *initems):
474 tf = tempfile.NamedTemporaryFile(mode='wb')
475 afw = arfile.ArFileWriter(tf)
476
477 files = []
478 for ai, data in initems:
479 files.append(ai.name)
480 assert ai.size == len(data)
481 afw.addfile(ai, io.BytesIO(data))
482 afw.flush()
483
484 output = subprocess.check_output(['ar', 't', tf.name])
485 self.assertMultiLineEqual('\n'.join(files), output.decode('utf-8').strip())
486 tf.close()
487
488
489 class TestCLI(unittest.TestCase):
490
491 def runCLI(self, args):
492 orig_stdout = sys.stdout
493 orig_stderr = sys.stderr
494 try:
495 sys.stdout = io.StringIO()
496 sys.stderr = io.StringIO()
497 cli.main('artool', args)
498 return sys.stdout.getvalue(), sys.stderr.getvalue()
499 finally:
500 sys.stdout = orig_stdout
501 sys.stderr = orig_stderr
502
503 def assertCLI(self, *initems, **kw):
504 extra_args = kw.get('extra_args', [])
505
506 indir = None
507 ardir = None
508 outdir = None
509 try:
510 indir = tempfile.mkdtemp().decode(sys.getfilesystemencoding())
511 ardir = tempfile.mkdtemp().decode(sys.getfilesystemencoding())
512 outdir = tempfile.mkdtemp().decode(sys.getfilesystemencoding())
513
514 arp = os.path.join(ardir, 'out.ar')
515 assert not os.path.exists(arp)
516
517 # Write out a directory tree
518 files = []
519 for fp, contents in initems:
520 fn = os.path.join(indir, fp)
521 dn = os.path.dirname(fn)
522 if not os.path.exists(dn):
523 os.makedirs(dn)
524
525 with file(fn, 'wb') as f:
526 f.write(contents)
527
528 files.append(fp)
529
530 files.sort()
531 fileslist = '\n'.join(files)
532
533 # Create an archive from a directory
534 self.runCLI(['create', '--filename', arp, indir] + extra_args)
535 self.assertTrue(
536 os.path.exists(arp), '%s file should exists' % arp)
537
538 # List the archive contents
539 output, _ = self.runCLI(['list', '--filename', arp])
540 filesoutput = '\n'.join(sorted(output[:-1].split('\n')))
541 self.assertMultiLineEqual(fileslist, filesoutput)
542
543 # Extract the archive
544 os.chdir(outdir)
545 self.runCLI(['extract', '--filename', arp] + extra_args)
546
547 # Walk the directory tree and collect the extracted output
548 outitems = []
549 for root, _, files in os.walk(outdir):
550 for fn in files:
551 fp = os.path.join(root, fn)
552 outitems.append([fp[len(outdir)+1:], file(fp, 'rb').read()])
553
554 # Check the two are equal
555 self.assertSequenceEqual(sorted(initems), sorted(outitems))
556
557 finally:
558 if indir:
559 shutil.rmtree(indir, ignore_errors=True)
560 if ardir:
561 shutil.rmtree(ardir, ignore_errors=True)
562 if outdir:
563 shutil.rmtree(outdir, ignore_errors=True)
564
565 def testSimple1(self):
566 self.assertCLI(['file1', 'contents1'])
567
568 def testFullStat(self):
569 self.assertCLI(
570 ['file1', 'contents1'],
571 extra_args=['--dont-use-defaults'])
572
573 def testMultiple(self):
574 self.assertCLI(
575 ['file1', 'contents1'],
576 ['dir1/file2', 'contents2'],
577 ['dir2/dir3/file3', 'contents3'],
578 ['file4', 'contents4'],
579 )
580
581 def testUnicodeContents(self):
582 self.assertCLI(['file1', u'\u2603'.encode('utf-8')])
583
584 def testFilenameSpaces(self):
585 self.assertCLI(
586 ['f f1', 'contents1'],
587 ['d d1/file2', 'contents2'],
588 ['d d1/f f3', 'contents3'],
589 ['file4', 'contents4'],
590 )
591
592 def testBigFile(self):
593 self.assertCLI(['bigfile', 'data'*1024*1024*10])
594
595 @unittest.skipIf(
596 not filesystem_supports_unicode(), 'no unicode file support')
597 def testUnicode(self):
598 self.assertCLI([u'\u2603', u'\U0001f4a9'.encode('utf-8')])
599
600
601 if __name__ == '__main__':
602 doctest.testmod(arfile)
603 unittest.main()
OLDNEW
« no previous file with comments | « client/libs/arfile/arfile.py ('k') | client/libs/arfile/cli.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698