mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
bug 664197 - integrate mozinfo + ManifestDestiny update into xpcshell harness. r=jmaher
--HG-- extra : rebase_source : 1783e1a39d592defa279488f0cd0266311ed35ea
This commit is contained in:
parent
5bb3d4e7af
commit
62b37a06ef
@ -115,6 +115,11 @@ ifdef ENABLE_TESTS
|
||||
libs:: $(topsrcdir)/tools/rb/fix_stack_using_bpsyms.py
|
||||
$(INSTALL) $< $(DIST)/bin
|
||||
|
||||
# Unit tests for ManifestParser
|
||||
check::
|
||||
$(PYTHON) $(topsrcdir)/config/pythonpath.py -I$(srcdir) \
|
||||
$(srcdir)/tests/test.py
|
||||
|
||||
ifeq ($(OS_ARCH),Darwin)
|
||||
libs:: $(topsrcdir)/tools/rb/fix-macosx-stack.pl
|
||||
$(INSTALL) $< $(DIST)/bin
|
||||
|
@ -44,20 +44,265 @@ Mozilla universal manifest parser
|
||||
# this file lives at
|
||||
# http://hg.mozilla.org/automation/ManifestDestiny/raw-file/tip/manifestparser.py
|
||||
|
||||
__all__ = ['ManifestParser', 'TestManifest', 'convert']
|
||||
__all__ = ['read_ini', # .ini reader
|
||||
'ManifestParser', 'TestManifest', 'convert', # manifest handling
|
||||
'parse', 'ParseError', 'ExpressionParser'] # conditional expression parser
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from fnmatch import fnmatch
|
||||
from optparse import OptionParser
|
||||
|
||||
version = '0.3.1' # package version
|
||||
version = '0.5.1' # package version
|
||||
try:
|
||||
from setuptools import setup
|
||||
except ImportError:
|
||||
setup = None
|
||||
|
||||
# we need relpath, but it is introduced in python 2.6
|
||||
# http://docs.python.org/library/os.path.html
|
||||
try:
|
||||
relpath = os.path.relpath
|
||||
except AttributeError:
|
||||
def relpath(path, start):
|
||||
"""
|
||||
Return a relative version of a path
|
||||
from /usr/lib/python2.6/posixpath.py
|
||||
"""
|
||||
|
||||
if not path:
|
||||
raise ValueError("no path specified")
|
||||
|
||||
start_list = os.path.abspath(start).split(os.path.sep)
|
||||
path_list = os.path.abspath(path).split(os.path.sep)
|
||||
|
||||
# Work out how much of the filepath is shared by start and path.
|
||||
i = len(os.path.commonprefix([start_list, path_list]))
|
||||
|
||||
rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
|
||||
if not rel_list:
|
||||
return start
|
||||
return os.path.join(*rel_list)
|
||||
|
||||
# expr.py
|
||||
# from:
|
||||
# http://k0s.org/mozilla/hg/expressionparser
|
||||
# http://hg.mozilla.org/users/tmielczarek_mozilla.com/expressionparser
|
||||
|
||||
# Implements a top-down parser/evaluator for simple boolean expressions.
|
||||
# ideas taken from http://effbot.org/zone/simple-top-down-parsing.htm
|
||||
#
|
||||
# Rough grammar:
|
||||
# expr := literal
|
||||
# | '(' expr ')'
|
||||
# | expr '&&' expr
|
||||
# | expr '||' expr
|
||||
# | expr '==' expr
|
||||
# | expr '!=' expr
|
||||
# literal := BOOL
|
||||
# | INT
|
||||
# | STRING
|
||||
# | IDENT
|
||||
# BOOL := true|false
|
||||
# INT := [0-9]+
|
||||
# STRING := "[^"]*"
|
||||
# IDENT := [A-Za-z_]\w*
|
||||
|
||||
# Identifiers take their values from a mapping dictionary passed as the second
|
||||
# argument.
|
||||
|
||||
# Glossary (see above URL for details):
|
||||
# - nud: null denotation
|
||||
# - led: left detonation
|
||||
# - lbp: left binding power
|
||||
# - rbp: right binding power
|
||||
|
||||
class ident_token(object):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
def nud(self, parser):
|
||||
# identifiers take their value from the value mappings passed
|
||||
# to the parser
|
||||
return parser.value(self.value)
|
||||
|
||||
class literal_token(object):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
def nud(self, parser):
|
||||
return self.value
|
||||
|
||||
class eq_op_token(object):
|
||||
"=="
|
||||
def led(self, parser, left):
|
||||
return left == parser.expression(self.lbp)
|
||||
|
||||
class neq_op_token(object):
|
||||
"!="
|
||||
def led(self, parser, left):
|
||||
return left != parser.expression(self.lbp)
|
||||
|
||||
class not_op_token(object):
|
||||
"!"
|
||||
def nud(self, parser):
|
||||
return not parser.expression()
|
||||
|
||||
class and_op_token(object):
|
||||
"&&"
|
||||
def led(self, parser, left):
|
||||
right = parser.expression(self.lbp)
|
||||
return left and right
|
||||
|
||||
class or_op_token(object):
|
||||
"||"
|
||||
def led(self, parser, left):
|
||||
right = parser.expression(self.lbp)
|
||||
return left or right
|
||||
|
||||
class lparen_token(object):
|
||||
"("
|
||||
def nud(self, parser):
|
||||
expr = parser.expression()
|
||||
parser.advance(rparen_token)
|
||||
return expr
|
||||
|
||||
class rparen_token(object):
|
||||
")"
|
||||
|
||||
class end_token(object):
|
||||
"""always ends parsing"""
|
||||
|
||||
### derived literal tokens
|
||||
|
||||
class bool_token(literal_token):
|
||||
def __init__(self, value):
|
||||
value = {'true':True, 'false':False}[value]
|
||||
literal_token.__init__(self, value)
|
||||
|
||||
class int_token(literal_token):
|
||||
def __init__(self, value):
|
||||
literal_token.__init__(self, int(value))
|
||||
|
||||
class string_token(literal_token):
|
||||
def __init__(self, value):
|
||||
literal_token.__init__(self, value[1:-1])
|
||||
|
||||
precedence = [(end_token, rparen_token),
|
||||
(or_op_token,),
|
||||
(and_op_token,),
|
||||
(eq_op_token, neq_op_token),
|
||||
(lparen_token,),
|
||||
]
|
||||
for index, rank in enumerate(precedence):
|
||||
for token in rank:
|
||||
token.lbp = index # lbp = lowest left binding power
|
||||
|
||||
class ParseError(Exception):
|
||||
"""errror parsing conditional expression"""
|
||||
|
||||
class ExpressionParser(object):
|
||||
def __init__(self, text, valuemapping, strict=False):
|
||||
"""
|
||||
Initialize the parser with input |text|, and |valuemapping| as
|
||||
a dict mapping identifier names to values.
|
||||
"""
|
||||
self.text = text
|
||||
self.valuemapping = valuemapping
|
||||
self.strict = strict
|
||||
|
||||
def _tokenize(self):
|
||||
"""
|
||||
Lex the input text into tokens and yield them in sequence.
|
||||
"""
|
||||
# scanner callbacks
|
||||
def bool_(scanner, t): return bool_token(t)
|
||||
def identifier(scanner, t): return ident_token(t)
|
||||
def integer(scanner, t): return int_token(t)
|
||||
def eq(scanner, t): return eq_op_token()
|
||||
def neq(scanner, t): return neq_op_token()
|
||||
def or_(scanner, t): return or_op_token()
|
||||
def and_(scanner, t): return and_op_token()
|
||||
def lparen(scanner, t): return lparen_token()
|
||||
def rparen(scanner, t): return rparen_token()
|
||||
def string_(scanner, t): return string_token(t)
|
||||
def not_(scanner, t): return not_op_token()
|
||||
|
||||
scanner = re.Scanner([
|
||||
(r"true|false", bool_),
|
||||
(r"[a-zA-Z_]\w*", identifier),
|
||||
(r"[0-9]+", integer),
|
||||
(r'("[^"]*")|(\'[^\']*\')', string_),
|
||||
(r"==", eq),
|
||||
(r"!=", neq),
|
||||
(r"\|\|", or_),
|
||||
(r"!", not_),
|
||||
(r"&&", and_),
|
||||
(r"\(", lparen),
|
||||
(r"\)", rparen),
|
||||
(r"\s+", None), # skip whitespace
|
||||
])
|
||||
tokens, remainder = scanner.scan(self.text)
|
||||
for t in tokens:
|
||||
yield t
|
||||
yield end_token()
|
||||
|
||||
def value(self, ident):
|
||||
"""
|
||||
Look up the value of |ident| in the value mapping passed in the
|
||||
constructor.
|
||||
"""
|
||||
if self.strict:
|
||||
return self.valuemapping[ident]
|
||||
else:
|
||||
return self.valuemapping.get(ident, None)
|
||||
|
||||
def advance(self, expected):
|
||||
"""
|
||||
Assert that the next token is an instance of |expected|, and advance
|
||||
to the next token.
|
||||
"""
|
||||
if not isinstance(self.token, expected):
|
||||
raise Exception, "Unexpected token!"
|
||||
self.token = self.iter.next()
|
||||
|
||||
def expression(self, rbp=0):
|
||||
"""
|
||||
Parse and return the value of an expression until a token with
|
||||
right binding power greater than rbp is encountered.
|
||||
"""
|
||||
t = self.token
|
||||
self.token = self.iter.next()
|
||||
left = t.nud(self)
|
||||
while rbp < self.token.lbp:
|
||||
t = self.token
|
||||
self.token = self.iter.next()
|
||||
left = t.led(self, left)
|
||||
return left
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
Parse and return the value of the expression in the text
|
||||
passed to the constructor. Raises a ParseError if the expression
|
||||
could not be parsed.
|
||||
"""
|
||||
try:
|
||||
self.iter = self._tokenize()
|
||||
self.token = self.iter.next()
|
||||
return self.expression()
|
||||
except:
|
||||
raise ParseError("could not parse: %s; variables: %s" % (self.text, self.valuemapping))
|
||||
|
||||
__call__ = parse
|
||||
|
||||
def parse(text, **values):
|
||||
"""
|
||||
Parse and evaluate a boolean expression in |text|. Use |values| to look
|
||||
up the value of identifiers referenced in the expression. Returns the final
|
||||
value of the expression. A ParseError will be raised if parsing fails.
|
||||
"""
|
||||
return ExpressionParser(text, values).parse()
|
||||
|
||||
def normalize_path(path):
|
||||
"""normalize a relative path"""
|
||||
if sys.platform.startswith('win'):
|
||||
@ -126,7 +371,7 @@ def read_ini(fp, variables=None, default='DEFAULT',
|
||||
|
||||
# if there aren't any sections yet, something bad happen
|
||||
if not section_names:
|
||||
raise Exception('No sections yet :(')
|
||||
raise Exception('No sections found')
|
||||
|
||||
# (key, value) pair
|
||||
for separator in separators:
|
||||
@ -174,6 +419,7 @@ class ManifestParser(object):
|
||||
self.tests = []
|
||||
self.strict = strict
|
||||
self.rootdir = None
|
||||
self.relativeRoot = None
|
||||
if manifests:
|
||||
self.read(*manifests)
|
||||
|
||||
@ -242,13 +488,16 @@ class ManifestParser(object):
|
||||
|
||||
### methods for querying manifests
|
||||
|
||||
def query(self, *checks):
|
||||
def query(self, *checks, **kw):
|
||||
"""
|
||||
general query function for tests
|
||||
- checks : callable conditions to test if the test fulfills the query
|
||||
"""
|
||||
tests = kw.get('tests', None)
|
||||
if tests is None:
|
||||
tests = self.tests
|
||||
retval = []
|
||||
for test in self.tests:
|
||||
for test in tests:
|
||||
for check in checks:
|
||||
if not check(test):
|
||||
break
|
||||
@ -256,7 +505,7 @@ class ManifestParser(object):
|
||||
retval.append(test)
|
||||
return retval
|
||||
|
||||
def get(self, _key=None, inverse=False, tags=None, **kwargs):
|
||||
def get(self, _key=None, inverse=False, tags=None, tests=None, **kwargs):
|
||||
# TODO: pass a dict instead of kwargs since you might hav
|
||||
# e.g. 'inverse' as a key in the dict
|
||||
|
||||
@ -271,7 +520,7 @@ class ManifestParser(object):
|
||||
|
||||
# make some check functions
|
||||
if inverse:
|
||||
has_tags = lambda test: tags.isdisjoint(test.keys())
|
||||
has_tags = lambda test: not tags.intersection(test.keys())
|
||||
def dict_query(test):
|
||||
for key, value in kwargs.items():
|
||||
if test.get(key) == value:
|
||||
@ -286,7 +535,7 @@ class ManifestParser(object):
|
||||
return True
|
||||
|
||||
# query the tests
|
||||
tests = self.query(has_tags, dict_query)
|
||||
tests = self.query(has_tags, dict_query, tests=tests)
|
||||
|
||||
# if a key is given, return only a list of that key
|
||||
# useful for keys like 'name' or 'path'
|
||||
@ -365,7 +614,7 @@ class ManifestParser(object):
|
||||
|
||||
path = test['name']
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.relpath(test['path'], self.rootdir)
|
||||
path = relpath(test['path'], self.rootdir)
|
||||
print >> fp, '[%s]' % path
|
||||
|
||||
# reserved keywords:
|
||||
@ -410,7 +659,7 @@ class ManifestParser(object):
|
||||
rootdir = self.rootdir
|
||||
|
||||
# copy the manifests + tests
|
||||
manifests = [os.path.relpath(manifest, rootdir) for manifest in self.manifests()]
|
||||
manifests = [relpath(manifest, rootdir) for manifest in self.manifests()]
|
||||
for manifest in manifests:
|
||||
destination = os.path.join(directory, manifest)
|
||||
dirname = os.path.dirname(destination)
|
||||
@ -428,7 +677,7 @@ class ManifestParser(object):
|
||||
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))
|
||||
destination = os.path.join(directory, relpath(test['path'], rootdir))
|
||||
shutil.copy(source, destination)
|
||||
# TODO: ensure that all of the tests are below the from_dir
|
||||
|
||||
@ -451,13 +700,13 @@ class ManifestParser(object):
|
||||
# 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)
|
||||
_relpath = 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)
|
||||
destination = os.path.join(rootdir, _relpath)
|
||||
shutil.copy(source, destination)
|
||||
|
||||
|
||||
@ -467,19 +716,17 @@ class TestManifest(ManifestParser):
|
||||
specific harnesses may subclass from this if they need more logic
|
||||
"""
|
||||
|
||||
def filter(self, tag, value, tests=None):
|
||||
def filter(self, values, tests):
|
||||
"""
|
||||
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
|
||||
run_tag = 'run-if'
|
||||
skip_tag = 'skip-if'
|
||||
fail_tag = 'fail-if'
|
||||
|
||||
# loop over test
|
||||
for test in tests:
|
||||
@ -487,21 +734,27 @@ class TestManifest(ManifestParser):
|
||||
|
||||
# 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)
|
||||
condition = test[run_tag]
|
||||
if not parse(condition, **values):
|
||||
reason = '%s: %s' % (run_tag, condition)
|
||||
|
||||
# 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)
|
||||
condition = test[skip_tag]
|
||||
if parse(condition, **values):
|
||||
reason = '%s: %s' % (skip_tag, condition)
|
||||
|
||||
# mark test as disabled if there's a reason
|
||||
if reason:
|
||||
test.setdefault('disabled', reason)
|
||||
|
||||
def active_tests(self, exists=True, disabled=True, **tags):
|
||||
# mark test as a fail if so indicated
|
||||
if fail_tag in test:
|
||||
condition = test[fail_tag]
|
||||
if parse(condition, **values):
|
||||
test['expected'] = 'fail'
|
||||
|
||||
def active_tests(self, exists=True, disabled=True, **values):
|
||||
"""
|
||||
- exists : return only existing tests
|
||||
- disabled : whether to return disabled tests
|
||||
@ -510,13 +763,16 @@ class TestManifest(ManifestParser):
|
||||
|
||||
tests = [i.copy() for i in self.tests] # shallow copy
|
||||
|
||||
# mark all tests as passing unless indicated otherwise
|
||||
for test in tests:
|
||||
test['expected'] = test.get('expected', 'pass')
|
||||
|
||||
# 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)
|
||||
self.filter(values, tests)
|
||||
|
||||
# ignore disabled tests if specified
|
||||
if not disabled:
|
||||
@ -763,7 +1019,7 @@ class SetupCLI(CLICommand):
|
||||
|
||||
setup(name='ManifestDestiny',
|
||||
version=version,
|
||||
description="universal reader for manifests",
|
||||
description="Universal manifests for Mozilla test harnesses",
|
||||
long_description=description,
|
||||
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
keywords='mozilla manifests',
|
||||
|
188
build/mozinfo.py
Executable file
188
build/mozinfo.py
Executable file
@ -0,0 +1,188 @@
|
||||
#!/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 Corporation Code.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# Mikeal Rogers.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2008
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Mikeal Rogers <mikeal.rogers@gmail.com>
|
||||
# Henrik Skupin <hskupin@mozilla.com>
|
||||
# Clint Talbert <ctalbert@mozilla.com>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either 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 *****
|
||||
|
||||
"""
|
||||
file for interface to transform introspected system information to a format
|
||||
pallatable to Mozilla
|
||||
|
||||
Information:
|
||||
- os : what operating system ['win', 'mac', 'linux', ...]
|
||||
- bits : 32 or 64
|
||||
- processor : processor architecture ['x86', 'x86_64', 'ppc', ...]
|
||||
- version : operating system version string
|
||||
|
||||
For windows, the service pack information is also included
|
||||
"""
|
||||
|
||||
# TODO: it might be a good idea of adding a system name (e.g. 'Ubuntu' for
|
||||
# linux) to the information; I certainly wouldn't want anyone parsing this
|
||||
# information and having behaviour depend on it
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
|
||||
# keep a copy of the os module since updating globals overrides this
|
||||
_os = os
|
||||
|
||||
class unknown(object):
|
||||
"""marker class for unknown information"""
|
||||
def __nonzero__(self):
|
||||
return False
|
||||
def __str__(self):
|
||||
return 'UNKNOWN'
|
||||
unknown = unknown() # singleton
|
||||
|
||||
# get system information
|
||||
|
||||
info = {'os': unknown,
|
||||
'processor': unknown,
|
||||
'version': unknown,
|
||||
'bits': unknown }
|
||||
|
||||
(system, node, release, version, machine, processor) = platform.uname()
|
||||
(bits, linkage) = platform.architecture()
|
||||
|
||||
if system in ["Microsoft", "Windows"]:
|
||||
info['os'] = 'win'
|
||||
|
||||
# There is a Python bug on Windows to determine platform values
|
||||
# http://bugs.python.org/issue7860
|
||||
if "PROCESSOR_ARCHITEW6432" in os.environ:
|
||||
processor = os.environ.get("PROCESSOR_ARCHITEW6432", processor)
|
||||
else:
|
||||
processor = os.environ.get('PROCESSOR_ARCHITECTURE', processor)
|
||||
system = os.environ.get("OS", system).replace('_', ' ')
|
||||
service_pack = os.sys.getwindowsversion()[4]
|
||||
info['service_pack'] = service_pack
|
||||
elif system == "Linux":
|
||||
(distro, version, codename) = platform.dist()
|
||||
version = distro + " " + version
|
||||
if not processor:
|
||||
processor = machine
|
||||
info['os'] = 'linux'
|
||||
elif system == "Darwin":
|
||||
(release, versioninfo, machine) = platform.mac_ver()
|
||||
version = "OS X " + release
|
||||
info['os'] = 'mac'
|
||||
elif sys.platform in ('solaris', 'sunos5'):
|
||||
info['os'] = 'unix'
|
||||
version = sys.platform
|
||||
|
||||
# processor type and bits
|
||||
if processor in ["i386", "i686"]:
|
||||
if bits == "32bit":
|
||||
processor = "x86"
|
||||
elif bits == "64bit":
|
||||
processor = "x86_64"
|
||||
elif processor == "AMD64":
|
||||
bits = "64bit"
|
||||
processor = "x86_64"
|
||||
elif processor == "Power Macintosh":
|
||||
processor = "ppc"
|
||||
bits = re.search('(\d+)bit', bits).group(1)
|
||||
|
||||
info.update({'version': version,
|
||||
'processor': processor,
|
||||
'bits': int(bits),
|
||||
})
|
||||
|
||||
def update(new_info):
|
||||
"""update the info"""
|
||||
info.update(new_info)
|
||||
globals().update(info)
|
||||
|
||||
update({})
|
||||
|
||||
choices = {'os': ['linux', 'win', 'mac', 'unix'],
|
||||
'bits': [32, 64],
|
||||
'processor': ['x86', 'x86_64', 'ppc']}
|
||||
|
||||
# exports
|
||||
__all__ = info.keys()
|
||||
__all__ += ['info', 'unknown', 'main', 'choices']
|
||||
|
||||
|
||||
def main(args=None):
|
||||
|
||||
# parse the command line
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser()
|
||||
for key in choices:
|
||||
parser.add_option('--%s' % key, dest=key,
|
||||
action='store_true', default=False,
|
||||
help="display choices for %s" % key)
|
||||
options, args = parser.parse_args()
|
||||
|
||||
# args are JSON blobs to override info
|
||||
if args:
|
||||
try:
|
||||
from json import loads
|
||||
except ImportError:
|
||||
try:
|
||||
from simplejson import loads
|
||||
except ImportError:
|
||||
def loads(string):
|
||||
"""*really* simple json; will not work with unicode"""
|
||||
return eval(string, {'true': True, 'false': False, 'null': None})
|
||||
for arg in args:
|
||||
if _os.path.exists(arg):
|
||||
string = file(arg).read()
|
||||
else:
|
||||
string = arg
|
||||
update(loads(string))
|
||||
|
||||
# print out choices if requested
|
||||
flag = False
|
||||
for key, value in options.__dict__.items():
|
||||
if value is True:
|
||||
print '%s choices: %s' % (key, ' '.join([str(choice)
|
||||
for choice in choices[key]]))
|
||||
flag = True
|
||||
if flag: return
|
||||
|
||||
# otherwise, print out all info
|
||||
for key, value in info.items():
|
||||
print '%s: %s' % (key, value)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
11
build/tests/filter-example.ini
Normal file
11
build/tests/filter-example.ini
Normal file
@ -0,0 +1,11 @@
|
||||
# illustrate test filters based on various categories
|
||||
|
||||
[windowstest]
|
||||
run-if = os == 'win'
|
||||
|
||||
[fleem]
|
||||
skip-if = os == 'mac'
|
||||
|
||||
[linuxtest]
|
||||
skip-if = (os == 'mac') || (os == 'win')
|
||||
fail-if = toolkit == 'cocoa'
|
1
build/tests/fleem
Normal file
1
build/tests/fleem
Normal file
@ -0,0 +1 @@
|
||||
# dummy spot for "fleem" test
|
11
build/tests/include-example.ini
Normal file
11
build/tests/include-example.ini
Normal file
@ -0,0 +1,11 @@
|
||||
[DEFAULT]
|
||||
foo = bar
|
||||
|
||||
[include:include/bar.ini]
|
||||
|
||||
[fleem]
|
||||
|
||||
[include:include/foo.ini]
|
||||
red = roses
|
||||
blue = violets
|
||||
yellow = daffodils
|
4
build/tests/include/bar.ini
Normal file
4
build/tests/include/bar.ini
Normal file
@ -0,0 +1,4 @@
|
||||
[DEFAULT]
|
||||
foo = fleem
|
||||
|
||||
[crash-handling]
|
1
build/tests/include/crash-handling
Normal file
1
build/tests/include/crash-handling
Normal file
@ -0,0 +1 @@
|
||||
# dummy spot for "crash-handling" test
|
1
build/tests/include/flowers
Normal file
1
build/tests/include/flowers
Normal file
@ -0,0 +1 @@
|
||||
# dummy spot for "flowers" test
|
5
build/tests/include/foo.ini
Normal file
5
build/tests/include/foo.ini
Normal file
@ -0,0 +1,5 @@
|
||||
[DEFAULT]
|
||||
blue = ocean
|
||||
|
||||
[flowers]
|
||||
yellow = submarine
|
80
build/tests/mozmill-example.ini
Normal file
80
build/tests/mozmill-example.ini
Normal file
@ -0,0 +1,80 @@
|
||||
[testAddons/testDisableEnablePlugin.js]
|
||||
[testAddons/testGetAddons.js]
|
||||
[testAddons/testSearchAddons.js]
|
||||
[testAwesomeBar/testAccessLocationBar.js]
|
||||
[testAwesomeBar/testCheckItemHighlight.js]
|
||||
[testAwesomeBar/testEscapeAutocomplete.js]
|
||||
[testAwesomeBar/testFaviconInAutocomplete.js]
|
||||
[testAwesomeBar/testGoButton.js]
|
||||
[testAwesomeBar/testLocationBarSearches.js]
|
||||
[testAwesomeBar/testPasteLocationBar.js]
|
||||
[testAwesomeBar/testSuggestHistoryBookmarks.js]
|
||||
[testAwesomeBar/testVisibleItemsMax.js]
|
||||
[testBookmarks/testAddBookmarkToMenu.js]
|
||||
[testCookies/testDisableCookies.js]
|
||||
[testCookies/testEnableCookies.js]
|
||||
[testCookies/testRemoveAllCookies.js]
|
||||
[testCookies/testRemoveCookie.js]
|
||||
[testDownloading/testCloseDownloadManager.js]
|
||||
[testDownloading/testDownloadStates.js]
|
||||
[testDownloading/testOpenDownloadManager.js]
|
||||
[testFindInPage/testFindInPage.js]
|
||||
[testFormManager/testAutoCompleteOff.js]
|
||||
[testFormManager/testBasicFormCompletion.js]
|
||||
[testFormManager/testClearFormHistory.js]
|
||||
[testFormManager/testDisableFormManager.js]
|
||||
[testGeneral/testGoogleSuggestions.js]
|
||||
[testGeneral/testStopReloadButtons.js]
|
||||
[testInstallation/testBreakpadInstalled.js]
|
||||
[testLayout/testNavigateFTP.js]
|
||||
[testPasswordManager/testPasswordNotSaved.js]
|
||||
[testPasswordManager/testPasswordSavedAndDeleted.js]
|
||||
[testPopups/testPopupsAllowed.js]
|
||||
[testPopups/testPopupsBlocked.js]
|
||||
[testPreferences/testPaneRetention.js]
|
||||
[testPreferences/testPreferredLanguage.js]
|
||||
[testPreferences/testRestoreHomepageToDefault.js]
|
||||
[testPreferences/testSetToCurrentPage.js]
|
||||
[testPreferences/testSwitchPanes.js]
|
||||
[testPrivateBrowsing/testAboutPrivateBrowsing.js]
|
||||
[testPrivateBrowsing/testCloseWindow.js]
|
||||
[testPrivateBrowsing/testDisabledElements.js]
|
||||
[testPrivateBrowsing/testDisabledPermissions.js]
|
||||
[testPrivateBrowsing/testDownloadManagerClosed.js]
|
||||
[testPrivateBrowsing/testGeolocation.js]
|
||||
[testPrivateBrowsing/testStartStopPBMode.js]
|
||||
[testPrivateBrowsing/testTabRestoration.js]
|
||||
[testPrivateBrowsing/testTabsDismissedOnStop.js]
|
||||
[testSearch/testAddMozSearchProvider.js]
|
||||
[testSearch/testFocusAndSearch.js]
|
||||
[testSearch/testGetMoreSearchEngines.js]
|
||||
[testSearch/testOpenSearchAutodiscovery.js]
|
||||
[testSearch/testRemoveSearchEngine.js]
|
||||
[testSearch/testReorderSearchEngines.js]
|
||||
[testSearch/testRestoreDefaults.js]
|
||||
[testSearch/testSearchSelection.js]
|
||||
[testSearch/testSearchSuggestions.js]
|
||||
[testSecurity/testBlueLarry.js]
|
||||
[testSecurity/testDefaultPhishingEnabled.js]
|
||||
[testSecurity/testDefaultSecurityPrefs.js]
|
||||
[testSecurity/testEncryptedPageWarning.js]
|
||||
[testSecurity/testGreenLarry.js]
|
||||
[testSecurity/testGreyLarry.js]
|
||||
[testSecurity/testIdentityPopupOpenClose.js]
|
||||
[testSecurity/testSSLDisabledErrorPage.js]
|
||||
[testSecurity/testSafeBrowsingNotificationBar.js]
|
||||
[testSecurity/testSafeBrowsingWarningPages.js]
|
||||
[testSecurity/testSecurityInfoViaMoreInformation.js]
|
||||
[testSecurity/testSecurityNotification.js]
|
||||
[testSecurity/testSubmitUnencryptedInfoWarning.js]
|
||||
[testSecurity/testUnknownIssuer.js]
|
||||
[testSecurity/testUntrustedConnectionErrorPage.js]
|
||||
[testSessionStore/testUndoTabFromContextMenu.js]
|
||||
[testTabbedBrowsing/testBackgroundTabScrolling.js]
|
||||
[testTabbedBrowsing/testCloseTab.js]
|
||||
[testTabbedBrowsing/testNewTab.js]
|
||||
[testTabbedBrowsing/testNewWindow.js]
|
||||
[testTabbedBrowsing/testOpenInBackground.js]
|
||||
[testTabbedBrowsing/testOpenInForeground.js]
|
||||
[testTechnicalTools/testAccessPageInfoDialog.js]
|
||||
[testToolbar/testBackForwardButtons.js]
|
26
build/tests/mozmill-restart-example.ini
Normal file
26
build/tests/mozmill-restart-example.ini
Normal file
@ -0,0 +1,26 @@
|
||||
[DEFAULT]
|
||||
type = restart
|
||||
|
||||
[restartTests/testExtensionInstallUninstall/test2.js]
|
||||
foo = bar
|
||||
|
||||
[restartTests/testExtensionInstallUninstall/test1.js]
|
||||
foo = baz
|
||||
|
||||
[restartTests/testExtensionInstallUninstall/test3.js]
|
||||
[restartTests/testSoftwareUpdateAutoProxy/test2.js]
|
||||
[restartTests/testSoftwareUpdateAutoProxy/test1.js]
|
||||
[restartTests/testMasterPassword/test1.js]
|
||||
[restartTests/testExtensionInstallGetAddons/test2.js]
|
||||
[restartTests/testExtensionInstallGetAddons/test1.js]
|
||||
[restartTests/testMultipleExtensionInstallation/test2.js]
|
||||
[restartTests/testMultipleExtensionInstallation/test1.js]
|
||||
[restartTests/testThemeInstallUninstall/test2.js]
|
||||
[restartTests/testThemeInstallUninstall/test1.js]
|
||||
[restartTests/testThemeInstallUninstall/test3.js]
|
||||
[restartTests/testDefaultBookmarks/test1.js]
|
||||
[softwareUpdate/testFallbackUpdate/test2.js]
|
||||
[softwareUpdate/testFallbackUpdate/test1.js]
|
||||
[softwareUpdate/testFallbackUpdate/test3.js]
|
||||
[softwareUpdate/testDirectUpdate/test2.js]
|
||||
[softwareUpdate/testDirectUpdate/test1.js]
|
2
build/tests/path-example.ini
Normal file
2
build/tests/path-example.ini
Normal file
@ -0,0 +1,2 @@
|
||||
[foo]
|
||||
path = fleem
|
104
build/tests/test.py
Executable file
104
build/tests/test.py
Executable file
@ -0,0 +1,104 @@
|
||||
#!/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 *****
|
||||
|
||||
"""tests for ManifestDestiny"""
|
||||
|
||||
import doctest
|
||||
import os
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
|
||||
def run_tests(raise_on_error=False, report_first=False):
|
||||
|
||||
# add results here
|
||||
results = {}
|
||||
|
||||
# doctest arguments
|
||||
directory = os.path.dirname(os.path.abspath(__file__))
|
||||
extraglobs = {}
|
||||
doctest_args = dict(extraglobs=extraglobs,
|
||||
module_relative=False,
|
||||
raise_on_error=raise_on_error)
|
||||
if report_first:
|
||||
doctest_args['optionflags'] = doctest.REPORT_ONLY_FIRST_FAILURE
|
||||
|
||||
# gather tests
|
||||
directory = os.path.dirname(os.path.abspath(__file__))
|
||||
tests = [ test for test in os.listdir(directory)
|
||||
if test.endswith('.txt') and test.startswith('test_')]
|
||||
os.chdir(directory)
|
||||
|
||||
# run the tests
|
||||
for test in tests:
|
||||
try:
|
||||
results[test] = doctest.testfile(test, **doctest_args)
|
||||
except doctest.DocTestFailure, failure:
|
||||
raise
|
||||
except doctest.UnexpectedException, failure:
|
||||
raise failure.exc_info[0], failure.exc_info[1], failure.exc_info[2]
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def main(args=sys.argv[1:]):
|
||||
|
||||
# parse command line options
|
||||
parser = OptionParser(description=__doc__)
|
||||
parser.add_option('--raise', dest='raise_on_error',
|
||||
default=False, action='store_true',
|
||||
help="raise on first error")
|
||||
parser.add_option('--report-first', dest='report_first',
|
||||
default=False, action='store_true',
|
||||
help="report the first error only (all tests will still run)")
|
||||
options, args = parser.parse_args(args)
|
||||
|
||||
# run the tests
|
||||
results = run_tests(**options.__dict__)
|
||||
|
||||
# check for failure
|
||||
failed = False
|
||||
for result in results.values():
|
||||
if result[0]: # failure count; http://docs.python.org/library/doctest.html#basic-api
|
||||
failed = True
|
||||
break
|
||||
if failed:
|
||||
sys.exit(1) # error
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
120
build/tests/test_expressionparser.txt
Normal file
120
build/tests/test_expressionparser.txt
Normal file
@ -0,0 +1,120 @@
|
||||
Test Expressionparser
|
||||
=====================
|
||||
|
||||
Test the conditional expression parser.
|
||||
|
||||
Boilerplate::
|
||||
|
||||
>>> from manifestparser import parse
|
||||
|
||||
Test basic values::
|
||||
|
||||
>>> parse("1")
|
||||
1
|
||||
>>> parse("100")
|
||||
100
|
||||
>>> parse("true")
|
||||
True
|
||||
>>> parse("false")
|
||||
False
|
||||
>>> '' == parse('""')
|
||||
True
|
||||
>>> parse('"foo bar"')
|
||||
'foo bar'
|
||||
>>> parse("'foo bar'")
|
||||
'foo bar'
|
||||
>>> parse("foo", foo=1)
|
||||
1
|
||||
>>> parse("bar", bar=True)
|
||||
True
|
||||
>>> parse("abc123", abc123="xyz")
|
||||
'xyz'
|
||||
|
||||
Test equality::
|
||||
|
||||
>>> parse("true == true")
|
||||
True
|
||||
>>> parse("false == false")
|
||||
True
|
||||
>>> parse("false == false")
|
||||
True
|
||||
>>> parse("1 == 1")
|
||||
True
|
||||
>>> parse("100 == 100")
|
||||
True
|
||||
>>> parse('"some text" == "some text"')
|
||||
True
|
||||
>>> parse("true != false")
|
||||
True
|
||||
>>> parse("1 != 2")
|
||||
True
|
||||
>>> parse('"text" != "other text"')
|
||||
True
|
||||
>>> parse("foo == true", foo=True)
|
||||
True
|
||||
>>> parse("foo == 1", foo=1)
|
||||
True
|
||||
>>> parse('foo == "bar"', foo='bar')
|
||||
True
|
||||
>>> parse("foo == bar", foo=True, bar=True)
|
||||
True
|
||||
>>> parse("true == foo", foo=True)
|
||||
True
|
||||
>>> parse("foo != true", foo=False)
|
||||
True
|
||||
>>> parse("foo != 2", foo=1)
|
||||
True
|
||||
>>> parse('foo != "bar"', foo='abc')
|
||||
True
|
||||
>>> parse("foo != bar", foo=True, bar=False)
|
||||
True
|
||||
>>> parse("true != foo", foo=False)
|
||||
True
|
||||
>>> parse("!false")
|
||||
True
|
||||
|
||||
Test conjunctions::
|
||||
|
||||
>>> parse("true && true")
|
||||
True
|
||||
>>> parse("true || false")
|
||||
True
|
||||
>>> parse("false || false")
|
||||
False
|
||||
>>> parse("true && false")
|
||||
False
|
||||
>>> parse("true || false && false")
|
||||
True
|
||||
|
||||
Test parentheses::
|
||||
|
||||
>>> parse("(true)")
|
||||
True
|
||||
>>> parse("(10)")
|
||||
10
|
||||
>>> parse('("foo")')
|
||||
'foo'
|
||||
>>> parse("(foo)", foo=1)
|
||||
1
|
||||
>>> parse("(true == true)")
|
||||
True
|
||||
>>> parse("(true != false)")
|
||||
True
|
||||
>>> parse("(true && true)")
|
||||
True
|
||||
>>> parse("(true || false)")
|
||||
True
|
||||
>>> parse("(true && true || false)")
|
||||
True
|
||||
>>> parse("(true || false) && false")
|
||||
False
|
||||
>>> parse("(true || false) && true")
|
||||
True
|
||||
>>> parse("true && (true || false)")
|
||||
True
|
||||
>>> parse("true && (true || false)")
|
||||
True
|
||||
>>> parse("(true && false) || (true && (true || false))")
|
||||
True
|
||||
|
||||
|
217
build/tests/test_manifestparser.txt
Normal file
217
build/tests/test_manifestparser.txt
Normal file
@ -0,0 +1,217 @@
|
||||
Test the manifest parser
|
||||
========================
|
||||
|
||||
You must have ManifestDestiny installed before running these tests.
|
||||
Run ``python manifestparser.py setup develop`` with setuptools installed.
|
||||
|
||||
Ensure basic parser is sane::
|
||||
|
||||
>>> from manifestparser import ManifestParser
|
||||
>>> parser = ManifestParser()
|
||||
>>> parser.read('mozmill-example.ini')
|
||||
>>> tests = parser.tests
|
||||
>>> len(tests) == len(file('mozmill-example.ini').read().strip().splitlines())
|
||||
True
|
||||
|
||||
Ensure that capitalization and order aren't an issue:
|
||||
|
||||
>>> lines = ['[%s]' % test['name'] for test in tests]
|
||||
>>> lines == file('mozmill-example.ini').read().strip().splitlines()
|
||||
True
|
||||
|
||||
Show how you select subsets of tests:
|
||||
|
||||
>>> parser.read('mozmill-restart-example.ini')
|
||||
>>> restart_tests = parser.get(type='restart')
|
||||
>>> len(restart_tests) < len(parser.tests)
|
||||
True
|
||||
>>> import os
|
||||
>>> len(restart_tests) == len(parser.get(manifest=os.path.abspath('mozmill-restart-example.ini')))
|
||||
True
|
||||
>>> assert not [test for test in restart_tests if test['manifest'] != os.path.abspath('mozmill-restart-example.ini')]
|
||||
>>> parser.get('name', tags=['foo'])
|
||||
['restartTests/testExtensionInstallUninstall/test2.js', 'restartTests/testExtensionInstallUninstall/test1.js']
|
||||
>>> parser.get('name', foo='bar')
|
||||
['restartTests/testExtensionInstallUninstall/test2.js']
|
||||
|
||||
Illustrate how include works::
|
||||
|
||||
>>> parser = ManifestParser(manifests=('include-example.ini',))
|
||||
|
||||
All of the tests should be included, in order::
|
||||
|
||||
>>> parser.get('name')
|
||||
['crash-handling', 'fleem', 'flowers']
|
||||
>>> [(test['name'], os.path.basename(test['manifest'])) for test in parser.tests]
|
||||
[('crash-handling', 'bar.ini'), ('fleem', 'include-example.ini'), ('flowers', 'foo.ini')]
|
||||
|
||||
The manifests should be there too::
|
||||
|
||||
>>> len(parser.manifests())
|
||||
3
|
||||
|
||||
We're already in the root directory::
|
||||
|
||||
>>> os.getcwd() == parser.rootdir
|
||||
True
|
||||
|
||||
DEFAULT values should persist across includes, unless they're
|
||||
overwritten. In this example, include-example.ini sets foo=bar, but
|
||||
its overridden to fleem in bar.ini::
|
||||
|
||||
>>> parser.get('name', foo='bar')
|
||||
['fleem', 'flowers']
|
||||
>>> parser.get('name', foo='fleem')
|
||||
['crash-handling']
|
||||
|
||||
Passing parameters in the include section allows defining variables in
|
||||
the submodule scope:
|
||||
|
||||
>>> parser.get('name', tags=['red'])
|
||||
['flowers']
|
||||
|
||||
However, this should be overridable from the DEFAULT section in the
|
||||
included file and that overridable via the key directly connected to
|
||||
the test::
|
||||
|
||||
>>> parser.get(name='flowers')[0]['blue']
|
||||
'ocean'
|
||||
>>> parser.get(name='flowers')[0]['yellow']
|
||||
'submarine'
|
||||
|
||||
You can query multiple times if you need to::
|
||||
|
||||
>>> flowers = parser.get(foo='bar')
|
||||
>>> len(flowers)
|
||||
2
|
||||
>>> roses = parser.get(tests=flowers, red='roses')
|
||||
|
||||
Using the inverse flag should invert the set of tests returned::
|
||||
|
||||
>>> parser.get('name', inverse=True, tags=['red'])
|
||||
['crash-handling', 'fleem']
|
||||
|
||||
All of the included tests actually exist::
|
||||
|
||||
>>> [i['name'] for i in parser.missing()]
|
||||
[]
|
||||
|
||||
Write the output to a manifest:
|
||||
|
||||
>>> from StringIO import StringIO
|
||||
>>> buffer = StringIO()
|
||||
>>> parser.write(fp=buffer, global_kwargs={'foo': 'bar'})
|
||||
>>> buffer.getvalue().strip()
|
||||
'[DEFAULT]\nfoo = bar\n\n[fleem]\n\n[include/flowers]\nblue = ocean\nred = roses\nyellow = submarine'
|
||||
|
||||
Test our ability to convert a static directory structure to a
|
||||
manifest. First, stub out a directory with files in it::
|
||||
|
||||
>>> import shutil, tempfile
|
||||
>>> def create_stub():
|
||||
... directory = tempfile.mkdtemp()
|
||||
... for i in 'foo', 'bar', 'fleem':
|
||||
... file(os.path.join(directory, i), 'w').write(i)
|
||||
... subdir = os.path.join(directory, 'subdir')
|
||||
... os.mkdir(subdir)
|
||||
... file(os.path.join(subdir, 'subfile'), 'w').write('baz')
|
||||
... return directory
|
||||
>>> stub = create_stub()
|
||||
>>> os.path.exists(stub) and os.path.isdir(stub)
|
||||
True
|
||||
|
||||
Make a manifest for it::
|
||||
|
||||
>>> from manifestparser import convert
|
||||
>>> print convert([stub])
|
||||
[bar]
|
||||
[fleem]
|
||||
[foo]
|
||||
[subdir/subfile]
|
||||
>>> shutil.rmtree(stub)
|
||||
|
||||
Now do the same thing but keep the manifests in place::
|
||||
|
||||
>>> stub = create_stub()
|
||||
>>> convert([stub], write='manifest.ini')
|
||||
>>> sorted(os.listdir(stub))
|
||||
['bar', 'fleem', 'foo', 'manifest.ini', 'subdir']
|
||||
>>> parser = ManifestParser()
|
||||
>>> parser.read(os.path.join(stub, 'manifest.ini'))
|
||||
>>> [i['name'] for i in parser.tests]
|
||||
['subfile', 'bar', 'fleem', 'foo']
|
||||
>>> parser = ManifestParser()
|
||||
>>> parser.read(os.path.join(stub, 'subdir', 'manifest.ini'))
|
||||
>>> len(parser.tests)
|
||||
1
|
||||
>>> parser.tests[0]['name']
|
||||
'subfile'
|
||||
>>> shutil.rmtree(stub)
|
||||
|
||||
Test our ability to copy a set of manifests::
|
||||
|
||||
>>> tempdir = tempfile.mkdtemp()
|
||||
>>> manifest = ManifestParser(manifests=('include-example.ini',))
|
||||
>>> manifest.copy(tempdir)
|
||||
>>> sorted(os.listdir(tempdir))
|
||||
['fleem', 'include', 'include-example.ini']
|
||||
>>> sorted(os.listdir(os.path.join(tempdir, 'include')))
|
||||
['bar.ini', 'crash-handling', 'flowers', 'foo.ini']
|
||||
>>> from_manifest = ManifestParser(manifests=('include-example.ini',))
|
||||
>>> to_manifest = os.path.join(tempdir, 'include-example.ini')
|
||||
>>> to_manifest = ManifestParser(manifests=(to_manifest,))
|
||||
>>> to_manifest.get('name') == from_manifest.get('name')
|
||||
True
|
||||
>>> shutil.rmtree(tempdir)
|
||||
|
||||
Test our ability to update tests from a manifest and a directory of
|
||||
files::
|
||||
|
||||
>>> tempdir = tempfile.mkdtemp()
|
||||
>>> for i in range(10):
|
||||
... file(os.path.join(tempdir, str(i)), 'w').write(str(i))
|
||||
|
||||
First, make a manifest::
|
||||
|
||||
>>> manifest = convert([tempdir])
|
||||
>>> newtempdir = tempfile.mkdtemp()
|
||||
>>> manifest_file = os.path.join(newtempdir, 'manifest.ini')
|
||||
>>> file(manifest_file,'w').write(manifest)
|
||||
>>> manifest = ManifestParser(manifests=(manifest_file,))
|
||||
>>> manifest.get('name') == [str(i) for i in range(10)]
|
||||
True
|
||||
|
||||
All of the tests are initially missing::
|
||||
|
||||
>>> [i['name'] for i in manifest.missing()] == [str(i) for i in range(10)]
|
||||
True
|
||||
|
||||
But then we copy one over::
|
||||
|
||||
>>> manifest.get('name', name='1')
|
||||
['1']
|
||||
>>> manifest.update(tempdir, name='1')
|
||||
>>> sorted(os.listdir(newtempdir))
|
||||
['1', 'manifest.ini']
|
||||
|
||||
Update that one file and copy all the "tests"::
|
||||
|
||||
>>> file(os.path.join(tempdir, '1'), 'w').write('secret door')
|
||||
>>> manifest.update(tempdir)
|
||||
>>> sorted(os.listdir(newtempdir))
|
||||
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'manifest.ini']
|
||||
>>> file(os.path.join(newtempdir, '1')).read().strip()
|
||||
'secret door'
|
||||
|
||||
Clean up::
|
||||
|
||||
>>> shutil.rmtree(tempdir)
|
||||
>>> shutil.rmtree(newtempdir)
|
||||
|
||||
You can override the path in the section too. This shows that you can
|
||||
use a relative path::
|
||||
|
||||
>>> manifest = ManifestParser(manifests=('path-example.ini',))
|
||||
>>> manifest.tests[0]['path'] == os.path.abspath('fleem')
|
||||
True
|
||||
|
32
build/tests/test_testmanifest.txt
Normal file
32
build/tests/test_testmanifest.txt
Normal file
@ -0,0 +1,32 @@
|
||||
Test the Test Manifest
|
||||
======================
|
||||
|
||||
Boilerplate::
|
||||
|
||||
>>> import os
|
||||
|
||||
Test filtering based on platform::
|
||||
|
||||
>>> from manifestparser import TestManifest
|
||||
>>> manifest = TestManifest(manifests=('filter-example.ini',))
|
||||
>>> [i['name'] for i in manifest.active_tests(os='win', disabled=False, exists=False)]
|
||||
['windowstest', 'fleem']
|
||||
>>> [i['name'] for i in manifest.active_tests(os='linux', disabled=False, exists=False)]
|
||||
['fleem', 'linuxtest']
|
||||
|
||||
Look for existing tests. There is only one::
|
||||
|
||||
>>> [i['name'] for i in manifest.active_tests()]
|
||||
['fleem']
|
||||
|
||||
You should be able to expect failures::
|
||||
|
||||
>>> last_test = manifest.active_tests(exists=False, toolkit='gtk2')[-1]
|
||||
>>> last_test['name']
|
||||
'linuxtest'
|
||||
>>> last_test['expected']
|
||||
'pass'
|
||||
>>> last_test = manifest.active_tests(exists=False, toolkit='cocoa')[-1]
|
||||
>>> last_test['expected']
|
||||
'fail'
|
||||
|
@ -37,4 +37,6 @@ sys.argv.pop(0)
|
||||
script = sys.argv[0]
|
||||
|
||||
sys.path[0:0] = [os.path.dirname(script)] + paths
|
||||
execfile(script, {'__name__': '__main__', '__file__': script})
|
||||
__name__ = '__main__'
|
||||
__file__ = script
|
||||
execfile(script)
|
||||
|
@ -156,6 +156,7 @@ xpcshell-tests:
|
||||
-I$(topsrcdir)/build \
|
||||
$(testxpcsrcdir)/runxpcshelltests.py \
|
||||
--symbols-path=$(DIST)/crashreporter-symbols \
|
||||
--build-info-json=$(DEPTH)/mozinfo.json \
|
||||
$(EXTRA_TEST_ARGS) \
|
||||
$(LIBXUL_DIST)/bin/xpcshell \
|
||||
$(foreach dir,$(XPCSHELL_TESTS),$(testxpcobjdir)/$(relativesrcdir)/$(dir))
|
||||
@ -168,6 +169,7 @@ check-interactive:
|
||||
-I$(topsrcdir)/build \
|
||||
$(testxpcsrcdir)/runxpcshelltests.py \
|
||||
--symbols-path=$(DIST)/crashreporter-symbols \
|
||||
--build-info-json=$(DEPTH)/mozinfo.json \
|
||||
--test-path=$(SOLO_FILE) \
|
||||
--profile-name=$(MOZ_APP_NAME) \
|
||||
--interactive \
|
||||
@ -180,6 +182,7 @@ check-one:
|
||||
-I$(topsrcdir)/build \
|
||||
$(testxpcsrcdir)/runxpcshelltests.py \
|
||||
--symbols-path=$(DIST)/crashreporter-symbols \
|
||||
--build-info-json=$(DEPTH)/mozinfo.json \
|
||||
--test-path=$(SOLO_FILE) \
|
||||
--profile-name=$(MOZ_APP_NAME) \
|
||||
--verbose \
|
||||
|
@ -166,6 +166,7 @@ xpcshell-tests:
|
||||
-I$(topsrcdir)/build \
|
||||
$(topsrcdir)/testing/xpcshell/runxpcshelltests.py \
|
||||
--manifest=$(DEPTH)/_tests/xpcshell/xpcshell.ini \
|
||||
--build-info-json=$(DEPTH)/mozinfo.json \
|
||||
--no-logfiles \
|
||||
$(SYMBOLS_PATH) \
|
||||
$(TEST_PATH_ARG) $(EXTRA_TEST_ARGS) \
|
||||
|
@ -64,6 +64,7 @@ TEST_HARNESS_FILES := \
|
||||
EXTRA_BUILD_FILES := \
|
||||
automationutils.py \
|
||||
manifestparser.py \
|
||||
mozinfo.py \
|
||||
poster.zip \
|
||||
$(NULL)
|
||||
|
||||
@ -86,10 +87,16 @@ libs::
|
||||
$(INSTALL) $(srcdir)/xpcshell.ini $(DEPTH)/_tests/xpcshell
|
||||
cp $(srcdir)/xpcshell.ini $(DEPTH)/_tests/xpcshell/all-test-dirs.list
|
||||
|
||||
# Run selftests
|
||||
check::
|
||||
OBJDIR=$(DEPTH) $(PYTHON) $(topsrcdir)/config/pythonpath.py \
|
||||
-I$(topsrcdir)/build $(srcdir)/selftest.py
|
||||
|
||||
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 -)
|
||||
@cp $(DEPTH)/mozinfo.json $(PKG_STAGE)/xpcshell
|
||||
@(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 -)
|
||||
|
4
testing/xpcshell/example/unit/test_fail.js
Normal file
4
testing/xpcshell/example/unit/test_fail.js
Normal file
@ -0,0 +1,4 @@
|
||||
function run_test() {
|
||||
// This test expects to fail.
|
||||
do_check_true(false);
|
||||
}
|
4
testing/xpcshell/example/unit/test_skip.js
Normal file
4
testing/xpcshell/example/unit/test_skip.js
Normal file
@ -0,0 +1,4 @@
|
||||
function run_test() {
|
||||
// This test expects to fail.
|
||||
do_check_true(false);
|
||||
}
|
@ -11,3 +11,9 @@ tail =
|
||||
[test_location.js]
|
||||
[test_profile.js]
|
||||
[test_sample.js]
|
||||
|
||||
[test_fail.js]
|
||||
fail-if = true
|
||||
|
||||
[test_skip.js]
|
||||
skip-if = true
|
||||
|
@ -44,9 +44,17 @@ from optparse import OptionParser
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
from tempfile import mkdtemp, gettempdir
|
||||
import manifestparser
|
||||
import mozinfo
|
||||
|
||||
from automationutils import *
|
||||
|
||||
#TODO: replace this with json.loads when Python 2.6 is required.
|
||||
def parse_json(j):
|
||||
"""
|
||||
Awful hack to parse a restricted subset of JSON strings into Python dicts.
|
||||
"""
|
||||
return eval(j, {'true':True,'false':False,'null':None})
|
||||
|
||||
""" Control-C handling """
|
||||
gotSIGINT = False
|
||||
def markGotSIGINT(signum, stackFrame):
|
||||
@ -58,9 +66,9 @@ class XPCShellTests(object):
|
||||
log = logging.getLogger()
|
||||
oldcwd = os.getcwd()
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, log=sys.stdout):
|
||||
""" Init logging """
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
handler = logging.StreamHandler(log)
|
||||
self.log.setLevel(logging.INFO)
|
||||
self.log.addHandler(handler)
|
||||
|
||||
@ -71,7 +79,7 @@ class XPCShellTests(object):
|
||||
|
||||
if we are chunking tests, it will be done here as well
|
||||
"""
|
||||
mp = manifestparser.ManifestParser(strict=False)
|
||||
mp = manifestparser.TestManifest(strict=False)
|
||||
if self.manifest is None:
|
||||
for testdir in self.testdirs:
|
||||
if testdir:
|
||||
@ -80,7 +88,7 @@ class XPCShellTests(object):
|
||||
mp.read(self.manifest)
|
||||
self.buildTestPath()
|
||||
|
||||
self.alltests = mp.tests
|
||||
self.alltests = mp.active_tests(**mozinfo.info)
|
||||
|
||||
if self.singleFile is None and self.totalChunks > 1:
|
||||
self.chunkTests()
|
||||
@ -139,6 +147,9 @@ class XPCShellTests(object):
|
||||
else:
|
||||
self.xrePath = os.path.abspath(self.xrePath)
|
||||
|
||||
if self.mozInfo is None:
|
||||
self.mozInfo = os.path.join(self.testharnessdir, "mozinfo.json")
|
||||
|
||||
def buildEnvironment(self):
|
||||
"""
|
||||
Create and returns a dictionary of self.env to include all the appropriate env variables and values.
|
||||
@ -281,7 +292,7 @@ class XPCShellTests(object):
|
||||
profileDir = mkdtemp()
|
||||
self.env["XPCSHELL_TEST_PROFILE_DIR"] = profileDir
|
||||
if self.interactive or self.singleFile:
|
||||
print "TEST-INFO | profile dir is %s" % profileDir
|
||||
self.log.info("TEST-INFO | profile dir is %s" % profileDir)
|
||||
return profileDir
|
||||
|
||||
def setupLeakLogging(self):
|
||||
@ -373,7 +384,7 @@ class XPCShellTests(object):
|
||||
interactive=False, verbose=False, keepGoing=False, logfiles=True,
|
||||
thisChunk=1, totalChunks=1, debugger=None,
|
||||
debuggerArgs=None, debuggerInteractive=False,
|
||||
profileName=None):
|
||||
profileName=None, mozInfo=None):
|
||||
"""Run xpcshell tests.
|
||||
|
||||
|xpcshell|, is the xpcshell executable to use to run the tests.
|
||||
@ -394,7 +405,8 @@ class XPCShellTests(object):
|
||||
|debuggerInfo|, if set, specifies the debugger and debugger arguments
|
||||
that will be used to launch xpcshell.
|
||||
|profileName|, if set, specifies the name of the application for the profile
|
||||
directory if running only a subset of tests
|
||||
directory if running only a subset of tests.
|
||||
|mozInfo|, if set, specifies specifies build configuration information, either as a filename containing JSON, or a dict.
|
||||
"""
|
||||
|
||||
global gotSIGINT
|
||||
@ -413,6 +425,7 @@ class XPCShellTests(object):
|
||||
self.thisChunk = thisChunk
|
||||
self.debuggerInfo = getDebuggerInfo(self.oldcwd, debugger, debuggerArgs, debuggerInteractive)
|
||||
self.profileName = profileName or "xpcshell"
|
||||
self.mozInfo = mozInfo
|
||||
|
||||
# If we have an interactive debugger, disable ctrl-c.
|
||||
if self.debuggerInfo and self.debuggerInfo["interactive"]:
|
||||
@ -420,15 +433,27 @@ class XPCShellTests(object):
|
||||
|
||||
if not testdirs and not manifest:
|
||||
# nothing to test!
|
||||
print >>sys.stderr, "Error: No test dirs or test manifest specified!"
|
||||
self.log.error("Error: No test dirs or test manifest specified!")
|
||||
return False
|
||||
|
||||
passCount = 0
|
||||
failCount = 0
|
||||
self.testCount = 0
|
||||
self.passCount = 0
|
||||
self.failCount = 0
|
||||
self.todoCount = 0
|
||||
|
||||
self.setAbsPath()
|
||||
self.buildXpcsRunArgs()
|
||||
self.buildEnvironment()
|
||||
|
||||
# Handle filenames in mozInfo
|
||||
if not isinstance(self.mozInfo, dict):
|
||||
mozInfoFile = self.mozInfo
|
||||
if not os.path.isfile(mozInfoFile):
|
||||
self.log.error("Error: couldn't find mozinfo.json at '%s'. Perhaps you need to use --build-info-json?" % mozInfoFile)
|
||||
return False
|
||||
self.mozInfo = parse_json(open(mozInfoFile).read())
|
||||
mozinfo.update(self.mozInfo)
|
||||
|
||||
pStdout, pStderr = self.getPipes()
|
||||
|
||||
self.buildTestList()
|
||||
@ -441,6 +466,16 @@ class XPCShellTests(object):
|
||||
if self.testPath and name.find(self.testPath) == -1:
|
||||
continue
|
||||
|
||||
self.testCount += 1
|
||||
|
||||
# Check for skipped tests
|
||||
if 'disabled' in test:
|
||||
self.log.info("TEST-INFO | skipping %s | %s" %
|
||||
(name, test['disabled']))
|
||||
continue
|
||||
# Check for known-fail tests
|
||||
expected = test['expected'] == 'pass'
|
||||
|
||||
testdir = os.path.dirname(name)
|
||||
self.buildXpcsCmd(testdir)
|
||||
testHeadFiles = self.getHeadFiles(test)
|
||||
@ -456,7 +491,7 @@ class XPCShellTests(object):
|
||||
replaceBackSlashes(name)]
|
||||
|
||||
try:
|
||||
print "TEST-INFO | %s | running test ..." % name
|
||||
self.log.info("TEST-INFO | %s | running test ..." % name)
|
||||
|
||||
proc = self.launchProcess(cmdH + cmdT + self.xpcsRunArgs,
|
||||
stdout=pStdout, stderr=pStderr, env=self.env, cwd=testdir)
|
||||
@ -474,22 +509,29 @@ class XPCShellTests(object):
|
||||
|
||||
def print_stdout(stdout):
|
||||
"""Print stdout line-by-line to avoid overflowing buffers."""
|
||||
print ">>>>>>>"
|
||||
self.log.info(">>>>>>>")
|
||||
for line in stdout.splitlines():
|
||||
print line
|
||||
print "<<<<<<<"
|
||||
self.log.info(line)
|
||||
self.log.info("<<<<<<<")
|
||||
|
||||
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))
|
||||
result = not ((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)))
|
||||
|
||||
if result != expected:
|
||||
self.log.error("TEST-UNEXPECTED-%s | %s | test failed (with xpcshell return code: %d), see following log:" % ("FAIL" if expected else "PASS", name, self.getReturnCode(proc)))
|
||||
print_stdout(stdout)
|
||||
failCount += 1
|
||||
self.failCount += 1
|
||||
else:
|
||||
print "TEST-PASS | %s | test passed" % name
|
||||
self.log.info("TEST-%s | %s | test passed" % ("PASS" if expected else "KNOWN-FAIL", name))
|
||||
if verbose:
|
||||
print_stdout(stdout)
|
||||
passCount += 1
|
||||
if expected:
|
||||
self.passCount += 1
|
||||
else:
|
||||
self.todoCount += 1
|
||||
|
||||
checkForCrashes(testdir, self.symbolsPath, testName=name)
|
||||
# Find child process(es) leak log(s), if any: See InitLog() in
|
||||
@ -509,24 +551,25 @@ class XPCShellTests(object):
|
||||
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"
|
||||
self.log.error("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
|
||||
if self.testCount == 0:
|
||||
self.log.error("TEST-UNEXPECTED-FAIL | runxpcshelltests.py | No tests run. Did you pass an invalid --test-path?")
|
||||
self.failCount = 1
|
||||
|
||||
print """INFO | Result summary:
|
||||
self.log.info("""INFO | Result summary:
|
||||
INFO | Passed: %d
|
||||
INFO | Failed: %d""" % (passCount, failCount)
|
||||
INFO | Failed: %d
|
||||
INFO | Todo: %d""" % (self.passCount, self.failCount, self.todoCount))
|
||||
|
||||
if gotSIGINT and not keepGoing:
|
||||
print "TEST-UNEXPECTED-FAIL | Received SIGINT (control-C), so stopped run. " \
|
||||
"(Use --keep-going to keep running tests after killing one with SIGINT)"
|
||||
log.error("TEST-UNEXPECTED-FAIL | Received SIGINT (control-C), so stopped run. " \
|
||||
"(Use --keep-going to keep running tests after killing one with SIGINT)")
|
||||
return False
|
||||
return failCount == 0
|
||||
return self.failCount == 0
|
||||
|
||||
class XPCShellOptions(OptionParser):
|
||||
def __init__(self):
|
||||
@ -564,6 +607,9 @@ class XPCShellOptions(OptionParser):
|
||||
self.add_option("--profile-name",
|
||||
type = "string", dest="profileName", default=None,
|
||||
help="name of application profile being tested")
|
||||
self.add_option("--build-info-json",
|
||||
type = "string", dest="mozInfo", default=None,
|
||||
help="path to a mozinfo.json including information about the build configuration. defaults to looking for mozinfo.json next to the script.")
|
||||
|
||||
def main():
|
||||
parser = XPCShellOptions()
|
||||
|
211
testing/xpcshell/selftest.py
Normal file
211
testing/xpcshell/selftest.py
Normal file
@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Any copyright is dedicated to the Public Domain.
|
||||
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||
#
|
||||
|
||||
import sys, os, unittest, tempfile, shutil
|
||||
from StringIO import StringIO
|
||||
|
||||
from runxpcshelltests import XPCShellTests
|
||||
|
||||
objdir = os.path.abspath(os.environ["OBJDIR"])
|
||||
xpcshellBin = os.path.join(objdir, "dist", "bin", "xpcshell")
|
||||
if sys.platform == "win32":
|
||||
xpcshellBin += ".exe"
|
||||
|
||||
SIMPLE_PASSING_TEST = "function run_test() { do_check_true(true); }"
|
||||
SIMPLE_FAILING_TEST = "function run_test() { do_check_true(false); }"
|
||||
|
||||
class XPCShellTestsTests(unittest.TestCase):
|
||||
"""
|
||||
Yes, these are unit tests for a unit test harness.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.log = StringIO()
|
||||
self.tempdir = tempfile.mkdtemp()
|
||||
self.x = XPCShellTests(log=self.log)
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tempdir)
|
||||
|
||||
def writeFile(self, name, contents):
|
||||
"""
|
||||
Write |contents| to a file named |name| in the temp directory,
|
||||
and return the full path to the file.
|
||||
"""
|
||||
fullpath = os.path.join(self.tempdir, name)
|
||||
with open(fullpath, "w") as f:
|
||||
f.write(contents)
|
||||
return fullpath
|
||||
|
||||
def writeManifest(self, tests):
|
||||
"""
|
||||
Write an xpcshell.ini in the temp directory and set
|
||||
self.manifest to its pathname. |tests| is a list containing
|
||||
either strings (for test names), or tuples with a test name
|
||||
as the first element and manifest conditions as the following
|
||||
elements.
|
||||
"""
|
||||
testlines = []
|
||||
for t in tests:
|
||||
testlines.append("[%s]" % (t if isinstance(t, basestring)
|
||||
else t[0]))
|
||||
if isinstance(t, tuple):
|
||||
testlines.extend(t[1:])
|
||||
self.manifest = self.writeFile("xpcshell.ini", """
|
||||
[DEFAULT]
|
||||
head =
|
||||
tail =
|
||||
|
||||
""" + "\n".join(testlines))
|
||||
|
||||
def assertTestResult(self, expected, mozInfo={}):
|
||||
"""
|
||||
Assert that self.x.runTests with manifest=self.manifest
|
||||
returns |expected|.
|
||||
"""
|
||||
self.assertEquals(expected,
|
||||
self.x.runTests(xpcshellBin,
|
||||
manifest=self.manifest,
|
||||
mozInfo=mozInfo),
|
||||
msg="""Tests should have %s, log:
|
||||
========
|
||||
%s
|
||||
========
|
||||
""" % ("passed" if expected else "failed", self.log.getvalue()))
|
||||
|
||||
def _assertLog(self, s, expected):
|
||||
l = self.log.getvalue()
|
||||
self.assertEqual(expected, s in l,
|
||||
msg="""Value %s %s in log:
|
||||
========
|
||||
%s
|
||||
========""" % (s, "expected" if expected else "not expected", l))
|
||||
|
||||
def assertInLog(self, s):
|
||||
"""
|
||||
Assert that the string |s| is contained in self.log.
|
||||
"""
|
||||
self._assertLog(s, True)
|
||||
|
||||
def assertNotInLog(self, s):
|
||||
"""
|
||||
Assert that the string |s| is not contained in self.log.
|
||||
"""
|
||||
self._assertLog(s, False)
|
||||
|
||||
def testPass(self):
|
||||
"""
|
||||
Check that a simple test without any manifest conditions passes.
|
||||
"""
|
||||
self.writeFile("test_basic.js", SIMPLE_PASSING_TEST)
|
||||
self.writeManifest(["test_basic.js"])
|
||||
|
||||
self.assertTestResult(True)
|
||||
self.assertEquals(1, self.x.testCount)
|
||||
self.assertEquals(1, self.x.passCount)
|
||||
self.assertEquals(0, self.x.failCount)
|
||||
self.assertEquals(0, self.x.todoCount)
|
||||
self.assertInLog("TEST-PASS")
|
||||
self.assertNotInLog("TEST-UNEXPECTED-FAIL")
|
||||
|
||||
def testFail(self):
|
||||
"""
|
||||
Check that a simple failing test without any manifest conditions fails.
|
||||
"""
|
||||
self.writeFile("test_basic.js", SIMPLE_FAILING_TEST)
|
||||
self.writeManifest(["test_basic.js"])
|
||||
|
||||
self.assertTestResult(False)
|
||||
self.assertEquals(1, self.x.testCount)
|
||||
self.assertEquals(0, self.x.passCount)
|
||||
self.assertEquals(1, self.x.failCount)
|
||||
self.assertEquals(0, self.x.todoCount)
|
||||
self.assertInLog("TEST-UNEXPECTED-FAIL")
|
||||
self.assertNotInLog("TEST-PASS")
|
||||
|
||||
def testPassFail(self):
|
||||
"""
|
||||
Check that running more than one test works.
|
||||
"""
|
||||
self.writeFile("test_pass.js", SIMPLE_PASSING_TEST)
|
||||
self.writeFile("test_fail.js", SIMPLE_FAILING_TEST)
|
||||
self.writeManifest(["test_pass.js", "test_fail.js"])
|
||||
|
||||
self.assertTestResult(False)
|
||||
self.assertEquals(2, self.x.testCount)
|
||||
self.assertEquals(1, self.x.passCount)
|
||||
self.assertEquals(1, self.x.failCount)
|
||||
self.assertEquals(0, self.x.todoCount)
|
||||
self.assertInLog("TEST-PASS")
|
||||
self.assertInLog("TEST-UNEXPECTED-FAIL")
|
||||
|
||||
def testSkip(self):
|
||||
"""
|
||||
Check that a simple failing test skipped in the manifest does
|
||||
not cause failure.
|
||||
"""
|
||||
self.writeFile("test_basic.js", SIMPLE_FAILING_TEST)
|
||||
self.writeManifest([("test_basic.js", "skip-if = true")])
|
||||
self.assertTestResult(True)
|
||||
self.assertEquals(1, self.x.testCount)
|
||||
self.assertEquals(0, self.x.passCount)
|
||||
self.assertEquals(0, self.x.failCount)
|
||||
self.assertEquals(0, self.x.todoCount)
|
||||
self.assertNotInLog("TEST-UNEXPECTED-FAIL")
|
||||
self.assertNotInLog("TEST-PASS")
|
||||
|
||||
def testKnownFail(self):
|
||||
"""
|
||||
Check that a simple failing test marked as known-fail in the manifest
|
||||
does not cause failure.
|
||||
"""
|
||||
self.writeFile("test_basic.js", SIMPLE_FAILING_TEST)
|
||||
self.writeManifest([("test_basic.js", "fail-if = true")])
|
||||
self.assertTestResult(True)
|
||||
self.assertEquals(1, self.x.testCount)
|
||||
self.assertEquals(0, self.x.passCount)
|
||||
self.assertEquals(0, self.x.failCount)
|
||||
self.assertEquals(1, self.x.todoCount)
|
||||
self.assertInLog("TEST-KNOWN-FAIL")
|
||||
# This should be suppressed because the harness doesn't include
|
||||
# the full log from the xpcshell run when things pass.
|
||||
self.assertNotInLog("TEST-UNEXPECTED-FAIL")
|
||||
self.assertNotInLog("TEST-PASS")
|
||||
|
||||
def testUnexpectedPass(self):
|
||||
"""
|
||||
Check that a simple failing test marked as known-fail in the manifest
|
||||
that passes causes an unexpected pass.
|
||||
"""
|
||||
self.writeFile("test_basic.js", SIMPLE_PASSING_TEST)
|
||||
self.writeManifest([("test_basic.js", "fail-if = true")])
|
||||
self.assertTestResult(False)
|
||||
self.assertEquals(1, self.x.testCount)
|
||||
self.assertEquals(0, self.x.passCount)
|
||||
self.assertEquals(1, self.x.failCount)
|
||||
self.assertEquals(0, self.x.todoCount)
|
||||
# From the outer (Python) harness
|
||||
self.assertInLog("TEST-UNEXPECTED-PASS")
|
||||
self.assertNotInLog("TEST-KNOWN-FAIL")
|
||||
# From the inner (JS) harness
|
||||
self.assertInLog("TEST-PASS")
|
||||
|
||||
def testReturnNonzero(self):
|
||||
"""
|
||||
Check that a test where xpcshell returns nonzero fails.
|
||||
"""
|
||||
self.writeFile("test_error.js", "throw 'foo'")
|
||||
self.writeManifest(["test_error.js"])
|
||||
|
||||
self.assertTestResult(False)
|
||||
self.assertEquals(1, self.x.testCount)
|
||||
self.assertEquals(0, self.x.passCount)
|
||||
self.assertEquals(1, self.x.failCount)
|
||||
self.assertEquals(0, self.x.todoCount)
|
||||
self.assertInLog("TEST-UNEXPECTED-FAIL")
|
||||
self.assertNotInLog("TEST-PASS")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
Reference in New Issue
Block a user