mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-08 16:27:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
7
testing/mozbase/moztest/moztest/__init__.py
Normal file
7
testing/mozbase/moztest/moztest/__init__.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import adapters
|
||||
|
||||
__all__ = ['adapters']
|
||||
7
testing/mozbase/moztest/moztest/adapters/__init__.py
Normal file
7
testing/mozbase/moztest/moztest/adapters/__init__.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import unit
|
||||
|
||||
__all__ = ['unit']
|
||||
225
testing/mozbase/moztest/moztest/adapters/unit.py
Normal file
225
testing/mozbase/moztest/moztest/adapters/unit.py
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
try:
|
||||
from unittest import TextTestResult
|
||||
except ImportError:
|
||||
# bug 971243 - python 2.6 compatibilty
|
||||
from unittest import _TextTestResult as TextTestResult
|
||||
|
||||
"""Adapter used to output structuredlog messages from unittest
|
||||
testsuites"""
|
||||
|
||||
|
||||
def get_test_class_name(test):
|
||||
"""
|
||||
This method is used to return the full class name from a
|
||||
:class:`unittest.TestCase` instance.
|
||||
|
||||
It is used as a default to define the "class_name" extra value
|
||||
passed in structured loggers. You can override the default by
|
||||
implementing a "get_test_class_name" method on you TestCase subclass.
|
||||
"""
|
||||
return "%s.%s" % (test.__class__.__module__,
|
||||
test.__class__.__name__)
|
||||
|
||||
|
||||
def get_test_method_name(test):
|
||||
"""
|
||||
This method is used to return the full method name from a
|
||||
:class:`unittest.TestCase` instance.
|
||||
|
||||
It is used as a default to define the "method_name" extra value
|
||||
passed in structured loggers. You can override the default by
|
||||
implementing a "get_test_method_name" method on you TestCase subclass.
|
||||
"""
|
||||
return test._testMethodName
|
||||
|
||||
|
||||
class StructuredTestResult(TextTestResult):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.logger = kwargs.pop('logger')
|
||||
self.test_list = kwargs.pop("test_list", [])
|
||||
self.result_callbacks = kwargs.pop('result_callbacks', [])
|
||||
self.passed = 0
|
||||
self.testsRun = 0
|
||||
TextTestResult.__init__(self, *args, **kwargs)
|
||||
|
||||
def call_callbacks(self, test, status):
|
||||
debug_info = {}
|
||||
for callback in self.result_callbacks:
|
||||
info = callback(test, status)
|
||||
if info is not None:
|
||||
debug_info.update(info)
|
||||
return debug_info
|
||||
|
||||
def startTestRun(self):
|
||||
# This would be an opportunity to call the logger's suite_start action,
|
||||
# however some users may use multiple suites, and per the structured
|
||||
# logging protocol, this action should only be called once.
|
||||
pass
|
||||
|
||||
def startTest(self, test):
|
||||
self.testsRun += 1
|
||||
self.logger.test_start(test.id())
|
||||
|
||||
def stopTest(self, test):
|
||||
pass
|
||||
|
||||
def stopTestRun(self):
|
||||
# This would be an opportunity to call the logger's suite_end action,
|
||||
# however some users may use multiple suites, and per the structured
|
||||
# logging protocol, this action should only be called once.
|
||||
pass
|
||||
|
||||
def _extract_err_message(self, err):
|
||||
# Format an exception message in the style of unittest's _exc_info_to_string
|
||||
# while maintaining a division between a traceback and a message.
|
||||
exc_ty, val, _ = err
|
||||
exc_msg = "".join(traceback.format_exception_only(exc_ty, val))
|
||||
if self.buffer:
|
||||
output_msg = "\n".join([sys.stdout.getvalue(), sys.stderr.getvalue()])
|
||||
return "".join([exc_msg, output_msg])
|
||||
return exc_msg.rstrip()
|
||||
|
||||
def _extract_stacktrace(self, err, test):
|
||||
# Format an exception stack in the style of unittest's _exc_info_to_string
|
||||
# while maintaining a division between a traceback and a message.
|
||||
# This is mostly borrowed from unittest.result._exc_info_to_string.
|
||||
|
||||
exctype, value, tb = err
|
||||
while tb and self._is_relevant_tb_level(tb):
|
||||
tb = tb.tb_next
|
||||
# Header usually included by print_exception
|
||||
lines = ["Traceback (most recent call last):\n"]
|
||||
if exctype is test.failureException:
|
||||
length = self._count_relevant_tb_levels(tb)
|
||||
lines += traceback.format_tb(tb, length)
|
||||
else:
|
||||
lines += traceback.format_tb(tb)
|
||||
return "".join(lines)
|
||||
|
||||
def _get_class_method_name(self, test):
|
||||
if hasattr(test, 'get_test_class_name'):
|
||||
class_name = test.get_test_class_name()
|
||||
else:
|
||||
class_name = get_test_class_name(test)
|
||||
|
||||
if hasattr(test, 'get_test_method_name'):
|
||||
method_name = test.get_test_method_name()
|
||||
else:
|
||||
method_name = get_test_method_name(test)
|
||||
|
||||
return {
|
||||
'class_name': class_name,
|
||||
'method_name': method_name
|
||||
}
|
||||
|
||||
def addError(self, test, err):
|
||||
self.errors.append((test, self._exc_info_to_string(err, test)))
|
||||
extra = self.call_callbacks(test, "ERROR")
|
||||
extra.update(self._get_class_method_name(test))
|
||||
self.logger.test_end(test.id(),
|
||||
"ERROR",
|
||||
message=self._extract_err_message(err),
|
||||
expected="PASS",
|
||||
stack=self._extract_stacktrace(err, test),
|
||||
extra=extra)
|
||||
|
||||
def addFailure(self, test, err):
|
||||
extra = self.call_callbacks(test, "FAIL")
|
||||
extra.update(self._get_class_method_name(test))
|
||||
self.logger.test_end(test.id(),
|
||||
"FAIL",
|
||||
message=self._extract_err_message(err),
|
||||
expected="PASS",
|
||||
stack=self._extract_stacktrace(err, test),
|
||||
extra=extra)
|
||||
|
||||
def addSuccess(self, test):
|
||||
extra = self._get_class_method_name(test)
|
||||
self.logger.test_end(test.id(),
|
||||
"PASS",
|
||||
expected="PASS",
|
||||
extra=extra)
|
||||
|
||||
def addExpectedFailure(self, test, err):
|
||||
extra = self.call_callbacks(test, "FAIL")
|
||||
extra.update(self._get_class_method_name(test))
|
||||
self.logger.test_end(test.id(),
|
||||
"FAIL",
|
||||
message=self._extract_err_message(err),
|
||||
expected="FAIL",
|
||||
stack=self._extract_stacktrace(err, test),
|
||||
extra=extra)
|
||||
|
||||
def addUnexpectedSuccess(self, test):
|
||||
extra = self.call_callbacks(test, "PASS")
|
||||
extra.update(self._get_class_method_name(test))
|
||||
self.logger.test_end(test.id(),
|
||||
"PASS",
|
||||
expected="FAIL",
|
||||
extra=extra)
|
||||
|
||||
def addSkip(self, test, reason):
|
||||
extra = self.call_callbacks(test, "SKIP")
|
||||
extra.update(self._get_class_method_name(test))
|
||||
self.logger.test_end(test.id(),
|
||||
"SKIP",
|
||||
message=reason,
|
||||
expected="PASS",
|
||||
extra=extra)
|
||||
|
||||
|
||||
class StructuredTestRunner(unittest.TextTestRunner):
|
||||
|
||||
resultclass = StructuredTestResult
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""TestRunner subclass designed for structured logging.
|
||||
|
||||
:params logger: A ``StructuredLogger`` to use for logging the test run.
|
||||
:params test_list: An optional list of tests that will be passed along
|
||||
the `suite_start` message.
|
||||
|
||||
"""
|
||||
|
||||
self.logger = kwargs.pop("logger")
|
||||
self.test_list = kwargs.pop("test_list", [])
|
||||
self.result_callbacks = kwargs.pop("result_callbacks", [])
|
||||
unittest.TextTestRunner.__init__(self, **kwargs)
|
||||
|
||||
def _makeResult(self):
|
||||
return self.resultclass(self.stream,
|
||||
self.descriptions,
|
||||
self.verbosity,
|
||||
logger=self.logger,
|
||||
test_list=self.test_list)
|
||||
|
||||
def run(self, test):
|
||||
"""Run the given test case or test suite."""
|
||||
result = self._makeResult()
|
||||
result.failfast = self.failfast
|
||||
result.buffer = self.buffer
|
||||
startTime = time.time()
|
||||
startTestRun = getattr(result, 'startTestRun', None)
|
||||
if startTestRun is not None:
|
||||
startTestRun()
|
||||
try:
|
||||
test(result)
|
||||
finally:
|
||||
stopTestRun = getattr(result, 'stopTestRun', None)
|
||||
if stopTestRun is not None:
|
||||
stopTestRun()
|
||||
stopTime = time.time()
|
||||
if hasattr(result, 'time_taken'):
|
||||
result.time_taken = stopTime - startTime
|
||||
|
||||
return result
|
||||
0
testing/mozbase/moztest/moztest/output/__init__.py
Normal file
0
testing/mozbase/moztest/moztest/output/__init__.py
Normal file
73
testing/mozbase/moztest/moztest/output/autolog.py
Normal file
73
testing/mozbase/moztest/moztest/output/autolog.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
from mozautolog import RESTfulAutologTestGroup
|
||||
|
||||
from base import Output, count, long_name
|
||||
|
||||
|
||||
class AutologOutput(Output):
|
||||
|
||||
def __init__(self, es_server='buildbot-es.metrics.scl3.mozilla.com:9200',
|
||||
rest_server='http://brasstacks.mozilla.com/autologserver',
|
||||
name='moztest',
|
||||
harness='moztest'):
|
||||
self.es_server = es_server
|
||||
self.rest_server = rest_server
|
||||
|
||||
def serialize(self, results_collection, file_obj):
|
||||
grps = self.make_testgroups(results_collection)
|
||||
for g in grps:
|
||||
file_obj.write(g.serialize())
|
||||
|
||||
def make_testgroups(self, results_collection):
|
||||
testgroups = []
|
||||
for context in results_collection.contexts:
|
||||
coll = results_collection.subset(lambda t: t.context == context)
|
||||
passed = coll.tests_with_result('PASS')
|
||||
failed = coll.tests_with_result('UNEXPECTED-FAIL')
|
||||
unexpected_passes = coll.tests_with_result('UNEXPECTED-PASS')
|
||||
errors = coll.tests_with_result('ERROR')
|
||||
skipped = coll.tests_with_result('SKIPPED')
|
||||
known_fails = coll.tests_with_result('KNOWN-FAIL')
|
||||
|
||||
testgroup = RESTfulAutologTestGroup(
|
||||
testgroup=context.testgroup,
|
||||
os=context.os,
|
||||
platform=context.arch,
|
||||
harness=context.harness,
|
||||
server=self.es_server,
|
||||
restserver=self.rest_server,
|
||||
machine=context.hostname,
|
||||
logfile=context.logfile,
|
||||
)
|
||||
testgroup.add_test_suite(
|
||||
testsuite=results_collection.suite_name,
|
||||
elapsedtime=coll.time_taken,
|
||||
passed=count(passed),
|
||||
failed=count(failed) + count(errors) + count(unexpected_passes),
|
||||
todo=count(skipped) + count(known_fails),
|
||||
)
|
||||
testgroup.set_primary_product(
|
||||
tree=context.tree,
|
||||
revision=context.revision,
|
||||
productname=context.product,
|
||||
buildtype=context.buildtype,
|
||||
)
|
||||
# need to call this again since we already used the generator
|
||||
for f in coll.tests_with_result('UNEXPECTED-FAIL'):
|
||||
testgroup.add_test_failure(
|
||||
test=long_name(f),
|
||||
text='\n'.join(f.output),
|
||||
status=f.result,
|
||||
)
|
||||
testgroups.append(testgroup)
|
||||
return testgroups
|
||||
|
||||
def post(self, data):
|
||||
msg = "Must pass in a list returned by make_testgroups."
|
||||
for d in data:
|
||||
assert isinstance(d, RESTfulAutologTestGroup), msg
|
||||
d.submit()
|
||||
53
testing/mozbase/moztest/moztest/output/base.py
Normal file
53
testing/mozbase/moztest/moztest/output/base.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
from __future__ import with_statement
|
||||
from contextlib import closing
|
||||
from StringIO import StringIO
|
||||
|
||||
try:
|
||||
from abc import abstractmethod
|
||||
except ImportError:
|
||||
# abc is python 2.6+
|
||||
# from https://github.com/mozilla/mozbase/blob/master/mozdevice/mozdevice/devicemanager.py
|
||||
def abstractmethod(method):
|
||||
line = method.func_code.co_firstlineno
|
||||
filename = method.func_code.co_filename
|
||||
|
||||
def not_implemented(*args, **kwargs):
|
||||
raise NotImplementedError('Abstract method %s at File "%s", '
|
||||
'line %s should be implemented by a concrete class' %
|
||||
(repr(method), filename, line))
|
||||
return not_implemented
|
||||
|
||||
|
||||
class Output(object):
|
||||
""" Abstract base class for outputting test results """
|
||||
|
||||
@abstractmethod
|
||||
def serialize(self, results_collection, file_obj):
|
||||
""" Writes the string representation of the results collection
|
||||
to the given file object"""
|
||||
|
||||
def dump_string(self, results_collection):
|
||||
""" Returns the string representation of the results collection """
|
||||
with closing(StringIO()) as s:
|
||||
self.serialize(results_collection, s)
|
||||
return s.getvalue()
|
||||
|
||||
|
||||
# helper functions
|
||||
def count(iterable):
|
||||
""" Return the count of an iterable. Useful for generators. """
|
||||
c = 0
|
||||
for i in iterable:
|
||||
c += 1
|
||||
return c
|
||||
|
||||
|
||||
def long_name(test):
|
||||
if test.test_class:
|
||||
return '%s.%s' % (test.test_class, test.name)
|
||||
return test.name
|
||||
93
testing/mozbase/moztest/moztest/output/xunit.py
Normal file
93
testing/mozbase/moztest/moztest/output/xunit.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
import xml.dom.minidom as dom
|
||||
|
||||
from base import Output, count
|
||||
from moztest.results import TestResult
|
||||
|
||||
|
||||
class XUnitOutput(Output):
|
||||
""" Class for writing xUnit formatted test results in an XML file """
|
||||
|
||||
def serialize(self, results_collection, file_obj):
|
||||
""" Writes the xUnit formatted results to the given file object """
|
||||
|
||||
def _extract_xml(test_result, text='', result='Pass'):
|
||||
if not isinstance(text, basestring):
|
||||
text = '\n'.join(text)
|
||||
|
||||
cls_name = test_result.test_class
|
||||
|
||||
# if the test class is not already created, create it
|
||||
if cls_name not in classes:
|
||||
cls = doc.createElement('class')
|
||||
cls.setAttribute('name', cls_name)
|
||||
assembly.appendChild(cls)
|
||||
classes[cls_name] = cls
|
||||
|
||||
t = doc.createElement('test')
|
||||
t.setAttribute('name', test_result.name)
|
||||
t.setAttribute('result', result)
|
||||
|
||||
if result == 'Fail':
|
||||
f = doc.createElement('failure')
|
||||
st = doc.createElement('stack-trace')
|
||||
st.appendChild(doc.createTextNode(text))
|
||||
|
||||
f.appendChild(st)
|
||||
t.appendChild(f)
|
||||
|
||||
elif result == 'Skip':
|
||||
r = doc.createElement('reason')
|
||||
msg = doc.createElement('message')
|
||||
msg.appendChild(doc.createTextNode(text))
|
||||
|
||||
r.appendChild(msg)
|
||||
t.appendChild(f)
|
||||
|
||||
cls = classes[cls_name]
|
||||
cls.appendChild(t)
|
||||
|
||||
doc = dom.Document()
|
||||
|
||||
failed = sum([count(results_collection.tests_with_result(t))
|
||||
for t in TestResult.FAIL_RESULTS])
|
||||
passed = count(results_collection.tests_with_result('PASS'))
|
||||
skipped = count(results_collection.tests_with_result('SKIPPED'))
|
||||
|
||||
assembly = doc.createElement('assembly')
|
||||
assembly.setAttribute('name', results_collection.suite_name)
|
||||
assembly.setAttribute('time', str(results_collection.time_taken))
|
||||
assembly.setAttribute('total', str(len(results_collection)))
|
||||
assembly.setAttribute('passed', str(passed))
|
||||
assembly.setAttribute('failed', str(failed))
|
||||
assembly.setAttribute('skipped', str(skipped))
|
||||
|
||||
classes = {} # str -> xml class element
|
||||
|
||||
for tr in results_collection.tests_with_result('ERROR'):
|
||||
_extract_xml(tr, text=tr.output, result='Fail')
|
||||
|
||||
for tr in results_collection.tests_with_result('UNEXPECTED-FAIL'):
|
||||
_extract_xml(tr, text=tr.output, result='Fail')
|
||||
|
||||
for tr in results_collection.tests_with_result('UNEXPECTED-PASS'):
|
||||
_extract_xml(tr, text='UNEXPECTED-PASS', result='Fail')
|
||||
|
||||
for tr in results_collection.tests_with_result('SKIPPED'):
|
||||
_extract_xml(tr, text=tr.output, result='Skip')
|
||||
|
||||
for tr in results_collection.tests_with_result('KNOWN-FAIL'):
|
||||
_extract_xml(tr, text=tr.output, result='Pass')
|
||||
|
||||
for tr in results_collection.tests_with_result('PASS'):
|
||||
_extract_xml(tr, result='Pass')
|
||||
|
||||
for cls in classes.itervalues():
|
||||
assembly.appendChild(cls)
|
||||
|
||||
doc.appendChild(assembly)
|
||||
file_obj.write(doc.toxml(encoding='utf-8'))
|
||||
323
testing/mozbase/moztest/moztest/results.py
Normal file
323
testing/mozbase/moztest/moztest/results.py
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
import time
|
||||
import os
|
||||
import mozinfo
|
||||
|
||||
|
||||
class TestContext(object):
|
||||
""" Stores context data about the test """
|
||||
|
||||
attrs = ['hostname', 'arch', 'env', 'os', 'os_version', 'tree', 'revision',
|
||||
'product', 'logfile', 'testgroup', 'harness', 'buildtype']
|
||||
|
||||
def __init__(self, hostname='localhost', tree='', revision='', product='',
|
||||
logfile=None, arch='', operating_system='', testgroup='',
|
||||
harness='moztest', buildtype=''):
|
||||
self.hostname = hostname
|
||||
self.arch = arch or mozinfo.processor
|
||||
self.env = os.environ.copy()
|
||||
self.os = operating_system or mozinfo.os
|
||||
self.os_version = mozinfo.version
|
||||
self.tree = tree
|
||||
self.revision = revision
|
||||
self.product = product
|
||||
self.logfile = logfile
|
||||
self.testgroup = testgroup
|
||||
self.harness = harness
|
||||
self.buildtype = buildtype
|
||||
|
||||
def __str__(self):
|
||||
return '%s (%s, %s)' % (self.hostname, self.os, self.arch)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s>' % self.__str__()
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, TestContext):
|
||||
return False
|
||||
diffs = [a for a in self.attrs if getattr(self, a) != getattr(other, a)]
|
||||
return len(diffs) == 0
|
||||
|
||||
def __hash__(self):
|
||||
def get(attr):
|
||||
value = getattr(self, attr)
|
||||
if isinstance(value, dict):
|
||||
value = frozenset(value.items())
|
||||
return value
|
||||
return hash(frozenset([get(a) for a in self.attrs]))
|
||||
|
||||
|
||||
class TestResult(object):
|
||||
""" Stores test result data """
|
||||
|
||||
FAIL_RESULTS = [
|
||||
'UNEXPECTED-PASS',
|
||||
'UNEXPECTED-FAIL',
|
||||
'ERROR',
|
||||
]
|
||||
COMPUTED_RESULTS = FAIL_RESULTS + [
|
||||
'PASS',
|
||||
'KNOWN-FAIL',
|
||||
'SKIPPED',
|
||||
]
|
||||
POSSIBLE_RESULTS = [
|
||||
'PASS',
|
||||
'FAIL',
|
||||
'SKIP',
|
||||
'ERROR',
|
||||
]
|
||||
|
||||
def __init__(self, name, test_class='', time_start=None, context=None,
|
||||
result_expected='PASS'):
|
||||
""" Create a TestResult instance.
|
||||
name = name of the test that is running
|
||||
test_class = the class that the test belongs to
|
||||
time_start = timestamp (seconds since UNIX epoch) of when the test started
|
||||
running; if not provided, defaults to the current time
|
||||
! Provide 0 if you only have the duration
|
||||
context = TestContext instance; can be None
|
||||
result_expected = string representing the expected outcome of the test"""
|
||||
|
||||
msg = "Result '%s' not in possible results: %s" %\
|
||||
(result_expected, ', '.join(self.POSSIBLE_RESULTS))
|
||||
assert isinstance(name, basestring), "name has to be a string"
|
||||
assert result_expected in self.POSSIBLE_RESULTS, msg
|
||||
|
||||
self.name = name
|
||||
self.test_class = test_class
|
||||
self.context = context
|
||||
self.time_start = time_start if time_start is not None else time.time()
|
||||
self.time_end = None
|
||||
self._result_expected = result_expected
|
||||
self._result_actual = None
|
||||
self.result = None
|
||||
self.filename = None
|
||||
self.description = None
|
||||
self.output = []
|
||||
self.reason = None
|
||||
|
||||
@property
|
||||
def test_name(self):
|
||||
return '%s.py %s.%s' % (self.test_class.split('.')[0],
|
||||
self.test_class,
|
||||
self.name)
|
||||
|
||||
def __str__(self):
|
||||
return '%s | %s (%s) | %s' % (self.result or 'PENDING',
|
||||
self.name, self.test_class, self.reason)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s>' % self.__str__()
|
||||
|
||||
def calculate_result(self, expected, actual):
|
||||
if actual == 'ERROR':
|
||||
return 'ERROR'
|
||||
if actual == 'SKIP':
|
||||
return 'SKIPPED'
|
||||
|
||||
if expected == 'PASS':
|
||||
if actual == 'PASS':
|
||||
return 'PASS'
|
||||
if actual == 'FAIL':
|
||||
return 'UNEXPECTED-FAIL'
|
||||
|
||||
if expected == 'FAIL':
|
||||
if actual == 'PASS':
|
||||
return 'UNEXPECTED-PASS'
|
||||
if actual == 'FAIL':
|
||||
return 'KNOWN-FAIL'
|
||||
|
||||
# if actual is skip or error, we return at the beginning, so if we get
|
||||
# here it is definitely some kind of error
|
||||
return 'ERROR'
|
||||
|
||||
def infer_results(self, computed_result):
|
||||
assert computed_result in self.COMPUTED_RESULTS
|
||||
if computed_result == 'UNEXPECTED-PASS':
|
||||
expected = 'FAIL'
|
||||
actual = 'PASS'
|
||||
elif computed_result == 'UNEXPECTED-FAIL':
|
||||
expected = 'PASS'
|
||||
actual = 'FAIL'
|
||||
elif computed_result == 'KNOWN-FAIL':
|
||||
expected = actual = 'FAIL'
|
||||
elif computed_result == 'SKIPPED':
|
||||
expected = actual = 'SKIP'
|
||||
else:
|
||||
return
|
||||
self._result_expected = expected
|
||||
self._result_actual = actual
|
||||
|
||||
def finish(self, result, time_end=None, output=None, reason=None):
|
||||
""" Marks the test as finished, storing its end time and status
|
||||
! Provide the duration as time_end if you only have that. """
|
||||
|
||||
if result in self.POSSIBLE_RESULTS:
|
||||
self._result_actual = result
|
||||
self.result = self.calculate_result(self._result_expected,
|
||||
self._result_actual)
|
||||
elif result in self.COMPUTED_RESULTS:
|
||||
self.infer_results(result)
|
||||
self.result = result
|
||||
else:
|
||||
valid = self.POSSIBLE_RESULTS + self.COMPUTED_RESULTS
|
||||
msg = "Result '%s' not valid. Need one of: %s" %\
|
||||
(result, ', '.join(valid))
|
||||
raise ValueError(msg)
|
||||
|
||||
# use lists instead of multiline strings
|
||||
if isinstance(output, basestring):
|
||||
output = output.splitlines()
|
||||
|
||||
self.time_end = time_end if time_end is not None else time.time()
|
||||
self.output = output or self.output
|
||||
self.reason = reason
|
||||
|
||||
@property
|
||||
def finished(self):
|
||||
""" Boolean saying if the test is finished or not """
|
||||
return self.result is not None
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
""" Returns the time it took for the test to finish. If the test is
|
||||
not finished, returns the elapsed time so far """
|
||||
if self.result is not None:
|
||||
return self.time_end - self.time_start
|
||||
else:
|
||||
# returns the elapsed time
|
||||
return time.time() - self.time_start
|
||||
|
||||
|
||||
class TestResultCollection(list):
|
||||
""" Container class that stores test results """
|
||||
|
||||
resultClass = TestResult
|
||||
|
||||
def __init__(self, suite_name, time_taken=0, resultClass=None):
|
||||
list.__init__(self)
|
||||
self.suite_name = suite_name
|
||||
self.time_taken = time_taken
|
||||
if resultClass is not None:
|
||||
self.resultClass = resultClass
|
||||
|
||||
def __str__(self):
|
||||
return "%s (%.2fs)\n%s" % (self.suite_name, self.time_taken,
|
||||
list.__str__(self))
|
||||
|
||||
def subset(self, predicate):
|
||||
tests = self.filter(predicate)
|
||||
duration = 0
|
||||
sub = TestResultCollection(self.suite_name)
|
||||
for t in tests:
|
||||
sub.append(t)
|
||||
duration += t.duration
|
||||
sub.time_taken = duration
|
||||
return sub
|
||||
|
||||
@property
|
||||
def contexts(self):
|
||||
""" List of unique contexts for the test results contained """
|
||||
cs = [tr.context for tr in self]
|
||||
return list(set(cs))
|
||||
|
||||
def filter(self, predicate):
|
||||
""" Returns a generator of TestResults that satisfy a given predicate """
|
||||
return (tr for tr in self if predicate(tr))
|
||||
|
||||
def tests_with_result(self, result):
|
||||
""" Returns a generator of TestResults with the given result """
|
||||
msg = "Result '%s' not in possible results: %s" %\
|
||||
(result, ', '.join(self.resultClass.COMPUTED_RESULTS))
|
||||
assert result in self.resultClass.COMPUTED_RESULTS, msg
|
||||
return self.filter(lambda t: t.result == result)
|
||||
|
||||
@property
|
||||
def tests(self):
|
||||
""" Generator of all tests in the collection """
|
||||
return (t for t in self)
|
||||
|
||||
def add_result(self, test, result_expected='PASS',
|
||||
result_actual='PASS', output='', context=None):
|
||||
def get_class(test):
|
||||
return test.__class__.__module__ + '.' + test.__class__.__name__
|
||||
|
||||
t = self.resultClass(name=str(test).split()[0], test_class=get_class(test),
|
||||
time_start=0, result_expected=result_expected,
|
||||
context=context)
|
||||
t.finish(result_actual, time_end=0, reason=relevant_line(output),
|
||||
output=output)
|
||||
self.append(t)
|
||||
|
||||
@property
|
||||
def num_failures(self):
|
||||
fails = 0
|
||||
for t in self:
|
||||
if t.result in self.resultClass.FAIL_RESULTS:
|
||||
fails += 1
|
||||
return fails
|
||||
|
||||
def add_unittest_result(self, result, context=None):
|
||||
""" Adds the python unittest result provided to the collection"""
|
||||
if hasattr(result, 'time_taken'):
|
||||
self.time_taken += result.time_taken
|
||||
|
||||
for test, output in result.errors:
|
||||
self.add_result(test, result_actual='ERROR', output=output)
|
||||
|
||||
for test, output in result.failures:
|
||||
self.add_result(test, result_actual='FAIL',
|
||||
output=output)
|
||||
|
||||
if hasattr(result, 'unexpectedSuccesses'):
|
||||
for test in result.unexpectedSuccesses:
|
||||
self.add_result(test, result_expected='FAIL',
|
||||
result_actual='PASS')
|
||||
|
||||
if hasattr(result, 'skipped'):
|
||||
for test, output in result.skipped:
|
||||
self.add_result(test, result_expected='SKIP',
|
||||
result_actual='SKIP', output=output)
|
||||
|
||||
if hasattr(result, 'expectedFailures'):
|
||||
for test, output in result.expectedFailures:
|
||||
self.add_result(test, result_expected='FAIL',
|
||||
result_actual='FAIL', output=output)
|
||||
|
||||
# unittest does not store these by default
|
||||
if hasattr(result, 'tests_passed'):
|
||||
for test in result.tests_passed:
|
||||
self.add_result(test)
|
||||
|
||||
@classmethod
|
||||
def from_unittest_results(cls, context, *results):
|
||||
""" Creates a TestResultCollection containing the given python
|
||||
unittest results """
|
||||
|
||||
if not results:
|
||||
return cls('from unittest')
|
||||
|
||||
# all the TestResult instances share the same context
|
||||
context = context or TestContext()
|
||||
|
||||
collection = cls('from %s' % results[0].__class__.__name__)
|
||||
|
||||
for result in results:
|
||||
collection.add_unittest_result(result, context)
|
||||
|
||||
return collection
|
||||
|
||||
|
||||
# used to get exceptions/errors from tracebacks
|
||||
def relevant_line(s):
|
||||
KEYWORDS = ('Error:', 'Exception:', 'error:', 'exception:')
|
||||
lines = s.splitlines()
|
||||
for line in lines:
|
||||
for keyword in KEYWORDS:
|
||||
if keyword in line:
|
||||
return line
|
||||
return 'N/A'
|
||||
26
testing/mozbase/moztest/setup.py
Normal file
26
testing/mozbase/moztest/setup.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
PACKAGE_VERSION = '0.8'
|
||||
|
||||
# dependencies
|
||||
deps = ['mozinfo']
|
||||
|
||||
setup(name='moztest',
|
||||
version=PACKAGE_VERSION,
|
||||
description="Package for storing and outputting Mozilla test results",
|
||||
long_description="see http://mozbase.readthedocs.org/",
|
||||
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
keywords='mozilla',
|
||||
author='Mozilla Automation and Tools team',
|
||||
author_email='tools@lists.mozilla.org',
|
||||
url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase',
|
||||
license='MPL',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=deps,
|
||||
)
|
||||
1
testing/mozbase/moztest/tests/manifest.ini
Normal file
1
testing/mozbase/moztest/tests/manifest.ini
Normal file
|
|
@ -0,0 +1 @@
|
|||
[test.py]
|
||||
55
testing/mozbase/moztest/tests/test.py
Normal file
55
testing/mozbase/moztest/tests/test.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import math
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from moztest.results import TestContext, TestResult, TestResultCollection
|
||||
|
||||
|
||||
class Result(unittest.TestCase):
|
||||
|
||||
def test_results(self):
|
||||
self.assertRaises(AssertionError,
|
||||
lambda: TestResult('test', result_expected='hello'))
|
||||
t = TestResult('test')
|
||||
self.assertRaises(ValueError, lambda: t.finish(result='good bye'))
|
||||
|
||||
def test_time(self):
|
||||
now = time.time()
|
||||
t = TestResult('test')
|
||||
time.sleep(1)
|
||||
t.finish('PASS')
|
||||
duration = time.time() - now
|
||||
self.assertTrue(math.fabs(duration - t.duration) < 1)
|
||||
|
||||
def test_custom_time(self):
|
||||
t = TestResult('test', time_start=0)
|
||||
t.finish(result='PASS', time_end=1000)
|
||||
self.assertEqual(t.duration, 1000)
|
||||
|
||||
|
||||
class Collection(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
c1 = TestContext('host1')
|
||||
c2 = TestContext('host2')
|
||||
c3 = TestContext('host2')
|
||||
c3.os = 'B2G'
|
||||
c4 = TestContext('host1')
|
||||
|
||||
t1 = TestResult('t1', context=c1)
|
||||
t2 = TestResult('t2', context=c2)
|
||||
t3 = TestResult('t3', context=c3)
|
||||
t4 = TestResult('t4', context=c4)
|
||||
|
||||
self.collection = TestResultCollection('tests')
|
||||
self.collection.extend([t1, t2, t3, t4])
|
||||
|
||||
def test_unique_contexts(self):
|
||||
self.assertEqual(len(self.collection.contexts), 3)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue