Integrating latest 47acbe8
This commit is contained in:
+13
-7
@@ -14,14 +14,19 @@ import sys
|
||||
from ly_test_tools import WINDOWS
|
||||
|
||||
|
||||
def _get_test_launcher_cmd():
|
||||
def _get_test_launcher_cmd(build_dir=None):
|
||||
"""
|
||||
Helper function to determine the test launcher command for the current platform
|
||||
:param build_dir: the --build-directory arg that is passed to determine which build dir to use. Can also be None
|
||||
:return: The test launcher command
|
||||
"""
|
||||
python_runner = "python3.cmd"
|
||||
build_arg = ""
|
||||
if build_dir:
|
||||
build_arg = f"--build-directory {build_dir} "
|
||||
|
||||
python_runner = "python.cmd"
|
||||
if not WINDOWS:
|
||||
python_runner = "python3.sh"
|
||||
python_runner = "python.sh"
|
||||
current_dir = sys.executable
|
||||
|
||||
# Look upward a handful of levels to check for the LY python entry point script
|
||||
@@ -29,12 +34,12 @@ def _get_test_launcher_cmd():
|
||||
for _ in range(10):
|
||||
python_wrapper = os.path.join(current_dir, python_runner)
|
||||
if os.path.exists(python_wrapper):
|
||||
return f"{python_wrapper} -m pytest "
|
||||
return f"{python_wrapper} -m pytest{build_arg} "
|
||||
# Using an explicit else to avoid aberrant behavior from following filesystem links
|
||||
else:
|
||||
current_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir))
|
||||
|
||||
return f"{sys.executable} -m pytest "
|
||||
return f"{sys.executable} -m pytest{build_arg} "
|
||||
|
||||
|
||||
def _format_cmd(launcher_cmd, test_path, nodeid):
|
||||
@@ -65,17 +70,18 @@ def _format_cmd(launcher_cmd, test_path, nodeid):
|
||||
return f"{launcher_cmd}{test_id_argument}"
|
||||
|
||||
|
||||
def build_rerun_commands(test_path, nodeids):
|
||||
def build_rerun_commands(test_path, nodeids, build_dir=None):
|
||||
"""
|
||||
Builds a list of commands to run tests
|
||||
|
||||
:param test_path: File or directory that contains the test(s) that were run
|
||||
:param nodeids: List of test node ids, with parametrized values
|
||||
:param build_dir: the --build-directory arg that is passed to determine which build dir to use. Can also be None
|
||||
:return: A list of commands to re-run tests
|
||||
"""
|
||||
|
||||
commands = []
|
||||
test_launcher_cmd = _get_test_launcher_cmd()
|
||||
test_launcher_cmd = _get_test_launcher_cmd(build_dir)
|
||||
|
||||
for nodeid in nodeids:
|
||||
commands.append(_format_cmd(test_launcher_cmd, test_path, nodeid))
|
||||
|
||||
@@ -13,32 +13,39 @@ import os
|
||||
import ly_test_tools._internal.pytest_plugin.failed_test_rerun_command as rerun
|
||||
|
||||
|
||||
def _add_commands(terminalreporter, header, test_path, nodeids):
|
||||
def _add_commands(terminalreporter, header, test_path, nodeids, build_dir=None):
|
||||
"""
|
||||
Add test re-run commands to the TerminalReporter object
|
||||
:param terminalreporter: Pytest's TerminalReporter object that contains test result information.
|
||||
:param header: Message to write to TerminalReporter before list is added
|
||||
:param test_path: File or directory that contains the test(s) that to run
|
||||
:param nodeids: List of test node ids, with parametrized values
|
||||
:param build_dir: the --build-directory arg that is passed to determine which build dir to use. Can also be None
|
||||
"""
|
||||
|
||||
terminalreporter.write_line(header)
|
||||
|
||||
if nodeids:
|
||||
commands = rerun.build_rerun_commands(test_path, nodeids)
|
||||
commands = rerun.build_rerun_commands(test_path, nodeids, build_dir)
|
||||
for command in commands:
|
||||
terminalreporter.write_line(command)
|
||||
else:
|
||||
terminalreporter.write_line("Error, Test node id list is empty!")
|
||||
|
||||
|
||||
def pytest_terminal_summary(terminalreporter, exitstatus):
|
||||
def pytest_terminal_summary(terminalreporter, exitstatus, config):
|
||||
"""
|
||||
Pytest's hook for terminal reporting. This hook is invoked at the end of the test session.
|
||||
|
||||
:param terminalreporter: Pytest's TerminalReporter object that contains test result information.
|
||||
:param exitstatus: Exit that will be returned to the system
|
||||
"""
|
||||
# Check to see if the build directory was passed
|
||||
build_dir = None
|
||||
try:
|
||||
build_dir = config.known_args_namespace.build_directory
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Add to the TerminalReport a section for failed test re-running
|
||||
failures = terminalreporter.stats.get('failed', [])
|
||||
@@ -62,7 +69,7 @@ def pytest_terminal_summary(terminalreporter, exitstatus):
|
||||
terminalreporter,
|
||||
"Use the following commands to re-run each test that failed locally\n"
|
||||
"(NOTE: The 'PYTHON' or 'PYTHONPATH' environment variables need values for accurate commands): ",
|
||||
test_path, nodeids)
|
||||
test_path, nodeids, build_dir)
|
||||
|
||||
if error_count:
|
||||
nodeids = [os.path.basename(report.nodeid) for report in errors]
|
||||
@@ -71,4 +78,4 @@ def pytest_terminal_summary(terminalreporter, exitstatus):
|
||||
terminalreporter,
|
||||
"Use the following commands to re-run each test that had errors locally\n"
|
||||
"(NOTE: The 'PYTHON' or 'PYTHONPATH' environment variables need values for accurate commands): ",
|
||||
test_path, nodeids)
|
||||
test_path, nodeids, build_dir)
|
||||
|
||||
@@ -126,6 +126,7 @@ def kill_processes_with_name_not_started_from(name, path):
|
||||
else:
|
||||
logger.warning(f"Path:'{path}' not found")
|
||||
|
||||
|
||||
def kill_process_with_pid(pid, raise_on_missing=False):
|
||||
"""
|
||||
Kills the process with the specified pid
|
||||
@@ -367,7 +368,10 @@ def _safe_kill_process_list(proc_list):
|
||||
except Exception: # purposefully broad
|
||||
logger.warning("Unexpected exception while terminating process", exc_info=True)
|
||||
|
||||
psutil.wait_procs(proc_list, timeout=30, callback=on_terminate)
|
||||
try:
|
||||
psutil.wait_procs(proc_list, timeout=30, callback=on_terminate)
|
||||
except Exception: # purposefully broad
|
||||
logger.warning("Unexpected exception while waiting for process to terminate", exc_info=True)
|
||||
|
||||
|
||||
def _terminate_and_confirm_dead(proc):
|
||||
|
||||
@@ -220,7 +220,7 @@ def platform_enabled(workspace: pytest.fixture, platform: str) -> bool:
|
||||
Checks to see if the platform specified is enabled for the current build of LY.
|
||||
|
||||
:param workspace: The current testing workspace
|
||||
:param platform: The name of the platform to lookup. Example Platforms: 'Xenia', 'Provo', 'Salem'
|
||||
:param platform: The name of the platform to lookup.
|
||||
:return: True if the platform is enabled, False if not.
|
||||
"""
|
||||
user_settings_file = os.path.join(workspace.paths.dev(), "_WAF_", "user_settings.options")
|
||||
|
||||
@@ -16,9 +16,10 @@ ly_add_pytest(
|
||||
NAME LyTestTools_UnitTests
|
||||
TEST_SUITE smoke
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/unit/
|
||||
COMPONENT TestTools
|
||||
)
|
||||
|
||||
if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedTesting IN_LIST LY_PROJECTS)
|
||||
if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedTesting IN_LIST LY_PROJECTS_NAME)
|
||||
# Integration tests.
|
||||
ly_add_pytest(
|
||||
NAME LyTestTools_IntegTests_Sanity_smoke_no_gpu
|
||||
@@ -31,6 +32,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
|
||||
AutomatedTesting.GameLauncher
|
||||
AutomatedTesting.Assets
|
||||
Legacy::CryRenderNULL
|
||||
COMPONENT TestTools
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
@@ -38,6 +40,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/integ/test_process_utils.py
|
||||
TEST_SERIAL
|
||||
TEST_SUITE smoke
|
||||
COMPONENT TestTools
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
@@ -50,6 +53,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
|
||||
AutomatedTesting.GameLauncher
|
||||
AutomatedTesting.Assets
|
||||
Legacy::CryRenderNULL
|
||||
COMPONENT TestTools
|
||||
)
|
||||
|
||||
# Regression tests.
|
||||
@@ -64,5 +68,6 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
|
||||
AutomatedTesting.GameLauncher
|
||||
AutomatedTesting.Assets
|
||||
Legacy::CryRenderNULL
|
||||
COMPONENT TestTools
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -49,7 +49,7 @@ class TestRerunCommand(object):
|
||||
@mock.patch('ly_test_tools._internal.pytest_plugin.failed_test_rerun_command.WINDOWS', True)
|
||||
@mock.patch('os.path.exists', mock.MagicMock(return_value=True))
|
||||
def test_GetLauncherCommand_WindowsPythonInterpreter_WindowsPythonEntrypointReturned(self):
|
||||
python_script = 'python3.cmd'
|
||||
python_script = 'python.cmd'
|
||||
expected = f"{os.path.join(TestRerunCommand.MOCK_FOO_EXE, python_script)} -m pytest "
|
||||
|
||||
under_test = failed_test_rerun_command._get_test_launcher_cmd()
|
||||
@@ -59,7 +59,7 @@ class TestRerunCommand(object):
|
||||
@mock.patch('ly_test_tools._internal.pytest_plugin.failed_test_rerun_command.WINDOWS', False)
|
||||
@mock.patch('os.path.exists', mock.MagicMock(return_value=True))
|
||||
def test_GetLauncherCommand_NonWindowsPythonInterpreter_NonWindowsPythonEntrypointReturned(self):
|
||||
python_script = 'python3.sh'
|
||||
python_script = 'python.sh'
|
||||
expected = f"{os.path.join(TestRerunCommand.MOCK_FOO_EXE, python_script)} -m pytest "
|
||||
|
||||
under_test = failed_test_rerun_command._get_test_launcher_cmd()
|
||||
|
||||
@@ -337,6 +337,17 @@ class Test(unittest.TestCase):
|
||||
mock_wait.assert_called()
|
||||
mock_log_warn.assert_called()
|
||||
|
||||
@mock.patch('psutil.wait_procs')
|
||||
@mock.patch('logging.Logger.warning')
|
||||
def test_SafeKillProcList_RaisesError_NoRaiseAndLogsError(self, mock_log_warn, mock_wait_procs):
|
||||
mock_wait_procs.side_effect = psutil.PermissionError()
|
||||
proc_mock = mock.MagicMock()
|
||||
|
||||
process_utils._safe_kill_process_list(proc_mock)
|
||||
|
||||
mock_wait_procs.assert_called()
|
||||
mock_log_warn.assert_called()
|
||||
|
||||
@mock.patch('psutil.process_iter')
|
||||
@mock.patch('logging.Logger.debug')
|
||||
def test_SafeGetProc_CannotAccess_LogAndReturnNone(self, mock_log_debug, mock_psiter):
|
||||
|
||||
@@ -23,7 +23,7 @@ class TestTerminalReport(object):
|
||||
|
||||
@mock.patch('ly_test_tools._internal.pytest_plugin.failed_test_rerun_command.build_rerun_commands')
|
||||
def test_AddCommands_MockCommands_CommandsAdded(self, mock_build_commands):
|
||||
mock_build_commands.side_effect = lambda path, nodes: nodes
|
||||
mock_build_commands.side_effect = lambda path, nodes, dir: nodes
|
||||
mock_reporter = mock.MagicMock()
|
||||
header = 'This is a header'
|
||||
test_path = 'Foo'
|
||||
@@ -55,8 +55,9 @@ class TestTerminalReport(object):
|
||||
def test_TerminalSummary_NoErrorsNoFailures_EmptyReport(self, mock_add_commands):
|
||||
mock_report = mock.MagicMock()
|
||||
mock_report.stats.get.return_value = []
|
||||
mock_config = mock.MagicMock()
|
||||
|
||||
terminal_report.pytest_terminal_summary(mock_report, 0)
|
||||
terminal_report.pytest_terminal_summary(mock_report, 0, mock_config)
|
||||
|
||||
mock_add_commands.assert_not_called()
|
||||
mock_report.config.getoption.assert_not_called()
|
||||
@@ -68,8 +69,9 @@ class TestTerminalReport(object):
|
||||
mock_node = mock.MagicMock()
|
||||
mock_node.nodeid = 'something'
|
||||
mock_report.stats.get.return_value = [mock_node, mock_node]
|
||||
mock_config = mock.MagicMock()
|
||||
|
||||
terminal_report.pytest_terminal_summary(mock_report, 0)
|
||||
terminal_report.pytest_terminal_summary(mock_report, 0, mock_config)
|
||||
|
||||
assert len(mock_add_commands.mock_calls) == 2
|
||||
mock_report.config.getoption.assert_called()
|
||||
@@ -84,8 +86,9 @@ class TestTerminalReport(object):
|
||||
node_id = os.path.join('C:', mock_base)
|
||||
mock_node.nodeid = node_id
|
||||
mock_report.stats.get.side_effect = [[mock_node], []] # first item is failure list
|
||||
mock_config = mock.MagicMock()
|
||||
|
||||
terminal_report.pytest_terminal_summary(mock_report, 0)
|
||||
terminal_report.pytest_terminal_summary(mock_report, 0, mock_config)
|
||||
|
||||
mock_basename.assert_called_with(node_id)
|
||||
|
||||
@@ -98,8 +101,9 @@ class TestTerminalReport(object):
|
||||
node_id = os.path.join('C:', mock_base)
|
||||
mock_node.nodeid = node_id
|
||||
mock_report.stats.get.side_effect = [[], [mock_node]] # second item is error list
|
||||
mock_config = mock.MagicMock()
|
||||
|
||||
terminal_report.pytest_terminal_summary(mock_report, 0)
|
||||
terminal_report.pytest_terminal_summary(mock_report, 0, mock_config)
|
||||
|
||||
mock_basename.assert_called_with(node_id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user