Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
michael@0 | 1 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 4 | |
michael@0 | 5 | import unittest |
michael@0 | 6 | |
michael@0 | 7 | import shutil |
michael@0 | 8 | import os |
michael@0 | 9 | import re |
michael@0 | 10 | import sys |
michael@0 | 11 | import random |
michael@0 | 12 | import copy |
michael@0 | 13 | from string import letters |
michael@0 | 14 | |
michael@0 | 15 | ''' |
michael@0 | 16 | Test case infrastructure for MozZipFile. |
michael@0 | 17 | |
michael@0 | 18 | This isn't really a unit test, but a test case generator and runner. |
michael@0 | 19 | For a given set of files, lengths, and number of writes, we create |
michael@0 | 20 | a testcase for every combination of the three. There are some |
michael@0 | 21 | symmetries used to reduce the number of test cases, the first file |
michael@0 | 22 | written is always the first file, the second is either the first or |
michael@0 | 23 | the second, the third is one of the first three. That is, if we |
michael@0 | 24 | had 4 files, but only three writes, the fourth file would never even |
michael@0 | 25 | get tried. |
michael@0 | 26 | |
michael@0 | 27 | The content written to the jars is pseudorandom with a fixed seed. |
michael@0 | 28 | ''' |
michael@0 | 29 | |
michael@0 | 30 | if not __file__: |
michael@0 | 31 | __file__ = sys.argv[0] |
michael@0 | 32 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) |
michael@0 | 33 | |
michael@0 | 34 | from MozZipFile import ZipFile |
michael@0 | 35 | import zipfile |
michael@0 | 36 | |
michael@0 | 37 | leafs = ( |
michael@0 | 38 | 'firstdir/oneleaf', |
michael@0 | 39 | 'seconddir/twoleaf', |
michael@0 | 40 | 'thirddir/with/sub/threeleaf') |
michael@0 | 41 | _lengths = map(lambda n: n * 64, [16, 64, 80]) |
michael@0 | 42 | lengths = 3 |
michael@0 | 43 | writes = 5 |
michael@0 | 44 | |
michael@0 | 45 | def givenlength(i): |
michael@0 | 46 | '''Return a length given in the _lengths array to allow manual |
michael@0 | 47 | tuning of which lengths of zip entries to use. |
michael@0 | 48 | ''' |
michael@0 | 49 | return _lengths[i] |
michael@0 | 50 | |
michael@0 | 51 | |
michael@0 | 52 | def prod(*iterables): |
michael@0 | 53 | ''''Tensor product of a list of iterables. |
michael@0 | 54 | |
michael@0 | 55 | This generator returns lists of items, one of each given |
michael@0 | 56 | iterable. It iterates over all possible combinations. |
michael@0 | 57 | ''' |
michael@0 | 58 | for item in iterables[0]: |
michael@0 | 59 | if len(iterables) == 1: |
michael@0 | 60 | yield [item] |
michael@0 | 61 | else: |
michael@0 | 62 | for others in prod(*iterables[1:]): |
michael@0 | 63 | yield [item] + others |
michael@0 | 64 | |
michael@0 | 65 | |
michael@0 | 66 | def getid(descs): |
michael@0 | 67 | 'Convert a list of ints to a string.' |
michael@0 | 68 | return reduce(lambda x,y: x+'{0}{1}'.format(*tuple(y)), descs,'') |
michael@0 | 69 | |
michael@0 | 70 | |
michael@0 | 71 | def getContent(length): |
michael@0 | 72 | 'Get pseudo random content of given length.' |
michael@0 | 73 | rv = [None] * length |
michael@0 | 74 | for i in xrange(length): |
michael@0 | 75 | rv[i] = random.choice(letters) |
michael@0 | 76 | return ''.join(rv) |
michael@0 | 77 | |
michael@0 | 78 | |
michael@0 | 79 | def createWriter(sizer, *items): |
michael@0 | 80 | 'Helper method to fill in tests, one set of writes, one for each item' |
michael@0 | 81 | locitems = copy.deepcopy(items) |
michael@0 | 82 | for item in locitems: |
michael@0 | 83 | item['length'] = sizer(item.pop('length', 0)) |
michael@0 | 84 | def helper(self): |
michael@0 | 85 | mode = 'w' |
michael@0 | 86 | if os.path.isfile(self.f): |
michael@0 | 87 | mode = 'a' |
michael@0 | 88 | zf = ZipFile(self.f, mode, self.compression) |
michael@0 | 89 | for item in locitems: |
michael@0 | 90 | self._write(zf, **item) |
michael@0 | 91 | zf = None |
michael@0 | 92 | pass |
michael@0 | 93 | return helper |
michael@0 | 94 | |
michael@0 | 95 | def createTester(name, *writes): |
michael@0 | 96 | '''Helper method to fill in tests, calls into a list of write |
michael@0 | 97 | helper methods. |
michael@0 | 98 | ''' |
michael@0 | 99 | _writes = copy.copy(writes) |
michael@0 | 100 | def tester(self): |
michael@0 | 101 | for w in _writes: |
michael@0 | 102 | getattr(self, w)() |
michael@0 | 103 | self._verifyZip() |
michael@0 | 104 | pass |
michael@0 | 105 | # unit tests get confused if the method name isn't test... |
michael@0 | 106 | tester.__name__ = name |
michael@0 | 107 | return tester |
michael@0 | 108 | |
michael@0 | 109 | class TestExtensiveStored(unittest.TestCase): |
michael@0 | 110 | '''Unit tests for MozZipFile |
michael@0 | 111 | |
michael@0 | 112 | The testcase are actually populated by code following the class |
michael@0 | 113 | definition. |
michael@0 | 114 | ''' |
michael@0 | 115 | |
michael@0 | 116 | stage = "mozzipfilestage" |
michael@0 | 117 | compression = zipfile.ZIP_STORED |
michael@0 | 118 | |
michael@0 | 119 | def leaf(self, *leafs): |
michael@0 | 120 | return os.path.join(self.stage, *leafs) |
michael@0 | 121 | def setUp(self): |
michael@0 | 122 | if os.path.exists(self.stage): |
michael@0 | 123 | shutil.rmtree(self.stage) |
michael@0 | 124 | os.mkdir(self.stage) |
michael@0 | 125 | self.f = self.leaf('test.jar') |
michael@0 | 126 | self.ref = {} |
michael@0 | 127 | self.seed = 0 |
michael@0 | 128 | |
michael@0 | 129 | def tearDown(self): |
michael@0 | 130 | self.f = None |
michael@0 | 131 | self.ref = None |
michael@0 | 132 | |
michael@0 | 133 | def _verifyZip(self): |
michael@0 | 134 | zf = zipfile.ZipFile(self.f) |
michael@0 | 135 | badEntry = zf.testzip() |
michael@0 | 136 | self.failIf(badEntry, badEntry) |
michael@0 | 137 | zlist = zf.namelist() |
michael@0 | 138 | zlist.sort() |
michael@0 | 139 | vlist = self.ref.keys() |
michael@0 | 140 | vlist.sort() |
michael@0 | 141 | self.assertEqual(zlist, vlist) |
michael@0 | 142 | for leaf, content in self.ref.iteritems(): |
michael@0 | 143 | zcontent = zf.read(leaf) |
michael@0 | 144 | self.assertEqual(content, zcontent) |
michael@0 | 145 | |
michael@0 | 146 | def _write(self, zf, seed=None, leaf=0, length=0): |
michael@0 | 147 | if seed is None: |
michael@0 | 148 | seed = self.seed |
michael@0 | 149 | self.seed += 1 |
michael@0 | 150 | random.seed(seed) |
michael@0 | 151 | leaf = leafs[leaf] |
michael@0 | 152 | content = getContent(length) |
michael@0 | 153 | self.ref[leaf] = content |
michael@0 | 154 | zf.writestr(leaf, content) |
michael@0 | 155 | dir = os.path.dirname(self.leaf('stage', leaf)) |
michael@0 | 156 | if not os.path.isdir(dir): |
michael@0 | 157 | os.makedirs(dir) |
michael@0 | 158 | open(self.leaf('stage', leaf), 'w').write(content) |
michael@0 | 159 | |
michael@0 | 160 | # all leafs in all lengths |
michael@0 | 161 | atomics = list(prod(xrange(len(leafs)), xrange(lengths))) |
michael@0 | 162 | |
michael@0 | 163 | # populate TestExtensiveStore with testcases |
michael@0 | 164 | for w in xrange(writes): |
michael@0 | 165 | # Don't iterate over all files for the the first n passes, |
michael@0 | 166 | # those are redundant as long as w < lengths. |
michael@0 | 167 | # There are symmetries in the trailing end, too, but I don't know |
michael@0 | 168 | # how to reduce those out right now. |
michael@0 | 169 | nonatomics = [list(prod(range(min(i,len(leafs))), xrange(lengths))) |
michael@0 | 170 | for i in xrange(1, w+1)] + [atomics] |
michael@0 | 171 | for descs in prod(*nonatomics): |
michael@0 | 172 | suffix = getid(descs) |
michael@0 | 173 | dicts = [dict(leaf=leaf, length=length) for leaf, length in descs] |
michael@0 | 174 | setattr(TestExtensiveStored, '_write' + suffix, |
michael@0 | 175 | createWriter(givenlength, *dicts)) |
michael@0 | 176 | setattr(TestExtensiveStored, 'test' + suffix, |
michael@0 | 177 | createTester('test' + suffix, '_write' + suffix)) |
michael@0 | 178 | |
michael@0 | 179 | # now create another round of tests, with two writing passes |
michael@0 | 180 | # first, write all file combinations into the jar, close it, |
michael@0 | 181 | # and then write all atomics again. |
michael@0 | 182 | # This should catch more or less all artifacts generated |
michael@0 | 183 | # by the final ordering step when closing the jar. |
michael@0 | 184 | files = [list(prod([i], xrange(lengths))) for i in xrange(len(leafs))] |
michael@0 | 185 | allfiles = reduce(lambda l,r:l+r, |
michael@0 | 186 | [list(prod(*files[:(i+1)])) for i in xrange(len(leafs))]) |
michael@0 | 187 | |
michael@0 | 188 | for first in allfiles: |
michael@0 | 189 | testbasename = 'test{0}_'.format(getid(first)) |
michael@0 | 190 | test = [None, '_write' + getid(first), None] |
michael@0 | 191 | for second in atomics: |
michael@0 | 192 | test[0] = testbasename + getid([second]) |
michael@0 | 193 | test[2] = '_write' + getid([second]) |
michael@0 | 194 | setattr(TestExtensiveStored, test[0], createTester(*test)) |
michael@0 | 195 | |
michael@0 | 196 | class TestExtensiveDeflated(TestExtensiveStored): |
michael@0 | 197 | 'Test all that has been tested with ZIP_STORED with DEFLATED, too.' |
michael@0 | 198 | compression = zipfile.ZIP_DEFLATED |
michael@0 | 199 | |
michael@0 | 200 | if __name__ == '__main__': |
michael@0 | 201 | unittest.main() |