diff options
Diffstat (limited to 'config/tests')
27 files changed, 1572 insertions, 0 deletions
diff --git a/config/tests/chrome.manifest.flat b/config/tests/chrome.manifest.flat new file mode 100644 index 000000000..6c0a7e536 --- /dev/null +++ b/config/tests/chrome.manifest.flat @@ -0,0 +1,4 @@ +content test chrome/test/one +locale ab-X-stuff chrome/test/three +overlay chrome://one/file.xml chrome://two/otherfile.xml +skin test classic chrome/test/one diff --git a/config/tests/makefiles/autodeps/Makefile.in b/config/tests/makefiles/autodeps/Makefile.in new file mode 100644 index 000000000..ea21c5a8e --- /dev/null +++ b/config/tests/makefiles/autodeps/Makefile.in @@ -0,0 +1,36 @@ +# -*- makefile -*- +# +# 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/. +# + +export STANDALONE_MAKEFILE=1 +PYTHON ?= python +PYTEST = $(PYTHON) -E + +# python -B not supported by older interpreters +export PYTHONDONTWRITEBYTECODE=1 + +include $(topsrcdir)/config/rules.mk + +autotgt_tests = .deps/autotargets.mk.ts + +tgts =\ + .deps/.mkdir.done\ + $(autotgt_tests) + $(NULL) + +export MAKE + +##------------------_## +##---] TARGETS [---## +##------------------_## +all:: + +check:: $(tgts) + +# Only run unit test when autotargets.mk is modified +$(autotgt_tests): $(topsrcdir)/config/makefiles/autotargets.mk + $(PYTEST) $(srcdir)/check_mkdir.tpy + @$(TOUCH) $@ diff --git a/config/tests/makefiles/autodeps/check_mkdir.tpy b/config/tests/makefiles/autodeps/check_mkdir.tpy new file mode 100644 index 000000000..ad633c781 --- /dev/null +++ b/config/tests/makefiles/autodeps/check_mkdir.tpy @@ -0,0 +1,269 @@ +#!/usr/bin/env python +# +# 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/. +# + +import os +import sys +import tempfile + +from subprocess import call +from shutil import rmtree + +import logging +import unittest + + +def banner(): + """ + Display interpreter and system info for the test env + """ + print '*' * 75 + cmd = os.path.basename(__file__) + print "%s: python version is %s" % (cmd, sys.version) + print '*' * 75 + + +def myopts(vals): + """ + Storage for extra command line args passed. + + Returns: + hash - argparse::Namespace object values + """ + + if not hasattr(myopts, 'vals'): + if 'argparse' in sys.modules: + tmp = { } # key existance enables unittest module debug + else: + tmp = { 'debug': False, 'verbose': False } + + for k in dir(vals): + if k[0:1] == '_': + continue + tmp[k] = getattr(vals, k) + myopts.vals = tmp + + return myopts.vals + + +def path2posix(src): + """ + Normalize directory path syntax + + Keyword arguments: + src - path to normalize + + Returns: + scalar - a file path with drive separators and windows slashes removed + + Todo: + move to {build,config,tools,toolkit}/python for use in a library + """ + + ## (drive, tail) = os.path.splitdrive(src) + ## Support path testing on all platforms + drive = '' + winpath = src.find(':') + if -1 != winpath and 10 > winpath: + (drive, tail) = src.split(':', 1) + + if drive: + todo = [ '', drive.rstrip(':').lstrip('/').lstrip('\\') ] + todo.extend( tail.lstrip('/').lstrip('\\').split('\\') ) # c:\a => [a] + else: # os.name == 'posix' + todo = src.split('\\') + + dst = '/'.join(todo) + return dst + + +def checkMkdir(work, debug=False): + """ + Verify arg permutations for directory mutex creation. + + Keyword arguments: + None + + Returns: + Exception on error + + Note: + Exception() rather than self.assertTrue() is used in this test + function to enable scatch cleanup on test exit/failure conditions. + Not guaranteed by python closures on early exit. + """ + + logging.debug("Testing: checkMkdir") + + # On Windows, don't convert paths to POSIX + skipposix = sys.platform == "win32" + if skipposix: + path = os.path.abspath(__file__) + dirname_fun = os.path.dirname + else: + path = path2posix(os.path.abspath(__file__)) + import posixpath + dirname_fun = posixpath.dirname + + src = dirname_fun(path) + # root is 5 directories up from path + root = reduce(lambda x, _: dirname_fun(x), xrange(5), path) + + rootP = path2posix(root) + srcP = path2posix(src) + workP = path2posix(work) + + # C:\foo -vs- /c/foo + # [0] command paths use /c/foo + # [1] os.path.exists() on mingw() requires C:\ + paths = [ + "mkdir_bycall", # function generated + "mkdir_bydep", # explicit dependency + "mkdir_bygen", # by GENERATED_DIRS macro + ] + + ## Use make from the parent "make check" call when available + cmd = { 'make': 'make' } + shell0 = os.environ.get('MAKE') + if shell0: + shell = os.path.splitext(shell0)[0] # strip: .exe, .py + if -1 != shell.find('make'): + print "MAKE COMMAND FOUND: %s" % (shell0) + cmd['make'] = shell0 if skipposix else path2posix(shell0) + + args = [] + args.append('%s' % (cmd['make'])) + args.append('-C %s' % (work if skipposix else workP)) + args.append("-f %s/testor.tmpl" % (src if skipposix else srcP)) + args.append('topsrcdir=%s' % (root if skipposix else rootP)) + args.append('deps_mkdir_bycall=%s' % paths[0]) + args.append('deps_mkdir_bydep=%s' % paths[1]) + args.append('deps_mkdir_bygen=%s' % paths[2]) + args.append('checkup') # target + + # Call will fail on mingw with output redirected ?!? + if debug: + pass + if False: # if not debug: + args.append('>/dev/null') + + cmd = '%s' % (' '.join(args)) + logging.debug("Running: %s" % (cmd)) + rc = call(cmd, shell=True) + if rc: + raise Exception("make failed ($?=%s): cmd=%s" % (rc, cmd)) + + for i in paths: + path = os.path.join(work, i) + logging.debug("Did testing mkdir(%s) succeed?" % (path)) + if not os.path.exists(path): + raise Exception("Test path %s does not exist" % (path)) + + +def parseargs(): + """ + Support additional command line arguments for testing + + Returns: + hash - arguments of interested parsed from the command line + """ + + opts = None + try: + import argparse2 + parser = argparse.ArgumentParser() + parser.add_argument('--debug', + action="store_true", + default=False, + help='Enable debug mode') + # Cannot overload verbose, Verbose: False enables debugging + parser.add_argument('--verbose', + action="store_true", + default=False, + help='Enable verbose mode') + parser.add_argument('unittest_args', + nargs='*' + # help='Slurp/pass remaining args to unittest' + ) + opts = parser.parse_args() + + except ImportError: + pass + + return opts + + +class TestMakeLogic(unittest.TestCase): + """ + Test suite used to validate makefile library rules and macros + """ + + def setUp(self): + opts = myopts(None) # NameSpace object not hash + self.debug = opts['debug'] + self.verbose = opts['verbose'] + + if self.debug: + logging.basicConfig(level=logging.DEBUG) + + if self.verbose: + print + print "ENVIRONMENT DUMP:" + print '=' * 75 + for k,v in os.environ.items(): + print "env{%s} => %s" % (k, v) + print + + + def test_path2posix(self): + + todo = { + '/dev/null' : '/dev/null', + 'A:\\a\\b\\c' : '/A/a/b/c', + 'B:/x/y' : '/B/x/y', + 'C:/x\\y/z' : '/C/x/y/z', + '//FOO/bar/tans': '//FOO/bar/tans', + '//X\\a/b\\c/d' : '//X/a/b/c/d', + '\\c:mozilla\\sandbox': '/c/mozilla/sandbox', + } + + for val,exp in todo.items(): + found = path2posix(val) + tst = "posix2path(%s): %s != %s)" % (val, exp, found) + self.assertEqual(exp, found, "%s: invalid path detected" % (tst)) + + + def test_mkdir(self): + """ + Verify directory creation rules and macros + """ + + failed = True + + # Exception handling is used to cleanup scratch space on error + try: + work = tempfile.mkdtemp() + checkMkdir(work, self.debug) + failed = False + finally: + if os.path.exists(work): + rmtree(work) + + self.assertFalse(failed, "Unit test failure detected") + + +if __name__ == '__main__': + banner() + opts = parseargs() + myopts(opts) + + if opts: + if hasattr(opts, 'unittest_args'): + sys.argv[1:] = opts.unittest_args + else: + sys.argv[1:] = [] + + unittest.main() diff --git a/config/tests/makefiles/autodeps/moz.build b/config/tests/makefiles/autodeps/moz.build new file mode 100644 index 000000000..28919c271 --- /dev/null +++ b/config/tests/makefiles/autodeps/moz.build @@ -0,0 +1,6 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + diff --git a/config/tests/makefiles/autodeps/testor.tmpl b/config/tests/makefiles/autodeps/testor.tmpl new file mode 100644 index 000000000..3134277e6 --- /dev/null +++ b/config/tests/makefiles/autodeps/testor.tmpl @@ -0,0 +1,64 @@ +# -*- makefile -*- +# +# 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/. +# + +########################################################################### +## Intent: Standalone unit tests for makefile rules and target logic +########################################################################### + +deps =$(NULL) +tgts =$(NULL) + +ifdef VERBOSE + tgts += show +endif + +# Define macros +include $(topsrcdir)/config/makefiles/makeutils.mk +include $(topsrcdir)/config/makefiles/autotargets.mk + +########################## +## Verify threadsafe mkdir +########################## +ifdef deps_mkdir_bycall + deps += $(call mkdir_deps,deps_mkdir_bycall) + tgts += check_mkdir +endif +ifdef deps_mkdir_bydep + deps += $(foreach dir,$(deps_mkdir_bydep),$(dir)/.mkdir.done) + tgts += check_mkdir +endif +ifdef deps_mkdir_bygen + GENERATED_DIRS += $(deps_mkdir_bygen) + tgts += check_mkdir +endif + +########################### +## Minimal environment load +########################### +MKDIR ?= mkdir -p +TOUCH ?= touch + +INCLUDED_CONFIG_MK = 1 +MOZILLA_DIR := $(topsrcdir) +include $(topsrcdir)/config/rules.mk + +##-------------------## +##---] TARGETS [---## +##-------------------## +all:: + +# Quarks: +# o Use of 'all' would trigger export target processing +checkup: $(tgts) + +# AUTO_DEPS - verify GENERATED_DIRS +check_mkdir: $(deps) $(AUTO_DEPS) + +show: + @echo "tgts=[$(tgts)]" + @echo "deps=[$(deps)]" + find $(dir $(deps)) -print diff --git a/config/tests/ref-simple/one/file.xml b/config/tests/ref-simple/one/file.xml new file mode 100644 index 000000000..21aacb9bd --- /dev/null +++ b/config/tests/ref-simple/one/file.xml @@ -0,0 +1 @@ +<?xml version="1.0"><doc/> diff --git a/config/tests/ref-simple/one/preproc b/config/tests/ref-simple/one/preproc new file mode 100644 index 000000000..3e04d6329 --- /dev/null +++ b/config/tests/ref-simple/one/preproc @@ -0,0 +1,2 @@ + +This is ab-X-stuff. diff --git a/config/tests/ref-simple/one/some.css b/config/tests/ref-simple/one/some.css new file mode 100644 index 000000000..a8a3ee47c --- /dev/null +++ b/config/tests/ref-simple/one/some.css @@ -0,0 +1,6 @@ +#div: { +/* this is a ID rule, and should stay intact */ +} +[lang=ab-X-stuff] { +/* this selector should match content with lang="ab-X-stuff" */ +} diff --git a/config/tests/ref-simple/three/l10nfile.txt b/config/tests/ref-simple/three/l10nfile.txt new file mode 100644 index 000000000..7ce7909ab --- /dev/null +++ b/config/tests/ref-simple/three/l10nfile.txt @@ -0,0 +1 @@ +localized content diff --git a/config/tests/ref-simple/two/otherfile.xml b/config/tests/ref-simple/two/otherfile.xml new file mode 100644 index 000000000..6c50abf6f --- /dev/null +++ b/config/tests/ref-simple/two/otherfile.xml @@ -0,0 +1 @@ +<?xml version="1.0"><otherdoc/> diff --git a/config/tests/src-simple/Makefile.in b/config/tests/src-simple/Makefile.in new file mode 100644 index 000000000..55fdfd867 --- /dev/null +++ b/config/tests/src-simple/Makefile.in @@ -0,0 +1,38 @@ +# +# 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/. + +LOCALE_SRCDIR = $(srcdir)/l10n + +EXTERNALLY_MANAGED_MAKE_FILE := 1 +STANDALONE_MAKEFILE := 1 +JAR_MANIFEST := $(srcdir)/jar.mn + +include $(topsrcdir)/config/config.mk + +XPI_NAME = test_jar_mn + +ACDEFINES += \ + -DAB_CD=ab-X-stuff \ + $(NULL) + +MY_MANIFEST = $(if $(USE_EXTENSION_MANIFEST), $(FINAL_TARGET)/chrome.manifest, $(FINAL_TARGET)/chrome/test.manifest) +REF_MANIFEST = $(if $(USE_EXTENSION_MANIFEST),chrome.manifest,test.manifest) + +check-%:: + if test -d $(FINAL_TARGET); then rm -rf $(FINAL_TARGET); fi; + $(MAKE) realchrome MOZ_JAR_MAKER_FILE_FORMAT=$* + @echo 'Comparing manifests...' + @if ! sort $(MY_MANIFEST) | diff --text -U 0 $(srcdir)/../$(REF_MANIFEST).$* - ; then \ + echo 'TEST-UNEXPECTED-FAIL | config/tests/$(REF_MANIFEST).$* | differing content in manifest!' ; \ + false; \ + fi + @if [ $* = 'jar' ]; then \ + $(UNZIP) -d $(FINAL_TARGET)/chrome/test $(FINAL_TARGET)/chrome/test.jar; \ + fi + @echo 'Comparing packages...' + @if ! diff -ur $(srcdir)/../ref-simple $(FINAL_TARGET)/chrome/test ; then\ + echo 'TEST-UNEXPECTED-FAIL | config/tests/ref-simple | different content in jar' ; \ + false; \ + fi diff --git a/config/tests/src-simple/jar.mn b/config/tests/src-simple/jar.mn new file mode 100644 index 000000000..12ed607b6 --- /dev/null +++ b/config/tests/src-simple/jar.mn @@ -0,0 +1,22 @@ +#filter substitution + +test.jar: +# test chrome with flags and path expansion +% content test %one +# test locale with variable substitution and path expansion +% locale @AB_CD@ %three +# test overlays +% overlay chrome://one/file.xml chrome://two/otherfile.xml +# test regular file, preprocessed file, preprocessed css + one/file.xml (thesrcdir/file.xml) +* one/preproc (thesrcdir/preproc.in) +* one/some.css (thesrcdir/some.css) +# test reference against topsrcdir + two/otherfile.xml (/config/tests/src-simple/thetopsrcdir/otherfile.xml) +# test reference against localesrcdir + three/l10nfile.txt (%l10nfile.txt) + +test.jar: +# test manifest update the locale one was already added above, add skin +% locale @AB_CD@ %three +% skin test classic %one diff --git a/config/tests/src-simple/l10n/l10nfile.txt b/config/tests/src-simple/l10n/l10nfile.txt new file mode 100644 index 000000000..7ce7909ab --- /dev/null +++ b/config/tests/src-simple/l10n/l10nfile.txt @@ -0,0 +1 @@ +localized content diff --git a/config/tests/src-simple/moz.build b/config/tests/src-simple/moz.build new file mode 100644 index 000000000..eb4454d28 --- /dev/null +++ b/config/tests/src-simple/moz.build @@ -0,0 +1,7 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file diff --git a/config/tests/src-simple/thesrcdir/file.xml b/config/tests/src-simple/thesrcdir/file.xml new file mode 100644 index 000000000..21aacb9bd --- /dev/null +++ b/config/tests/src-simple/thesrcdir/file.xml @@ -0,0 +1 @@ +<?xml version="1.0"><doc/> diff --git a/config/tests/src-simple/thesrcdir/preproc.in b/config/tests/src-simple/thesrcdir/preproc.in new file mode 100644 index 000000000..16d502418 --- /dev/null +++ b/config/tests/src-simple/thesrcdir/preproc.in @@ -0,0 +1,6 @@ +# This would be an processed out +# pretty lengthy +# license header. +# For example. + +#expand This is __AB_CD__. diff --git a/config/tests/src-simple/thesrcdir/some.css b/config/tests/src-simple/thesrcdir/some.css new file mode 100644 index 000000000..36171b4bb --- /dev/null +++ b/config/tests/src-simple/thesrcdir/some.css @@ -0,0 +1,6 @@ +#div: { +/* this is a ID rule, and should stay intact */ +} +%expand [lang=__AB_CD__] { +/* this selector should match content with lang="ab-X-stuff" */ +} diff --git a/config/tests/src-simple/thetopsrcdir/otherfile.xml b/config/tests/src-simple/thetopsrcdir/otherfile.xml new file mode 100644 index 000000000..6c50abf6f --- /dev/null +++ b/config/tests/src-simple/thetopsrcdir/otherfile.xml @@ -0,0 +1 @@ +<?xml version="1.0"><otherdoc/> diff --git a/config/tests/test.manifest.flat b/config/tests/test.manifest.flat new file mode 100644 index 000000000..0cf9dbf3a --- /dev/null +++ b/config/tests/test.manifest.flat @@ -0,0 +1,4 @@ +content test test/one +locale ab-X-stuff test/three +overlay chrome://one/file.xml chrome://two/otherfile.xml +skin test classic test/one diff --git a/config/tests/test.manifest.jar b/config/tests/test.manifest.jar new file mode 100644 index 000000000..e2700dfbe --- /dev/null +++ b/config/tests/test.manifest.jar @@ -0,0 +1,4 @@ +content test jar:test.jar!/one +locale ab-X-stuff jar:test.jar!/three +overlay chrome://one/file.xml chrome://two/otherfile.xml +skin test classic jar:test.jar!/one diff --git a/config/tests/test.manifest.symlink b/config/tests/test.manifest.symlink new file mode 100644 index 000000000..0cf9dbf3a --- /dev/null +++ b/config/tests/test.manifest.symlink @@ -0,0 +1,4 @@ +content test test/one +locale ab-X-stuff test/three +overlay chrome://one/file.xml chrome://two/otherfile.xml +skin test classic test/one diff --git a/config/tests/test_mozbuild_reading.py b/config/tests/test_mozbuild_reading.py new file mode 100644 index 000000000..8bfcd9f4e --- /dev/null +++ b/config/tests/test_mozbuild_reading.py @@ -0,0 +1,116 @@ +# 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 unicode_literals + +import os +import sys +import unittest + +from mozunit import main + +from mozbuild.base import MozbuildObject +from mozpack.files import FileFinder +from mozbuild.frontend.context import Files +from mozbuild.frontend.reader import ( + BuildReader, + EmptyConfig, +) + + +class TestMozbuildReading(unittest.TestCase): + # This hack is needed to appease running in automation. + def setUp(self): + self._old_env = dict(os.environ) + os.environ.pop('MOZCONFIG', None) + os.environ.pop('MOZ_OBJDIR', None) + + def tearDown(self): + os.environ.clear() + os.environ.update(self._old_env) + + def _mozbuilds(self, reader): + if not hasattr(self, '_mozbuild_paths'): + self._mozbuild_paths = set(reader.all_mozbuild_paths()) + + return self._mozbuild_paths + + @unittest.skip('failing in SpiderMonkey builds') + def test_filesystem_traversal_reading(self): + """Reading moz.build according to filesystem traversal works. + + We attempt to read every known moz.build file via filesystem traversal. + + If this test fails, it means that metadata extraction will fail. + """ + mb = MozbuildObject.from_environment(detect_virtualenv_mozinfo=False) + config = mb.config_environment + reader = BuildReader(config) + all_paths = self._mozbuilds(reader) + paths, contexts = reader.read_relevant_mozbuilds(all_paths) + self.assertEqual(set(paths), all_paths) + self.assertGreaterEqual(len(contexts), len(paths)) + + def test_filesystem_traversal_no_config(self): + """Reading moz.build files via filesystem traversal mode with no build config. + + This is similar to the above test except no build config is applied. + This will likely fail in more scenarios than the above test because a + lot of moz.build files assumes certain variables are present. + """ + here = os.path.abspath(os.path.dirname(__file__)) + root = os.path.normpath(os.path.join(here, '..', '..')) + config = EmptyConfig(root) + reader = BuildReader(config) + all_paths = self._mozbuilds(reader) + paths, contexts = reader.read_relevant_mozbuilds(all_paths) + self.assertEqual(set(paths.keys()), all_paths) + self.assertGreaterEqual(len(contexts), len(paths)) + + + def test_orphan_file_patterns(self): + if sys.platform == 'win32': + raise unittest.SkipTest('failing on windows builds') + + mb = MozbuildObject.from_environment(detect_virtualenv_mozinfo=False) + + try: + config = mb.config_environment + except Exception as e: + if e.message == 'config.status not available. Run configure.': + raise unittest.SkipTest('failing without config.status') + raise + + if config.substs['MOZ_BUILD_APP'] == 'js': + raise unittest.SkipTest('failing in Spidermonkey builds') + + reader = BuildReader(config) + all_paths = self._mozbuilds(reader) + _, contexts = reader.read_relevant_mozbuilds(all_paths) + + finder = FileFinder(config.topsrcdir, find_executables=False, + ignore=['obj*']) + + def pattern_exists(pat): + return [p for p in finder.find(pat)] != [] + + for ctx in contexts: + if not isinstance(ctx, Files): + continue + relsrcdir = ctx.relsrcdir + if not pattern_exists(os.path.join(relsrcdir, ctx.pattern)): + self.fail("The pattern '%s' in a Files() entry in " + "'%s' corresponds to no files in the tree.\n" + "Please update this entry." % + (ctx.pattern, ctx.main_path)) + test_files = ctx['IMPACTED_TESTS'].files + for p in test_files: + if not pattern_exists(os.path.relpath(p.full_path, config.topsrcdir)): + self.fail("The pattern '%s' in a dependent tests entry " + "in '%s' corresponds to no files in the tree.\n" + "Please update this entry." % + (p, ctx.main_path)) + +if __name__ == '__main__': + main() diff --git a/config/tests/unit-expandlibs.py b/config/tests/unit-expandlibs.py new file mode 100644 index 000000000..3c7e5a44d --- /dev/null +++ b/config/tests/unit-expandlibs.py @@ -0,0 +1,431 @@ +import subprocess +import unittest +import sys +import os +import imp +from tempfile import mkdtemp +from shutil import rmtree +import mozunit + +from UserString import UserString +# Create a controlled configuration for use by expandlibs +config_win = { + 'AR': 'lib', + 'AR_EXTRACT': '', + 'DLL_PREFIX': '', + 'LIB_PREFIX': '', + 'OBJ_SUFFIX': '.obj', + 'LIB_SUFFIX': '.lib', + 'DLL_SUFFIX': '.dll', + 'IMPORT_LIB_SUFFIX': '.lib', + 'LIBS_DESC_SUFFIX': '.desc', + 'EXPAND_LIBS_LIST_STYLE': 'list', +} +config_unix = { + 'AR': 'ar', + 'AR_EXTRACT': 'ar -x', + 'DLL_PREFIX': 'lib', + 'LIB_PREFIX': 'lib', + 'OBJ_SUFFIX': '.o', + 'LIB_SUFFIX': '.a', + 'DLL_SUFFIX': '.so', + 'IMPORT_LIB_SUFFIX': '', + 'LIBS_DESC_SUFFIX': '.desc', + 'EXPAND_LIBS_LIST_STYLE': 'linkerscript', +} + +config = sys.modules['expandlibs_config'] = imp.new_module('expandlibs_config') + +from expandlibs import LibDescriptor, ExpandArgs, relativize +from expandlibs_gen import generate +from expandlibs_exec import ExpandArgsMore, SectionFinder + +def Lib(name): + return config.LIB_PREFIX + name + config.LIB_SUFFIX + +def Obj(name): + return name + config.OBJ_SUFFIX + +def Dll(name): + return config.DLL_PREFIX + name + config.DLL_SUFFIX + +def ImportLib(name): + if not len(config.IMPORT_LIB_SUFFIX): return Dll(name) + return config.LIB_PREFIX + name + config.IMPORT_LIB_SUFFIX + +class TestRelativize(unittest.TestCase): + def test_relativize(self): + '''Test relativize()''' + os_path_exists = os.path.exists + def exists(path): + return True + os.path.exists = exists + self.assertEqual(relativize(os.path.abspath(os.curdir)), os.curdir) + self.assertEqual(relativize(os.path.abspath(os.pardir)), os.pardir) + self.assertEqual(relativize(os.path.join(os.curdir, 'a')), 'a') + self.assertEqual(relativize(os.path.join(os.path.abspath(os.curdir), 'a')), 'a') + # relativize is expected to return the absolute path if it is shorter + self.assertEqual(relativize(os.sep), os.sep) + os.path.exists = os.path.exists + +class TestLibDescriptor(unittest.TestCase): + def test_serialize(self): + '''Test LibDescriptor's serialization''' + desc = LibDescriptor() + desc[LibDescriptor.KEYS[0]] = ['a', 'b'] + self.assertEqual(str(desc), "{0} = a b".format(LibDescriptor.KEYS[0])) + desc['unsupported-key'] = ['a'] + self.assertEqual(str(desc), "{0} = a b".format(LibDescriptor.KEYS[0])) + desc[LibDescriptor.KEYS[1]] = ['c', 'd', 'e'] + self.assertEqual(str(desc), + "{0} = a b\n{1} = c d e" + .format(LibDescriptor.KEYS[0], LibDescriptor.KEYS[1])) + desc[LibDescriptor.KEYS[0]] = [] + self.assertEqual(str(desc), "{0} = c d e".format(LibDescriptor.KEYS[1])) + + def test_read(self): + '''Test LibDescriptor's initialization''' + desc_list = ["# Comment", + "{0} = a b".format(LibDescriptor.KEYS[1]), + "", # Empty line + "foo = bar", # Should be discarded + "{0} = c d e".format(LibDescriptor.KEYS[0])] + desc = LibDescriptor(desc_list) + self.assertEqual(desc[LibDescriptor.KEYS[1]], ['a', 'b']) + self.assertEqual(desc[LibDescriptor.KEYS[0]], ['c', 'd', 'e']) + self.assertEqual(False, 'foo' in desc) + +def wrap_method(conf, wrapped_method): + '''Wrapper used to call a test with a specific configuration''' + def _method(self): + for key in conf: + setattr(config, key, conf[key]) + self.init() + try: + wrapped_method(self) + except: + raise + finally: + self.cleanup() + return _method + +class ReplicateTests(type): + '''Replicates tests for unix and windows variants''' + def __new__(cls, clsName, bases, dict): + for name in [key for key in dict if key.startswith('test_')]: + dict[name + '_unix'] = wrap_method(config_unix, dict[name]) + dict[name + '_unix'].__doc__ = dict[name].__doc__ + ' (unix)' + dict[name + '_win'] = wrap_method(config_win, dict[name]) + dict[name + '_win'].__doc__ = dict[name].__doc__ + ' (win)' + del dict[name] + return type.__new__(cls, clsName, bases, dict) + +class TestCaseWithTmpDir(unittest.TestCase): + __metaclass__ = ReplicateTests + def init(self): + self.tmpdir = os.path.abspath(mkdtemp(dir=os.curdir)) + + def cleanup(self): + rmtree(self.tmpdir) + + def touch(self, files): + for f in files: + open(f, 'w').close() + + def tmpfile(self, *args): + return os.path.join(self.tmpdir, *args) + +class TestExpandLibsGen(TestCaseWithTmpDir): + def test_generate(self): + '''Test library descriptor generation''' + files = [self.tmpfile(f) for f in + [Lib('a'), Obj('b'), Lib('c'), Obj('d'), Obj('e'), Lib('f')]] + self.touch(files[:-1]) + self.touch([files[-1] + config.LIBS_DESC_SUFFIX]) + + desc = generate(files) + self.assertEqual(desc['OBJS'], [self.tmpfile(Obj(s)) for s in ['b', 'd', 'e']]) + self.assertEqual(desc['LIBS'], [self.tmpfile(Lib(s)) for s in ['a', 'c', 'f']]) + + self.assertRaises(Exception, generate, files + [self.tmpfile(Obj('z'))]) + self.assertRaises(Exception, generate, files + [self.tmpfile(Lib('y'))]) + +class TestExpandInit(TestCaseWithTmpDir): + def init(self): + ''' Initializes test environment for library expansion tests''' + super(TestExpandInit, self).init() + # Create 2 fake libraries, each containing 3 objects, and the second + # including the first one and another library. + os.mkdir(self.tmpfile('libx')) + os.mkdir(self.tmpfile('liby')) + self.libx_files = [self.tmpfile('libx', Obj(f)) for f in ['g', 'h', 'i']] + self.liby_files = [self.tmpfile('liby', Obj(f)) for f in ['j', 'k', 'l']] + [self.tmpfile('liby', Lib('z'))] + self.touch(self.libx_files + self.liby_files) + with open(self.tmpfile('libx', Lib('x') + config.LIBS_DESC_SUFFIX), 'w') as f: + f.write(str(generate(self.libx_files))) + with open(self.tmpfile('liby', Lib('y') + config.LIBS_DESC_SUFFIX), 'w') as f: + f.write(str(generate(self.liby_files + [self.tmpfile('libx', Lib('x'))]))) + + # Create various objects and libraries + self.arg_files = [self.tmpfile(f) for f in [Lib('a'), Obj('b'), Obj('c'), Lib('d'), Obj('e')]] + # We always give library names (LIB_PREFIX/SUFFIX), even for + # dynamic/import libraries + self.files = self.arg_files + [self.tmpfile(ImportLib('f'))] + self.arg_files += [self.tmpfile(Lib('f'))] + self.touch(self.files) + + def assertRelEqual(self, args1, args2): + self.assertEqual(args1, [relativize(a) for a in args2]) + +class TestExpandArgs(TestExpandInit): + def test_expand(self): + '''Test library expansion''' + # Expanding arguments means libraries with a descriptor are expanded + # with the descriptor content, and import libraries are used when + # a library doesn't exist + args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) + self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files) + + # When a library exists at the same time as a descriptor, we still use + # the descriptor. + self.touch([self.tmpfile('libx', Lib('x'))]) + args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) + self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files) + + self.touch([self.tmpfile('liby', Lib('y'))]) + args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) + self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files) + +class TestExpandArgsMore(TestExpandInit): + def test_makelist(self): + '''Test grouping object files in lists''' + # ExpandArgsMore does the same as ExpandArgs + with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args: + self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files) + + # But also has an extra method replacing object files with a list + args.makelist() + # self.files has objects at #1, #2, #4 + self.assertRelEqual(args[:3], ['foo', '-bar'] + self.files[:1]) + self.assertRelEqual(args[4:], [self.files[3]] + self.files[5:] + [self.tmpfile('liby', Lib('z'))]) + + # Check the list file content + objs = [f for f in self.files + self.liby_files + self.libx_files if f.endswith(config.OBJ_SUFFIX)] + if config.EXPAND_LIBS_LIST_STYLE == "linkerscript": + self.assertNotEqual(args[3][0], '@') + filename = args[3] + content = ['INPUT("{0}")'.format(relativize(f)) for f in objs] + with open(filename, 'r') as f: + self.assertEqual([l.strip() for l in f.readlines() if len(l.strip())], content) + elif config.EXPAND_LIBS_LIST_STYLE == "list": + self.assertEqual(args[3][0], '@') + filename = args[3][1:] + content = objs + with open(filename, 'r') as f: + self.assertRelEqual([l.strip() for l in f.readlines() if len(l.strip())], content) + + tmp = args.tmp + # Check that all temporary files are properly removed + self.assertEqual(True, all([not os.path.exists(f) for f in tmp])) + + def test_extract(self): + '''Test library extraction''' + # Divert subprocess.call + subprocess_call = subprocess.call + subprocess_check_output = subprocess.check_output + def call(args, **kargs): + if config.AR == 'lib': + self.assertEqual(args[:2], [config.AR, '-NOLOGO']) + self.assertTrue(args[2].startswith('-EXTRACT:')) + extract = [args[2][len('-EXTRACT:'):]] + self.assertTrue(extract) + args = args[3:] + else: + # The command called is always AR_EXTRACT + ar_extract = config.AR_EXTRACT.split() + self.assertEqual(args[:len(ar_extract)], ar_extract) + args = args[len(ar_extract):] + # Remaining argument is always one library + self.assertEqual(len(args), 1) + arg = args[0] + self.assertEqual(os.path.splitext(arg)[1], config.LIB_SUFFIX) + # Simulate file extraction + lib = os.path.splitext(os.path.basename(arg))[0] + if config.AR != 'lib': + extract = [lib, lib + '2'] + extract = [os.path.join(kargs['cwd'], f) for f in extract] + if config.AR != 'lib': + extract = [Obj(f) for f in extract] + if not lib in extracted: + extracted[lib] = [] + extracted[lib].extend(extract) + self.touch(extract) + subprocess.call = call + + def check_output(args, **kargs): + # The command called is always AR + ar = config.AR + self.assertEqual(args[0:3], [ar, '-NOLOGO', '-LIST']) + # Remaining argument is always one library + self.assertRelEqual([os.path.splitext(arg)[1] for arg in args[3:]], +[config.LIB_SUFFIX]) + # Simulate LIB -NOLOGO -LIST + lib = os.path.splitext(os.path.basename(args[3]))[0] + return '%s\n%s\n' % (Obj(lib), Obj(lib + '2')) + subprocess.check_output = check_output + + # ExpandArgsMore does the same as ExpandArgs + self.touch([self.tmpfile('liby', Lib('y'))]) + for iteration in (1, 2): + with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args: + files = self.files + self.liby_files + self.libx_files + + self.assertRelEqual(args, ['foo', '-bar'] + files) + + extracted = {} + # ExpandArgsMore also has an extra method extracting static libraries + # when possible + args.extract() + + # With AR_EXTRACT, it uses the descriptors when there are, and + # actually + # extracts the remaining libraries + extracted_args = [] + for f in files: + if f.endswith(config.LIB_SUFFIX): + base = os.path.splitext(os.path.basename(f))[0] + # On the first iteration, we test the behavior of + # extracting archives that don't have a copy of their + # contents next to them, which is to use the file + # extracted from the archive in a temporary directory. + # On the second iteration, we test extracting archives + # that do have a copy of their contents next to them, + # in which case those contents are used instead of the + # temporarily extracted files. + if iteration == 1: + extracted_args.extend(sorted(extracted[base])) + else: + dirname = os.path.dirname(f[len(self.tmpdir)+1:]) + if base.endswith('f'): + dirname = os.path.join(dirname, 'foo', 'bar') + extracted_args.extend([self.tmpfile(dirname, Obj(base)), self.tmpfile(dirname, Obj(base + '2'))]) + else: + extracted_args.append(f) + self.assertRelEqual(args, ['foo', '-bar'] + extracted_args) + + tmp = args.tmp + # Check that all temporary files are properly removed + self.assertEqual(True, all([not os.path.exists(f) for f in tmp])) + + # Create archives contents next to them for the second iteration. + base = os.path.splitext(Lib('_'))[0] + self.touch(self.tmpfile(Obj(base.replace('_', suffix))) for suffix in ('a', 'a2', 'd', 'd2')) + try: + os.makedirs(self.tmpfile('foo', 'bar')) + except: + pass + self.touch(self.tmpfile('foo', 'bar', Obj(base.replace('_', suffix))) for suffix in ('f', 'f2')) + self.touch(self.tmpfile('liby', Obj(base.replace('_', suffix))) for suffix in ('z', 'z2')) + + # Restore subprocess.call and subprocess.check_output + subprocess.call = subprocess_call + subprocess.check_output = subprocess_check_output + +class FakeProcess(object): + def __init__(self, out, err = ''): + self.out = out + self.err = err + + def communicate(self): + return (self.out, self.err) + +OBJDUMPS = { +'foo.o': ''' +00000000 g F .text\t00000001 foo +00000000 g F .text._Z6foobarv\t00000001 _Z6foobarv +00000000 g F .text.hello\t00000001 hello +00000000 g F .text._ZThn4_6foobarv\t00000001 _ZThn4_6foobarv +''', +'bar.o': ''' +00000000 g F .text.hi\t00000001 hi +00000000 g F .text.hot._Z6barbazv\t00000001 .hidden _Z6barbazv +''', +} + +PRINT_ICF = ''' +ld: ICF folding section '.text.hello' in file 'foo.o'into '.text.hi' in file 'bar.o' +ld: ICF folding section '.foo' in file 'foo.o'into '.foo' in file 'bar.o' +''' + +class SubprocessPopen(object): + def __init__(self, test): + self.test = test + + def __call__(self, args, stdout = None, stderr = None): + self.test.assertEqual(stdout, subprocess.PIPE) + self.test.assertEqual(stderr, subprocess.PIPE) + if args[0] == 'objdump': + self.test.assertEqual(args[1], '-t') + self.test.assertTrue(args[2] in OBJDUMPS) + return FakeProcess(OBJDUMPS[args[2]]) + else: + return FakeProcess('', PRINT_ICF) + +class TestSectionFinder(unittest.TestCase): + def test_getSections(self): + '''Test SectionFinder''' + # Divert subprocess.Popen + subprocess_popen = subprocess.Popen + subprocess.Popen = SubprocessPopen(self) + config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript' + config.OBJ_SUFFIX = '.o' + config.LIB_SUFFIX = '.a' + finder = SectionFinder(['foo.o', 'bar.o']) + self.assertEqual(finder.getSections('foobar'), []) + self.assertEqual(finder.getSections('_Z6barbazv'), ['.text.hot._Z6barbazv']) + self.assertEqual(finder.getSections('_Z6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv']) + self.assertEqual(finder.getSections('_ZThn4_6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv']) + subprocess.Popen = subprocess_popen + +class TestSymbolOrder(unittest.TestCase): + def test_getOrderedSections(self): + '''Test ExpandMoreArgs' _getOrderedSections''' + # Divert subprocess.Popen + subprocess_popen = subprocess.Popen + subprocess.Popen = SubprocessPopen(self) + config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript' + config.OBJ_SUFFIX = '.o' + config.LIB_SUFFIX = '.a' + config.LD_PRINT_ICF_SECTIONS = '' + args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o']) + self.assertEqual(args._getOrderedSections(['_Z6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv']) + self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv']) + subprocess.Popen = subprocess_popen + + def test_getFoldedSections(self): + '''Test ExpandMoreArgs' _getFoldedSections''' + # Divert subprocess.Popen + subprocess_popen = subprocess.Popen + subprocess.Popen = SubprocessPopen(self) + config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections' + args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o']) + self.assertEqual(args._getFoldedSections(), {'.text.hello': ['.text.hi'], '.text.hi': ['.text.hello']}) + subprocess.Popen = subprocess_popen + + def test_getOrderedSectionsWithICF(self): + '''Test ExpandMoreArgs' _getOrderedSections, with ICF''' + # Divert subprocess.Popen + subprocess_popen = subprocess.Popen + subprocess.Popen = SubprocessPopen(self) + config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript' + config.OBJ_SUFFIX = '.o' + config.LIB_SUFFIX = '.a' + config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections' + args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o']) + self.assertEqual(args._getOrderedSections(['hello', '_Z6barbazv']), ['.text.hello', '.text.hi', '.text.hot._Z6barbazv']) + self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', 'hi', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hi', '.text.hello', '.text.hot._Z6barbazv']) + subprocess.Popen = subprocess_popen + + +if __name__ == '__main__': + mozunit.main() diff --git a/config/tests/unit-mozunit.py b/config/tests/unit-mozunit.py new file mode 100644 index 000000000..69798f4ba --- /dev/null +++ b/config/tests/unit-mozunit.py @@ -0,0 +1,86 @@ +# 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/. + +import sys +import os +from mozunit import main, MockedOpen +import unittest +from tempfile import mkstemp + +class TestMozUnit(unittest.TestCase): + def test_mocked_open(self): + # Create a temporary file on the file system. + (fd, path) = mkstemp() + with os.fdopen(fd, 'w') as file: + file.write('foobar'); + + self.assertFalse(os.path.exists('file1')) + self.assertFalse(os.path.exists('file2')) + + with MockedOpen({'file1': 'content1', + 'file2': 'content2'}): + self.assertTrue(os.path.exists('file1')) + self.assertTrue(os.path.exists('file2')) + self.assertFalse(os.path.exists('foo/file1')) + + # Check the contents of the files given at MockedOpen creation. + self.assertEqual(open('file1', 'r').read(), 'content1') + self.assertEqual(open('file2', 'r').read(), 'content2') + + # Check that overwriting these files alters their content. + with open('file1', 'w') as file: + file.write('foo') + self.assertTrue(os.path.exists('file1')) + self.assertEqual(open('file1', 'r').read(), 'foo') + + # ... but not until the file is closed. + file = open('file2', 'w') + file.write('bar') + self.assertEqual(open('file2', 'r').read(), 'content2') + file.close() + self.assertEqual(open('file2', 'r').read(), 'bar') + + # Check that appending to a file does append + with open('file1', 'a') as file: + file.write('bar') + self.assertEqual(open('file1', 'r').read(), 'foobar') + + self.assertFalse(os.path.exists('file3')) + + # Opening a non-existing file ought to fail. + self.assertRaises(IOError, open, 'file3', 'r') + self.assertFalse(os.path.exists('file3')) + + # Check that writing a new file does create the file. + with open('file3', 'w') as file: + file.write('baz') + self.assertEqual(open('file3', 'r').read(), 'baz') + self.assertTrue(os.path.exists('file3')) + + # Check the content of the file created outside MockedOpen. + self.assertEqual(open(path, 'r').read(), 'foobar') + + # Check that overwriting a file existing on the file system + # does modify its content. + with open(path, 'w') as file: + file.write('bazqux') + self.assertEqual(open(path, 'r').read(), 'bazqux') + + with MockedOpen(): + # Check that appending to a file existing on the file system + # does modify its content. + with open(path, 'a') as file: + file.write('bazqux') + self.assertEqual(open(path, 'r').read(), 'foobarbazqux') + + # Check that the file was not actually modified on the file system. + self.assertEqual(open(path, 'r').read(), 'foobar') + os.remove(path) + + # Check that the file created inside MockedOpen wasn't actually + # created. + self.assertRaises(IOError, open, 'file3', 'r') + +if __name__ == "__main__": + main() diff --git a/config/tests/unit-nsinstall.py b/config/tests/unit-nsinstall.py new file mode 100644 index 000000000..2a230841a --- /dev/null +++ b/config/tests/unit-nsinstall.py @@ -0,0 +1,174 @@ +import unittest + +import os, sys, os.path, time +from tempfile import mkdtemp +from shutil import rmtree +import mozunit +from mozprocess import processhandler + +from nsinstall import nsinstall +import nsinstall as nsinstall_module +NSINSTALL_PATH = nsinstall_module.__file__ + +# Run the non-ASCII tests on (a) Windows, or (b) any platform with +# sys.stdin.encoding set to UTF-8 +import codecs +RUN_NON_ASCII_TESTS = (sys.platform == "win32" or + (sys.stdin.encoding is not None and + codecs.lookup(sys.stdin.encoding) == codecs.lookup("utf-8"))) + +class TestNsinstall(unittest.TestCase): + """ + Unit tests for nsinstall.py + """ + def setUp(self): + self.tmpdir = mkdtemp() + + def tearDown(self): + # Unicode strings means non-ASCII children can be deleted properly on + # Windows + if sys.stdin.encoding is None: + tmpdir = unicode(self.tmpdir) + else: + tmpdir = unicode(self.tmpdir, sys.stdin.encoding) + rmtree(tmpdir) + + # utility methods for tests + def touch(self, file, dir=None): + if dir is None: + dir = self.tmpdir + f = os.path.join(dir, file) + open(f, 'w').close() + return f + + def mkdirs(self, dir): + d = os.path.join(self.tmpdir, dir) + os.makedirs(d) + return d + + def test_nsinstall_D(self): + "Test nsinstall -D <dir>" + testdir = os.path.join(self.tmpdir, "test") + self.assertEqual(nsinstall(["-D", testdir]), 0) + self.assert_(os.path.isdir(testdir)) + + def test_nsinstall_basic(self): + "Test nsinstall <file> <dir>" + testfile = self.touch("testfile") + testdir = self.mkdirs("testdir") + self.assertEqual(nsinstall([testfile, testdir]), 0) + self.assert_(os.path.isfile(os.path.join(testdir, "testfile"))) + + def test_nsinstall_basic_recursive(self): + "Test nsinstall <dir> <dest dir>" + sourcedir = self.mkdirs("sourcedir") + self.touch("testfile", sourcedir) + Xfile = self.touch("Xfile", sourcedir) + copieddir = self.mkdirs("sourcedir/copieddir") + self.touch("testfile2", copieddir) + Xdir = self.mkdirs("sourcedir/Xdir") + self.touch("testfile3", Xdir) + + destdir = self.mkdirs("destdir") + + self.assertEqual(nsinstall([sourcedir, destdir, + '-X', Xfile, + '-X', Xdir]), 0) + + testdir = os.path.join(destdir, "sourcedir") + self.assert_(os.path.isdir(testdir)) + self.assert_(os.path.isfile(os.path.join(testdir, "testfile"))) + self.assert_(not os.path.exists(os.path.join(testdir, "Xfile"))) + self.assert_(os.path.isdir(os.path.join(testdir, "copieddir"))) + self.assert_(os.path.isfile(os.path.join(testdir, "copieddir", "testfile2"))) + self.assert_(not os.path.exists(os.path.join(testdir, "Xdir"))) + + def test_nsinstall_multiple(self): + "Test nsinstall <three files> <dest dir>" + testfiles = [self.touch("testfile1"), + self.touch("testfile2"), + self.touch("testfile3")] + testdir = self.mkdirs("testdir") + self.assertEqual(nsinstall(testfiles + [testdir]), 0) + for f in testfiles: + self.assert_(os.path.isfile(os.path.join(testdir, + os.path.basename(f)))) + + def test_nsinstall_dir_exists(self): + "Test nsinstall <dir> <dest dir>, where <dest dir>/<dir> already exists" + srcdir = self.mkdirs("test") + destdir = self.mkdirs("testdir/test") + self.assertEqual(nsinstall([srcdir, os.path.dirname(destdir)]), 0) + self.assert_(os.path.isdir(destdir)) + + def test_nsinstall_t(self): + "Test that nsinstall -t works (preserve timestamp)" + testfile = self.touch("testfile") + testdir = self.mkdirs("testdir") + # set mtime to now - 30 seconds + t = int(time.time()) - 30 + os.utime(testfile, (t, t)) + self.assertEqual(nsinstall(["-t", testfile, testdir]), 0) + destfile = os.path.join(testdir, "testfile") + self.assert_(os.path.isfile(destfile)) + self.assertEqual(os.stat(testfile).st_mtime, + os.stat(destfile).st_mtime) + + if sys.platform != "win32": + # can't run this test on windows, don't have real file modes there + def test_nsinstall_m(self): + "Test that nsinstall -m works (set mode)" + testfile = self.touch("testfile") + mode = 0o600 + os.chmod(testfile, mode) + testdir = self.mkdirs("testdir") + self.assertEqual(nsinstall(["-m", "{0:04o}" + .format(mode), testfile, testdir]), 0) + destfile = os.path.join(testdir, "testfile") + self.assert_(os.path.isfile(destfile)) + self.assertEqual(os.stat(testfile).st_mode, + os.stat(destfile).st_mode) + + def test_nsinstall_d(self): + "Test that nsinstall -d works (create directories in target)" + # -d makes no sense to me, but ok! + testfile = self.touch("testfile") + testdir = self.mkdirs("testdir") + destdir = os.path.join(testdir, "subdir") + self.assertEqual(nsinstall(["-d", testfile, destdir]), 0) + self.assert_(os.path.isdir(os.path.join(destdir, "testfile"))) + + if RUN_NON_ASCII_TESTS: + def test_nsinstall_non_ascii(self): + "Test that nsinstall handles non-ASCII files" + filename = u"\u2325\u3452\u2415\u5081" + testfile = self.touch(filename) + testdir = self.mkdirs(u"\u4241\u1D04\u1414") + self.assertEqual(nsinstall([testfile.encode("utf-8"), + testdir.encode("utf-8")]), 0) + + destfile = os.path.join(testdir, filename) + self.assert_(os.path.isfile(destfile)) + + def test_nsinstall_non_ascii_subprocess(self): + "Test that nsinstall as a subprocess handles non-ASCII files" + filename = u"\u2325\u3452\u2415\u5081" + testfile = self.touch(filename) + testdir = self.mkdirs(u"\u4241\u1D04\u1414") + # We don't use subprocess because it can't handle Unicode on + # Windows <http://bugs.python.org/issue1759845>. mozprocess calls + # CreateProcessW directly so it's perfect. + p = processhandler.ProcessHandlerMixin([sys.executable, + NSINSTALL_PATH, + testfile, testdir]) + p.run() + rv = p.waitForFinish() + + self.assertEqual(rv, 0) + destfile = os.path.join(testdir, filename) + self.assert_(os.path.isfile(destfile)) + + #TODO: implement -R, -l, -L and test them! + +if __name__ == '__main__': + mozunit.main() diff --git a/config/tests/unit-printprereleasesuffix.py b/config/tests/unit-printprereleasesuffix.py new file mode 100644 index 000000000..4cf8d2f0e --- /dev/null +++ b/config/tests/unit-printprereleasesuffix.py @@ -0,0 +1,80 @@ +import unittest + +import sys +import os.path +import mozunit + +from printprereleasesuffix import get_prerelease_suffix + +class TestGetPreReleaseSuffix(unittest.TestCase): + """ + Unit tests for the get_prerelease_suffix function + """ + + def test_alpha_1(self): + """test 1a1 version string""" + self.c = get_prerelease_suffix('1a1') + self.assertEqual(self.c, ' 1 Alpha 1') + + def test_alpha_10(self): + """test 1.2a10 version string""" + self.c = get_prerelease_suffix('1.2a10') + self.assertEqual(self.c, ' 1.2 Alpha 10') + + def test_beta_3(self): + """test 1.2.3b3 version string""" + self.c = get_prerelease_suffix('1.2.3b3') + self.assertEqual(self.c, ' 1.2.3 Beta 3') + + def test_beta_30(self): + """test 1.2.3.4b30 version string""" + self.c = get_prerelease_suffix('1.2.3.4b30') + self.assertEqual(self.c, ' 1.2.3.4 Beta 30') + + def test_release_1(self): + """test 1.2.3.4 version string""" + self.c = get_prerelease_suffix('1.2.3.4') + self.assertEqual(self.c, '') + + def test_alpha_1_pre(self): + """test 1.2a1pre version string""" + self.c = get_prerelease_suffix('1.2a1pre') + self.assertEqual(self.c, '') + + def test_beta_10_pre(self): + """test 3.4b10pre version string""" + self.c = get_prerelease_suffix('3.4b10pre') + self.assertEqual(self.c, '') + + def test_pre_0(self): + """test 1.2pre0 version string""" + self.c = get_prerelease_suffix('1.2pre0') + self.assertEqual(self.c, '') + + def test_pre_1_b(self): + """test 1.2pre1b version string""" + self.c = get_prerelease_suffix('1.2pre1b') + self.assertEqual(self.c, '') + + def test_a_a(self): + """test 1.2aa version string""" + self.c = get_prerelease_suffix('1.2aa') + self.assertEqual(self.c, '') + + def test_b_b(self): + """test 1.2bb version string""" + self.c = get_prerelease_suffix('1.2bb') + self.assertEqual(self.c, '') + + def test_a_b(self): + """test 1.2ab version string""" + self.c = get_prerelease_suffix('1.2ab') + self.assertEqual(self.c, '') + + def test_plus(self): + """test 1.2+ version string """ + self.c = get_prerelease_suffix('1.2+') + self.assertEqual(self.c, '') + +if __name__ == '__main__': + mozunit.main() diff --git a/config/tests/unitMozZipFile.py b/config/tests/unitMozZipFile.py new file mode 100644 index 000000000..f9c0419ab --- /dev/null +++ b/config/tests/unitMozZipFile.py @@ -0,0 +1,201 @@ +# 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/. + +import unittest + +import shutil +import os +import re +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() |