Remove runtests.py and it's supporting code

This commit is contained in:
Pedro Algarvio 2020-09-03 09:56:33 +01:00 committed by Megan Wilhite
parent 2627e6ca1c
commit e47d5b0bd0
28 changed files with 27 additions and 4647 deletions

View file

@ -359,85 +359,15 @@ def _run_with_coverage(session, *test_cmd, env=None):
shutil.move(".coverage", os.path.join("artifacts", "coverage", ".coverage"))
def _runtests(session, coverage, cmd_args):
# Create required artifacts directories
_create_ci_directories()
env = {}
if IS_DARWIN:
# Don't nuke our multiprocessing efforts objc!
# https://stackoverflow.com/questions/50168647/multiprocessing-causes-python-to-crash-and-gives-an-error-may-have-been-in-progr
env["OBJC_DISABLE_INITIALIZE_FORK_SAFETY"] = "YES"
try:
if coverage is True:
_run_with_coverage(
session,
"coverage",
"run",
os.path.join("tests", "runtests.py"),
*cmd_args,
env=env
)
else:
cmd_args = ["python", os.path.join("tests", "runtests.py")] + list(cmd_args)
session.run(*cmd_args, env=env)
except CommandFailed: # pylint: disable=try-except-raise
# Disabling re-running failed tests for the time being
raise
# pylint: disable=unreachable
names_file_path = os.path.join("artifacts", "failed-tests.txt")
session.log("Re-running failed tests if possible")
session.install(
"--progress-bar=off", "xunitparser==1.3.3", silent=PIP_INSTALL_SILENT
def _runtests(session):
session.error(
"""\n\nruntests.py support has been removed from Salt. Please try `nox -e '{0}'` """
"""or `nox -e '{0}' -- --help` to know more about the supported CLI flags.\n"""
"For more information, please check "
"https://docs.saltproject.io/en/latest/topics/development/tests/index.html#running-the-tests\n..".format(
session._runner.global_config.sessions[0].replace("runtests", "pytest")
)
session.run(
"python",
os.path.join(
"tests", "support", "generate-names-file-from-failed-test-reports.py"
),
names_file_path,
)
if not os.path.exists(names_file_path):
session.log(
"Failed tests file(%s) was not found. Not rerunning failed tests.",
names_file_path,
)
# raise the original exception
raise
with open(names_file_path) as rfh:
contents = rfh.read().strip()
if not contents:
session.log(
"The failed tests file(%s) is empty. Not rerunning failed tests.",
names_file_path,
)
# raise the original exception
raise
failed_tests_count = len(contents.splitlines())
if failed_tests_count > 500:
# 500 test failures?! Something else must have gone wrong, don't even bother
session.error(
"Total failed tests({}) > 500. No point on re-running the failed tests".format(
failed_tests_count
)
)
for idx, flag in enumerate(cmd_args[:]):
if "--names-file=" in flag:
cmd_args.pop(idx)
break
elif flag == "--names-file":
cmd_args.pop(idx) # pop --names-file
cmd_args.pop(idx) # pop the actual names file
break
cmd_args.append("--names-file={}".format(names_file_path))
if coverage is True:
_run_with_coverage(
session, "coverage", "run", "-m", "tests.runtests", *cmd_args
)
else:
session.run("python", os.path.join("tests", "runtests.py"), *cmd_args)
# pylint: enable=unreachable
)
@nox.session(python=_PYTHON_VERSIONS, name="runtests-parametrized")
@ -448,33 +378,7 @@ def runtests_parametrized(session, coverage, transport, crypto):
"""
DO NOT CALL THIS NOX SESSION DIRECTLY
"""
# Install requirements
_install_requirements(session, transport, "unittest-xml-reporting==2.5.2")
if crypto:
session.run(
"pip",
"uninstall",
"-y",
"m2crypto",
"pycrypto",
"pycryptodome",
"pycryptodomex",
silent=True,
)
install_command = [
"--progress-bar=off",
"--constraint",
_get_pip_requirements_file(session, transport, crypto=True),
]
install_command.append(crypto)
session.install(*install_command, silent=PIP_INSTALL_SILENT)
cmd_args = [
"--tests-logfile={}".format(RUNTESTS_LOGFILE),
"--transport={}".format(transport),
] + session.posargs
_runtests(session, coverage, cmd_args)
_runtests(session)
@nox.session(python=_PYTHON_VERSIONS)
@ -483,15 +387,7 @@ def runtests(session, coverage):
"""
runtests.py session with zeromq transport and default crypto
"""
session.notify(
find_session_runner(
session,
"runtests-parametrized-{}".format(session.python),
coverage=coverage,
crypto=None,
transport="zeromq",
)
)
_runtests(session)
@nox.session(python=_PYTHON_VERSIONS, name="runtests-tcp")
@ -500,15 +396,7 @@ def runtests_tcp(session, coverage):
"""
runtests.py session with TCP transport and default crypto
"""
session.notify(
find_session_runner(
session,
"runtests-parametrized-{}".format(session.python),
coverage=coverage,
crypto=None,
transport="tcp",
)
)
_runtests(session)
@nox.session(python=_PYTHON_VERSIONS, name="runtests-zeromq")
@ -517,15 +405,7 @@ def runtests_zeromq(session, coverage):
"""
runtests.py session with zeromq transport and default crypto
"""
session.notify(
find_session_runner(
session,
"runtests-parametrized-{}".format(session.python),
coverage=coverage,
crypto=None,
transport="zeromq",
)
)
_runtests(session)
@nox.session(python=_PYTHON_VERSIONS, name="runtests-m2crypto")
@ -534,15 +414,7 @@ def runtests_m2crypto(session, coverage):
"""
runtests.py session with zeromq transport and m2crypto
"""
session.notify(
find_session_runner(
session,
"runtests-parametrized-{}".format(session.python),
coverage=coverage,
crypto="m2crypto",
transport="zeromq",
)
)
_runtests(session)
@nox.session(python=_PYTHON_VERSIONS, name="runtests-tcp-m2crypto")
@ -551,15 +423,7 @@ def runtests_tcp_m2crypto(session, coverage):
"""
runtests.py session with TCP transport and m2crypto
"""
session.notify(
find_session_runner(
session,
"runtests-parametrized-{}".format(session.python),
coverage=coverage,
crypto="m2crypto",
transport="tco",
)
)
_runtests(session)
@nox.session(python=_PYTHON_VERSIONS, name="runtests-zeromq-m2crypto")
@ -568,15 +432,7 @@ def runtests_zeromq_m2crypto(session, coverage):
"""
runtests.py session with zeromq transport and m2crypto
"""
session.notify(
find_session_runner(
session,
"runtests-parametrized-{}".format(session.python),
coverage=coverage,
crypto="m2crypto",
transport="zeromq",
)
)
_runtests(session)
@nox.session(python=_PYTHON_VERSIONS, name="runtests-pycryptodome")
@ -585,15 +441,7 @@ def runtests_pycryptodome(session, coverage):
"""
runtests.py session with zeromq transport and pycryptodome
"""
session.notify(
find_session_runner(
session,
"runtests-parametrized-{}".format(session.python),
coverage=coverage,
crypto="pycryptodome",
transport="zeromq",
)
)
_runtests(session)
@nox.session(python=_PYTHON_VERSIONS, name="runtests-tcp-pycryptodome")
@ -602,15 +450,7 @@ def runtests_tcp_pycryptodome(session, coverage):
"""
runtests.py session with TCP transport and pycryptodome
"""
session.notify(
find_session_runner(
session,
"runtests-parametrized-{}".format(session.python),
coverage=coverage,
crypto="pycryptodome",
transport="tcp",
)
)
_runtests(session)
@nox.session(python=_PYTHON_VERSIONS, name="runtests-zeromq-pycryptodome")
@ -619,15 +459,7 @@ def runtests_zeromq_pycryptodome(session, coverage):
"""
runtests.py session with zeromq transport and pycryptodome
"""
session.notify(
find_session_runner(
session,
"runtests-parametrized-{}".format(session.python),
coverage=coverage,
crypto="pycryptodome",
transport="zeromq",
)
)
_runtests(session)
@nox.session(python=_PYTHON_VERSIONS, name="runtests-cloud")
@ -636,21 +468,7 @@ def runtests_cloud(session, coverage):
"""
runtests.py cloud tests session
"""
# Install requirements
_install_requirements(session, "zeromq", "unittest-xml-reporting==2.2.1")
requirements_file = os.path.join(
"requirements", "static", "ci", _get_pydir(session), "cloud.txt"
)
install_command = ["--progress-bar=off", "-r", requirements_file]
session.install(*install_command, silent=PIP_INSTALL_SILENT)
cmd_args = [
"--tests-logfile={}".format(RUNTESTS_LOGFILE),
"--cloud-provider-tests",
] + session.posargs
_runtests(session, coverage, cmd_args)
_runtests(session)
@nox.session(python=_PYTHON_VERSIONS, name="runtests-tornado")
@ -659,13 +477,7 @@ def runtests_tornado(session, coverage):
"""
runtests.py tornado tests session
"""
# Install requirements
_install_requirements(session, "zeromq", "unittest-xml-reporting==2.2.1")
session.install("--progress-bar=off", "tornado==5.0.2", silent=PIP_INSTALL_SILENT)
session.install("--progress-bar=off", "pyzmq==17.0.0", silent=PIP_INSTALL_SILENT)
cmd_args = ["--tests-logfile={}".format(RUNTESTS_LOGFILE)] + session.posargs
_runtests(session, coverage, cmd_args)
_runtests(session)
@nox.session(python=_PYTHON_VERSIONS, name="pytest-parametrized")
@ -921,10 +733,6 @@ def _pytest(session, coverage, cmd_args):
# Create required artifacts directories
_create_ci_directories()
session.run(
"pip", "uninstall", "-y", "pytest-salt", silent=True,
)
env = {"PYTEST_SESSION": "1", "CI_RUN": "1" if CI_RUN else "0"}
if IS_DARWIN:
# Don't nuke our multiprocessing efforts objc!

View file

@ -1,7 +1,6 @@
mock >= 3.0.0
# PyTest
pytest >= 6.1.0
pytest-salt
pytest-salt-factories >= 0.120.2
pytest-tempdir >= 2019.10.12
pytest-helpers-namespace >= 2019.1.8

View file

@ -189,7 +189,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -194,7 +194,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -194,7 +194,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -92,7 +92,6 @@ pyparsing==2.4.5 # via packaging
pyrsistent==0.17.3 # via jsonschema
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -188,7 +188,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -193,7 +193,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -193,7 +193,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -91,7 +91,6 @@ pyparsing==2.4.5 # via packaging
pyrsistent==0.17.3 # via jsonschema
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -189,7 +189,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc, netmiko
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -194,7 +194,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc, netmiko
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -194,7 +194,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc, netmiko
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -87,7 +87,6 @@ pyparsing==2.4.5 # via packaging
pyrsistent==0.17.3 # via jsonschema
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -188,7 +188,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc, netmiko
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -193,7 +193,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc, netmiko
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -193,7 +193,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc, netmiko
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -188,7 +188,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc, netmiko
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -193,7 +193,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc, netmiko
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

View file

@ -194,7 +194,6 @@ pyrsistent==0.17.3 # via jsonschema
pyserial==3.4 # via junos-eznc, netmiko
pytest-helpers-namespace==2019.1.8
pytest-salt-factories==0.120.2
pytest-salt==2020.1.27
pytest-subtests==0.4.0
pytest-tempdir==2019.10.12
pytest==6.1.2

File diff suppressed because it is too large Load diff

View file

@ -27,11 +27,6 @@
$ salt-ssh localhost state.sls custom_module
localhost:
olleh
This test can be run in a small test suite with:
$ python tests/runtests.py -C --ssh
"""
import pytest

View file

@ -1,136 +0,0 @@
# -*- coding: utf-8 -*-
"""
:codeauthor: Pedro Algarvio (pedro@algarvio.me)
:copyright: Copyright 2015 by the SaltStack Team, see AUTHORS for more details.
:license: Apache 2.0, see LICENSE for more details.
pytestsalt.engines.pytest_engine
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Simple salt engine which will setup a socket to accept connections allowing us to know
when a daemon is up and running
"""
# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import errno
import logging
import os
import socket
import sys
import salt.utils.asynchronous
# Import salt libs
import salt.utils.event
# Import 3rd-party libs
from salt.ext.tornado import gen, ioloop, iostream, netutil
log = logging.getLogger(__name__)
__virtualname__ = "salt_runtests"
def __virtual__():
if __opts__["__role"] != "master":
return False
return "runtests_conn_check_port" in __opts__ # pylint: disable=undefined-variable
def start():
pytest_engine = PyTestEngine(__opts__) # pylint: disable=undefined-variable
pytest_engine.start()
class PyTestEngine(object):
def __init__(self, opts):
self.opts = opts
self.sock = None
self.stop_sending_events_file = opts.get("pytest_stop_sending_events_file")
def start(self):
self.io_loop = ioloop.IOLoop()
self.io_loop.make_current()
self.io_loop.add_callback(self._start)
self.io_loop.start()
@gen.coroutine
def _start(self):
port = int(self.opts["runtests_conn_check_port"])
log.info(
"Starting Pytest Engine(role=%s, id=%s) on port %s",
self.opts["__role"],
self.opts["id"],
port,
)
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.setblocking(0)
# bind the socket to localhost on the config provided port
self.sock.bind(("localhost", port))
# become a server socket
self.sock.listen(5)
with salt.utils.asynchronous.current_ioloop(self.io_loop):
netutil.add_accept_handler(
self.sock, self.handle_connection,
)
if self.opts["__role"] == "master":
yield self.fire_master_started_event()
def handle_connection(self, connection, address):
log.warning(
"Accepted connection from %s. Role: %s", address, self.opts["__role"]
)
# We just need to know that the daemon running the engine is alive...
try:
connection.shutdown(socket.SHUT_RDWR) # pylint: disable=no-member
connection.close()
except socket.error as exc:
if not sys.platform.startswith("darwin"):
raise
try:
if exc.errno != errno.ENOTCONN:
raise
except AttributeError:
# This is not macOS !?
pass
@gen.coroutine
def fire_master_started_event(self):
log.info("Firing salt-%s started event...", self.opts["__role"])
start_event_tag = "salt/{}/{}/start".format(
self.opts["__role"], self.opts["id"]
)
log.info(
"Firing salt-%s started event. Tag: %s",
self.opts["__role"],
start_event_tag,
)
load = {"id": self.opts["id"], "tag": start_event_tag, "data": {}}
# One minute should be more than enough to fire these events every second in order
# for pytest-salt to pickup that the master is running
with salt.utils.event.get_master_event(
self.opts, self.opts["sock_dir"], listen=False
) as event_bus:
timeout = 30
while True:
if self.stop_sending_events_file and not os.path.exists(
self.stop_sending_events_file
):
log.info(
'The stop sending events file "marker" is done. Stop sending events...'
)
break
timeout -= 1
try:
event_bus.fire_event(load, start_event_tag, timeout=500)
if timeout <= 0:
break
yield gen.sleep(1)
except iostream.StreamClosedError:
break

View file

@ -1,110 +0,0 @@
"""
:codeauthor: Pedro Algarvio (pedro@algarvio.me)
:copyright: Copyright 2016 by the SaltStack Team, see AUTHORS for more details.
:license: Apache 2.0, see LICENSE for more details.
pytestsalt.salt.log_handlers.pytest_log_handler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Salt External Logging Handler
"""
import errno
import logging
import socket
import threading
from multiprocessing import Queue
import salt.log.setup
import salt.utils.msgpack
from salt.utils.platform import is_darwin
log = logging.getLogger(__name__)
__virtualname__ = "runtests_log_handler"
def __virtual__():
if "runtests_log_port" not in __opts__:
return False, "'runtests_log_port' not in options"
return (
False,
"runtests external logging handler is temporarily disabled for Python 3 tests",
)
def setup_handlers():
port = __opts__["runtests_log_port"]
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.connect(("localhost", port))
except OSError as exc:
if exc.errno == errno.ECONNREFUSED:
log.warning("Failed to connect to log server")
return
finally:
try:
sock.shutdown(socket.SHUT_RDWR)
except OSError:
pass
sock.close()
# One million log messages is more than enough to queue.
# Above that value, if `process_queue` can't process fast enough,
# start dropping. This will contain a memory leak in case `process_queue`
# can't process fast enough of in case it can't deliver the log records at all.
if is_darwin():
queue_size = 32767
else:
queue_size = 10000000
queue = Queue(queue_size)
handler = salt.log.setup.QueueHandler(queue)
level = salt.log.setup.LOG_LEVELS[
(__opts__.get("runtests_log_level") or "error").lower()
]
handler.setLevel(level)
process_queue_thread = threading.Thread(target=process_queue, args=(port, queue))
process_queue_thread.daemon = True
process_queue_thread.start()
return handler
def process_queue(port, queue):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.connect(("localhost", port))
except OSError as exc:
if exc.errno == errno.ECONNREFUSED:
sock.shutdown(socket.SHUT_RDWR)
sock.close()
log.warning("Failed to connect to log server")
return
while True:
try:
record = queue.get()
if record is None:
# A sentinel to stop processing the queue
break
# Just log everything, filtering will happen on the main process
# logging handlers
sock.sendall(salt.utils.msgpack.dumps(record.__dict__, use_bin_type=True))
except (OSError, EOFError, KeyboardInterrupt, SystemExit):
if hasattr(exc, "errno") and exc.errno != errno.EPIPE:
log.exception(exc)
try:
sock.shutdown(socket.SHUT_RDWR)
sock.close()
except OSError:
pass
break
except Exception as exc: # pylint: disable=broad-except
log.warning(
"An exception occurred in the pytest salt logging " "queue thread: %s",
exc,
exc_info_on_loglevel=logging.DEBUG,
)

View file

@ -1,975 +1,15 @@
#!/usr/bin/env python
"""
Discover all instances of unittest.TestCase in this directory.
"""
# pylint: disable=file-perms
import collections
import os
import sys
import time
import warnings
TESTS_DIR = os.path.dirname(os.path.normpath(os.path.abspath(__file__)))
if os.name == "nt":
TESTS_DIR = TESTS_DIR.replace("\\", "\\\\")
CODE_DIR = os.path.dirname(TESTS_DIR)
# Let's inject CODE_DIR so salt is importable if not there already
if "" in sys.path:
sys.path.remove("")
if TESTS_DIR in sys.path:
sys.path.remove(TESTS_DIR)
if CODE_DIR in sys.path and sys.path[0] != CODE_DIR:
sys.path.remove(CODE_DIR)
if CODE_DIR not in sys.path:
sys.path.insert(0, CODE_DIR)
if TESTS_DIR not in sys.path:
sys.path.insert(1, TESTS_DIR)
try:
import tests
if not tests.__file__.startswith(CODE_DIR):
print("Found tests module not from salt in {}".format(tests.__file__))
sys.modules.pop("tests")
module_dir = os.path.dirname(tests.__file__)
if module_dir in sys.path:
sys.path.remove(module_dir)
del tests
except ImportError:
pass
try:
from tests.support.paths import TMP, SYS_TMP_DIR, INTEGRATION_TEST_DIR
from tests.support.paths import CODE_DIR as SALT_ROOT
except ImportError as exc:
try:
import tests
print("Found tests module not from salt in {}".format(tests.__file__))
except ImportError:
print("Unable to import salt test module")
print("PYTHONPATH:", os.environ.get("PYTHONPATH"))
print("Current sys.path:")
import pprint
pprint.pprint(sys.path)
raise
from tests.integration import TestDaemon, TestDaemonStartFailed # isort:skip
import salt.utils.platform # isort:skip
if not salt.utils.platform.is_windows():
import resource
from tests.support.parser import PNUM, print_header # isort:skip
from tests.support.parser.cover import SaltCoverageTestingParser # isort:skip
XML_OUTPUT_DIR = os.environ.get(
"SALT_XML_TEST_REPORTS_DIR", os.path.join(TMP, "xml-test-reports")
)
HTML_OUTPUT_DIR = os.environ.get(
"SALT_HTML_TEST_REPORTS_DIR", os.path.join(TMP, "html-test-reports")
)
TEST_DIR = os.path.dirname(INTEGRATION_TEST_DIR)
try:
if SALT_ROOT:
os.chdir(SALT_ROOT)
except OSError as err:
print("Failed to change directory to salt's source: {}".format(err))
# Soft and hard limits on max open filehandles
MAX_OPEN_FILES = {
"integration": {"soft_limit": 3072, "hard_limit": 4096},
"unit": {"soft_limit": 1024, "hard_limit": 2048},
}
# Combine info from command line options and test suite directories. A test
# suite is a python package of test modules relative to the tests directory.
TEST_SUITES_UNORDERED = {
"unit": {"display_name": "Unit", "path": "unit"},
"kitchen": {"display_name": "Kitchen", "path": "kitchen"},
"multimaster": {"display_name": "Multimaster", "path": "multimaster"},
"module": {"display_name": "Module", "path": "integration/modules"},
"state": {"display_name": "State", "path": "integration/states"},
"cli": {"display_name": "CLI", "path": "integration/cli"},
"client": {"display_name": "Client", "path": "integration/client"},
"doc": {"display_name": "Documentation", "path": "integration/doc"},
"ext_pillar": {"display_name": "External Pillar", "path": "integration/pillar"},
"grains": {"display_name": "Grains", "path": "integration/grains"},
"shell": {"display_name": "Shell", "path": "integration/shell"},
"runners": {"display_name": "Runners", "path": "integration/runners"},
"renderers": {"display_name": "Renderers", "path": "integration/renderers"},
"returners": {"display_name": "Returners", "path": "integration/returners"},
"ssh-int": {"display_name": "SSH Integration", "path": "integration/ssh"},
"spm": {"display_name": "SPM", "path": "integration/spm"},
"loader": {"display_name": "Loader", "path": "integration/loader"},
"outputter": {"display_name": "Outputter", "path": "integration/output"},
"fileserver": {"display_name": "Fileserver", "path": "integration/fileserver"},
"wheel": {"display_name": "Wheel", "path": "integration/wheel"},
"api": {"display_name": "NetAPI", "path": "integration/netapi"},
"cloud_provider": {
"display_name": "Cloud Provider",
"path": "integration/cloud/clouds",
},
"minion": {"display_name": "Minion", "path": "integration/minion"},
"proxy": {"display_name": "Proxy", "path": "integration/proxy"},
"external_api": {"display_name": "ExternalAPIs", "path": "integration/externalapi"},
"daemons": {"display_name": "Daemon", "path": "integration/daemons"},
"sdb": {"display_name": "Sdb", "path": "integration/sdb"},
}
TEST_SUITES = collections.OrderedDict(
sorted(TEST_SUITES_UNORDERED.items(), key=lambda x: x[0])
)
class SaltTestsuiteParser(SaltCoverageTestingParser):
support_docker_execution = True
support_destructive_tests_selection = True
support_expensive_tests_selection = True
source_code_basedir = SALT_ROOT
def _get_suites(
self,
include_unit=False,
include_cloud_provider=False,
include_proxy=False,
include_kitchen=False,
include_multimaster=False,
):
"""
Return a set of all test suites except unit and cloud provider tests
unless requested
"""
suites = set(TEST_SUITES.keys())
if not include_unit:
suites -= {"unit"}
if not include_cloud_provider:
suites -= {"cloud_provider"}
if not include_proxy:
suites -= {"proxy"}
if not include_kitchen:
suites -= {"kitchen"}
# Multimaster tests now run under PyTest
suites -= {"multimaster"}
return suites
def _check_enabled_suites(
self,
include_unit=False,
include_cloud_provider=False,
include_proxy=False,
include_kitchen=False,
include_multimaster=False,
):
"""
Query whether test suites have been enabled
"""
suites = self._get_suites(
include_unit=include_unit,
include_cloud_provider=include_cloud_provider,
include_proxy=include_proxy,
include_kitchen=include_kitchen,
include_multimaster=include_multimaster,
)
return any([getattr(self.options, suite) for suite in suites])
def _enable_suites(
self,
include_unit=False,
include_cloud_provider=False,
include_proxy=False,
include_kitchen=False,
include_multimaster=False,
):
"""
Enable test suites for current test run
"""
suites = self._get_suites(
include_unit=include_unit,
include_cloud_provider=include_cloud_provider,
include_proxy=include_proxy,
include_kitchen=include_kitchen,
include_multimaster=include_multimaster,
)
for suite in suites:
setattr(self.options, suite, True)
def setup_additional_options(self):
self.add_option(
"--sysinfo",
default=False,
action="store_true",
help="Print some system information.",
)
self.add_option(
"--transport",
default="zeromq",
choices=("zeromq", "tcp"),
help=(
"Select which transport to run the integration tests with, "
"zeromq or tcp. Default: %default"
),
)
self.add_option(
"--interactive",
default=False,
action="store_true",
help="Do not run any tests. Simply start the daemons.",
)
self.output_options_group.add_option(
"--no-colors",
"--no-colours",
default=False,
action="store_true",
help="Disable colour printing.",
)
self.test_selection_group.add_option(
"-m",
"--module",
"--module-tests",
dest="module",
default=False,
action="store_true",
help="Run tests for modules",
)
self.test_selection_group.add_option(
"-S",
"--state",
"--state-tests",
dest="state",
default=False,
action="store_true",
help="Run tests for states",
)
self.test_selection_group.add_option(
"-C",
"--cli",
"--cli-tests",
dest="cli",
default=False,
action="store_true",
help="Run tests for cli",
)
self.test_selection_group.add_option(
"-c",
"--client",
"--client-tests",
dest="client",
default=False,
action="store_true",
help="Run tests for client",
)
self.test_selection_group.add_option(
"-d",
"--doc",
"--doc-tests",
dest="doc",
default=False,
action="store_true",
help="Run tests for documentation",
)
self.test_selection_group.add_option(
"-I",
"--ext-pillar",
"--ext-pillar-tests",
dest="ext_pillar",
default=False,
action="store_true",
help="Run ext_pillar tests",
)
self.test_selection_group.add_option(
"-G",
"--grains",
"--grains-tests",
dest="grains",
default=False,
action="store_true",
help="Run tests for grains",
)
self.test_selection_group.add_option(
"-s",
"--shell",
"--shell-tests",
dest="shell",
default=False,
action="store_true",
help="Run shell tests",
)
self.test_selection_group.add_option(
"-r",
"--runners",
"--runner-tests",
dest="runners",
default=False,
action="store_true",
help="Run salt/runners/*.py tests",
)
self.test_selection_group.add_option(
"-R",
"--renderers",
"--renderer-tests",
dest="renderers",
default=False,
action="store_true",
help="Run salt/renderers/*.py tests",
)
self.test_selection_group.add_option(
"--reactor",
dest="reactor",
default=False,
action="store_true",
help="Run salt/reactor/*.py tests",
)
self.test_selection_group.add_option(
"--minion",
"--minion-tests",
dest="minion",
default=False,
action="store_true",
help="Run tests for minion",
)
self.test_selection_group.add_option(
"--returners",
dest="returners",
default=False,
action="store_true",
help="Run salt/returners/*.py tests",
)
self.test_selection_group.add_option(
"--spm",
dest="spm",
default=False,
action="store_true",
help="Run spm integration tests",
)
self.test_selection_group.add_option(
"--setup",
dest="setup",
default=False,
action="store_true",
help="Run setup integration tests",
)
self.test_selection_group.add_option(
"-l",
"--loader",
"--loader-tests",
dest="loader",
default=False,
action="store_true",
help="Run loader tests",
)
self.test_selection_group.add_option(
"-u",
"--unit",
"--unit-tests",
dest="unit",
default=False,
action="store_true",
help="Run unit tests",
)
self.test_selection_group.add_option(
"-k",
"--kitchen",
"--kitchen-tests",
dest="kitchen",
default=False,
action="store_true",
help="Run kitchen tests",
)
self.test_selection_group.add_option(
"--fileserver",
"--fileserver-tests",
dest="fileserver",
default=False,
action="store_true",
help="Run Fileserver tests",
)
self.test_selection_group.add_option(
"-w",
"--wheel",
"--wheel-tests",
dest="wheel",
action="store_true",
default=False,
help="Run wheel tests",
)
self.test_selection_group.add_option(
"-o",
"--outputter",
"--outputter-tests",
dest="outputter",
action="store_true",
default=False,
help="Run outputter tests",
)
self.test_selection_group.add_option(
"--cloud-provider",
"--cloud-provider-tests",
dest="cloud_provider",
action="store_true",
default=False,
help=(
"Run cloud provider tests. These tests create and delete "
"instances on cloud providers. Must provide valid credentials "
"in salt/tests/integration/files/conf/cloud.*.d to run tests."
),
)
self.test_selection_group.add_option(
"--ssh",
"--ssh-tests",
dest="ssh",
action="store_true",
default=False,
help="Run salt-ssh tests. These tests will spin up a temporary "
"SSH server on your machine. In certain environments, this "
"may be insecure! Default: False",
)
self.test_selection_group.add_option(
"--ssh-int",
dest="ssh-int",
action="store_true",
default=False,
help="Run salt-ssh integration tests. Requires to be run with --ssh"
"to spin up the SSH server on your machine.",
)
self.test_selection_group.add_option(
"-A",
"--api",
"--api-tests",
dest="api",
action="store_true",
default=False,
help="Run salt-api tests",
)
self.test_selection_group.add_option(
"--sdb",
"--sdb-tests",
dest="sdb",
action="store_true",
default=False,
help="Run sdb tests",
)
self.test_selection_group.add_option(
"-P",
"--proxy",
"--proxy-tests",
dest="proxy",
action="store_true",
default=False,
help="Run salt-proxy tests",
)
self.test_selection_group.add_option(
"--external",
"--external-api",
"--external-api-tests",
dest="external_api",
action="store_true",
default=False,
help="Run venafi runner tests",
)
self.test_selection_group.add_option(
"--daemons",
"--daemon-tests",
dest="daemons",
action="store_true",
default=False,
help="Run salt/daemons/*.py tests",
)
self.test_selection_group.add_option(
"--scheduler",
dest="scheduler",
action="store_true",
default=False,
help="Run scheduler integration tests",
)
self.test_selection_group.add_option(
"--logging",
dest="logging",
action="store_true",
default=False,
help="Run logging integration tests",
)
self.test_selection_group.add_option(
"--multimaster",
dest="multimaster",
action="store_true",
default=False,
help="Start multimaster daemons and run multimaster integration tests",
)
def validate_options(self):
if self.options.cloud_provider or self.options.external_api:
# Turn on expensive tests execution
os.environ["EXPENSIVE_TESTS"] = "True"
# This fails even with salt.utils.platform imported in the global
# scope, unless we import it again here.
import salt.utils.platform
if salt.utils.platform.is_windows():
import salt.utils.win_functions
current_user = salt.utils.win_functions.get_current_user()
if current_user == "SYSTEM":
is_admin = True
else:
is_admin = salt.utils.win_functions.is_admin(current_user)
if (
self.options.coverage
and any(
(self.options.name, not is_admin, not self.options.run_destructive)
)
and self._check_enabled_suites(include_unit=True)
):
warnings.warn("Test suite not running with elevated priviledges")
else:
is_admin = os.geteuid() == 0
if (
self.options.coverage
and any(
(self.options.name, not is_admin, not self.options.run_destructive)
)
and self._check_enabled_suites(include_unit=True)
):
self.error(
"No sense in generating the tests coverage report when "
"not running the full test suite, including the "
"destructive tests, as 'root'. It would only produce "
"incorrect results."
)
# When no tests are specifically enumerated on the command line, setup
# a default run: +unit -cloud_provider
if not self.options.name and not self._check_enabled_suites(
include_unit=True,
include_cloud_provider=True,
include_proxy=True,
include_kitchen=True,
include_multimaster=True,
):
self._enable_suites(include_unit=True, include_multimaster=True)
self.start_coverage(
branch=True, source=[os.path.join(SALT_ROOT, "salt")],
)
# Print out which version of python this test suite is running on
print(" * Python Version: {}".format(" ".join(sys.version.split())))
# Transplant configuration
TestDaemon.transplant_configs(transport=self.options.transport)
def post_execution_cleanup(self):
SaltCoverageTestingParser.post_execution_cleanup(self)
if self.options.clean:
TestDaemon.clean()
def run_integration_suite(self, path="", display_name=""):
"""
Run an integration test suite
"""
full_path = os.path.join(TEST_DIR, path)
return self.run_suite(
full_path, display_name, suffix="test_*.py", failfast=self.options.failfast,
)
def start_daemons_only(self):
if not salt.utils.platform.is_windows():
self.set_filehandle_limits("integration")
try:
print_header(
" * Setting up Salt daemons for interactive use",
top=False,
width=getattr(self.options, "output_columns", PNUM),
)
except TypeError:
print_header(" * Setting up Salt daemons for interactive use", top=False)
try:
with TestDaemon(self):
print_header(" * Salt daemons started")
master_conf = TestDaemon.config("master")
minion_conf = TestDaemon.config("minion")
proxy_conf = TestDaemon.config("proxy")
sub_minion_conf = TestDaemon.config("sub_minion")
syndic_conf = TestDaemon.config("syndic")
syndic_master_conf = TestDaemon.config("syndic_master")
print_header(" * Syndic master configuration values (MoM)", top=False)
print("interface: {}".format(syndic_master_conf["interface"]))
print("publish port: {}".format(syndic_master_conf["publish_port"]))
print("return port: {}".format(syndic_master_conf["ret_port"]))
print("\n")
print_header(" * Syndic configuration values", top=True)
print("interface: {}".format(syndic_conf["interface"]))
print("syndic master: {}".format(syndic_conf["syndic_master"]))
print(
"syndic master port: {}".format(syndic_conf["syndic_master_port"])
)
print("\n")
print_header(" * Master configuration values", top=True)
print("interface: {}".format(master_conf["interface"]))
print("publish port: {}".format(master_conf["publish_port"]))
print("return port: {}".format(master_conf["ret_port"]))
print("\n")
print_header(" * Minion configuration values", top=True)
print("interface: {}".format(minion_conf["interface"]))
print("master: {}".format(minion_conf["master"]))
print("master port: {}".format(minion_conf["master_port"]))
if minion_conf["ipc_mode"] == "tcp":
print("tcp pub port: {}".format(minion_conf["tcp_pub_port"]))
print("tcp pull port: {}".format(minion_conf["tcp_pull_port"]))
print("\n")
print_header(" * Sub Minion configuration values", top=True)
print("interface: {}".format(sub_minion_conf["interface"]))
print("master: {}".format(sub_minion_conf["master"]))
print("master port: {}".format(sub_minion_conf["master_port"]))
if sub_minion_conf["ipc_mode"] == "tcp":
print("tcp pub port: {}".format(sub_minion_conf["tcp_pub_port"]))
print("tcp pull port: {}".format(sub_minion_conf["tcp_pull_port"]))
print("\n")
print_header(" * Proxy Minion configuration values", top=True)
print("interface: {}".format(proxy_conf["interface"]))
print("master: {}".format(proxy_conf["master"]))
print("master port: {}".format(proxy_conf["master_port"]))
if minion_conf["ipc_mode"] == "tcp":
print("tcp pub port: {}".format(proxy_conf["tcp_pub_port"]))
print("tcp pull port: {}".format(proxy_conf["tcp_pull_port"]))
print("\n")
print_header(
" Your client configuration is at {}".format(
TestDaemon.config_location()
)
)
print(
"To access the minion: salt -c {} minion test.ping".format(
TestDaemon.config_location()
)
)
while True:
time.sleep(1)
except TestDaemonStartFailed:
self.exit(status=2)
def set_filehandle_limits(self, limits="integration"):
"""
Set soft and hard limits on open file handles at required thresholds
for integration tests or unit tests
"""
# Get current limits
if salt.utils.platform.is_windows():
import win32file
prev_hard = win32file._getmaxstdio()
prev_soft = 512
else:
prev_soft, prev_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
# Get required limits
min_soft = MAX_OPEN_FILES[limits]["soft_limit"]
min_hard = MAX_OPEN_FILES[limits]["hard_limit"]
# Check minimum required limits
set_limits = False
if prev_soft < min_soft:
soft = min_soft
set_limits = True
else:
soft = prev_soft
if prev_hard < min_hard:
hard = min_hard
set_limits = True
else:
hard = prev_hard
# Increase limits
if set_limits:
print(
" * Max open files settings is too low (soft: {}, hard: {}) "
"for running the tests".format(prev_soft, prev_hard)
)
print(
" * Trying to raise the limits to soft: "
"{}, hard: {}".format(soft, hard)
)
try:
if salt.utils.platform.is_windows():
hard = 2048 if hard > 2048 else hard
win32file._setmaxstdio(hard)
else:
resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard))
except Exception as err: # pylint: disable=broad-except
print(
"ERROR: Failed to raise the max open files settings -> "
"{}".format(err)
)
print("Please issue the following command on your console:")
print(" ulimit -n {}".format(soft))
self.exit()
finally:
print("~" * getattr(self.options, "output_columns", PNUM))
def run_integration_tests(self):
"""
Execute the integration tests suite
"""
named_tests = []
named_unit_test = []
if self.options.name:
for test in self.options.name:
if test.startswith(
(
"tests.unit.",
"unit.",
"test.kitchen.",
"kitchen.",
"test.multimaster.",
"multimaster.",
)
):
named_unit_test.append(test)
continue
named_tests.append(test)
if (
(
self.options.unit
or self.options.kitchen
or self.options.multimaster
or named_unit_test
)
and not named_tests
and (
self.options.from_filenames
or not self._check_enabled_suites(include_cloud_provider=True)
)
):
# We're either not running any integration test suites, or we're
# only running unit tests by passing --unit or by passing only
# `unit.<whatever>` to --name. We don't need the tests daemon
# running
return [True]
if not salt.utils.platform.is_windows():
self.set_filehandle_limits("integration")
try:
print_header(
" * Setting up Salt daemons to execute tests",
top=False,
width=getattr(self.options, "output_columns", PNUM),
)
except TypeError:
print_header(" * Setting up Salt daemons to execute tests", top=False)
status = []
# Return an empty status if no tests have been enabled
if (
not self._check_enabled_suites(
include_cloud_provider=True, include_proxy=True
)
and not self.options.name
):
return status
try:
with TestDaemon(self):
if self.options.name:
for name in self.options.name:
name = name.strip()
if not name:
continue
if os.path.isfile(name):
if not name.endswith(".py"):
continue
if name.startswith(
(
os.path.join("tests", "unit"),
os.path.join("tests", "multimaster"),
)
):
continue
results = self.run_suite(
os.path.dirname(name),
name,
suffix=os.path.basename(name),
failfast=self.options.failfast,
load_from_name=False,
)
status.append(results)
continue
if name.startswith(
(
"tests.unit.",
"unit.",
"tests.multimaster.",
"multimaster.",
)
):
continue
results = self.run_suite(
"",
name,
suffix="test_*.py",
load_from_name=True,
failfast=self.options.failfast,
)
status.append(results)
return status
for suite in TEST_SUITES:
if (
suite != "unit"
and suite != "multimaster"
and getattr(self.options, suite)
):
status.append(self.run_integration_suite(**TEST_SUITES[suite]))
return status
except TestDaemonStartFailed:
self.exit(status=2)
def run_unit_tests(self):
"""
Execute the unit tests
"""
named_unit_test = []
if self.options.name:
for test in self.options.name:
if not test.startswith(("tests.unit.", "unit.")):
continue
named_unit_test.append(test)
if not named_unit_test and (
self.options.from_filenames or not self.options.unit
):
# We are not explicitly running the unit tests and none of the
# names passed to --name (or derived via --from-filenames) is a
# unit test.
return [True]
status = []
if self.options.unit:
# MacOS needs more open filehandles for running unit test suite
self.set_filehandle_limits("unit")
results = self.run_suite(
os.path.join(TEST_DIR, "unit"),
"Unit",
suffix="test_*.py",
failfast=self.options.failfast,
)
status.append(results)
# We executed ALL unittests, we can skip running unittests by name
# below
return status
for name in named_unit_test:
results = self.run_suite(
os.path.join(TEST_DIR, "unit"),
name,
suffix="test_*.py",
load_from_name=True,
failfast=self.options.failfast,
)
status.append(results)
return status
def run_kitchen_tests(self):
"""
Execute the kitchen tests
"""
named_kitchen_test = []
if self.options.name:
for test in self.options.name:
if not test.startswith(("tests.kitchen.", "kitchen.")):
continue
named_kitchen_test.append(test)
if not self.options.kitchen and not named_kitchen_test:
# We are not explicitly running the unit tests and none of the
# names passed to --name is a unit test.
return [True]
status = []
if self.options.kitchen:
results = self.run_suite(
os.path.join(TEST_DIR, "kitchen"), "Kitchen", suffix="test_*.py"
)
status.append(results)
# We executed ALL unittests, we can skip running unittests by name
# below
return status
for name in named_kitchen_test:
results = self.run_suite(
os.path.join(TEST_DIR, "kitchen"),
name,
suffix="test_*.py",
load_from_name=True,
)
status.append(results)
return status
def main(**kwargs):
"""
Parse command line options for running specific tests
"""
try:
parser = SaltTestsuiteParser(
TEST_DIR,
xml_output_dir=XML_OUTPUT_DIR,
tests_logfile=os.path.join(SYS_TMP_DIR, "salt-runtests.log"),
)
parser.parse_args()
# Override parser options (helpful when importing runtests.py and
# running from within a REPL). Using kwargs.items() to avoid importing
# six, as this feature will rarely be used.
for key, val in kwargs.items():
setattr(parser.options, key, val)
overall_status = []
if parser.options.interactive:
if parser.options.multimaster:
print(
"Multimaster tests now run under PyTest",
file=sys.stderr,
flush=True,
)
else:
parser.start_daemons_only()
status = parser.run_integration_tests()
overall_status.extend(status)
status = parser.run_unit_tests()
overall_status.extend(status)
status = parser.run_kitchen_tests()
overall_status.extend(status)
false_count = overall_status.count(False)
if false_count > 0:
parser.finalize(1)
parser.finalize(0)
except KeyboardInterrupt:
print("\nCaught keyboard interrupt. Exiting.\n")
exit(0)
def main():
print(
"\nruntests.py support has been removed from Salt. Please try `nox -e 'pytest-3(coverage=True)'` "
"or `nox -e 'pytest-3(coverage=True)' -- --help` to know more about the supported CLI flags.\n"
"For more information, please check https://docs.saltproject.io/en/latest/topics/development/tests/index.html#running-the-tests",
file=sys.stderr,
)
if __name__ == "__main__":

File diff suppressed because it is too large Load diff

View file

@ -1,245 +0,0 @@
# -*- coding: utf-8 -*-
"""
tests.support.parser.cover
~~~~~~~~~~~~~~~~~~~~~~~~~~
Code coverage aware testing parser
:codeauthor: Pedro Algarvio (pedro@algarvio.me)
:copyright: Copyright 2013 by the SaltStack Team, see AUTHORS for more details.
:license: Apache 2.0, see LICENSE for more details.
"""
# pylint: disable=repr-flag-used-in-string
# Import python libs
from __future__ import absolute_import, print_function
import os
import re
import shutil
import sys
import warnings
# Import Salt libs
import salt.utils.json
# Import salt testing libs
from tests.support.parser import SaltTestingParser
# Import coverage libs
try:
import coverage
COVERAGE_AVAILABLE = True
except ImportError:
COVERAGE_AVAILABLE = False
try:
import multiprocessing.util
# Force forked multiprocessing processes to be measured as well
def multiprocessing_stop(coverage_object):
"""
Save the multiprocessing process coverage object
"""
coverage_object.stop()
coverage_object.save()
def multiprocessing_start(obj):
coverage_options = salt.utils.json.loads(
os.environ.get("COVERAGE_OPTIONS", "{}")
)
if not coverage_options:
return
if coverage_options.get("data_suffix", False) is False:
return
coverage_object = coverage.coverage(**coverage_options)
coverage_object.start()
multiprocessing.util.Finalize(
None, multiprocessing_stop, args=(coverage_object,), exitpriority=1000
)
if COVERAGE_AVAILABLE:
multiprocessing.util.register_after_fork(
multiprocessing_start, multiprocessing_start
)
except ImportError:
pass
if COVERAGE_AVAILABLE:
# Cover any processes if the environ variables are present
coverage.process_startup()
class SaltCoverageTestingParser(SaltTestingParser):
"""
Code coverage aware testing option parser
"""
def __init__(self, *args, **kwargs):
if (
kwargs.pop("html_output_from_env", None) is not None
or kwargs.pop("html_output_dir", None) is not None
):
warnings.warn(
"The unit tests HTML support was removed from {0}. Please "
"stop passing 'html_output_dir' or 'html_output_from_env' "
"as arguments to {0}".format(self.__class__.__name__),
category=DeprecationWarning,
stacklevel=2,
)
SaltTestingParser.__init__(self, *args, **kwargs)
self.code_coverage = None
# Add the coverage related options
self.output_options_group.add_option(
"--coverage",
default=False,
action="store_true",
help="Run tests and report code coverage",
)
self.output_options_group.add_option(
"--no-processes-coverage",
default=False,
action="store_true",
help="Do not track subprocess and/or multiprocessing processes",
)
self.output_options_group.add_option(
"--coverage-xml",
default=None,
help="If provided, the path to where a XML report of the code "
"coverage will be written to",
)
self.output_options_group.add_option(
"--coverage-html",
default=None,
help=(
"The directory where the generated HTML coverage report "
"will be saved to. The directory, if existing, will be "
"deleted before the report is generated."
),
)
def _validate_options(self):
if (
self.options.coverage_xml or self.options.coverage_html
) and not self.options.coverage:
self.options.coverage = True
if self.options.coverage is True and COVERAGE_AVAILABLE is False:
self.error(
"Cannot run tests with coverage report. "
"Please install coverage>=3.5.3"
)
if self.options.coverage is True:
coverage_version = tuple(
[
int(part)
for part in re.search(r"([0-9.]+)", coverage.__version__)
.group(0)
.split(".")
]
)
if coverage_version < (3, 5, 3):
# Should we just print the error instead of exiting?
self.error(
"Versions lower than 3.5.3 of the coverage library are "
"know to produce incorrect results. Please consider "
"upgrading..."
)
SaltTestingParser._validate_options(self)
def pre_execution_cleanup(self):
if self.options.coverage_html is not None:
if os.path.isdir(self.options.coverage_html):
shutil.rmtree(self.options.coverage_html)
if self.options.coverage_xml is not None:
if os.path.isfile(self.options.coverage_xml):
os.unlink(self.options.coverage_xml)
SaltTestingParser.pre_execution_cleanup(self)
def start_coverage(self, **coverage_options):
"""
Start code coverage.
You can pass any coverage options as keyword arguments. For the
available options please see:
http://nedbatchelder.com/code/coverage/api.html
"""
if self.options.coverage is False:
return
if coverage_options.pop("track_processes", None) is not None:
raise RuntimeWarning(
"Please stop passing 'track_processes' to "
"'start_coverage()'. It's now the default and "
"'--no-processes-coverage' was added to the parser to "
"disable it."
)
print(" * Starting Coverage")
if self.options.no_processes_coverage is False:
# Update environ so that any subprocess started on tests are also
# included in the report
coverage_options["data_suffix"] = True
os.environ["COVERAGE_PROCESS_START"] = ""
os.environ["COVERAGE_OPTIONS"] = salt.utils.json.dumps(coverage_options)
# Setup coverage
self.code_coverage = coverage.coverage(**coverage_options)
self.code_coverage.start()
def stop_coverage(self, save_coverage=True):
"""
Stop code coverage.
"""
if self.options.coverage is False:
return
# Clean up environment
os.environ.pop("COVERAGE_OPTIONS", None)
os.environ.pop("COVERAGE_PROCESS_START", None)
print(" * Stopping coverage")
self.code_coverage.stop()
if save_coverage:
print(" * Saving coverage info")
self.code_coverage.save()
if self.options.no_processes_coverage is False:
# Combine any multiprocessing coverage data files
sys.stdout.write(" * Combining multiple coverage info files ... ")
sys.stdout.flush()
self.code_coverage.combine()
print("Done.")
if self.options.coverage_xml is not None:
sys.stdout.write(
" * Generating Coverage XML Report At {0!r} ... ".format(
self.options.coverage_xml
)
)
sys.stdout.flush()
self.code_coverage.xml_report(outfile=self.options.coverage_xml)
print("Done.")
if self.options.coverage_html is not None:
sys.stdout.write(
" * Generating Coverage HTML Report Under {0!r} ... ".format(
self.options.coverage_html
)
)
sys.stdout.flush()
self.code_coverage.html_report(directory=self.options.coverage_html)
print("Done.")
def finalize(self, exit_code=0):
if self.options.coverage is True:
self.stop_coverage(save_coverage=True)
SaltTestingParser.finalize(self, exit_code)

View file

@ -1,219 +0,0 @@
"""
:copyright: Copyright 2017 by the SaltStack Team, see AUTHORS for more details.
:license: Apache 2.0, see LICENSE for more details.
tests.support.processes
~~~~~~~~~~~~~~~~~~~~~~~
Process handling utilities
"""
import logging
from saltfactories.utils.processes import ( # pylint: disable=unused-import
collect_child_processes,
terminate_process,
terminate_process_list,
)
from tests.support.cli_scripts import ScriptPathMixin, get_script_path
from tests.support.runtests import RUNTIME_VARS
try:
from pytestsalt.fixtures.daemons import Salt as PytestSalt
from pytestsalt.fixtures.daemons import SaltCall as PytestSaltCall
from pytestsalt.fixtures.daemons import SaltKey as PytestSaltKey
from pytestsalt.fixtures.daemons import SaltMaster as PytestSaltMaster
from pytestsalt.fixtures.daemons import SaltMinion as PytestSaltMinion
from pytestsalt.fixtures.daemons import SaltProxy as PytestSaltProxy
from pytestsalt.fixtures.daemons import SaltRun as PytestSaltRun
from pytestsalt.fixtures.daemons import SaltSyndic as PytestSaltSyndic
except ImportError:
# If this happens, we are running under pytest which uninstalls pytest-salt due to impatabilites
# These imports won't actually work but these classes are only used when running under runtests,
# so, we're just making sure we also don't hit NameError's
from tests.support.saltfactories_compat import SaltCallCLI as PytestSaltCall
from tests.support.saltfactories_compat import SaltCLI as PytestSalt
from tests.support.saltfactories_compat import SaltKeyCLI as PytestSaltKey
from tests.support.saltfactories_compat import SaltMaster as PytestSaltMaster
from tests.support.saltfactories_compat import SaltMinion as PytestSaltMinion
from tests.support.saltfactories_compat import SaltProxyMinion as PytestSaltProxy
from tests.support.saltfactories_compat import SaltRunCLI as PytestSaltRun
from tests.support.saltfactories_compat import SaltSyndic as PytestSaltSyndic
log = logging.getLogger(__name__)
class GetSaltRunFixtureMixin(ScriptPathMixin):
"""
Override this classes `get_salt_run_fixture` because we're still not running under pytest
"""
def get_salt_run_fixture(self):
pass
class Salt(ScriptPathMixin, PytestSalt):
"""
Class which runs salt-call commands
"""
def __init__(self, *args, **kwargs):
super().__init__(None, *args, **kwargs)
class SaltCall(ScriptPathMixin, PytestSaltCall):
"""
Class which runs salt-call commands
"""
def __init__(self, *args, **kwargs):
super().__init__(None, *args, **kwargs)
class SaltKey(ScriptPathMixin, PytestSaltKey):
"""
Class which runs salt-key commands
"""
def __init__(self, *args, **kwargs):
super().__init__(None, *args, **kwargs)
class SaltRun(ScriptPathMixin, PytestSaltRun):
"""
Class which runs salt-run commands
"""
def __init__(self, *args, **kwargs):
super().__init__(None, *args, **kwargs)
class SaltProxy(GetSaltRunFixtureMixin, PytestSaltProxy):
"""
Class which runs the salt-proxy daemon
"""
class SaltMinion(GetSaltRunFixtureMixin, PytestSaltMinion):
"""
Class which runs the salt-minion daemon
"""
class SaltMaster(GetSaltRunFixtureMixin, PytestSaltMaster):
"""
Class which runs the salt-master daemon
"""
class SaltSyndic(GetSaltRunFixtureMixin, PytestSaltSyndic):
"""
Class which runs the salt-syndic daemon
"""
def start_daemon(
daemon_name=None,
daemon_id=None,
daemon_log_prefix=None,
daemon_cli_script_name=None,
daemon_config=None,
daemon_config_dir=None,
daemon_class=None,
bin_dir_path=None,
fail_hard=False,
start_timeout=10,
slow_stop=False,
environ=None,
cwd=None,
event_listener_config_dir=None,
):
"""
Returns a running salt daemon
"""
# Old config name
daemon_config["pytest_port"] = daemon_config["runtests_conn_check_port"]
# New config name
daemon_config["pytest_engine_port"] = daemon_config["runtests_conn_check_port"]
request = None
if fail_hard:
fail_method = RuntimeError
else:
fail_method = RuntimeWarning
log.info("[%s] Starting pytest %s(%s)", daemon_name, daemon_log_prefix, daemon_id)
attempts = 0
process = None
get_script_path(RUNTIME_VARS.TMP_SCRIPT_DIR, daemon_cli_script_name)
while attempts <= 3: # pylint: disable=too-many-nested-blocks
attempts += 1
try:
process = daemon_class(
request=request,
config=daemon_config,
config_dir=daemon_config_dir,
bin_dir_path=bin_dir_path,
log_prefix=daemon_log_prefix,
cli_script_name=daemon_cli_script_name,
slow_stop=slow_stop,
environ=environ,
cwd=cwd,
event_listener_config_dir=event_listener_config_dir,
)
except TypeError:
process = daemon_class(
request=request,
config=daemon_config,
config_dir=daemon_config_dir,
bin_dir_path=bin_dir_path,
log_prefix=daemon_log_prefix,
cli_script_name=daemon_cli_script_name,
slow_stop=slow_stop,
environ=environ,
cwd=cwd,
)
process.start()
if process.is_alive():
try:
connectable = process.wait_until_running(timeout=start_timeout)
if connectable is False:
connectable = process.wait_until_running(timeout=start_timeout / 2)
if connectable is False:
process.terminate()
if attempts >= 3:
fail_method(
"The pytest {}({}) has failed to confirm running status "
"after {} attempts".format(
daemon_name, daemon_id, attempts
)
)
continue
except Exception as exc: # pylint: disable=broad-except
log.exception("[%s] %s", daemon_log_prefix, exc, exc_info=True)
terminate_process(process.pid, kill_children=True, slow_stop=slow_stop)
if attempts >= 3:
raise fail_method(str(exc))
continue
log.info(
"[%s] The pytest %s(%s) is running and accepting commands "
"after %d attempts",
daemon_log_prefix,
daemon_name,
daemon_id,
attempts,
)
break
else:
terminate_process(process.pid, kill_children=True, slow_stop=slow_stop)
continue
else:
if process is not None:
terminate_process(process.pid, kill_children=True, slow_stop=slow_stop)
raise fail_method(
"The pytest {}({}) has failed to start after {} attempts".format(
daemon_name, daemon_id, attempts - 1
)
)
return process