bug 664197 - integrate mozinfo + ManifestDestiny update into xpcshell harness. r=jmaher

--HG--
extra : rebase_source : 1783e1a39d592defa279488f0cd0266311ed35ea
This commit is contained in:
Ted Mielczarek 2011-06-21 08:12:40 -04:00
parent 5bb3d4e7af
commit 62b37a06ef
26 changed files with 1410 additions and 62 deletions

View File

@ -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

View File

@ -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
@ -509,14 +762,17 @@ 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
View 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()

View 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
View File

@ -0,0 +1 @@
# dummy spot for "fleem" test

View File

@ -0,0 +1,11 @@
[DEFAULT]
foo = bar
[include:include/bar.ini]
[fleem]
[include:include/foo.ini]
red = roses
blue = violets
yellow = daffodils

View File

@ -0,0 +1,4 @@
[DEFAULT]
foo = fleem
[crash-handling]

View File

@ -0,0 +1 @@
# dummy spot for "crash-handling" test

View File

@ -0,0 +1 @@
# dummy spot for "flowers" test

View File

@ -0,0 +1,5 @@
[DEFAULT]
blue = ocean
[flowers]
yellow = submarine

View 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]

View 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]

View File

@ -0,0 +1,2 @@
[foo]
path = fleem

104
build/tests/test.py Executable file
View 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()

View 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

View 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

View 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'

View File

@ -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)

View File

@ -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 \

View File

@ -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) \

View File

@ -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 -)

View File

@ -0,0 +1,4 @@
function run_test() {
// This test expects to fail.
do_check_true(false);
}

View File

@ -0,0 +1,4 @@
function run_test() {
// This test expects to fail.
do_check_true(false);
}

View File

@ -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

View File

@ -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()
@ -138,7 +146,10 @@ class XPCShellTests(object):
self.xrePath = os.path.dirname(self.xpcshell)
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()

View 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()