blob: 426449af6f7330f6f4338fd6cacc7bf325d7b5af [file] [log] [blame]
Xavier Ducrohetb9582242009-12-01 13:03:49 -08001#!/usr/bin/python2.4
2#
3# Copyright (C) 2008 Google Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18"""Tests for divide_and_compress.py.
19
20TODO(jmatt): Add tests for module methods.
21"""
22
23__author__ = 'jmatt@google.com (Justin Mattson)'
24
25import os
26import stat
27import unittest
28import zipfile
29
30import divide_and_compress
31import mox
32
33
34class BagOfParts(object):
35 """Just a generic class that I can use to assign random attributes to."""
36
37 def NoOp(self):
38 x = 1
39
40
41class ValidAndRemoveTests(unittest.TestCase):
42 """Test the ArchiveIsValid and RemoveLastFile methods."""
43
44 def setUp(self):
45 """Prepare the test.
46
47 Construct some mock objects for use with the tests.
48 """
49 self.my_mox = mox.Mox()
50 file1 = BagOfParts()
51 file1.filename = 'file1.txt'
52 file1.contents = 'This is a test file'
53 file2 = BagOfParts()
54 file2.filename = 'file2.txt'
55 file2.contents = ('akdjfk;djsf;kljdslkfjslkdfjlsfjkdvn;kn;2389rtu4i'
56 'tn;ghf8:89H*hp748FJw80fu9WJFpwf39pujens;fihkhjfk'
57 'sdjfljkgsc n;iself')
58 self.files = {'file1': file1, 'file2': file2}
59
60 def tearDown(self):
61 """Remove any stubs we've created."""
62 self.my_mox.UnsetStubs()
63
64 def testArchiveIsValid(self):
65 """Test the DirectoryZipper.ArchiveIsValid method.
66
67 Run two tests, one that we expect to pass and one that we expect to fail
68 """
69 test_file_size = 1056730
70 self.my_mox.StubOutWithMock(os, 'stat')
71 os.stat('/foo/0.zip').AndReturn([test_file_size])
72 self.my_mox.StubOutWithMock(stat, 'ST_SIZE')
73 stat.ST_SIZE = 0
74 os.stat('/baz/0.zip').AndReturn([test_file_size])
75 mox.Replay(os.stat)
76 test_target = divide_and_compress.DirectoryZipper('/foo/', 'bar',
77 test_file_size - 1, True)
78
79 self.assertEqual(False, test_target.ArchiveIsValid(),
80 msg=('ERROR: Test failed, ArchiveIsValid should have '
81 'returned false, but returned true'))
82
83 test_target = divide_and_compress.DirectoryZipper('/baz/', 'bar',
84 test_file_size + 1, True)
85 self.assertEqual(True, test_target.ArchiveIsValid(),
86 msg=('ERROR: Test failed, ArchiveIsValid should have'
87 ' returned true, but returned false'))
88
89 def testRemoveLastFile(self):
90 """Test DirectoryZipper.RemoveLastFile method.
91
92 Construct a ZipInfo mock object with two records, verify that write is
93 only called once on the new ZipFile object.
94 """
95 source = self.CreateZipSource()
96 dest = self.CreateZipDestination()
97 source_path = ''.join([os.getcwd(), '/0-old.zip'])
98 dest_path = ''.join([os.getcwd(), '/0.zip'])
99 test_target = divide_and_compress.DirectoryZipper(
100 ''.join([os.getcwd(), '/']), 'dummy', 1024*1024, True)
101 self.my_mox.StubOutWithMock(test_target, 'OpenZipFileAtPath')
102 test_target.OpenZipFileAtPath(source_path, mode='r').AndReturn(source)
103 test_target.OpenZipFileAtPath(dest_path,
104 compress=zipfile.ZIP_DEFLATED,
105 mode='w').AndReturn(dest)
106 self.my_mox.StubOutWithMock(os, 'rename')
107 os.rename(dest_path, source_path)
108 self.my_mox.StubOutWithMock(os, 'unlink')
109 os.unlink(source_path)
110
111 self.my_mox.ReplayAll()
112 test_target.RemoveLastFile()
113 self.my_mox.VerifyAll()
114
115 def CreateZipSource(self):
116 """Create a mock zip sourec object.
117
118 Read should only be called once, because the second file is the one
119 being removed.
120
121 Returns:
122 A configured mocked
123 """
124
125 source_zip = self.my_mox.CreateMock(zipfile.ZipFile)
126 source_zip.infolist().AndReturn([self.files['file1'], self.files['file1']])
127 source_zip.infolist().AndReturn([self.files['file1'], self.files['file1']])
128 source_zip.read(self.files['file1'].filename).AndReturn(
129 self.files['file1'].contents)
130 source_zip.close()
131 return source_zip
132
133 def CreateZipDestination(self):
134 """Create mock destination zip.
135
136 Write should only be called once, because there are two files in the
137 source zip and we expect the second to be removed.
138
139 Returns:
140 A configured mocked
141 """
142
143 dest_zip = mox.MockObject(zipfile.ZipFile)
144 dest_zip.writestr(self.files['file1'].filename,
145 self.files['file1'].contents)
146 dest_zip.close()
147 return dest_zip
148
149
150class FixArchiveTests(unittest.TestCase):
151 """Tests for the DirectoryZipper.FixArchive method."""
152
153 def setUp(self):
154 """Create a mock file object."""
155 self.my_mox = mox.Mox()
156 self.file1 = BagOfParts()
157 self.file1.filename = 'file1.txt'
158 self.file1.contents = 'This is a test file'
159
160 def tearDown(self):
161 """Unset any mocks that we've created."""
162 self.my_mox.UnsetStubs()
163
164 def _InitMultiFileData(self):
165 """Create an array of mock file objects.
166
167 Create three mock file objects that we can use for testing.
168 """
169 self.multi_file_dir = []
170
171 file1 = BagOfParts()
172 file1.filename = 'file1.txt'
173 file1.contents = 'kjaskl;jkdjfkja;kjsnbvjnvnbuewklriujalvjsd'
174 self.multi_file_dir.append(file1)
175
176 file2 = BagOfParts()
177 file2.filename = 'file2.txt'
178 file2.contents = ('He entered the room and there in the center, it was.'
179 ' Looking upon the thing, suddenly he could not remember'
180 ' whether he had actually seen it before or whether'
181 ' his memory of it was merely the effect of something'
182 ' so often being imagined that it had long since become '
183 ' manifest in his mind.')
184 self.multi_file_dir.append(file2)
185
186 file3 = BagOfParts()
187 file3.filename = 'file3.txt'
188 file3.contents = 'Whoa, what is \'file2.txt\' all about?'
189 self.multi_file_dir.append(file3)
190
191 def testSingleFileArchive(self):
192 """Test behavior of FixArchive when the archive has a single member.
193
194 We expect that when this method is called with an archive that has a
195 single member that it will return False and unlink the archive.
196 """
197 test_target = divide_and_compress.DirectoryZipper(
198 ''.join([os.getcwd(), '/']), 'dummy', 1024*1024, True)
199 self.my_mox.StubOutWithMock(test_target, 'OpenZipFileAtPath')
200 test_target.OpenZipFileAtPath(
201 ''.join([os.getcwd(), '/0.zip']), mode='r').AndReturn(
202 self.CreateSingleFileMock())
203 self.my_mox.StubOutWithMock(os, 'unlink')
204 os.unlink(''.join([os.getcwd(), '/0.zip']))
205 self.my_mox.ReplayAll()
206 self.assertEqual(False, test_target.FixArchive('SIZE'))
207 self.my_mox.VerifyAll()
208
209 def CreateSingleFileMock(self):
210 """Create a mock ZipFile object for testSingleFileArchive.
211
212 We just need it to return a single member infolist twice
213
214 Returns:
215 A configured mock object
216 """
217 mock_zip = self.my_mox.CreateMock(zipfile.ZipFile)
218 mock_zip.infolist().AndReturn([self.file1])
219 mock_zip.infolist().AndReturn([self.file1])
220 mock_zip.close()
221 return mock_zip
222
223 def testMultiFileArchive(self):
224 """Test behavior of DirectoryZipper.FixArchive with a multi-file archive.
225
226 We expect that FixArchive will rename the old archive, adding '-old' before
227 '.zip', read all the members except the last one of '-old' into a new
228 archive with the same name as the original, and then unlink the '-old' copy
229 """
230 test_target = divide_and_compress.DirectoryZipper(
231 ''.join([os.getcwd(), '/']), 'dummy', 1024*1024, True)
232 self.my_mox.StubOutWithMock(test_target, 'OpenZipFileAtPath')
233 test_target.OpenZipFileAtPath(
234 ''.join([os.getcwd(), '/0.zip']), mode='r').AndReturn(
235 self.CreateMultiFileMock())
236 self.my_mox.StubOutWithMock(test_target, 'RemoveLastFile')
237 test_target.RemoveLastFile(''.join([os.getcwd(), '/0.zip']))
238 self.my_mox.StubOutWithMock(os, 'stat')
239 os.stat(''.join([os.getcwd(), '/0.zip'])).AndReturn([49302])
240 self.my_mox.StubOutWithMock(stat, 'ST_SIZE')
241 stat.ST_SIZE = 0
242 self.my_mox.ReplayAll()
243 self.assertEqual(True, test_target.FixArchive('SIZE'))
244 self.my_mox.VerifyAll()
245
246 def CreateMultiFileMock(self):
247 """Create mock ZipFile object for use with testMultiFileArchive.
248
249 The mock just needs to return the infolist mock that is prepared in
250 InitMultiFileData()
251
252 Returns:
253 A configured mock object
254 """
255 self._InitMultiFileData()
256 mock_zip = self.my_mox.CreateMock(zipfile.ZipFile)
257 mock_zip.infolist().AndReturn(self.multi_file_dir)
258 mock_zip.close()
259 return mock_zip
260
261
262class AddFileToArchiveTest(unittest.TestCase):
263 """Test behavior of method to add a file to an archive."""
264
265 def setUp(self):
266 """Setup the arguments for the DirectoryZipper object."""
267 self.my_mox = mox.Mox()
268 self.output_dir = '%s/' % os.getcwd()
269 self.file_to_add = 'file.txt'
270 self.input_dir = '/foo/bar/baz/'
271
272 def tearDown(self):
273 self.my_mox.UnsetStubs()
274
275 def testAddFileToArchive(self):
276 """Test the DirectoryZipper.AddFileToArchive method.
277
278 We are testing a pretty trivial method, we just expect it to look at the
279 file its adding, so that it possible can through out a warning.
280 """
281 test_target = divide_and_compress.DirectoryZipper(self.output_dir,
282 self.input_dir,
283 1024*1024, True)
284 self.my_mox.StubOutWithMock(test_target, 'OpenZipFileAtPath')
285 archive_mock = self.CreateArchiveMock()
286 test_target.OpenZipFileAtPath(
287 ''.join([self.output_dir, '0.zip']),
288 compress=zipfile.ZIP_DEFLATED).AndReturn(archive_mock)
289 self.StubOutOsModule()
290 self.my_mox.ReplayAll()
291 test_target.AddFileToArchive(''.join([self.input_dir, self.file_to_add]),
292 zipfile.ZIP_DEFLATED)
293 self.my_mox.VerifyAll()
294
295 def StubOutOsModule(self):
296 """Create a mock for the os.path and os.stat objects.
297
298 Create a stub that will return the type (file or directory) and size of the
299 object that is to be added.
300 """
301 self.my_mox.StubOutWithMock(os.path, 'isfile')
302 os.path.isfile(''.join([self.input_dir, self.file_to_add])).AndReturn(True)
303 self.my_mox.StubOutWithMock(os, 'stat')
304 os.stat(''.join([self.input_dir, self.file_to_add])).AndReturn([39480])
305 self.my_mox.StubOutWithMock(stat, 'ST_SIZE')
306 stat.ST_SIZE = 0
307
308 def CreateArchiveMock(self):
309 """Create a mock ZipFile for use with testAddFileToArchive.
310
311 Just verify that write is called with the file we expect and that the
312 archive is closed after the file addition
313
314 Returns:
315 A configured mock object
316 """
317 archive_mock = self.my_mox.CreateMock(zipfile.ZipFile)
318 archive_mock.write(''.join([self.input_dir, self.file_to_add]),
319 self.file_to_add)
320 archive_mock.close()
321 return archive_mock
322
323
324class CompressDirectoryTest(unittest.TestCase):
325 """Test the master method of the class.
326
327 Testing with the following directory structure.
328 /dir1/
329 /dir1/file1.txt
330 /dir1/file2.txt
331 /dir1/dir2/
332 /dir1/dir2/dir3/
333 /dir1/dir2/dir4/
334 /dir1/dir2/dir4/file3.txt
335 /dir1/dir5/
336 /dir1/dir5/file4.txt
337 /dir1/dir5/file5.txt
338 /dir1/dir5/file6.txt
339 /dir1/dir5/file7.txt
340 /dir1/dir6/
341 /dir1/dir6/file8.txt
342
343 file1.txt., file2.txt, file3.txt should be in 0.zip
344 file4.txt should be in 1.zip
345 file5.txt, file6.txt should be in 2.zip
346 file7.txt will not be stored since it will be too large compressed
347 file8.txt should b in 3.zip
348 """
349
350 def setUp(self):
351 """Setup all the mocks for this test."""
352 self.my_mox = mox.Mox()
353
354 self.base_dir = '/dir1'
355 self.output_path = '/out_dir/'
356 self.test_target = divide_and_compress.DirectoryZipper(
357 self.output_path, self.base_dir, 1024*1024, True)
358
359 self.InitArgLists()
360 self.InitOsDotPath()
361 self.InitArchiveIsValid()
362 self.InitWriteIndexRecord()
363 self.InitAddFileToArchive()
364
365 def tearDown(self):
366 self.my_mox.UnsetStubs()
367
368 def testCompressDirectory(self):
369 """Test the DirectoryZipper.CompressDirectory method."""
370 self.my_mox.ReplayAll()
371 for arguments in self.argument_lists:
372 self.test_target.CompressDirectory(None, arguments[0], arguments[1])
373 self.my_mox.VerifyAll()
374
375 def InitAddFileToArchive(self):
376 """Setup mock for DirectoryZipper.AddFileToArchive.
377
378 Make sure that the files are added in the order we expect.
379 """
380 self.my_mox.StubOutWithMock(self.test_target, 'AddFileToArchive')
381 self.test_target.AddFileToArchive('/dir1/file1.txt', zipfile.ZIP_DEFLATED)
382 self.test_target.AddFileToArchive('/dir1/file2.txt', zipfile.ZIP_DEFLATED)
383 self.test_target.AddFileToArchive('/dir1/dir2/dir4/file3.txt',
384 zipfile.ZIP_DEFLATED)
385 self.test_target.AddFileToArchive('/dir1/dir5/file4.txt',
386 zipfile.ZIP_DEFLATED)
387 self.test_target.AddFileToArchive('/dir1/dir5/file4.txt',
388 zipfile.ZIP_DEFLATED)
389 self.test_target.AddFileToArchive('/dir1/dir5/file5.txt',
390 zipfile.ZIP_DEFLATED)
391 self.test_target.AddFileToArchive('/dir1/dir5/file5.txt',
392 zipfile.ZIP_DEFLATED)
393 self.test_target.AddFileToArchive('/dir1/dir5/file6.txt',
394 zipfile.ZIP_DEFLATED)
395 self.test_target.AddFileToArchive('/dir1/dir5/file7.txt',
396 zipfile.ZIP_DEFLATED)
397 self.test_target.AddFileToArchive('/dir1/dir5/file7.txt',
398 zipfile.ZIP_DEFLATED)
399 self.test_target.AddFileToArchive('/dir1/dir6/file8.txt',
400 zipfile.ZIP_DEFLATED)
401
402 def InitWriteIndexRecord(self):
403 """Setup mock for DirectoryZipper.WriteIndexRecord."""
404 self.my_mox.StubOutWithMock(self.test_target, 'WriteIndexRecord')
405
406 # we are trying to compress 8 files, but we should only attempt to
407 # write an index record 7 times, because one file is too large to be stored
408 self.test_target.WriteIndexRecord().AndReturn(True)
409 self.test_target.WriteIndexRecord().AndReturn(False)
410 self.test_target.WriteIndexRecord().AndReturn(False)
411 self.test_target.WriteIndexRecord().AndReturn(True)
412 self.test_target.WriteIndexRecord().AndReturn(True)
413 self.test_target.WriteIndexRecord().AndReturn(False)
414 self.test_target.WriteIndexRecord().AndReturn(True)
415
416 def InitArchiveIsValid(self):
417 """Mock out DirectoryZipper.ArchiveIsValid and DirectoryZipper.FixArchive.
418
419 Mock these methods out such that file1, file2, and file3 go into one
420 archive. file4 then goes into the next archive, file5 and file6 in the
421 next, file 7 should appear too large to compress into an archive, and
422 file8 goes into the final archive
423 """
424 self.my_mox.StubOutWithMock(self.test_target, 'ArchiveIsValid')
425 self.my_mox.StubOutWithMock(self.test_target, 'FixArchive')
426 self.test_target.ArchiveIsValid().AndReturn(True)
427 self.test_target.ArchiveIsValid().AndReturn(True)
428 self.test_target.ArchiveIsValid().AndReturn(True)
429
430 # should be file4.txt
431 self.test_target.ArchiveIsValid().AndReturn(False)
432 self.test_target.FixArchive('SIZE').AndReturn(True)
433 self.test_target.ArchiveIsValid().AndReturn(True)
434
435 # should be file5.txt
436 self.test_target.ArchiveIsValid().AndReturn(False)
437 self.test_target.FixArchive('SIZE').AndReturn(True)
438 self.test_target.ArchiveIsValid().AndReturn(True)
439 self.test_target.ArchiveIsValid().AndReturn(True)
440
441 # should be file7.txt
442 self.test_target.ArchiveIsValid().AndReturn(False)
443 self.test_target.FixArchive('SIZE').AndReturn(True)
444 self.test_target.ArchiveIsValid().AndReturn(False)
445 self.test_target.FixArchive('SIZE').AndReturn(False)
446 self.test_target.ArchiveIsValid().AndReturn(True)
447
448 def InitOsDotPath(self):
449 """Mock out os.path.isfile.
450
451 Mock this out so the things we want to appear as files appear as files and
452 the things we want to appear as directories appear as directories. Also
453 make sure that the order of file visits is as we expect (which is why
454 InAnyOrder isn't used here).
455 """
456 self.my_mox.StubOutWithMock(os.path, 'isfile')
457 os.path.isfile('/dir1/dir2').AndReturn(False)
458 os.path.isfile('/dir1/dir5').AndReturn(False)
459 os.path.isfile('/dir1/dir6').AndReturn(False)
460 os.path.isfile('/dir1/file1.txt').AndReturn(True)
461 os.path.isfile('/dir1/file2.txt').AndReturn(True)
462 os.path.isfile('/dir1/dir2/dir3').AndReturn(False)
463 os.path.isfile('/dir1/dir2/dir4').AndReturn(False)
464 os.path.isfile('/dir1/dir2/dir4/file3.txt').AndReturn(True)
465 os.path.isfile('/dir1/dir5/file4.txt').AndReturn(True)
466 os.path.isfile('/dir1/dir5/file4.txt').AndReturn(True)
467 os.path.isfile('/dir1/dir5/file5.txt').AndReturn(True)
468 os.path.isfile('/dir1/dir5/file5.txt').AndReturn(True)
469 os.path.isfile('/dir1/dir5/file6.txt').AndReturn(True)
470 os.path.isfile('/dir1/dir5/file7.txt').AndReturn(True)
471 os.path.isfile('/dir1/dir5/file7.txt').AndReturn(True)
472 os.path.isfile('/dir1/dir6/file8.txt').AndReturn(True)
473
474 def InitArgLists(self):
475 """Create the directory path => directory contents mappings."""
476 self.argument_lists = []
477 self.argument_lists.append(['/dir1',
478 ['file1.txt', 'file2.txt', 'dir2', 'dir5',
479 'dir6']])
480 self.argument_lists.append(['/dir1/dir2', ['dir3', 'dir4']])
481 self.argument_lists.append(['/dir1/dir2/dir3', []])
482 self.argument_lists.append(['/dir1/dir2/dir4', ['file3.txt']])
483 self.argument_lists.append(['/dir1/dir5',
484 ['file4.txt', 'file5.txt', 'file6.txt',
485 'file7.txt']])
486 self.argument_lists.append(['/dir1/dir6', ['file8.txt']])
487
488if __name__ == '__main__':
489 unittest.main()