-
Notifications
You must be signed in to change notification settings - Fork 26
/
test.py
159 lines (132 loc) · 5.61 KB
/
test.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# Copyright 2016-2018 Dirk Thomas
# Licensed under the Apache License, Version 2.0
from contextlib import suppress
import os
from pathlib import Path
import shutil
from colcon_cmake.task.cmake import CTEST_EXECUTABLE
from colcon_cmake.task.cmake import get_variable_from_cmake_cache
from colcon_core.event.test import TestFailure
from colcon_core.logging import colcon_logger
from colcon_core.plugin_system import satisfies_version
from colcon_core.shell import get_command_environment
from colcon_core.subprocess import check_output
from colcon_core.task import run
from colcon_core.task import TaskExtensionPoint
logger = colcon_logger.getChild(__name__)
class CmakeTestTask(TaskExtensionPoint):
"""Test CMake packages."""
def __init__(self): # noqa: D107
super().__init__()
satisfies_version(TaskExtensionPoint.EXTENSION_POINT_VERSION, '^1.0')
def add_arguments(self, *, parser): # noqa: D102
parser.add_argument(
'--ctest-args',
nargs='*', metavar='*', type=str.lstrip,
help='Pass arguments to CTest projects. '
'Arguments matching other options must be prefixed by a space,\n'
'e.g. --ctest-args " --help" (stdout might not be shown by '
'default, e.g. add `--event-handlers console_cohesion+`)')
async def test(self, *, additional_hooks=None): # noqa: D102
pkg = self.context.pkg
args = self.context.args
logger.info(
"Testing CMake package in '{args.path}'".format_map(locals()))
assert os.path.exists(args.build_base), \
'Has this package been built before?'
try:
env = await get_command_environment(
'test', args.build_base, self.context.dependencies)
except RuntimeError as e:
logger.error(str(e))
return 1
if CTEST_EXECUTABLE is None:
raise RuntimeError("Could not find 'ctest' executable")
# check if CTest has any tests
output = await check_output(
[CTEST_EXECUTABLE, '--show-only'],
cwd=args.build_base,
env=env)
for line in output.decode().splitlines():
if line.startswith(' '):
break
else:
logger.log(
5, "No ctests found in '{args.path}'".format_map(locals()))
return
# CTest arguments
ctest_args = [
# generate xml of test summary
'-D', 'ExperimentalTest', '--no-compress-output',
# show all test output
'-V',
'--force-new-ctest-process',
] + (args.ctest_args or [])
# Check for explicit '-C' or '--build-type' in args.ctest_args.
# If not, we'll add it based on the CMakeCache CMAKE_BUILD_TYPE value.
if '-C' not in ctest_args and '--build-config' not in ctest_args:
# choose configuration, required for multi-configuration generators
ctest_args[0:0] = [
'-C', self._get_configuration_from_cmake(args.build_base),
]
if args.retest_until_fail:
count = args.retest_until_fail + 1
ctest_args += [
'--repeat-until-fail', str(count),
]
# delete an existing Testing/TAG file to ensure a new tag name is used
tag_file = Path(args.build_base) / 'Testing' / 'TAG'
with suppress(FileNotFoundError):
tag_file.unlink()
rerun = 0
while True:
# invoke CTest
completed = await run(
self.context,
[CTEST_EXECUTABLE] + ctest_args,
cwd=args.build_base, env=env)
if not completed.returncode:
break
# try again if requested
if args.retest_until_pass > rerun:
if not rerun:
ctest_args += [
'--rerun-failed',
]
rerun += 1
continue
# CTest reports failing tests
if completed.returncode == 8:
self.context.put_event_into_queue(TestFailure(pkg.name))
# the return code should still be 0
break
return completed.returncode
# copy Testing/TAG and Testing/<tagname>/Test.xml to custom location
if args.test_result_base and args.test_result_base != args.build_base:
if not tag_file.is_file():
return
# find the latest Test.xml file
latest_xml_dir = tag_file.read_text().splitlines()[0]
latest_xml_path = tag_file.parent / latest_xml_dir / 'Test.xml'
if not latest_xml_path.exists():
logger.warning(
"Skipping '{tag_file}': could not find latest XML file "
"'{latest_xml_path}'".format_map(locals()))
return
dst = Path(args.test_result_base) / 'Testing' / latest_xml_dir
dst.mkdir(parents=True, exist_ok=True)
_copy_file(str(tag_file), str(dst.parent / tag_file.name))
_copy_file(str(latest_xml_path), str(dst / latest_xml_path.name))
def _get_configuration_from_cmake(self, build_base):
# get for CMake build type from the CMake cache
build_type = get_variable_from_cmake_cache(
build_base, 'CMAKE_BUILD_TYPE')
if build_type in ('Debug', 'MinSizeRel', 'RelWithDebInfo'):
return build_type
return 'Release'
def _copy_file(src, dst):
if os.path.islink(dst):
os.unlink(dst)
elif os.path.isdir(dst):
shutil.rmtree(dst)
shutil.copy2(src, dst)