1 |
commit: 4880642564e0285f7670d186aa254c3f9202f86d |
2 |
Author: Sebastian Pipping <sping <AT> gentoo <DOT> org> |
3 |
AuthorDate: Mon Jan 30 04:48:57 2023 +0000 |
4 |
Commit: Sebastian Pipping <sping <AT> gentoo <DOT> org> |
5 |
CommitDate: Mon Jan 30 04:53:23 2023 +0000 |
6 |
URL: https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=48806425 |
7 |
|
8 |
app-admin/salt: Fix importlib-metadata usage + fix salt-ssh for py3.11 hosts |
9 |
|
10 |
The Python 3.11 issue upstream: |
11 |
- https://github.com/saltstack/salt/issues/62676 |
12 |
- https://github.com/saltstack/salt/pull/62677 |
13 |
|
14 |
The importlib-metadata issue: |
15 |
- https://github.com/saltstack/salt/issues/62851 |
16 |
- https://github.com/saltstack/salt/pull/62854 |
17 |
|
18 |
Patches have been extracted from pull requests as following: |
19 |
- git clone https://github.com/saltstack/salt |
20 |
- cd salt |
21 |
- git diff b676e6338a7c094cb3335d11f851ac0e12222017^ 45b924bad865a00116d2e045fe71229f2dc3376e -- salt/utils/entrypoints.py > salt-3005.1-importlib-metadata-5-r1.patch |
22 |
- git diff 00352ae6e0ed0b80a75ec65cb925dd31a625010d^ 91efaea4975f37de97a88687d40e54e774151a8b -- salt/modules/file.py | head -n 123 > salt-3005.1-modules-file-python-3.11-host.patch |
23 |
|
24 |
Be sure to call salt-ssh with "--regen-thin" the first time |
25 |
after updating, to not end up running unpatched 3005.1(-r0) code. |
26 |
|
27 |
Closes: https://bugs.gentoo.org/875389 |
28 |
Closes: https://bugs.gentoo.org/883671 |
29 |
Signed-off-by: Sebastian Pipping <sping <AT> gentoo.org> |
30 |
|
31 |
.../salt-3005.1-importlib-metadata-5-r1.patch | 29 +++ |
32 |
...salt-3005.1-modules-file-python-3.11-host.patch | 123 ++++++++++++ |
33 |
app-admin/salt/salt-3005.1-r1.ebuild | 220 +++++++++++++++++++++ |
34 |
3 files changed, 372 insertions(+) |
35 |
|
36 |
diff --git a/app-admin/salt/files/salt-3005.1-importlib-metadata-5-r1.patch b/app-admin/salt/files/salt-3005.1-importlib-metadata-5-r1.patch |
37 |
new file mode 100644 |
38 |
index 000000000000..c4c8056c1a6a |
39 |
--- /dev/null |
40 |
+++ b/app-admin/salt/files/salt-3005.1-importlib-metadata-5-r1.patch |
41 |
@@ -0,0 +1,29 @@ |
42 |
+diff --git a/salt/utils/entrypoints.py b/salt/utils/entrypoints.py |
43 |
+index 3effa0b494..9452878ade 100644 |
44 |
+--- a/salt/utils/entrypoints.py |
45 |
++++ b/salt/utils/entrypoints.py |
46 |
+@@ -38,13 +38,20 @@ def iter_entry_points(group, name=None): |
47 |
+ entry_points_listing = [] |
48 |
+ entry_points = importlib_metadata.entry_points() |
49 |
+ |
50 |
+- for entry_point_group, entry_points_list in entry_points.items(): |
51 |
+- if entry_point_group != group: |
52 |
+- continue |
53 |
+- for entry_point in entry_points_list: |
54 |
++ try: |
55 |
++ for entry_point in entry_points.select(group=group): |
56 |
+ if name is not None and entry_point.name != name: |
57 |
+ continue |
58 |
+ entry_points_listing.append(entry_point) |
59 |
++ except AttributeError: |
60 |
++ # importlib-metadata<5.0.0 |
61 |
++ for entry_point_group, entry_points_list in entry_points.items(): |
62 |
++ if entry_point_group != group: |
63 |
++ continue |
64 |
++ for entry_point in entry_points_list: |
65 |
++ if name is not None and entry_point.name != name: |
66 |
++ continue |
67 |
++ entry_points_listing.append(entry_point) |
68 |
+ |
69 |
+ return entry_points_listing |
70 |
+ |
71 |
|
72 |
diff --git a/app-admin/salt/files/salt-3005.1-modules-file-python-3.11-host.patch b/app-admin/salt/files/salt-3005.1-modules-file-python-3.11-host.patch |
73 |
new file mode 100644 |
74 |
index 000000000000..2e9be8db18c0 |
75 |
--- /dev/null |
76 |
+++ b/app-admin/salt/files/salt-3005.1-modules-file-python-3.11-host.patch |
77 |
@@ -0,0 +1,123 @@ |
78 |
+diff --git a/salt/modules/file.py b/salt/modules/file.py |
79 |
+index f39d618203..93eeaf312e 100644 |
80 |
+--- a/salt/modules/file.py |
81 |
++++ b/salt/modules/file.py |
82 |
+@@ -16,7 +16,6 @@ import hashlib |
83 |
+ import itertools |
84 |
+ import logging |
85 |
+ import mmap |
86 |
+-import operator |
87 |
+ import os |
88 |
+ import re |
89 |
+ import shutil |
90 |
+@@ -28,7 +27,6 @@ import time |
91 |
+ import urllib.parse |
92 |
+ from collections import namedtuple |
93 |
+ from collections.abc import Iterable, Mapping |
94 |
+-from functools import reduce |
95 |
+ |
96 |
+ import salt.utils.args |
97 |
+ import salt.utils.atomicfile |
98 |
+@@ -1622,38 +1620,38 @@ def comment_line(path, regex, char="#", cmnt=True, backup=".bak"): |
99 |
+ |
100 |
+ def _get_flags(flags): |
101 |
+ """ |
102 |
+- Return an integer appropriate for use as a flag for the re module from a |
103 |
+- list of human-readable strings |
104 |
++ Return the names of the Regex flags that correspond to flags |
105 |
+ |
106 |
+ .. code-block:: python |
107 |
+ |
108 |
+- >>> _get_flags(['MULTILINE', 'IGNORECASE']) |
109 |
+- 10 |
110 |
++ >>> _get_flags(['IGNORECASE', 'MULTILINE']) |
111 |
++ re.IGNORECASE|re.MULTILINE |
112 |
+ >>> _get_flags('MULTILINE') |
113 |
+- 8 |
114 |
+- >>> _get_flags(2) |
115 |
+- 2 |
116 |
++ re.MULTILINE |
117 |
++ >>> _get_flags(8) |
118 |
++ re.MULTILINE |
119 |
++ >>> _get_flags(re.IGNORECASE) |
120 |
++ re.IGNORECASE |
121 |
+ """ |
122 |
+- if isinstance(flags, str): |
123 |
++ if isinstance(flags, re.RegexFlag): |
124 |
++ return flags |
125 |
++ elif isinstance(flags, int): |
126 |
++ return re.RegexFlag(flags) |
127 |
++ elif isinstance(flags, str): |
128 |
+ flags = [flags] |
129 |
+ |
130 |
+ if isinstance(flags, Iterable) and not isinstance(flags, Mapping): |
131 |
+- _flags_acc = [0] # An initial 0 avoids resucing on empty list, an error |
132 |
++ _flags = re.RegexFlag(0) |
133 |
+ for flag in flags: |
134 |
+- _flag = getattr(re, str(flag).upper()) |
135 |
+- |
136 |
+- if not isinstance(_flag, int): |
137 |
+- raise SaltInvocationError("Invalid re flag given: {}".format(flag)) |
138 |
+- |
139 |
+- _flags_acc.append(_flag) |
140 |
+- |
141 |
+- return reduce(operator.__or__, _flags_acc) |
142 |
+- elif isinstance(flags, int): |
143 |
+- return flags |
144 |
++ _flag = getattr(re.RegexFlag, str(flag).upper(), None) |
145 |
++ if not _flag: |
146 |
++ raise CommandExecutionError(f"Invalid re flag given: {flag}") |
147 |
++ _flags |= _flag |
148 |
++ return _flags |
149 |
+ else: |
150 |
+- raise SaltInvocationError( |
151 |
+- 'Invalid re flags: "{}", must be given either as a single flag ' |
152 |
+- "string, a list of strings, or as an integer".format(flags) |
153 |
++ raise CommandExecutionError( |
154 |
++ f'Invalid re flags: "{flags}", must be given either as a single flag ' |
155 |
++ "string, a list of strings, as an integer, or as an re flag" |
156 |
+ ) |
157 |
+ |
158 |
+ |
159 |
+@@ -2513,8 +2511,8 @@ def replace( |
160 |
+ "Only one of append and prepend_if_not_found is permitted" |
161 |
+ ) |
162 |
+ |
163 |
+- flags_num = _get_flags(flags) |
164 |
+- cpattern = re.compile(salt.utils.stringutils.to_bytes(pattern), flags_num) |
165 |
++ re_flags = _get_flags(flags) |
166 |
++ cpattern = re.compile(salt.utils.stringutils.to_bytes(pattern), re_flags) |
167 |
+ filesize = os.path.getsize(path) |
168 |
+ if bufsize == "file": |
169 |
+ bufsize = filesize |
170 |
+@@ -2582,7 +2580,7 @@ def replace( |
171 |
+ "^{}($|(?=\r\n))".format(re.escape(content)) |
172 |
+ ), |
173 |
+ r_data, |
174 |
+- flags=flags_num, |
175 |
++ flags=re_flags, |
176 |
+ ): |
177 |
+ # Content was found, so set found. |
178 |
+ found = True |
179 |
+@@ -3132,7 +3130,11 @@ def search(path, pattern, flags=8, bufsize=1, ignore_if_missing=False, multiline |
180 |
+ salt '*' file.search /etc/crontab 'mymaintenance.sh' |
181 |
+ """ |
182 |
+ if multiline: |
183 |
+- flags = _add_flags(flags, "MULTILINE") |
184 |
++ re_flags = _add_flags(flags, "MULTILINE") |
185 |
++ else: |
186 |
++ re_flags = _get_flags(flags) |
187 |
++ |
188 |
++ if re.RegexFlag.MULTILINE in re_flags: |
189 |
+ bufsize = "file" |
190 |
+ |
191 |
+ # This function wraps file.replace on purpose in order to enforce |
192 |
+@@ -3142,7 +3144,7 @@ def search(path, pattern, flags=8, bufsize=1, ignore_if_missing=False, multiline |
193 |
+ path, |
194 |
+ pattern, |
195 |
+ "", |
196 |
+- flags=flags, |
197 |
++ flags=re_flags, |
198 |
+ bufsize=bufsize, |
199 |
+ dry_run=True, |
200 |
+ search_only=True, |
201 |
|
202 |
diff --git a/app-admin/salt/salt-3005.1-r1.ebuild b/app-admin/salt/salt-3005.1-r1.ebuild |
203 |
new file mode 100644 |
204 |
index 000000000000..d0056bab8f71 |
205 |
--- /dev/null |
206 |
+++ b/app-admin/salt/salt-3005.1-r1.ebuild |
207 |
@@ -0,0 +1,220 @@ |
208 |
+# Copyright 1999-2023 Gentoo Authors |
209 |
+# Distributed under the terms of the GNU General Public License v2 |
210 |
+ |
211 |
+EAPI=8 |
212 |
+PYTHON_COMPAT=( python3_10 ) |
213 |
+ |
214 |
+DISTUTILS_USE_PEP517=setuptools |
215 |
+inherit systemd distutils-r1 |
216 |
+ |
217 |
+DESCRIPTION="Salt is a remote execution and configuration manager" |
218 |
+HOMEPAGE="https://www.saltstack.com/resources/community/ |
219 |
+ https://github.com/saltstack" |
220 |
+ |
221 |
+if [[ ${PV} == 9999* ]]; then |
222 |
+ inherit git-r3 |
223 |
+ EGIT_REPO_URI="https://github.com/${PN}stack/${PN}.git" |
224 |
+ EGIT_BRANCH="develop" |
225 |
+ SRC_URI="" |
226 |
+else |
227 |
+ SRC_URI="mirror://pypi/${PN:0:1}/${PN}/${P}.tar.gz" |
228 |
+ KEYWORDS="~amd64 ~arm ~arm64 ~riscv ~x86" |
229 |
+fi |
230 |
+ |
231 |
+LICENSE="Apache-2.0" |
232 |
+SLOT="0" |
233 |
+IUSE=" |
234 |
+ cheetah cherrypy ldap libcloud libvirt genshi gnupg keyring mako |
235 |
+ mongodb neutron nova openssl portage profile redis selinux test raet |
236 |
+ +zeromq vim-syntax |
237 |
+" |
238 |
+ |
239 |
+RDEPEND=" |
240 |
+ sys-apps/pciutils |
241 |
+ >=dev-python/distro-1.5[${PYTHON_USEDEP}] |
242 |
+ >=dev-python/jinja-3.0.3[${PYTHON_USEDEP}] |
243 |
+ dev-python/jmespath[${PYTHON_USEDEP}] |
244 |
+ dev-python/libnacl[${PYTHON_USEDEP}] |
245 |
+ >=dev-python/msgpack-1.0.0[${PYTHON_USEDEP}] |
246 |
+ >=dev-python/psutil-5.0.0[${PYTHON_USEDEP}] |
247 |
+ >=dev-python/pycryptodome-3.9.8[${PYTHON_USEDEP}] |
248 |
+ dev-python/pyyaml[${PYTHON_USEDEP}] |
249 |
+ >=dev-python/markupsafe-2.0.1[${PYTHON_USEDEP}] |
250 |
+ >=dev-python/requests-1.0.0[${PYTHON_USEDEP}] |
251 |
+ dev-python/setuptools[${PYTHON_USEDEP}] |
252 |
+ dev-python/tomli[${PYTHON_USEDEP}] |
253 |
+ dev-python/watchdog[${PYTHON_USEDEP}] |
254 |
+ libcloud? ( |
255 |
+ dev-python/aiohttp[${PYTHON_USEDEP}] |
256 |
+ dev-python/aiosignal[${PYTHON_USEDEP}] |
257 |
+ dev-python/async-timeout[${PYTHON_USEDEP}] |
258 |
+ >=dev-python/libcloud-2.5.0[${PYTHON_USEDEP}] |
259 |
+ ) |
260 |
+ mako? ( dev-python/mako[${PYTHON_USEDEP}] ) |
261 |
+ ldap? ( dev-python/python-ldap[${PYTHON_USEDEP}] ) |
262 |
+ libvirt? ( |
263 |
+ dev-python/libvirt-python[${PYTHON_USEDEP}] |
264 |
+ ) |
265 |
+ openssl? ( |
266 |
+ dev-libs/openssl:0=[-bindist(-)] |
267 |
+ dev-python/pyopenssl[${PYTHON_USEDEP}] |
268 |
+ ) |
269 |
+ raet? ( |
270 |
+ >=dev-python/libnacl-1.0.0[${PYTHON_USEDEP}] |
271 |
+ >=dev-python/ioflo-1.1.7[${PYTHON_USEDEP}] |
272 |
+ >=dev-python/raet-0.6.0[${PYTHON_USEDEP}] |
273 |
+ ) |
274 |
+ cherrypy? ( >=dev-python/cherrypy-3.2.2[${PYTHON_USEDEP}] ) |
275 |
+ cheetah? ( >=dev-python/cheetah3-3.2.2[${PYTHON_USEDEP}] ) |
276 |
+ genshi? ( dev-python/genshi[${PYTHON_USEDEP}] ) |
277 |
+ mongodb? ( dev-python/pymongo[${PYTHON_USEDEP}] ) |
278 |
+ portage? ( sys-apps/portage[${PYTHON_USEDEP}] ) |
279 |
+ keyring? ( dev-python/keyring[${PYTHON_USEDEP}] ) |
280 |
+ redis? ( dev-python/redis-py[${PYTHON_USEDEP}] ) |
281 |
+ selinux? ( sec-policy/selinux-salt ) |
282 |
+ nova? ( |
283 |
+ >=dev-python/python-novaclient-2.17.0[${PYTHON_USEDEP}] |
284 |
+ ) |
285 |
+ neutron? ( |
286 |
+ >=dev-python/python-neutronclient-2.3.6[${PYTHON_USEDEP}] |
287 |
+ ) |
288 |
+ gnupg? ( dev-python/python-gnupg[${PYTHON_USEDEP}] ) |
289 |
+ profile? ( dev-python/yappi[${PYTHON_USEDEP}] ) |
290 |
+ vim-syntax? ( app-vim/salt-vim ) |
291 |
+ zeromq? ( >=dev-python/pyzmq-19.0.0[${PYTHON_USEDEP}] ) |
292 |
+" |
293 |
+BDEPEND=" |
294 |
+ test? ( |
295 |
+ ${RDEPEND} |
296 |
+ >=dev-python/boto-2.32.1[${PYTHON_USEDEP}] |
297 |
+ dev-python/certifi[${PYTHON_USEDEP}] |
298 |
+ dev-python/cherrypy[${PYTHON_USEDEP}] |
299 |
+ >=dev-python/jsonschema-3.0[${PYTHON_USEDEP}] |
300 |
+ dev-python/mako[${PYTHON_USEDEP}] |
301 |
+ >=dev-python/mock-2.0.0[${PYTHON_USEDEP}] |
302 |
+ >=dev-python/moto-2.0.0[${PYTHON_USEDEP}] |
303 |
+ dev-python/passlib |
304 |
+ dev-python/pip[${PYTHON_USEDEP}] |
305 |
+ dev-python/pyopenssl[${PYTHON_USEDEP}] |
306 |
+ >=dev-python/pytest-7.0.1[${PYTHON_USEDEP}] |
307 |
+ >=dev-python/pytest-salt-factories-1.0.0_rc17[${PYTHON_USEDEP}] |
308 |
+ dev-python/pytest-tempdir[${PYTHON_USEDEP}] |
309 |
+ dev-python/pytest-helpers-namespace[${PYTHON_USEDEP}] |
310 |
+ dev-python/pytest-subtests[${PYTHON_USEDEP}] |
311 |
+ dev-python/pytest-shell-utilities[${PYTHON_USEDEP}] |
312 |
+ dev-python/pytest-skip-markers[${PYTHON_USEDEP}] |
313 |
+ dev-python/pytest-system-statistics[${PYTHON_USEDEP}] |
314 |
+ dev-python/flaky[${PYTHON_USEDEP}] |
315 |
+ dev-python/libcloud[${PYTHON_USEDEP}] |
316 |
+ net-dns/bind-tools |
317 |
+ >=dev-python/virtualenv-20.3.0[${PYTHON_USEDEP}] |
318 |
+ dev-util/yamllint[${PYTHON_USEDEP}] |
319 |
+ !x86? ( >=dev-python/boto3-1.17.67[${PYTHON_USEDEP}] ) |
320 |
+ ) |
321 |
+" |
322 |
+ |
323 |
+DOCS=( README.rst AUTHORS ) |
324 |
+ |
325 |
+REQUIRED_USE="|| ( raet zeromq ) |
326 |
+ test? ( cheetah genshi )" |
327 |
+RESTRICT="!test? ( test ) x86? ( test )" |
328 |
+ |
329 |
+PATCHES=( |
330 |
+ "${FILESDIR}/salt-3003-skip-tests-that-oom-machine.patch" |
331 |
+ "${FILESDIR}/salt-3003-gentoolkit-revdep.patch" |
332 |
+ "${FILESDIR}/salt-3002-tests.patch" |
333 |
+ "${FILESDIR}/salt-3003.1-tests.patch" |
334 |
+ "${FILESDIR}/salt-3005-relax-pyzmq-dep.patch" |
335 |
+ "${FILESDIR}/salt-3005-tests.patch" |
336 |
+ "${FILESDIR}/salt-3005.1-no-entry-points.patch" |
337 |
+ "${FILESDIR}/salt-3005.1-importlib-metadata-5-r1.patch" |
338 |
+ "${FILESDIR}/salt-3005.1-tests.patch" |
339 |
+ "${FILESDIR}/salt-3005.1-modules-file-python-3.11-host.patch" |
340 |
+) |
341 |
+ |
342 |
+python_prepare_all() { |
343 |
+ # remove tests with external dependencies that may not be available, and |
344 |
+ # tests that don't work in sandbox |
345 |
+ rm tests/unit/{test_{zypp_plugins,module_names},utils/test_extend}.py || die |
346 |
+ rm tests/unit/modules/test_boto_{vpc,secgroup,elb}.py || die |
347 |
+ rm tests/unit/states/test_boto_vpc.py || die |
348 |
+ rm tests/support/gitfs.py tests/unit/runners/test_git_pillar.py || die |
349 |
+ rm tests/pytests/functional/transport/server/test_req_channel.py || die |
350 |
+ rm tests/pytests/functional/utils/test_async_event_publisher.py || die |
351 |
+ rm tests/pytests/functional/runners/test_winrepo.py || die |
352 |
+ |
353 |
+ # tests that require network access |
354 |
+ rm tests/unit/{states,modules}/test_zcbuildout.py || die |
355 |
+ rm -r tests/integration/cloud || die |
356 |
+ rm -r tests/kitchen/tests/wordpress/tests || die |
357 |
+ rm tests/kitchen/test_kitchen.py || die |
358 |
+ rm tests/unit/modules/test_network.py || die |
359 |
+ rm tests/pytests/functional/modules/test_pip.py || die |
360 |
+ rm tests/pytests/unit/client/ssh/test_ssh.py || die |
361 |
+ rm -r tests/pytests/{integration,functional}/netapi tests/integration/netapi || die |
362 |
+ |
363 |
+ # tests require root access |
364 |
+ rm tests/integration/pillar/test_git_pillar.py || die |
365 |
+ rm tests/integration/states/test_supervisord.py || die |
366 |
+ |
367 |
+ # removes contextvars, see bug: https://bugs.gentoo.org/799431 |
368 |
+ sed -i '/^contextvars/d' requirements/base.txt || die |
369 |
+ |
370 |
+ # make sure pkg_resources doesn't bomb because pycrypto isn't installed |
371 |
+ find "${S}" -name '*.txt' -print0 | xargs -0 sed -e '/pycrypto>/ d ; /pycryptodomex/ d' -i || die |
372 |
+ # pycryptodome rather than pycryptodomex |
373 |
+ find "${S}" -name '*.py' -print0 | xargs -0 -- sed -i -e 's:Cryptodome:Crypto:g' -- || die |
374 |
+ |
375 |
+ distutils-r1_python_prepare_all |
376 |
+} |
377 |
+ |
378 |
+python_install_all() { |
379 |
+ local svc |
380 |
+ USE_SETUPTOOLS=1 distutils-r1_python_install_all |
381 |
+ |
382 |
+ for svc in minion master syndic api; do |
383 |
+ newinitd "${FILESDIR}"/${svc}-initd-5 salt-${svc} |
384 |
+ newconfd "${FILESDIR}"/${svc}-confd-1 salt-${svc} |
385 |
+ systemd_dounit "${FILESDIR}"/salt-${svc}.service |
386 |
+ done |
387 |
+ |
388 |
+ insinto /etc/${PN} |
389 |
+ doins -r conf/* |
390 |
+} |
391 |
+ |
392 |
+python_test() { |
393 |
+ # testsuite likes lots of files |
394 |
+ ulimit -n 4096 || die |
395 |
+ |
396 |
+ local -a disable_tests=( |
397 |
+ # doesn't like the distutils warning |
398 |
+ batch_retcode |
399 |
+ multiple_modules_in_batch |
400 |
+ # hangs indefinitely |
401 |
+ master_type_disable |
402 |
+ # needs root |
403 |
+ runas_env_sudo_group |
404 |
+ # don't like sandbox |
405 |
+ split_multibyte_characters_{shiftjis,unicode} |
406 |
+ # doesn't like sandbox env |
407 |
+ log_sanitize |
408 |
+ ) |
409 |
+ local textexpr |
410 |
+ testexpr=$(printf 'not %s and ' "${disable_tests[@]}") |
411 |
+ |
412 |
+ # ${T} is too long a path for the tests to work |
413 |
+ local TMPDIR |
414 |
+ TMPDIR="$(mktemp --directory --tmpdir=/tmp ${PN}-XXXX)" || die |
415 |
+ ( |
416 |
+ export TMPDIR |
417 |
+ cleanup() { rm -rf "${TMPDIR}" || die; } |
418 |
+ |
419 |
+ trap cleanup EXIT |
420 |
+ |
421 |
+ addwrite "${TMPDIR}" |
422 |
+ |
423 |
+ USE_SETUPTOOLS=1 NO_INTERNET=1 SHELL="/bin/bash" \ |
424 |
+ "${EPYTHON}" -m pytest -vv -k "${testexpr%and }" \ |
425 |
+ || die "testing failed with ${EPYTHON}" |
426 |
+ ) |
427 |
+} |