OLD | NEW |
---|---|
(Empty) | |
1 # Copyright 2016 The LUCI Authors. All rights reserved. | |
2 # Use of this source code is governed under the Apache License, Version 2.0 | |
3 # that can be found in the LICENSE file. | |
4 | |
5 import StringIO as SlowStringIO | |
6 import cStringIO as StringIO | |
7 import os | |
8 import shutil | |
9 import subprocess | |
10 import sys | |
11 import tempfile | |
12 import unittest | |
13 | |
14 import arfile | |
15 import cli | |
16 | |
17 if not hasattr(subprocess, 'DEVNULL'): | |
18 subprocess.DEVNULL = file(os.devnull, 'wb') | |
19 | |
20 | |
21 class CloseSaveStringIO(SlowStringIO.StringIO): | |
22 def close(self): | |
23 _value = self.getvalue() | |
24 self.getvalue = lambda: _value | |
25 SlowStringIO.StringIO.close(self) | |
26 | |
27 | |
28 AR_TEST_SIMPLE1 = ( | |
29 # ar file header | |
30 '!<arch>\n' | |
31 # File 1 | |
32 # ---------------------- | |
33 # (16 bytes) simple file | |
34 'filename1 ' | |
35 # (12 bytes) modification time | |
36 '123 ' | |
37 # (6 bytes) user id | |
38 '1000 ' | |
39 # (6 bytes) group id | |
40 '1000 ' | |
41 # (8 bytes) file mode | |
42 '100640 ' | |
43 # (10 bytes) data size | |
44 '6 ' | |
45 # (2 bytes) file magic | |
46 '\x60\n' | |
47 # File data | |
48 'abc123' | |
49 # Finished | |
50 '') | |
51 | |
52 AR_TEST_BSD1 = ( | |
53 # ar file header | |
54 '!<arch>\n' | |
55 # File 1 | |
56 # ---------------------- | |
57 # (16 bytes) BSD style filename length | |
58 '#1/9 ' | |
59 # (12 bytes) modification time | |
60 '1234 ' | |
61 # (6 bytes) user id | |
62 '1001 ' | |
63 # (6 bytes) group id | |
64 '1001 ' | |
65 # (8 bytes) file mode | |
66 '100644 ' | |
67 # (10 bytes) data size | |
68 '15 ' | |
69 # (2 bytes) file magic | |
70 '\x60\n' | |
71 # BSD style filename | |
72 'filename1' | |
73 # File data | |
74 'abc123' | |
75 # Padding | |
76 '\n' | |
77 # Finished | |
78 '') | |
79 | |
80 AR_TEST_BSD2 = ( | |
81 # ar file header | |
82 '!<arch>\n' | |
83 | |
84 # File 1 | |
85 # ---------------------- | |
86 # (16 bytes) filename len | |
87 '#1/5 ' | |
88 # (12 bytes) mtime | |
89 '1447140471 ' | |
90 # (6 bytes) owner id | |
91 '1000 ' | |
92 # (6 bytes) group id | |
93 '1000 ' | |
94 # (8 bytes) file mode | |
95 '100640 ' | |
96 # (10 bytes) Data size | |
97 '13 ' | |
98 # (2 bytes) File magic | |
99 '\x60\n' | |
100 # (9 bytes) File name | |
101 'file1' | |
102 # (6 bytes) File data | |
103 'contents' | |
104 # (1 byte) Padding | |
105 '\n' | |
106 | |
107 # File 2 | |
108 # ---------------------- | |
109 # (16 bytes) filename len | |
110 '#1/7 ' | |
111 # (12 bytes) mtime | |
112 '1447140471 ' | |
113 # (6 bytes) owner id | |
114 '1000 ' | |
115 # (6 bytes) group id | |
116 '1000 ' | |
117 # (8 bytes) file mode | |
118 '100640 ' | |
119 # (10 bytes) Data size | |
120 '10 ' | |
121 # (2 bytes) File magic | |
122 '\x60\n' | |
123 # (9 bytes) File name | |
124 'fileabc' | |
125 # (6 bytes) File data | |
126 '123' | |
127 # (0 byte) No padding | |
128 '' | |
129 | |
130 # File 3 | |
131 # ---------------------- | |
132 # (16 bytes) filename len | |
133 '#1/10 ' | |
134 # (12 bytes) mtime | |
135 '1447140471 ' | |
136 # (6 bytes) owner id | |
137 '1000 ' | |
138 # (6 bytes) group id | |
139 '1000 ' | |
140 # (8 bytes) file mode | |
141 '100640 ' | |
142 # (10 bytes) Data size | |
143 '16 ' | |
144 # (2 bytes) File magic | |
145 '\x60\n' | |
146 # (9 bytes) File name | |
147 'dir1/file1' | |
148 # (6 bytes) File data | |
149 '123abc' | |
150 # (0 byte) No padding | |
151 '' | |
152 | |
153 # Finished | |
154 '') | |
155 | |
156 | |
157 class TestArFileReader(unittest.TestCase): | |
158 | |
159 def testSimple1(self): | |
160 fileobj = StringIO.StringIO(AR_TEST_SIMPLE1) | |
161 | |
162 afri = iter(arfile.ArFileReader(fileobj)) | |
163 ai, af = afri.next() | |
164 self.assertIs(arfile.AR_FORMAT_SIMPLE, ai.format) | |
165 self.assertEqual('filename1', ai.name) | |
166 self.assertEqual(6, ai.size) | |
167 self.assertEqual(123, ai.mtime) | |
168 self.assertEqual(1000, ai.uid) | |
169 self.assertEqual(1000, ai.gid) | |
170 self.assertEqual('0100640', oct(ai.mode)) | |
171 self.assertEqual('abc123', af.read(ai.size)) | |
172 | |
173 def testBSD1(self): | |
174 fileobj = StringIO.StringIO(AR_TEST_BSD1) | |
175 | |
176 afri = iter(arfile.ArFileReader(fileobj)) | |
177 ai, af = afri.next() | |
178 self.assertIs(arfile.AR_FORMAT_BSD, ai.format) | |
179 self.assertEqual('filename1', ai.name) | |
180 self.assertEqual(6, ai.size) | |
181 self.assertEqual(1234, ai.mtime) | |
182 self.assertEqual(1001, ai.uid) | |
183 self.assertEqual(1001, ai.gid) | |
184 self.assertEqual('0100644', oct(ai.mode)) | |
185 self.assertEqual('abc123', af.read(ai.size)) | |
186 | |
187 def testBSD2(self): | |
188 fileobj = StringIO.StringIO(AR_TEST_BSD2) | |
189 | |
190 afri = iter(arfile.ArFileReader(fileobj)) | |
191 ai, af = afri.next() | |
192 self.assertIs(arfile.AR_FORMAT_BSD, ai.format) | |
193 self.assertEqual('file1', ai.name) | |
194 self.assertEqual(8, ai.size) | |
195 self.assertEqual(1447140471, ai.mtime) | |
196 self.assertEqual(1000, ai.uid) | |
197 self.assertEqual(1000, ai.gid) | |
198 self.assertEqual('0100640', oct(ai.mode)) | |
199 self.assertEqual('contents', af.read(ai.size)) | |
200 | |
201 ai, af = afri.next() | |
202 self.assertIs(arfile.AR_FORMAT_BSD, ai.format) | |
203 self.assertEqual('fileabc', ai.name) | |
204 self.assertEqual(3, ai.size) | |
205 self.assertEqual(1447140471, ai.mtime) | |
206 self.assertEqual(1000, ai.uid) | |
207 self.assertEqual(1000, ai.gid) | |
208 self.assertEqual('0100640', oct(ai.mode)) | |
209 self.assertEqual('123', af.read(ai.size)) | |
210 | |
211 ai, af = afri.next() | |
212 self.assertIs(arfile.AR_FORMAT_BSD, ai.format) | |
213 self.assertEqual('dir1/file1', ai.name) | |
214 self.assertEqual(6, ai.size) | |
215 self.assertEqual(1447140471, ai.mtime) | |
216 self.assertEqual(1000, ai.uid) | |
217 self.assertEqual(1000, ai.gid) | |
218 self.assertEqual('0100640', oct(ai.mode)) | |
219 self.assertEqual('123abc', af.read(ai.size)) | |
220 | |
221 | |
222 class TestArFileWriter(unittest.TestCase): | |
223 | |
224 def testSimple1(self): | |
225 fileobj = CloseSaveStringIO() | |
226 | |
227 afw = arfile.ArFileWriter(fileobj) | |
228 ai = arfile.ArInfo( | |
229 arfile.AR_FORMAT_SIMPLE, 'filename1', 6, 123, 1000, 1000, 0100640) | |
230 afw.addfile(ai, StringIO.StringIO('abc123')) | |
231 afw.close() | |
232 | |
233 self.assertMultiLineEqual(AR_TEST_SIMPLE1, fileobj.getvalue()) | |
234 | |
235 def testBSD1(self): | |
236 fileobj = CloseSaveStringIO() | |
237 | |
238 afw = arfile.ArFileWriter(fileobj) | |
239 ai = arfile.ArInfo( | |
240 arfile.AR_FORMAT_BSD, 'filename1', 6, 1234, 1001, 1001, 0100644) | |
241 afw.addfile(ai, StringIO.StringIO('abc123')) | |
242 afw.close() | |
243 | |
244 self.assertMultiLineEqual(AR_TEST_BSD1, fileobj.getvalue()) | |
245 | |
246 def testBSD2(self): | |
247 fileobj = CloseSaveStringIO() | |
248 | |
249 afw = arfile.ArFileWriter(fileobj) | |
250 afw.addfile( | |
251 arfile.ArInfo.fromdefault( | |
252 'file1', 8, arformat=arfile.AR_FORMAT_BSD), | |
253 StringIO.StringIO('contents')) | |
254 afw.addfile( | |
255 arfile.ArInfo.fromdefault( | |
256 'fileabc', 3, arformat=arfile.AR_FORMAT_BSD), | |
257 StringIO.StringIO('123')) | |
258 afw.addfile( | |
259 arfile.ArInfo.fromdefault( | |
260 'dir1/file1', 6, arformat=arfile.AR_FORMAT_BSD), | |
261 StringIO.StringIO('123abc')) | |
262 afw.close() | |
263 | |
264 self.assertMultiLineEqual(AR_TEST_BSD2, fileobj.getvalue()) | |
265 | |
M-A Ruel
2016/06/17 13:26:49
consistent spacing
| |
266 | |
267 | |
268 class BaseTestSuite(object): | |
269 | |
270 def testSimple1(self): | |
271 self.assertWorking( | |
272 ( | |
273 arfile.ArInfo( | |
274 arfile.AR_FORMAT_SIMPLE, 'filename1', | |
275 6, 123, 1000, 1000, 0100640), | |
276 'abc123')) | |
277 | |
278 def testBSD1(self): | |
279 self.assertWorking( | |
280 ( | |
281 arfile.ArInfo( | |
282 arfile.AR_FORMAT_BSD, 'filename1', | |
283 6, 123, 1000, 1000, 0100640), | |
284 'abc123')) | |
285 | |
286 def testBSD2(self): | |
287 self.assertWorking( | |
288 ( | |
289 arfile.ArInfo.fromdefault( | |
290 'file1', 8, arformat=arfile.AR_FORMAT_BSD), | |
291 'contents'), | |
292 ( | |
293 arfile.ArInfo.fromdefault( | |
294 'fileabc', 3, arformat=arfile.AR_FORMAT_BSD), | |
295 '123'), | |
296 ( | |
297 arfile.ArInfo.fromdefault( | |
298 'dir1/file1', 6, arformat=arfile.AR_FORMAT_BSD), | |
299 '123abc')) | |
300 | |
301 def testMixed(self): | |
302 self.assertWorking( | |
303 (arfile.ArInfo.fromdefault('file1', 0), ''), | |
304 (arfile.ArInfo.fromdefault('f f', 1), 'a'), | |
305 (arfile.ArInfo.fromdefault('123456789abcedefa', 1), 'a')) | |
306 | |
307 | |
308 class TestArRoundTrip(BaseTestSuite, unittest.TestCase): | |
309 | |
310 def assertWorking(self, *initems): | |
311 outfile = CloseSaveStringIO() | |
312 | |
313 afw = arfile.ArFileWriter(outfile) | |
314 for ai, data in initems: | |
315 assert ai.size == len(data) | |
316 afw.addfile(ai, StringIO.StringIO(data)) | |
317 afw.close() | |
318 | |
319 infile = StringIO.StringIO(outfile.getvalue()) | |
320 afr = arfile.ArFileReader(infile) | |
321 | |
322 outitems = [] | |
323 for ai, fd in afr: | |
324 data = fd.read(ai.size) | |
325 outitems.append((ai, data)) | |
326 | |
327 self.assertSequenceEqual(initems, outitems) | |
328 | |
329 | |
330 def system_has_ar(): | |
331 retcode = subprocess.call( | |
332 'ar', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) | |
333 return retcode == 1 | |
334 | |
335 | |
336 @unittest.skipIf(not system_has_ar(), 'no ar binary found.') | |
337 class TestArExternal(BaseTestSuite, unittest.TestCase): | |
338 | |
339 def assertWorking(self, *initems): | |
340 tf = tempfile.NamedTemporaryFile(mode='wb') | |
341 afw = arfile.ArFileWriter(tf) | |
342 | |
343 files = [] | |
344 for ai, data in initems: | |
345 files.append(ai.name) | |
346 assert ai.size == len(data) | |
347 afw.addfile(ai, StringIO.StringIO(data)) | |
348 afw.flush() | |
349 | |
350 output = subprocess.check_output(['ar', 't', tf.name]) | |
351 self.assertMultiLineEqual('\n'.join(files), output.strip()) | |
352 tf.close() | |
353 | |
354 | |
355 class TestCLI(unittest.TestCase): | |
356 | |
357 def runCLI(self, args): | |
358 orig_stdout = sys.stdout | |
359 orig_stderr = sys.stderr | |
360 try: | |
361 sys.stdout = StringIO.StringIO() | |
362 sys.stderr = StringIO.StringIO() | |
363 cli.main('artool', args) | |
364 return sys.stdout.getvalue(), sys.stderr.getvalue() | |
365 finally: | |
366 sys.stdout = orig_stdout | |
367 sys.stderr = orig_stderr | |
368 | |
369 def assertCLI(self, *initems): | |
370 indir = None | |
371 ardir = None | |
372 outdir = None | |
373 try: | |
374 indir = tempfile.mkdtemp() | |
375 ardir = tempfile.mkdtemp() | |
376 outdir = tempfile.mkdtemp() | |
377 | |
378 arp = os.path.join(ardir, 'out.ar') | |
379 assert not os.path.exists(arp) | |
380 | |
381 # Write out a directory tree | |
382 files = [] | |
383 for fp, contents in initems: | |
384 fn = os.path.join(indir, fp) | |
385 dn = os.path.dirname(fn) | |
386 if not os.path.exists(dn): | |
387 os.makedirs(dn) | |
388 | |
389 with file(fn, 'wb') as f: | |
390 f.write(contents) | |
391 | |
392 files.append(fp) | |
393 | |
394 files.sort() | |
395 fileslist = '\n'.join(files) | |
396 | |
397 # Create an archive from a directory | |
398 self.runCLI(['create', '--filename', arp, indir]) | |
399 self.assertTrue( | |
400 os.path.exists(arp), '%s file should exists' % arp) | |
401 | |
402 # List the archive contents | |
403 output, _ = self.runCLI(['list', '--filename', arp]) | |
404 filesoutput = '\n'.join(sorted(output[:-1].split('\n'))) | |
405 self.assertMultiLineEqual(fileslist, filesoutput) | |
406 | |
407 # Extract the archive | |
408 os.chdir(outdir) | |
409 self.runCLI(['extract', '--filename', arp]) | |
410 | |
411 # Walk the directory tree and collect the extracted output | |
412 outitems = [] | |
413 for root, _, files in os.walk(outdir): | |
414 for fn in files: | |
415 fp = os.path.join(root, fn) | |
416 outitems.append([fp[len(outdir)+1:], file(fp, 'rb').read()]) | |
417 | |
418 # Check the two are equal | |
419 self.assertSequenceEqual(sorted(initems), sorted(outitems)) | |
420 | |
421 finally: | |
422 if indir: | |
423 shutil.rmtree(indir, ignore_errors=True) | |
424 if ardir: | |
425 shutil.rmtree(ardir, ignore_errors=True) | |
426 if outdir: | |
427 shutil.rmtree(outdir, ignore_errors=True) | |
428 | |
429 | |
430 def testSimple1(self): | |
431 self.assertCLI( | |
432 ['file1', 'contents1'], | |
433 ) | |
434 | |
435 def testMultiple(self): | |
436 self.assertCLI( | |
437 ['file1', 'contents1'], | |
438 ['dir1/file2', 'contents2'], | |
439 ['dir2/dir3/file3', 'contents3'], | |
440 ['file4', 'contents4'], | |
441 ) | |
442 | |
443 def testUnicodeContents(self): | |
444 self.assertCLI( | |
445 ['file1', u'\u2603'.encode('utf-8')], | |
446 ) | |
447 | |
448 def testFilenameSpaces(self): | |
449 self.assertCLI( | |
450 ['f f1', 'contents1'], | |
451 ['d d1/file2', 'contents2'], | |
452 ['d d1/f f3', 'contents3'], | |
453 ['file4', 'contents4'], | |
454 ) | |
455 | |
456 def testBigFile(self): | |
457 self.assertCLI( | |
M-A Ruel
2016/06/17 13:26:49
you know this fits one line, right? :)
mithro
2016/06/22 13:05:30
Just making all the functions have the same format
| |
458 ['bigfile', 'data'*1024*1024*10], | |
459 ) | |
460 | |
M-A Ruel
2016/06/17 13:26:49
2 empty lines consistently
mithro
2016/06/22 13:05:30
Done.
| |
461 if __name__ == '__main__': | |
462 import subprocess | |
463 subprocess.call('python arfile.py', shell=True) | |
M-A Ruel
2016/06/17 13:26:49
I don't understand why this is not a test case
mithro
2016/06/22 13:05:30
This is running the doctests in the arfile.py
| |
464 unittest.main() | |
OLD | NEW |