Format-agnostic data structure for reports
Replace the JUnit-specific terms 'testsuite' and 'testcase' with 'spec' and 'package', respectively.
This commit is contained in:
parent
0598f70de2
commit
6c5dbdd9cd
@ -64,7 +64,7 @@ class InfoCollector(object):
|
|||||||
|
|
||||||
When exiting the context this change will be rolled-back.
|
When exiting the context this change will be rolled-back.
|
||||||
|
|
||||||
The data collected is available through the ``test_suites``
|
The data collected is available through the ``specs``
|
||||||
attribute once exited, and it's organized as a list where
|
attribute once exited, and it's organized as a list where
|
||||||
each item represents the installation of one of the spec.
|
each item represents the installation of one of the spec.
|
||||||
|
|
||||||
@ -77,49 +77,51 @@ class InfoCollector(object):
|
|||||||
|
|
||||||
def __init__(self, specs):
|
def __init__(self, specs):
|
||||||
#: Specs that will be installed
|
#: Specs that will be installed
|
||||||
self.specs = specs
|
self.input_specs = specs
|
||||||
#: Context that will be used to stamp the report from
|
#: This is where we record the data that will be included
|
||||||
#: the template file
|
#: in our report.
|
||||||
self.test_suites = []
|
self.specs = []
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
# Initialize the test suites with the data that
|
# Initialize the spec report with the data that is available upfront.
|
||||||
# is available upfront
|
for input_spec in self.input_specs:
|
||||||
for spec in self.specs:
|
|
||||||
name_fmt = '{0}_{1}'
|
name_fmt = '{0}_{1}'
|
||||||
name = name_fmt.format(spec.name, spec.dag_hash(length=7))
|
name = name_fmt.format(input_spec.name,
|
||||||
|
input_spec.dag_hash(length=7))
|
||||||
|
|
||||||
suite = {
|
spec = {
|
||||||
'name': name,
|
'name': name,
|
||||||
'nerrors': None,
|
'nerrors': None,
|
||||||
'nfailures': None,
|
'nfailures': None,
|
||||||
'ntests': None,
|
'npackages': None,
|
||||||
'time': None,
|
'time': None,
|
||||||
'timestamp': time.strftime(
|
'timestamp': time.strftime(
|
||||||
"%a, %d %b %Y %H:%M:%S", time.gmtime()
|
"%a, %d %b %Y %H:%M:%S", time.gmtime()
|
||||||
),
|
),
|
||||||
'properties': [],
|
'properties': [],
|
||||||
'testcases': []
|
'packages': []
|
||||||
}
|
}
|
||||||
|
|
||||||
self.test_suites.append(suite)
|
self.specs.append(spec)
|
||||||
|
|
||||||
Property = collections.namedtuple('Property', ['name', 'value'])
|
Property = collections.namedtuple('Property', ['name', 'value'])
|
||||||
suite['properties'].append(
|
spec['properties'].append(
|
||||||
Property('architecture', spec.architecture)
|
Property('architecture', input_spec.architecture)
|
||||||
)
|
)
|
||||||
suite['properties'].append(Property('compiler', spec.compiler))
|
spec['properties'].append(
|
||||||
|
Property('compiler', input_spec.compiler))
|
||||||
|
|
||||||
# Check which specs are already installed and mark them as skipped
|
# Check which specs are already installed and mark them as skipped
|
||||||
for dep in filter(lambda x: x.package.installed, spec.traverse()):
|
for dep in filter(lambda x: x.package.installed,
|
||||||
test_case = {
|
input_spec.traverse()):
|
||||||
|
package = {
|
||||||
'name': dep.name,
|
'name': dep.name,
|
||||||
'id': dep.dag_hash(),
|
'id': dep.dag_hash(),
|
||||||
'elapsed_time': '0.0',
|
'elapsed_time': '0.0',
|
||||||
'result': 'skipped',
|
'result': 'skipped',
|
||||||
'message': 'Spec already installed'
|
'message': 'Spec already installed'
|
||||||
}
|
}
|
||||||
suite['testcases'].append(test_case)
|
spec['packages'].append(package)
|
||||||
|
|
||||||
def gather_info(do_install):
|
def gather_info(do_install):
|
||||||
"""Decorates do_install to gather useful information for
|
"""Decorates do_install to gather useful information for
|
||||||
@ -134,7 +136,7 @@ def wrapper(pkg, *args, **kwargs):
|
|||||||
# We accounted before for what is already installed
|
# We accounted before for what is already installed
|
||||||
installed_on_entry = pkg.installed
|
installed_on_entry = pkg.installed
|
||||||
|
|
||||||
test_case = {
|
package = {
|
||||||
'name': pkg.name,
|
'name': pkg.name,
|
||||||
'id': pkg.spec.dag_hash(),
|
'id': pkg.spec.dag_hash(),
|
||||||
'elapsed_time': None,
|
'elapsed_time': None,
|
||||||
@ -147,42 +149,42 @@ def wrapper(pkg, *args, **kwargs):
|
|||||||
try:
|
try:
|
||||||
|
|
||||||
value = do_install(pkg, *args, **kwargs)
|
value = do_install(pkg, *args, **kwargs)
|
||||||
test_case['result'] = 'success'
|
package['result'] = 'success'
|
||||||
if installed_on_entry:
|
if installed_on_entry:
|
||||||
return
|
return
|
||||||
|
|
||||||
except spack.build_environment.InstallError as e:
|
except spack.build_environment.InstallError as e:
|
||||||
# An InstallError is considered a failure (the recipe
|
# An InstallError is considered a failure (the recipe
|
||||||
# didn't work correctly)
|
# didn't work correctly)
|
||||||
test_case['result'] = 'failure'
|
package['result'] = 'failure'
|
||||||
test_case['stdout'] = fetch_package_log(pkg)
|
package['stdout'] = fetch_package_log(pkg)
|
||||||
test_case['message'] = e.message or 'Installation failure'
|
package['message'] = e.message or 'Installation failure'
|
||||||
test_case['exception'] = e.traceback
|
package['exception'] = e.traceback
|
||||||
|
|
||||||
except (Exception, BaseException) as e:
|
except (Exception, BaseException) as e:
|
||||||
# Everything else is an error (the installation
|
# Everything else is an error (the installation
|
||||||
# failed outside of the child process)
|
# failed outside of the child process)
|
||||||
test_case['result'] = 'error'
|
package['result'] = 'error'
|
||||||
test_case['stdout'] = fetch_package_log(pkg)
|
package['stdout'] = fetch_package_log(pkg)
|
||||||
test_case['message'] = str(e) or 'Unknown error'
|
package['message'] = str(e) or 'Unknown error'
|
||||||
test_case['exception'] = traceback.format_exc()
|
package['exception'] = traceback.format_exc()
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
test_case['elapsed_time'] = time.time() - start_time
|
package['elapsed_time'] = time.time() - start_time
|
||||||
|
|
||||||
# Append the case to the correct test suites. In some
|
# Append the package to the correct spec report. In some
|
||||||
# cases it may happen that a spec that is asked to be
|
# cases it may happen that a spec that is asked to be
|
||||||
# installed explicitly will also be installed as a
|
# installed explicitly will also be installed as a
|
||||||
# dependency of another spec. In this case append to both
|
# dependency of another spec. In this case append to both
|
||||||
# test suites.
|
# spec reports.
|
||||||
for s in llnl.util.lang.dedupe([pkg.spec.root, pkg.spec]):
|
for s in llnl.util.lang.dedupe([pkg.spec.root, pkg.spec]):
|
||||||
name = name_fmt.format(s.name, s.dag_hash(length=7))
|
name = name_fmt.format(s.name, s.dag_hash(length=7))
|
||||||
try:
|
try:
|
||||||
item = next((
|
item = next((
|
||||||
x for x in self.test_suites
|
x for x in self.specs
|
||||||
if x['name'] == name
|
if x['name'] == name
|
||||||
))
|
))
|
||||||
item['testcases'].append(test_case)
|
item['packages'].append(package)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -199,16 +201,16 @@ def __exit__(self, exc_type, exc_val, exc_tb):
|
|||||||
# Restore the original method in PackageBase
|
# Restore the original method in PackageBase
|
||||||
spack.package.PackageBase.do_install = InfoCollector._backup_do_install
|
spack.package.PackageBase.do_install = InfoCollector._backup_do_install
|
||||||
|
|
||||||
for suite in self.test_suites:
|
for spec in self.specs:
|
||||||
suite['ntests'] = len(suite['testcases'])
|
spec['npackages'] = len(spec['packages'])
|
||||||
suite['nfailures'] = len(
|
spec['nfailures'] = len(
|
||||||
[x for x in suite['testcases'] if x['result'] == 'failure']
|
[x for x in spec['packages'] if x['result'] == 'failure']
|
||||||
)
|
)
|
||||||
suite['nerrors'] = len(
|
spec['nerrors'] = len(
|
||||||
[x for x in suite['testcases'] if x['result'] == 'error']
|
[x for x in spec['packages'] if x['result'] == 'error']
|
||||||
)
|
)
|
||||||
suite['time'] = sum([
|
spec['time'] = sum([
|
||||||
float(x['elapsed_time']) for x in suite['testcases']
|
float(x['elapsed_time']) for x in spec['packages']
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
@ -273,4 +275,4 @@ def __exit__(self, exc_type, exc_val, exc_tb):
|
|||||||
with open(self.filename, 'w') as f:
|
with open(self.filename, 'w') as f:
|
||||||
env = spack.tengine.make_environment()
|
env = spack.tengine.make_environment()
|
||||||
t = env.get_template(templates[self.format_name])
|
t = env.get_template(templates[self.format_name])
|
||||||
f.write(t.render({'test_suites': self.collector.test_suites}))
|
f.write(t.render({'specs': self.collector.specs}))
|
||||||
|
@ -6,41 +6,41 @@
|
|||||||
http://help.catchsoftware.com/display/ET/JUnit+Format
|
http://help.catchsoftware.com/display/ET/JUnit+Format
|
||||||
-->
|
-->
|
||||||
<testsuites>
|
<testsuites>
|
||||||
{% for suite in test_suites %}
|
{% for spec in specs %}
|
||||||
<testsuite name="{{ suite.name }}"
|
<testsuite name="{{ spec.name }}"
|
||||||
errors="{{ suite.nerrors }}"
|
errors="{{ spec.nerrors }}"
|
||||||
tests="{{ suite.ntests }}"
|
tests="{{ spec.npackages }}"
|
||||||
failures="{{ suite.nfailures }}"
|
failures="{{ spec.nfailures }}"
|
||||||
time="{{ suite.time }}"
|
time="{{ spec.time }}"
|
||||||
timestamp="{{ suite.timestamp }}" >
|
timestamp="{{ spec.timestamp }}" >
|
||||||
<properties>
|
<properties>
|
||||||
{% for property in suite.properties %}
|
{% for property in spec.properties %}
|
||||||
<property name="{{ property.name }}" value="{{ property.value }}" />
|
<property name="{{ property.name }}" value="{{ property.value }}" />
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</properties>
|
</properties>
|
||||||
{% for test in suite.testcases %}
|
{% for package in spec.packages %}
|
||||||
<testcase classname="{{ test.name }}"
|
<testcase classname="{{ package.name }}"
|
||||||
name="{{ test.id }}"
|
name="{{ package.id }}"
|
||||||
time="{{ test.elapsed_time }}">
|
time="{{ package.elapsed_time }}">
|
||||||
{% if test.result == 'failure' %}
|
{% if package.result == 'failure' %}
|
||||||
<failure message="{{ test.message }}">
|
<failure message="{{ package.message }}">
|
||||||
{{ test.exception }}
|
{{ package.exception }}
|
||||||
</failure>
|
</failure>
|
||||||
{% elif test.result == 'error' %}
|
{% elif package.result == 'error' %}
|
||||||
<error message="{{ test.message }}">
|
<error message="{{ package.message }}">
|
||||||
{{ test.exception }}
|
{{ package.exception }}
|
||||||
</error>
|
</error>
|
||||||
{% elif test.result == 'skipped' %}
|
{% elif package.result == 'skipped' %}
|
||||||
<skipped />
|
<skipped />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if test.stdout %}
|
{% if package.stdout %}
|
||||||
<system-out>
|
<system-out>
|
||||||
{{ test.stdout }}
|
{{ package.stdout }}
|
||||||
</system-out>
|
</system-out>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if test.stderr %}
|
{% if package.stderr %}
|
||||||
<system-err>
|
<system-err>
|
||||||
{{ test.stderr }}
|
{{ package.stderr }}
|
||||||
</system-err>
|
</system-err>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</testcase>
|
</testcase>
|
||||||
|
Loading…
Reference in New Issue
Block a user