author | Joel Maher <jmaher@mozilla.com> |
Fri, 20 May 2011 11:54:01 -0400 | |
changeset 70130 | 40195c0187d3069b848b012065476ab8868e6d0c |
parent 70129 | a365ca6c2379320357bf8ffa54006a916d02ed7d |
child 70131 | 9e2f8321f4800c3ff03f26bda29930afba94975a |
push id | 11 |
push user | ffxbld |
push date | Thu, 11 Aug 2011 21:43:38 +0000 |
treeherder | mozilla-release@cf0a29826586 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | ted, test-only |
bugs | 616999 |
milestone | 6.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
new file mode 100755 --- /dev/null +++ b/build/manifestparser.py @@ -0,0 +1,851 @@ +#!/usr/bin/env python + +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Mozilla.org. +# Portions created by the Initial Developer are Copyright (C) 2010 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Jeff Hammel <jhammel@mozilla.com> (Original author) +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +""" +Mozilla universal manifest parser +""" + +# this file lives at +# http://hg.mozilla.org/automation/ManifestDestiny/raw-file/tip/manifestparser.py + +__all__ = ['ManifestParser', 'TestManifest', 'convert'] + +import os +import shutil +import sys +from fnmatch import fnmatch +from optparse import OptionParser + +version = '0.3.1' # package version +try: + from setuptools import setup +except ImportError: + setup = None + +def normalize_path(path): + """normalize a relative path""" + if sys.platform.startswith('win'): + return path.replace('/', os.path.sep) + return path + +def read_ini(fp, variables=None, default='DEFAULT', + comments=';#', separators=('=', ':'), + strict=True): + """ + read an .ini file and return a list of [(section, values)] + - fp : file pointer or path to read + - variables : default set of variables + - default : name of the section for the default section + - comments : characters that if they start a line denote a comment + - separators : strings that denote key, value separation in order + - strict : whether to be strict about parsing + """ + + if variables is None: + variables = {} + + if isinstance(fp, basestring): + fp = file(fp) + + sections = [] + key = value = None + section_names = set([]) + + # read the lines + for line in fp.readlines(): + + stripped = line.strip() + + # ignore blank lines + if not stripped: + # reset key and value to avoid continuation lines + key = value = None + continue + + # ignore comment lines + if stripped[0] in comments: + continue + + # check for a new section + if len(stripped) > 2 and stripped[0] == '[' and stripped[-1] == ']': + section = stripped[1:-1].strip() + key = value = None + + # deal with DEFAULT section + if section.lower() == default.lower(): + if strict: + assert default not in section_names + section_names.add(default) + current_section = variables + continue + + if strict: + # make sure this section doesn't already exist + assert section not in section_names + + section_names.add(section) + current_section = {} + sections.append((section, current_section)) + continue + + # if there aren't any sections yet, something bad happen + if not section_names: + raise Exception('No sections yet :(') + + # (key, value) pair + for separator in separators: + if separator in stripped: + key, value = stripped.split(separator, 1) + key = key.strip() + value = value.strip() + + if strict: + # make sure this key isn't already in the section or empty + assert key + if current_section is not variables: + assert key not in current_section + + current_section[key] = value + break + else: + # continuation line ? + if line[0].isspace() and key: + value = '%s%s%s' % (value, os.linesep, stripped) + current_section[key] = value + else: + # something bad happen! + raise Exception("Not sure what you're trying to do") + + # interpret the variables + def interpret_variables(global_dict, local_dict): + variables = global_dict.copy() + variables.update(local_dict) + return variables + + sections = [(i, interpret_variables(variables, j)) for i, j in sections] + return sections + + +### objects for parsing manifests + +class ManifestParser(object): + """read .ini manifests""" + + ### methods for reading manifests + + def __init__(self, manifests=(), defaults=None, strict=True): + self._defaults = defaults or {} + self.tests = [] + self.strict = strict + self.rootdir = None + self.relativeRoot = None + if manifests: + self.read(*manifests) + + def getRelativeRoot(self): + return self.relativeRoot + + def read(self, *filenames, **defaults): + + # ensure all files exist + missing = [ filename for filename in filenames + if not os.path.exists(filename) ] + if missing: + raise IOError('Missing files: %s' % ', '.join(missing)) + + # process each file + for filename in filenames: + + # set the per file defaults + defaults = defaults.copy() or self._defaults.copy() + here = os.path.dirname(os.path.abspath(filename)) + self.relativeRoot = here + defaults['here'] = here + + if self.rootdir is None: + # set the root directory + # == the directory of the first manifest given + self.rootdir = here + + # read the configuration + sections = read_ini(fp=filename, variables=defaults, strict=self.strict) + + # get the tests + for section, data in sections: + + # a file to include + # TODO: keep track of included file structure: + # self.manifests = {'manifest.ini': 'relative/path.ini'} + if section.startswith('include:'): + include_file = section.split('include:', 1)[-1] + include_file = normalize_path(include_file) + if not os.path.isabs(include_file): + include_file = os.path.join(self.getRelativeRoot(), include_file) + if not os.path.exists(include_file): + if self.strict: + raise IOError("File '%s' does not exist" % include_file) + else: + continue + include_defaults = data.copy() + self.read(include_file, **include_defaults) + continue + + # otherwise an item + test = data + test['name'] = section + test['manifest'] = os.path.abspath(filename) + + # determine the path + path = test.get('path', section) + if '://' not in path: # don't futz with URLs + path = normalize_path(path) + if not os.path.isabs(path): + path = os.path.join(here, path) + test['path'] = path + + # append the item + self.tests.append(test) + + ### methods for querying manifests + + def query(self, *checks): + """ + general query function for tests + - checks : callable conditions to test if the test fulfills the query + """ + retval = [] + for test in self.tests: + for check in checks: + if not check(test): + break + else: + retval.append(test) + return retval + + def get(self, _key=None, inverse=False, tags=None, **kwargs): + # TODO: pass a dict instead of kwargs since you might hav + # e.g. 'inverse' as a key in the dict + + # TODO: tags should just be part of kwargs with None values + # (None == any is kinda weird, but probably still better) + + # fix up tags + if tags: + tags = set(tags) + else: + tags = set() + + # make some check functions + if inverse: + has_tags = lambda test: tags.isdisjoint(test.keys()) + def dict_query(test): + for key, value in kwargs.items(): + if test.get(key) == value: + return False + return True + else: + has_tags = lambda test: tags.issubset(test.keys()) + def dict_query(test): + for key, value in kwargs.items(): + if test.get(key) != value: + return False + return True + + # query the tests + tests = self.query(has_tags, dict_query) + + # if a key is given, return only a list of that key + # useful for keys like 'name' or 'path' + if _key: + return [test[_key] for test in tests] + + # return the tests + return tests + + def missing(self, tests=None): + """return list of tests that do not exist on the filesystem""" + if tests is None: + tests = self.tests + return [test for test in tests + if not os.path.exists(test['path'])] + + def manifests(self, tests=None): + """ + return manifests in order in which they appear in the tests + """ + if tests is None: + tests = self.tests + manifests = [] + for test in tests: + manifest = test.get('manifest') + if not manifest: + continue + if manifest not in manifests: + manifests.append(manifest) + return manifests + + ### methods for outputting from manifests + + def write(self, fp=sys.stdout, rootdir=None, + global_tags=None, global_kwargs=None, + local_tags=None, local_kwargs=None): + """ + write a manifest given a query + global and local options will be munged to do the query + globals will be written to the top of the file + locals (if given) will be written per test + """ + + # root directory + if rootdir is None: + rootdir = self.rootdir + + # sanitize input + global_tags = global_tags or set() + local_tags = local_tags or set() + global_kwargs = global_kwargs or {} + local_kwargs = local_kwargs or {} + + # create the query + tags = set([]) + tags.update(global_tags) + tags.update(local_tags) + kwargs = {} + kwargs.update(global_kwargs) + kwargs.update(local_kwargs) + + # get matching tests + tests = self.get(tags=tags, **kwargs) + + # print the .ini manifest + if global_tags or global_kwargs: + print >> fp, '[DEFAULT]' + for tag in global_tags: + print >> fp, '%s =' % tag + for key, value in global_kwargs.items(): + print >> fp, '%s = %s' % (key, value) + print >> fp + + for test in tests: + test = test.copy() # don't overwrite + + path = test['name'] + if not os.path.isabs(path): + path = os.path.relpath(test['path'], self.rootdir) + print >> fp, '[%s]' % path + + # reserved keywords: + reserved = ['path', 'name', 'here', 'manifest'] + for key in sorted(test.keys()): + if key in reserved: + continue + if key in global_kwargs: + continue + if key in global_tags and not test[key]: + continue + print >> fp, '%s = %s' % (key, test[key]) + print >> fp + + def copy(self, directory, rootdir=None, *tags, **kwargs): + """ + copy the manifests and associated tests + - directory : directory to copy to + - rootdir : root directory to copy to (if not given from manifests) + - tags : keywords the tests must have + - kwargs : key, values the tests must match + """ + # XXX note that copy does *not* filter the tests out of the + # resulting manifest; it just stupidly copies them over. + # ideally, it would reread the manifests and filter out the + # tests that don't match *tags and **kwargs + + # destination + if not os.path.exists(directory): + os.path.makedirs(directory) + else: + # sanity check + assert os.path.isdir(directory) + + # tests to copy + tests = self.get(tags=tags, **kwargs) + if not tests: + return # nothing to do! + + # root directory + if rootdir is None: + rootdir = self.rootdir + + # copy the manifests + tests + manifests = [os.path.relpath(manifest, rootdir) for manifest in self.manifests()] + for manifest in manifests: + destination = os.path.join(directory, manifest) + dirname = os.path.dirname(destination) + if not os.path.exists(dirname): + os.makedirs(dirname) + else: + # sanity check + assert os.path.isdir(dirname) + shutil.copy(os.path.join(rootdir, manifest), destination) + for test in tests: + if os.path.isabs(test['name']): + continue + source = test['path'] + if not os.path.exists(source): + print >> sys.stderr, "Missing test: '%s' does not exist!" % source + continue + # TODO: should err on strict + destination = os.path.join(directory, os.path.relpath(test['path'], rootdir)) + shutil.copy(source, destination) + # TODO: ensure that all of the tests are below the from_dir + + def update(self, from_dir, rootdir=None, *tags, **kwargs): + """ + update the tests as listed in a manifest from a directory + - from_dir : directory where the tests live + - rootdir : root directory to copy to (if not given from manifests) + - tags : keys the tests must have + - kwargs : key, values the tests must match + """ + + # get the tests + tests = self.get(tags=tags, **kwargs) + + # get the root directory + if not rootdir: + rootdir = self.rootdir + + # copy them! + for test in tests: + if not os.path.isabs(test['name']): + relpath = os.path.relpath(test['path'], rootdir) + source = os.path.join(from_dir, relpath) + if not os.path.exists(source): + # TODO err on strict + print >> sys.stderr, "Missing test: '%s'; skipping" % test['name'] + continue + destination = os.path.join(rootdir, relpath) + shutil.copy(source, destination) + + +class TestManifest(ManifestParser): + """ + apply logic to manifests; this is your integration layer :) + specific harnesses may subclass from this if they need more logic + """ + + def filter(self, tag, value, tests=None): + """ + filter on a specific list tag, e.g.: + run-if.os = win linux + skip-if.os = mac + """ + + if tests is None: + tests = self.tests + + # tags: + run_tag = 'run-if.' + tag + skip_tag = 'skip-if.' + tag + + # loop over test + for test in tests: + reason = None # reason to disable + + # tagged-values to run + if run_tag in test: + values = test[run_tag].split() + if value not in values: + reason = '%s %s not in run values %s' % (tag, value, values) + + # tagged-values to skip + if skip_tag in test: + values = test[skip_tag].split() + if value in values: + reason = '%s %s in skipped values %s' % (tag, value, values) + + # mark test as disabled if there's a reason + if reason: + test.setdefault('disabled', reason) + + def active_tests(self, exists=True, disabled=True, **tags): + """ + - exists : return only existing tests + - disabled : whether to return disabled tests + - tags : keys and values to filter on (e.g. `os = linux mac`) + """ + + tests = [i.copy() for i in self.tests] # shallow copy + + # ignore tests that do not exist + if exists: + tests = [test for test in tests if os.path.exists(test['path'])] + + # filter by tags + for tag, value in tags.items(): + self.filter(tag, value, tests) + + # ignore disabled tests if specified + if not disabled: + tests = [test for test in tests + if not 'disabled' in test] + + # return active tests + return tests + + def test_paths(self): + return [test['path'] for test in self.active_tests()] + + +### utility function(s); probably belongs elsewhere + +def convert(directories, pattern=None, ignore=(), write=None): + """ + convert directories to a simple manifest + """ + + retval = [] + include = [] + for directory in directories: + for dirpath, dirnames, filenames in os.walk(directory): + + # filter out directory names + dirnames = [ i for i in dirnames if i not in ignore ] + dirnames.sort() + + # reference only the subdirectory + _dirpath = dirpath + dirpath = dirpath.split(directory, 1)[-1].strip('/') + + if dirpath.split(os.path.sep)[0] in ignore: + continue + + # filter by glob + if pattern: + filenames = [filename for filename in filenames + if fnmatch(filename, pattern)] + + filenames.sort() + + # write a manifest for each directory + if write and (dirnames or filenames): + manifest = file(os.path.join(_dirpath, write), 'w') + for dirname in dirnames: + print >> manifest, '[include:%s]' % os.path.join(dirname, write) + for filename in filenames: + print >> manifest, '[%s]' % filename + manifest.close() + + # add to the list + retval.extend([os.path.join(dirpath, filename) + for filename in filenames]) + + if write: + return # the manifests have already been written! + + retval.sort() + retval = ['[%s]' % filename for filename in retval] + return '\n'.join(retval) + +### command line attributes + +class ParserError(Exception): + """error for exceptions while parsing the command line""" + +def parse_args(_args): + """ + parse and return: + --keys=value (or --key value) + -tags + args + """ + + # return values + _dict = {} + tags = [] + args = [] + + # parse the arguments + key = None + for arg in _args: + if arg.startswith('---'): + raise ParserError("arguments should start with '-' or '--' only") + elif arg.startswith('--'): + if key: + raise ParserError("Key %s still open" % key) + key = arg[2:] + if '=' in key: + key, value = key.split('=', 1) + _dict[key] = value + key = None + continue + elif arg.startswith('-'): + if key: + raise ParserError("Key %s still open" % key) + tags.append(arg[1:]) + continue + else: + if key: + _dict[key] = arg + continue + args.append(arg) + + # return values + return (_dict, tags, args) + + +### classes for subcommands + +class CLICommand(object): + usage = '%prog [options] command' + def __init__(self, parser): + self._parser = parser # master parser + def parser(self): + return OptionParser(usage=self.usage, description=self.__doc__, + add_help_option=False) + +class Copy(CLICommand): + usage = '%prog [options] copy manifest directory -tag1 -tag2 --key1=value1 --key2=value2 ...' + def __call__(self, options, args): + # parse the arguments + try: + kwargs, tags, args = parse_args(args) + except ParserError, e: + self._parser.error(e.message) + + # make sure we have some manifests, otherwise it will + # be quite boring + if not len(args) == 2: + HelpCLI(self._parser)(options, ['copy']) + return + + # read the manifests + # TODO: should probably ensure these exist here + manifests = ManifestParser() + manifests.read(args[0]) + + # print the resultant query + manifests.copy(args[1], None, *tags, **kwargs) + + +class CreateCLI(CLICommand): + """ + create a manifest from a list of directories + """ + usage = '%prog [options] create directory <directory> <...>' + + def parser(self): + parser = CLICommand.parser(self) + parser.add_option('-p', '--pattern', dest='pattern', + help="glob pattern for files") + parser.add_option('-i', '--ignore', dest='ignore', + default=[], action='append', + help='directories to ignore') + parser.add_option('-w', '--in-place', dest='in_place', + help='Write .ini files in place; filename to write to') + return parser + + def __call__(self, _options, args): + parser = self.parser() + options, args = parser.parse_args(args) + + # need some directories + if not len(args): + parser.print_usage() + return + + # add the directories to the manifest + for arg in args: + assert os.path.exists(arg) + assert os.path.isdir(arg) + manifest = convert(args, pattern=options.pattern, ignore=options.ignore, + write=options.in_place) + if manifest: + print manifest + + +class WriteCLI(CLICommand): + """ + write a manifest based on a query + """ + usage = '%prog [options] write manifest <manifest> -tag1 -tag2 --key1=value1 --key2=value2 ...' + def __call__(self, options, args): + + # parse the arguments + try: + kwargs, tags, args = parse_args(args) + except ParserError, e: + self._parser.error(e.message) + + # make sure we have some manifests, otherwise it will + # be quite boring + if not args: + HelpCLI(self._parser)(options, ['write']) + return + + # read the manifests + # TODO: should probably ensure these exist here + manifests = ManifestParser() + manifests.read(*args) + + # print the resultant query + manifests.write(global_tags=tags, global_kwargs=kwargs) + + +class HelpCLI(CLICommand): + """ + get help on a command + """ + usage = '%prog [options] help [command]' + + def __call__(self, options, args): + if len(args) == 1 and args[0] in commands: + commands[args[0]](self._parser).parser().print_help() + else: + self._parser.print_help() + print '\nCommands:' + for command in sorted(commands): + print ' %s : %s' % (command, commands[command].__doc__.strip()) + +class SetupCLI(CLICommand): + """ + setup using setuptools + """ + # use setup.py from the repo when you want to distribute to python! + # otherwise setuptools will complain that it can't find setup.py + # and result in a useless package + + usage = '%prog [options] setup [setuptools options]' + + def __call__(self, options, args): + sys.argv = [sys.argv[0]] + args + assert setup is not None, "You must have setuptools installed to use SetupCLI" + here = os.path.dirname(os.path.abspath(__file__)) + try: + filename = os.path.join(here, 'README.txt') + description = file(filename).read() + except: + description = '' + os.chdir(here) + + setup(name='ManifestDestiny', + version=version, + description="universal reader for manifests", + long_description=description, + classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers + keywords='mozilla manifests', + author='Jeff Hammel', + author_email='jhammel@mozilla.com', + url='https://wiki.mozilla.org/Auto-tools/Projects/ManifestDestiny', + license='MPL', + zip_safe=False, + py_modules=['manifestparser'], + install_requires=[ + # -*- Extra requirements: -*- + ], + entry_points=""" + [console_scripts] + manifestparser = manifestparser:main + """, + ) + + +class UpdateCLI(CLICommand): + """ + update the tests as listed in a manifest from a directory + """ + usage = '%prog [options] update manifest directory -tag1 -tag2 --key1=value1 --key2=value2 ...' + + def __call__(self, options, args): + # parse the arguments + try: + kwargs, tags, args = parse_args(args) + except ParserError, e: + self._parser.error(e.message) + + # make sure we have some manifests, otherwise it will + # be quite boring + if not len(args) == 2: + HelpCLI(self._parser)(options, ['update']) + return + + # read the manifests + # TODO: should probably ensure these exist here + manifests = ManifestParser() + manifests.read(args[0]) + + # print the resultant query + manifests.update(args[1], None, *tags, **kwargs) + + +# command -> class mapping +commands = { 'create': CreateCLI, + 'help': HelpCLI, + 'update': UpdateCLI, + 'write': WriteCLI } +if setup is not None: + commands['setup'] = SetupCLI + +def main(args=sys.argv[1:]): + """console_script entry point""" + + # set up an option parser + usage = '%prog [options] [command] ...' + description = __doc__ + parser = OptionParser(usage=usage, description=description) + parser.add_option('-s', '--strict', dest='strict', + action='store_true', default=False, + help='adhere strictly to errors') + parser.disable_interspersed_args() + + options, args = parser.parse_args(args) + + if not args: + HelpCLI(parser)(options, args) + parser.exit() + + # get the command + command = args[0] + if command not in commands: + parser.error("Command must be one of %s (you gave '%s')" % (', '.join(sorted(commands.keys())), command)) + + handler = commands[command](parser) + handler(options, args[1:]) + +if __name__ == '__main__': + main()
--- a/config/rules.mk +++ b/config/rules.mk @@ -137,19 +137,20 @@ define _INSTALL_TESTS $(TEST_INSTALLER) $(wildcard $(srcdir)/$(dir)/*) $(testxpcobjdir)/$(relativesrcdir)/$(dir) endef # do not remove the blank line! SOLO_FILE ?= $(error Specify a test filename in SOLO_FILE when using check-interactive or check-one) libs:: $(foreach dir,$(XPCSHELL_TESTS),$(_INSTALL_TESTS)) - $(PYTHON) $(MOZILLA_DIR)/config/buildlist.py \ - $(testxpcobjdir)/all-test-dirs.list \ - $(addprefix $(relativesrcdir)/,$(XPCSHELL_TESTS)) + $(PYTHON) $(MOZILLA_DIR)/build/xpccheck.py \ + $(topsrcdir) \ + $(topsrcdir)/testing/xpcshell/xpcshell.ini \ + $(addprefix $(MOZILLA_DIR)/$(relativesrcdir)/,$(XPCSHELL_TESTS)) testxpcsrcdir = $(topsrcdir)/testing/xpcshell # Execute all tests in the $(XPCSHELL_TESTS) directories. # See also testsuite-targets.mk 'xpcshell-tests' target for global execution. xpcshell-tests: $(PYTHON) -u $(topsrcdir)/config/pythonpath.py \ -I$(topsrcdir)/build \
--- a/js/src/config/rules.mk +++ b/js/src/config/rules.mk @@ -137,19 +137,20 @@ define _INSTALL_TESTS $(TEST_INSTALLER) $(wildcard $(srcdir)/$(dir)/*) $(testxpcobjdir)/$(relativesrcdir)/$(dir) endef # do not remove the blank line! SOLO_FILE ?= $(error Specify a test filename in SOLO_FILE when using check-interactive or check-one) libs:: $(foreach dir,$(XPCSHELL_TESTS),$(_INSTALL_TESTS)) - $(PYTHON) $(MOZILLA_DIR)/config/buildlist.py \ - $(testxpcobjdir)/all-test-dirs.list \ - $(addprefix $(relativesrcdir)/,$(XPCSHELL_TESTS)) + $(PYTHON) $(MOZILLA_DIR)/build/xpccheck.py \ + $(topsrcdir) \ + $(topsrcdir)/testing/xpcshell/xpcshell.ini \ + $(addprefix $(MOZILLA_DIR)/$(relativesrcdir)/,$(XPCSHELL_TESTS)) testxpcsrcdir = $(topsrcdir)/testing/xpcshell # Execute all tests in the $(XPCSHELL_TESTS) directories. # See also testsuite-targets.mk 'xpcshell-tests' target for global execution. xpcshell-tests: $(PYTHON) -u $(topsrcdir)/config/pythonpath.py \ -I$(topsrcdir)/build \
--- a/services/crypto/component/tests/Makefile.in +++ b/services/crypto/component/tests/Makefile.in @@ -35,16 +35,16 @@ # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** DEPTH = ../../../.. topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ -relativesrcdir = services/crypto/components/tests +relativesrcdir = services/crypto/component/tests include $(DEPTH)/config/autoconf.mk MODULE = test_services_crypto XPCSHELL_TESTS = unit include $(topsrcdir)/config/rules.mk
--- a/testing/testsuite-targets.mk +++ b/testing/testsuite-targets.mk @@ -160,17 +160,17 @@ GARBAGE += $(addsuffix .log,$(MOCHITESTS # Execute all xpcshell tests in the directories listed in the manifest. # See also config/rules.mk 'xpcshell-tests' target for local execution. # Usage: |make [TEST_PATH=...] [EXTRA_TEST_ARGS=...] xpcshell-tests|. xpcshell-tests: $(PYTHON) -u $(topsrcdir)/config/pythonpath.py \ -I$(topsrcdir)/build \ $(topsrcdir)/testing/xpcshell/runxpcshelltests.py \ - --manifest=$(DEPTH)/_tests/xpcshell/all-test-dirs.list \ + --manifest=$(DEPTH)/_tests/xpcshell/xpcshell.ini \ --no-logfiles \ $(SYMBOLS_PATH) \ $(TEST_PATH_ARG) $(EXTRA_TEST_ARGS) \ $(LIBXUL_DIST)/bin/xpcshell # install and run the mozmill tests $(DEPTH)/_tests/mozmill: $(MAKE) -C $(DEPTH)/testing/mozmill install-develop PKG_STAGE=../../_tests
--- a/testing/xpcshell/Makefile.in +++ b/testing/xpcshell/Makefile.in @@ -58,16 +58,17 @@ TEST_HARNESS_FILES := \ runxpcshelltests.py \ remotexpcshelltests.py \ head.js \ $(NULL) # Extra files needed from $(topsrcdir)/build EXTRA_BUILD_FILES := \ automationutils.py \ + manifestparser.py \ poster.zip \ $(NULL) # And files for running xpcshell remotely from $(topsrcdir)/build/mobile MOBILE_BUILD_FILES := \ devicemanager.py \ $(NULL) @@ -76,15 +77,19 @@ MOBILE_BUILD_FILES := \ TEST_HARNESS_COMPONENTS := \ httpd.js \ httpd.manifest \ $(NULL) # Rules for staging the necessary harness bits for a test package PKG_STAGE = $(DIST)/test-package-stage +libs:: + $(INSTALL) $(srcdir)/xpcshell.ini $(DEPTH)/_tests/xpcshell + cp $(srcdir)/xpcshell.ini $(DEPTH)/_tests/xpcshell/all-test-dirs.list + stage-package: $(NSINSTALL) -D $(PKG_STAGE)/xpcshell/tests @(cd $(srcdir) && tar $(TAR_CREATE_FLAGS) - $(TEST_HARNESS_FILES)) | (cd $(PKG_STAGE)/xpcshell && tar -xf -) @(cd $(topsrcdir)/build && tar $(TAR_CREATE_FLAGS) - $(EXTRA_BUILD_FILES)) | (cd $(PKG_STAGE)/xpcshell && tar -xf -) @(cd $(topsrcdir)/build/mobile && tar $(TAR_CREATE_FLAGS) - $(MOBILE_BUILD_FILES)) | (cd $(PKG_STAGE)/xpcshell && tar -xf -) @(cd $(DEPTH)/_tests/xpcshell/ && tar $(TAR_CREATE_FLAGS) - *) | (cd $(PKG_STAGE)/xpcshell/tests && tar -xf -) @(cd $(DIST)/bin/components && tar $(TAR_CREATE_FLAGS) - $(TEST_HARNESS_COMPONENTS)) | (cd $(PKG_STAGE)/bin/components && tar -xf -)
--- a/testing/xpcshell/runxpcshelltests.py +++ b/testing/xpcshell/runxpcshelltests.py @@ -38,16 +38,17 @@ # # ***** END LICENSE BLOCK ***** */ import re, sys, os, os.path, logging, shutil, signal, math from glob import glob from optparse import OptionParser from subprocess import Popen, PIPE, STDOUT from tempfile import mkdtemp, gettempdir +import manifestparser from automationutils import * """ Control-C handling """ gotSIGINT = False def markGotSIGINT(signum, stackFrame): global gotSIGINT gotSIGINT = True @@ -58,48 +59,33 @@ class XPCShellTests(object): oldcwd = os.getcwd() def __init__(self): """ Init logging """ handler = logging.StreamHandler(sys.stdout) self.log.setLevel(logging.INFO) self.log.addHandler(handler) - def readManifest(self): - """ - For a given manifest file, read the contents and populate self.testdirs - """ - manifestdir = os.path.dirname(self.manifest) - try: - f = open(self.manifest, "r") - for line in f: - path = os.path.join(manifestdir, line.rstrip()) - if os.path.isdir(path): - self.testdirs.append(path) - f.close() - except: - pass # just eat exceptions - def buildTestList(self): """ - Builds a dict of {"testdir" : ["testfile1", "testfile2", ...], "testdir2"...}. - If manifest is given override testdirs to build initial list of directories and tests. - If testpath is given, use that, otherwise chunk if requested. - The resulting set of tests end up in self.alltests + read the xpcshell.ini manifest and set self.alltests to be + an array of test objects. + + if we are chunking tests, it will be done here as well """ + mp = manifestparser.ManifestParser(strict=False) + if self.manifest is None: + for testdir in self.testdirs: + if testdir: + mp.read(os.path.join(testdir, 'xpcshell.ini')) + else: + mp.read(self.manifest) self.buildTestPath() - self.alltests = {} - if self.manifest is not None: - self.readManifest() - - for dir in self.testdirs: - tests = self.getTestFiles(dir) - if tests: - self.alltests[os.path.abspath(dir)] = tests + self.alltests = mp.tests if self.singleFile is None and self.totalChunks > 1: self.chunkTests() def chunkTests(self): """ Split the list of tests up into [totalChunks] pieces and filter the self.alltests based on thisChunk, so we only run a subset. @@ -240,60 +226,45 @@ class XPCShellTests(object): """ self.singleFile = None if self.testPath is not None: if self.testPath.endswith('.js'): # Split into path and file. if self.testPath.find('/') == -1: # Test only. self.singleFile = self.testPath - self.testPath = None else: # Both path and test. # Reuse |testPath| temporarily. self.testPath = self.testPath.rsplit('/', 1) self.singleFile = self.testPath[1] self.testPath = self.testPath[0] else: # Path only. # Simply remove optional ending separator. self.testPath = self.testPath.rstrip("/") - def getHeadFiles(self, testdir): - """ - Get the list of head files for a given test directory. - On a remote system, this is overloaded to list files in a remote directory structure. + + def getHeadFiles(self, test): """ - return [f for f in sorted(glob(os.path.join(testdir, "head_*.js"))) if os.path.isfile(f)] - - def getTailFiles(self, testdir): - """ - Get the list of tail files for a given test directory. - Tails are executed in the reverse order, to "match" heads order, - as in "h1-h2-h3 then t3-t2-t1". + test['head'] is a whitespace delimited list of head files. + return the list of head files as paths including the subdir if the head file exists On a remote system, this is overloaded to list files in a remote directory structure. """ - return [f for f in reversed(sorted(glob(os.path.join(testdir, "tail_*.js")))) if os.path.isfile(f)] + return [os.path.join(test['here'], f).strip() for f in sorted(test['head'].split(' ')) if os.path.isfile(os.path.join(test['here'], f))] - def getTestFiles(self, testdir): - """ - Ff a single test file was specified, we only want to execute that test, - otherwise return a list of all tests in a directory - - On a remote system, this is overloaded to find files in the remote directory structure. + def getTailFiles(self, test): """ - testfiles = sorted(glob(os.path.join(os.path.abspath(testdir), "test_*.js"))) - if self.singleFile: - if self.singleFile in [os.path.basename(x) for x in testfiles]: - testfiles = [os.path.abspath(os.path.join(testdir, self.singleFile))] - else: # not in this dir? skip it - return None - - return testfiles + test['tail'] is a whitespace delimited list of head files. + return the list of tail files as paths including the subdir if the tail file exists + + On a remote system, this is overloaded to list files in a remote directory structure. + """ + return [os.path.join(test['here'], f).strip() for f in sorted(test['tail'].split(' ')) if os.path.isfile(os.path.join(test['here'], f))] def setupProfileDir(self): """ Create a temporary folder for the profile and set appropriate environment variables. When running check-interactive and check-one, the directory is well-defined and retained for inspection once the tests complete. On a remote system, we overload this to use a remote path structure. @@ -457,94 +428,97 @@ class XPCShellTests(object): self.setAbsPath() self.buildXpcsRunArgs() self.buildEnvironment() pStdout, pStderr = self.getPipes() self.buildTestList() - for testdir in sorted(self.alltests.keys()): - if self.testPath and not testdir.endswith(self.testPath): + for test in self.alltests: + name = test['path'] + if self.singleFile and not name.endswith(self.singleFile): continue + if self.testPath and name.find(self.testPath) == -1: + continue + + testdir = os.path.dirname(name) self.buildXpcsCmd(testdir) - testHeadFiles = self.getHeadFiles(testdir) - testTailFiles = self.getTailFiles(testdir) + testHeadFiles = self.getHeadFiles(test) + testTailFiles = self.getTailFiles(test) cmdH = self.buildCmdHead(testHeadFiles, testTailFiles, self.xpcsCmd) - # Now execute each test individually. - for test in self.alltests[testdir]: - # create a temp dir that the JS harness can stick a profile in - self.profileDir = self.setupProfileDir() - self.leakLogFile = self.setupLeakLogging() + # create a temp dir that the JS harness can stick a profile in + self.profileDir = self.setupProfileDir() + self.leakLogFile = self.setupLeakLogging() - # The test file will have to be loaded after the head files. - cmdT = ['-e', 'const _TEST_FILE = ["%s"];' % - replaceBackSlashes(test)] + # The test file will have to be loaded after the head files. + cmdT = ['-e', 'const _TEST_FILE = ["%s"];' % + replaceBackSlashes(name)] - try: - print "TEST-INFO | %s | running test ..." % test + try: + print "TEST-INFO | %s | running test ..." % name - proc = self.launchProcess(cmdH + cmdT + self.xpcsRunArgs, - stdout=pStdout, stderr=pStderr, env=self.env, cwd=testdir) + proc = self.launchProcess(cmdH + cmdT + self.xpcsRunArgs, + stdout=pStdout, stderr=pStderr, env=self.env, cwd=testdir) - # Allow user to kill hung subprocess with SIGINT w/o killing this script - # - don't move this line above launchProcess, or child will inherit the SIG_IGN - signal.signal(signal.SIGINT, markGotSIGINT) - # |stderr == None| as |pStderr| was either |None| or redirected to |stdout|. - stdout, stderr = self.communicate(proc) - signal.signal(signal.SIGINT, signal.SIG_DFL) + # Allow user to kill hung subprocess with SIGINT w/o killing this script + # - don't move this line above launchProcess, or child will inherit the SIG_IGN + signal.signal(signal.SIGINT, markGotSIGINT) + # |stderr == None| as |pStderr| was either |None| or redirected to |stdout|. + stdout, stderr = self.communicate(proc) + signal.signal(signal.SIGINT, signal.SIG_DFL) - if interactive: - # Not sure what else to do here... - return True + if interactive: + # Not sure what else to do here... + return True - def print_stdout(stdout): - """Print stdout line-by-line to avoid overflowing buffers.""" - print ">>>>>>>" - for line in stdout.splitlines(): - print line - print "<<<<<<<" + def print_stdout(stdout): + """Print stdout line-by-line to avoid overflowing buffers.""" + print ">>>>>>>" + for line in stdout.splitlines(): + print line + print "<<<<<<<" - if (self.getReturnCode(proc) != 0) or \ - (stdout and re.search("^((parent|child): )?TEST-UNEXPECTED-", stdout, re.MULTILINE)) or \ - (stdout and re.search(": SyntaxError:", stdout, re.MULTILINE)): - print "TEST-UNEXPECTED-FAIL | %s | test failed (with xpcshell return code: %d), see following log:" % (test, self.getReturnCode(proc)) + if (self.getReturnCode(proc) != 0) or \ + (stdout and re.search("^((parent|child): )?TEST-UNEXPECTED-", stdout, re.MULTILINE)) or \ + (stdout and re.search(": SyntaxError:", stdout, re.MULTILINE)): + print "TEST-UNEXPECTED-FAIL | %s | test failed (with xpcshell return code: %d), see following log:" % (name, self.getReturnCode(proc)) + print_stdout(stdout) + failCount += 1 + else: + print "TEST-PASS | %s | test passed" % name + if verbose: print_stdout(stdout) - failCount += 1 - else: - print "TEST-PASS | %s | test passed" % test - if verbose: - print_stdout(stdout) - passCount += 1 + passCount += 1 - checkForCrashes(testdir, self.symbolsPath, testName=test) - # Find child process(es) leak log(s), if any: See InitLog() in - # xpcom/base/nsTraceRefcntImpl.cpp for logfile naming logic - leakLogs = [self.leakLogFile] - for childLog in glob(os.path.join(self.profileDir, "runxpcshelltests_leaks_*_pid*.log")): - if os.path.isfile(childLog): - leakLogs += [childLog] - for log in leakLogs: - dumpLeakLog(log, True) + checkForCrashes(testdir, self.symbolsPath, testName=name) + # Find child process(es) leak log(s), if any: See InitLog() in + # xpcom/base/nsTraceRefcntImpl.cpp for logfile naming logic + leakLogs = [self.leakLogFile] + for childLog in glob(os.path.join(self.profileDir, "runxpcshelltests_leaks_*_pid*.log")): + if os.path.isfile(childLog): + leakLogs += [childLog] + for log in leakLogs: + dumpLeakLog(log, True) - if self.logfiles and stdout: - self.createLogFile(test, stdout, leakLogs) - finally: - # We don't want to delete the profile when running check-interactive - # or check-one. - if self.profileDir and not self.interactive and not self.singleFile: - self.removeDir(self.profileDir) - if gotSIGINT: - print "TEST-UNEXPECTED-FAIL | Received SIGINT (control-C) during test execution" - if (keepGoing): - gotSIGINT = False - else: - break + if self.logfiles and stdout: + self.createLogFile(name, stdout, leakLogs) + finally: + # We don't want to delete the profile when running check-interactive + # or check-one. + if self.profileDir and not self.interactive and not self.singleFile: + self.removeDir(self.profileDir) + if gotSIGINT: + print "TEST-UNEXPECTED-FAIL | Received SIGINT (control-C) during test execution" + if (keepGoing): + gotSIGINT = False + else: + break if passCount == 0 and failCount == 0: print "TEST-UNEXPECTED-FAIL | runxpcshelltests.py | No tests run. Did you pass an invalid --test-path?" failCount = 1 print """INFO | Result summary: INFO | Passed: %d INFO | Failed: %d""" % (passCount, failCount)
--- a/toolkit/mozapps/extensions/test/Makefile.in +++ b/toolkit/mozapps/extensions/test/Makefile.in @@ -70,9 +70,10 @@ libs:: if [ -d $(ADDONSRC) ]; then \ $(EXIT_ON_ERROR) \ for dir in $(ADDONSRC)/*; do \ base=`basename $$dir` ; \ (cd $$dir && zip -r $(TESTXPI)/$$base.xpi *) \ done \ fi cd $(TESTROOT)/xpcshell/ && tar -cPf - . | (cd $(TESTROOT)/xpcshell-unpack && tar -xPvf - ) - + sed s/head_addons.js/head_addons.js\ head_unpack.js/ $(TESTROOT)/xpcshell-unpack/xpcshell.ini > $(TESTROOT)/xpcshell-unpack/xpcshell.in_ + mv $(TESTROOT)/xpcshell-unpack/xpcshell.in_ $(TESTROOT)/xpcshell-unpack/xpcshell.ini