summaryrefslogtreecommitdiffstats
path: root/config/tests
diff options
context:
space:
mode:
Diffstat (limited to 'config/tests')
-rw-r--r--config/tests/chrome.manifest.flat4
-rw-r--r--config/tests/makefiles/autodeps/Makefile.in36
-rw-r--r--config/tests/makefiles/autodeps/check_mkdir.tpy269
-rw-r--r--config/tests/makefiles/autodeps/moz.build6
-rw-r--r--config/tests/makefiles/autodeps/testor.tmpl64
-rw-r--r--config/tests/ref-simple/one/file.xml1
-rw-r--r--config/tests/ref-simple/one/preproc2
-rw-r--r--config/tests/ref-simple/one/some.css6
-rw-r--r--config/tests/ref-simple/three/l10nfile.txt1
-rw-r--r--config/tests/ref-simple/two/otherfile.xml1
-rw-r--r--config/tests/src-simple/Makefile.in38
-rw-r--r--config/tests/src-simple/jar.mn22
-rw-r--r--config/tests/src-simple/l10n/l10nfile.txt1
-rw-r--r--config/tests/src-simple/moz.build7
-rw-r--r--config/tests/src-simple/thesrcdir/file.xml1
-rw-r--r--config/tests/src-simple/thesrcdir/preproc.in6
-rw-r--r--config/tests/src-simple/thesrcdir/some.css6
-rw-r--r--config/tests/src-simple/thetopsrcdir/otherfile.xml1
-rw-r--r--config/tests/test.manifest.flat4
-rw-r--r--config/tests/test.manifest.jar4
-rw-r--r--config/tests/test.manifest.symlink4
-rw-r--r--config/tests/test_mozbuild_reading.py116
-rw-r--r--config/tests/unit-expandlibs.py431
-rw-r--r--config/tests/unit-mozunit.py86
-rw-r--r--config/tests/unit-nsinstall.py174
-rw-r--r--config/tests/unit-printprereleasesuffix.py80
-rw-r--r--config/tests/unitMozZipFile.py201
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()