summaryrefslogtreecommitdiffstats
path: root/config/tests/unitMozZipFile.py
diff options
context:
space:
mode:
Diffstat (limited to 'config/tests/unitMozZipFile.py')
-rw-r--r--config/tests/unitMozZipFile.py215
1 files changed, 215 insertions, 0 deletions
diff --git a/config/tests/unitMozZipFile.py b/config/tests/unitMozZipFile.py
new file mode 100644
index 0000000000..53cd05dcfe
--- /dev/null
+++ b/config/tests/unitMozZipFile.py
@@ -0,0 +1,215 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import
+import unittest
+
+import shutil
+import os
+import sys
+import random
+import copy
+from string import letters
+
+"""
+Test case infrastructure for MozZipFile.
+
+This isn't really a unit test, but a test case generator and runner.
+For a given set of files, lengths, and number of writes, we create
+a testcase for every combination of the three. There are some
+symmetries used to reduce the number of test cases, the first file
+written is always the first file, the second is either the first or
+the second, the third is one of the first three. That is, if we
+had 4 files, but only three writes, the fourth file would never even
+get tried.
+
+The content written to the jars is pseudorandom with a fixed seed.
+"""
+
+if not __file__:
+ __file__ = sys.argv[0]
+sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
+
+from MozZipFile import ZipFile
+import zipfile
+
+leafs = ("firstdir/oneleaf", "seconddir/twoleaf", "thirddir/with/sub/threeleaf")
+_lengths = map(lambda n: n * 64, [16, 64, 80])
+lengths = 3
+writes = 5
+
+
+def givenlength(i):
+ """Return a length given in the _lengths array to allow manual
+ tuning of which lengths of zip entries to use.
+ """
+ return _lengths[i]
+
+
+def prod(*iterables):
+ """'Tensor product of a list of iterables.
+
+ This generator returns lists of items, one of each given
+ iterable. It iterates over all possible combinations.
+ """
+ for item in iterables[0]:
+ if len(iterables) == 1:
+ yield [item]
+ else:
+ for others in prod(*iterables[1:]):
+ yield [item] + others
+
+
+def getid(descs):
+ "Convert a list of ints to a string."
+ return reduce(lambda x, y: x + "{0}{1}".format(*tuple(y)), descs, "")
+
+
+def getContent(length):
+ "Get pseudo random content of given length."
+ rv = [None] * length
+ for i in xrange(length):
+ rv[i] = random.choice(letters)
+ return "".join(rv)
+
+
+def createWriter(sizer, *items):
+ "Helper method to fill in tests, one set of writes, one for each item"
+ locitems = copy.deepcopy(items)
+ for item in locitems:
+ item["length"] = sizer(item.pop("length", 0))
+
+ def helper(self):
+ mode = "w"
+ if os.path.isfile(self.f):
+ mode = "a"
+ zf = ZipFile(self.f, mode, self.compression)
+ for item in locitems:
+ self._write(zf, **item)
+ zf = None
+ pass
+
+ return helper
+
+
+def createTester(name, *writes):
+ """Helper method to fill in tests, calls into a list of write
+ helper methods.
+ """
+ _writes = copy.copy(writes)
+
+ def tester(self):
+ for w in _writes:
+ getattr(self, w)()
+ self._verifyZip()
+ pass
+
+ # unit tests get confused if the method name isn't test...
+ tester.__name__ = name
+ return tester
+
+
+class TestExtensiveStored(unittest.TestCase):
+ """Unit tests for MozZipFile
+
+ The testcase are actually populated by code following the class
+ definition.
+ """
+
+ stage = "mozzipfilestage"
+ compression = zipfile.ZIP_STORED
+
+ def leaf(self, *leafs):
+ return os.path.join(self.stage, *leafs)
+
+ def setUp(self):
+ if os.path.exists(self.stage):
+ shutil.rmtree(self.stage)
+ os.mkdir(self.stage)
+ self.f = self.leaf("test.jar")
+ self.ref = {}
+ self.seed = 0
+
+ def tearDown(self):
+ self.f = None
+ self.ref = None
+
+ def _verifyZip(self):
+ zf = zipfile.ZipFile(self.f)
+ badEntry = zf.testzip()
+ self.failIf(badEntry, badEntry)
+ zlist = zf.namelist()
+ zlist.sort()
+ vlist = self.ref.keys()
+ vlist.sort()
+ self.assertEqual(zlist, vlist)
+ for leaf, content in self.ref.iteritems():
+ zcontent = zf.read(leaf)
+ self.assertEqual(content, zcontent)
+
+ def _write(self, zf, seed=None, leaf=0, length=0):
+ if seed is None:
+ seed = self.seed
+ self.seed += 1
+ random.seed(seed)
+ leaf = leafs[leaf]
+ content = getContent(length)
+ self.ref[leaf] = content
+ zf.writestr(leaf, content)
+ dir = os.path.dirname(self.leaf("stage", leaf))
+ if not os.path.isdir(dir):
+ os.makedirs(dir)
+ open(self.leaf("stage", leaf), "w").write(content)
+
+
+# all leafs in all lengths
+atomics = list(prod(xrange(len(leafs)), xrange(lengths)))
+
+# populate TestExtensiveStore with testcases
+for w in xrange(writes):
+ # Don't iterate over all files for the the first n passes,
+ # those are redundant as long as w < lengths.
+ # There are symmetries in the trailing end, too, but I don't know
+ # how to reduce those out right now.
+ nonatomics = [
+ list(prod(range(min(i, len(leafs))), xrange(lengths))) for i in xrange(1, w + 1)
+ ] + [atomics]
+ for descs in prod(*nonatomics):
+ suffix = getid(descs)
+ dicts = [dict(leaf=leaf, length=length) for leaf, length in descs]
+ setattr(
+ TestExtensiveStored, "_write" + suffix, createWriter(givenlength, *dicts)
+ )
+ setattr(
+ TestExtensiveStored,
+ "test" + suffix,
+ createTester("test" + suffix, "_write" + suffix),
+ )
+
+# now create another round of tests, with two writing passes
+# first, write all file combinations into the jar, close it,
+# and then write all atomics again.
+# This should catch more or less all artifacts generated
+# by the final ordering step when closing the jar.
+files = [list(prod([i], xrange(lengths))) for i in xrange(len(leafs))]
+allfiles = reduce(
+ lambda l, r: l + r, [list(prod(*files[: (i + 1)])) for i in xrange(len(leafs))]
+)
+
+for first in allfiles:
+ testbasename = "test{0}_".format(getid(first))
+ test = [None, "_write" + getid(first), None]
+ for second in atomics:
+ test[0] = testbasename + getid([second])
+ test[2] = "_write" + getid([second])
+ setattr(TestExtensiveStored, test[0], createTester(*test))
+
+
+class TestExtensiveDeflated(TestExtensiveStored):
+ "Test all that has been tested with ZIP_STORED with DEFLATED, too."
+ compression = zipfile.ZIP_DEFLATED
+
+
+if __name__ == "__main__":
+ unittest.main()