(Continuation) Implemented automation paralellization & standarization (#1718)
Engine improvements/fixes Fixed behavior that made the editor automated test to be sometimes stuck if lost the focus is lost. Added support for specifying multiple tests to in batch to the editor, this is achieved by passing --runpythontest with the tests separated by ';' Added new cmdline argument --project-user-path for overriding the user path. This allows to have multiple editors running writing logs and crash logs in different locations. Moved responsability of exiting after a test finishes/passes out of ExecuteByFilenameAsTest, callers will use the bool return to know if the test passed. Editor test batch and parallelization implementation: Now the external python portion of the editor tests will be specified via test specs which will generate the test. Requiring no code. This is almost a data-driven approach. Tests can be specified as single tests, parallel, batchable or batchable+parallel Command line arguments for pytest to override the maximum number of editors, disable parallelization or batching. Automated tests for testing this new editor testing utility Signed-off-by: Garcia Ruiz <aljanru@amazon.co.uk> Co-authored-by: Garcia Ruiz <aljanru@amazon.co.uk>
This commit is contained in:
+83
-35
@@ -11,14 +11,16 @@ import math
|
||||
import azlmbr
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.debug
|
||||
import json
|
||||
|
||||
import traceback
|
||||
|
||||
from typing import Callable, Tuple
|
||||
|
||||
class FailFast(Exception):
|
||||
"""
|
||||
Raise to stop proceeding through test steps.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -30,8 +32,8 @@ class TestHelper:
|
||||
# general.idle_wait_frames(1)
|
||||
|
||||
@staticmethod
|
||||
def open_level(directory, level):
|
||||
# type: (str, ) -> None
|
||||
def open_level(directory : str, level : str):
|
||||
# type: (str, str) -> None
|
||||
"""
|
||||
:param level: the name of the level folder in AutomatedTesting\\Physics\\
|
||||
|
||||
@@ -51,7 +53,7 @@ class TestHelper:
|
||||
general.idle_wait_frames(200)
|
||||
|
||||
@staticmethod
|
||||
def enter_game_mode(msgtuple_success_fail):
|
||||
def enter_game_mode(msgtuple_success_fail : Tuple[str, str]):
|
||||
# type: (tuple) -> None
|
||||
"""
|
||||
:param msgtuple_success_fail: The tuple with the expected/unexpected messages for entering game mode.
|
||||
@@ -65,7 +67,7 @@ class TestHelper:
|
||||
Report.critical_result(msgtuple_success_fail, general.is_in_game_mode())
|
||||
|
||||
@staticmethod
|
||||
def exit_game_mode(msgtuple_success_fail):
|
||||
def exit_game_mode(msgtuple_success_fail : Tuple[str, str]):
|
||||
# type: (tuple) -> None
|
||||
"""
|
||||
:param msgtuple_success_fail: The tuple with the expected/unexpected messages for exiting game mode.
|
||||
@@ -147,84 +149,130 @@ class Timeout:
|
||||
def timed_out(self):
|
||||
return time.time() > self.die_after
|
||||
|
||||
|
||||
class Report:
|
||||
_results = []
|
||||
_exception = None
|
||||
|
||||
@staticmethod
|
||||
def start_test(test_function):
|
||||
def start_test(test_function : Callable):
|
||||
"""
|
||||
Runs the test, outputs the report and asserts in case of failure.
|
||||
@param: The test function to execute
|
||||
"""
|
||||
Report._results = []
|
||||
Report._exception = None
|
||||
general.test_output(f"Starting test {test_function.__name__}...\n")
|
||||
try:
|
||||
test_function()
|
||||
except Exception as ex:
|
||||
Report._exception = traceback.format_exc()
|
||||
Report.report_results(test_function)
|
||||
|
||||
success, report_str = Report.get_report(test_function)
|
||||
# Print on the o3de console, for debugging purpuses
|
||||
print(report_str)
|
||||
# Print the report on the piped stdout of the application
|
||||
general.test_output(report_str)
|
||||
assert success, f"Test {test_function.__name__} failed"
|
||||
|
||||
@staticmethod
|
||||
def report_results(test_function):
|
||||
success = True
|
||||
report = f"Report for {test_function.__name__}:\n"
|
||||
def get_report(test_function : Callable) -> (bool, str):
|
||||
"""
|
||||
Outputs infomation on the editor console for the test
|
||||
@param msg: message to output
|
||||
@return: (success, report_information) tuple
|
||||
"""
|
||||
success = True
|
||||
report = f"Test {test_function.__name__} finished.\nReport:\n"
|
||||
# report_dict is a JSON that can be used to parse test run information from a external runner
|
||||
# The regular report string is intended to be used for manual debugging
|
||||
filename = os.path.splitext(os.path.basename(test_function.__code__.co_filename))[0]
|
||||
report_dict = {'name' : filename, 'success' : True, 'exception' : None}
|
||||
for result in Report._results:
|
||||
passed, info = result
|
||||
success = success and passed
|
||||
test_result_info = ""
|
||||
if passed:
|
||||
report += f"[SUCCESS] {info}\n"
|
||||
test_result_info = f"[SUCCESS] {info}"
|
||||
else:
|
||||
report += f"[FAILED ] {info}\n"
|
||||
test_result_info = f"[FAILED ] {info}"
|
||||
report += f"{test_result_info}\n"
|
||||
if Report._exception:
|
||||
report += "EXCEPTION raised:\n %s\n" % Report._exception[:-1].replace("\n", "\n ")
|
||||
exception_str = Report._exception[:-1].replace("\n", "\n ")
|
||||
report += "EXCEPTION raised:\n %s\n" % exception_str
|
||||
report_dict['exception'] = exception_str
|
||||
success = False
|
||||
report += "Test result: "
|
||||
report += "SUCCESS" if success else "FAILURE"
|
||||
print(report)
|
||||
general.report_test_result(success, report)
|
||||
report += "Test result: " + ("SUCCESS" if success else "FAILURE")
|
||||
report_dict['success'] = success
|
||||
report_dict['output'] = report
|
||||
report_json_str = json.dumps(report_dict)
|
||||
# For helping parsing, the json will be always contained between JSON_START JSON_END
|
||||
report += f"\nJSON_START({report_json_str})JSON_END\n"
|
||||
return success, report
|
||||
|
||||
@staticmethod
|
||||
def info(msg):
|
||||
def info(msg : str):
|
||||
"""
|
||||
Outputs infomation on the editor console for the test
|
||||
@param msg: message to output
|
||||
"""
|
||||
print("Info: {}".format(msg))
|
||||
|
||||
@staticmethod
|
||||
def success(msgtuple_success_fail):
|
||||
def success(msgtuple_success_fail : Tuple[str, str]):
|
||||
"""
|
||||
Given a test string tuple (success_string, failure_string), registers the test result as success
|
||||
@param msgtuple_success_fail: Two element tuple of success and failure strings
|
||||
"""
|
||||
outcome = "Success: {}".format(msgtuple_success_fail[0])
|
||||
print(outcome)
|
||||
Report._results.append((True, outcome))
|
||||
|
||||
@staticmethod
|
||||
def failure(msgtuple_success_fail):
|
||||
def failure(msgtuple_success_fail : Tuple[str, str]):
|
||||
"""
|
||||
Given a test string tuple (success_string, failure_string), registers the test result as failed
|
||||
@param msgtuple_success_fail: Two element tuple of success and failure strings
|
||||
"""
|
||||
outcome = "Failure: {}".format(msgtuple_success_fail[1])
|
||||
print(outcome)
|
||||
Report._results.append((False, outcome))
|
||||
|
||||
@staticmethod
|
||||
def result(msgtuple_success_fail, condition):
|
||||
if not isinstance(condition, bool):
|
||||
raise TypeError("condition argument must be a bool")
|
||||
def result(msgtuple_success_fail : Tuple[str, str], outcome : bool):
|
||||
"""
|
||||
Given a test string tuple (success_string, failure_string), registers the test result based on the
|
||||
given outcome
|
||||
@param msgtuple_success_fail: Two element tuple of success and failure strings
|
||||
@param outcome: True or False if the result has been a sucess or failure
|
||||
"""
|
||||
if not isinstance(outcome, bool):
|
||||
raise TypeError("outcome argument must be a bool")
|
||||
|
||||
if condition:
|
||||
if outcome:
|
||||
Report.success(msgtuple_success_fail)
|
||||
else:
|
||||
Report.failure(msgtuple_success_fail)
|
||||
return condition
|
||||
return outcome
|
||||
|
||||
@staticmethod
|
||||
def critical_result(msgtuple_success_fail, condition, fast_fail_message=None):
|
||||
def critical_result(msgtuple_success_fail : Tuple[str, str], outcome : bool, fast_fail_message : str = None):
|
||||
# type: (tuple, bool, str) -> None
|
||||
"""
|
||||
if condition is False we will fail fast
|
||||
if outcome is False we will fail fast
|
||||
|
||||
:param msgtuple_success_fail: messages to print based on the condition
|
||||
:param condition: success (True) or failure (False)
|
||||
:param msgtuple_success_fail: messages to print based on the outcome
|
||||
:param outcome: success (True) or failure (False)
|
||||
:param fast_fail_message: [optional] message to include on fast fail
|
||||
"""
|
||||
if not isinstance(condition, bool):
|
||||
raise TypeError("condition argument must be a bool")
|
||||
if not isinstance(outcome, bool):
|
||||
raise TypeError("outcome argument must be a bool")
|
||||
|
||||
if not Report.result(msgtuple_success_fail, condition):
|
||||
if not Report.result(msgtuple_success_fail, outcome):
|
||||
TestHelper.fail_fast(fast_fail_message)
|
||||
|
||||
# DEPRECATED: Use vector3_str()
|
||||
@staticmethod
|
||||
def info_vector3(vector3, label="", magnitude=None):
|
||||
def info_vector3(vector3 : azlmbr.math.Vector3, label : str ="", magnitude : float =None):
|
||||
# type: (azlmbr.math.Vector3, str, float) -> None
|
||||
"""
|
||||
prints the vector to the Report.info log. If applied, label will print first,
|
||||
@@ -390,4 +438,4 @@ def vector3_str(vector3):
|
||||
return "(x: {:.2f}, y: {:.2f}, z: {:.2f})".format(vector3.x, vector3.y, vector3.z)
|
||||
|
||||
def aabb_str(aabb):
|
||||
return "[Min: %s, Max: %s]" % (vector3_str(aabb.min), vector3_str(aabb.max))
|
||||
return "[Min: %s, Max: %s]" % (vector3_str(aabb.min), vector3_str(aabb.max))
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
def EditorTest_That_Crashes():
|
||||
import azlmbr.legacy.general as general
|
||||
general.crash()
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(EditorTest_That_Crashes)
|
||||
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
def EditorTest_That_Fails():
|
||||
assert False, "This test fails on purpose to test functionality"
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(EditorTest_That_Fails)
|
||||
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
def EditorTest_That_Passes():
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(EditorTest_That_Passes)
|
||||
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
def EditorTest_That_PassesToo():
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(EditorTest_That_PassesToo)
|
||||
@@ -0,0 +1,262 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
"""
|
||||
This suite contains the tests for editor_test utilities.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
import sys
|
||||
import importlib
|
||||
import re
|
||||
|
||||
from ly_test_tools import LAUNCHERS
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorTestSuite, Result
|
||||
from ly_test_tools.o3de.asset_processor import AssetProcessor
|
||||
import ly_test_tools.environment.process_utils as process_utils
|
||||
|
||||
import argparse, sys
|
||||
|
||||
@pytest.mark.SUITE_main
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
class TestEditorTest:
|
||||
|
||||
args = None
|
||||
path = None
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
TestEditorTest.args = sys.argv.copy()
|
||||
build_dir_arg_index = TestEditorTest.args.index("--build-directory")
|
||||
if build_dir_arg_index < 0:
|
||||
print("Error: Must pass --build-directory argument in order to run this test")
|
||||
sys.exit(-2)
|
||||
|
||||
TestEditorTest.args[build_dir_arg_index+1] = os.path.abspath(TestEditorTest.args[build_dir_arg_index+1])
|
||||
TestEditorTest.args.append("-s")
|
||||
TestEditorTest.path = os.path.dirname(os.path.abspath(__file__))
|
||||
cls._asset_processor = None
|
||||
|
||||
def teardown_class(cls):
|
||||
if cls._asset_processor:
|
||||
cls._asset_processor.stop(1)
|
||||
cls._asset_processor.teardown()
|
||||
|
||||
# Test runs #
|
||||
@classmethod
|
||||
def _run_single_test(cls, testdir, workspace, module_name):
|
||||
if cls._asset_processor is None:
|
||||
if not process_utils.process_exists("AssetProcessor", ignore_extensions=True):
|
||||
cls._asset_processor = AssetProcessor(workspace)
|
||||
cls._asset_processor.start()
|
||||
|
||||
testdir.makepyfile(
|
||||
f"""
|
||||
import pytest
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ly_test_tools import LAUNCHERS
|
||||
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorTestSuite
|
||||
|
||||
@pytest.mark.SUITE_main
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
class TestAutomation(EditorTestSuite):
|
||||
class test_single(EditorSingleTest):
|
||||
import {module_name} as test_module
|
||||
|
||||
""")
|
||||
result = testdir.runpytest(*TestEditorTest.args[2:])
|
||||
|
||||
def get_class(module_name):
|
||||
class test_single(EditorSingleTest):
|
||||
test_module = importlib.import_module(module_name)
|
||||
return test_single
|
||||
|
||||
output = "".join(result.outlines)
|
||||
extracted_results = EditorTestSuite._get_results_using_output([get_class(module_name)], output, output)
|
||||
extracted_result = next(iter(extracted_results.items()))
|
||||
return (extracted_result[1], result)
|
||||
|
||||
def test_single_passing_test(self, request, workspace, launcher_platform, testdir):
|
||||
(extracted_result, result) = TestEditorTest._run_single_test(testdir, workspace, "EditorTest_That_Passes")
|
||||
result.assert_outcomes(passed=1)
|
||||
assert isinstance(extracted_result, Result.Pass)
|
||||
|
||||
def test_single_failing_test(self, request, workspace, launcher_platform, testdir):
|
||||
(extracted_result, result) = TestEditorTest._run_single_test(testdir, workspace, "EditorTest_That_Fails")
|
||||
result.assert_outcomes(failed=1)
|
||||
assert isinstance(extracted_result, Result.Fail)
|
||||
|
||||
def test_single_crashing_test(self, request, workspace, launcher_platform, testdir):
|
||||
(extracted_result, result) = TestEditorTest._run_single_test(testdir, workspace, "EditorTest_That_Crashes")
|
||||
result.assert_outcomes(failed=1)
|
||||
assert isinstance(extracted_result, Result.Unknown)
|
||||
|
||||
@classmethod
|
||||
def _run_shared_test(cls, testdir, module_class_code, extra_cmd_line=None):
|
||||
if not extra_cmd_line:
|
||||
extra_cmd_line = []
|
||||
|
||||
if cls._asset_processor is None:
|
||||
if not process_utils.process_exists("AssetProcessor", ignore_extensions=True):
|
||||
cls._asset_processor = AssetProcessor(workspace)
|
||||
cls._asset_processor.start()
|
||||
|
||||
testdir.makepyfile(
|
||||
f"""
|
||||
import pytest
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ly_test_tools import LAUNCHERS
|
||||
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorTestSuite
|
||||
|
||||
@pytest.mark.SUITE_main
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
class TestAutomation(EditorTestSuite):
|
||||
{module_class_code}
|
||||
""")
|
||||
result = testdir.runpytest(*TestEditorTest.args[2:] + extra_cmd_line)
|
||||
return result
|
||||
|
||||
def test_batched_two_passing(self, request, workspace, launcher_platform, testdir):
|
||||
result = self._run_shared_test(testdir,
|
||||
"""
|
||||
class test_pass(EditorSharedTest):
|
||||
import EditorTest_That_Passes as test_module
|
||||
is_parallelizable = False
|
||||
|
||||
class test_2(EditorSharedTest):
|
||||
import EditorTest_That_PassesToo as test_module
|
||||
is_parallelizable = False
|
||||
"""
|
||||
)
|
||||
# 2 Passes +1(batch runner)
|
||||
result.assert_outcomes(passed=3)
|
||||
|
||||
def test_batched_one_pass_one_fail(self, request, workspace, launcher_platform, testdir):
|
||||
result = self._run_shared_test(testdir,
|
||||
"""
|
||||
class test_pass(EditorSharedTest):
|
||||
import EditorTest_That_Passes as test_module
|
||||
is_parallelizable = False
|
||||
|
||||
class test_fail(EditorSharedTest):
|
||||
import EditorTest_That_Fails as test_module
|
||||
is_parallelizable = False
|
||||
"""
|
||||
)
|
||||
# 1 Fail, 1 Passes +1(batch runner)
|
||||
result.assert_outcomes(passed=2, failed=1)
|
||||
|
||||
def test_batched_one_pass_one_fail_one_crash(self, request, workspace, launcher_platform, testdir):
|
||||
result = self._run_shared_test(testdir,
|
||||
"""
|
||||
class test_pass(EditorSharedTest):
|
||||
import EditorTest_That_Passes as test_module
|
||||
is_parallelizable = False
|
||||
|
||||
class test_fail(EditorSharedTest):
|
||||
import EditorTest_That_Fails as test_module
|
||||
is_parallelizable = False
|
||||
|
||||
class test_crash(EditorSharedTest):
|
||||
import EditorTest_That_Crashes as test_module
|
||||
is_parallelizable = False
|
||||
"""
|
||||
)
|
||||
# 2 Fail, 1 Passes + 1(batch runner)
|
||||
result.assert_outcomes(passed=2, failed=2)
|
||||
|
||||
def test_parallel_two_passing(self, request, workspace, launcher_platform, testdir):
|
||||
result = self._run_shared_test(testdir,
|
||||
"""
|
||||
class test_pass_1(EditorSharedTest):
|
||||
import EditorTest_That_Passes as test_module
|
||||
is_batchable = False
|
||||
|
||||
class test_pass_2(EditorSharedTest):
|
||||
import EditorTest_That_PassesToo as test_module
|
||||
is_batchable = False
|
||||
"""
|
||||
)
|
||||
# 2 Passes +1(parallel runner)
|
||||
result.assert_outcomes(passed=3)
|
||||
|
||||
def test_parallel_one_passing_one_failing_one_crashing(self, request, workspace, launcher_platform, testdir):
|
||||
result = self._run_shared_test(testdir,
|
||||
"""
|
||||
class test_pass(EditorSharedTest):
|
||||
import EditorTest_That_Passes as test_module
|
||||
is_batchable = False
|
||||
|
||||
class test_fail(EditorSharedTest):
|
||||
import EditorTest_That_Fails as test_module
|
||||
is_batchable = False
|
||||
|
||||
class test_crash(EditorSharedTest):
|
||||
import EditorTest_That_Crashes as test_module
|
||||
is_batchable = False
|
||||
"""
|
||||
)
|
||||
# 2 Fail, 1 Passes + 1(parallel runner)
|
||||
result.assert_outcomes(passed=2, failed=2)
|
||||
|
||||
def test_parallel_batched_two_passing(self, request, workspace, launcher_platform, testdir):
|
||||
result = self._run_shared_test(testdir,
|
||||
"""
|
||||
class test_pass_1(EditorSharedTest):
|
||||
import EditorTest_That_Passes as test_module
|
||||
|
||||
class test_pass_2(EditorSharedTest):
|
||||
import EditorTest_That_PassesToo as test_module
|
||||
"""
|
||||
)
|
||||
# 2 Passes +1(batched+parallel runner)
|
||||
result.assert_outcomes(passed=3)
|
||||
|
||||
def test_parallel_batched_one_passing_one_failing_one_crashing(self, request, workspace, launcher_platform, testdir):
|
||||
result = self._run_shared_test(testdir,
|
||||
"""
|
||||
class test_pass(EditorSharedTest):
|
||||
import EditorTest_That_Passes as test_module
|
||||
|
||||
class test_fail(EditorSharedTest):
|
||||
import EditorTest_That_Fails as test_module
|
||||
|
||||
class test_crash(EditorSharedTest):
|
||||
import EditorTest_That_Crashes as test_module
|
||||
"""
|
||||
)
|
||||
# 2 Fail, 1 Passes + 1(batched+parallel runner)
|
||||
result.assert_outcomes(passed=2, failed=2)
|
||||
|
||||
def test_selection_2_deselected_1_selected(self, request, workspace, launcher_platform, testdir):
|
||||
result = self._run_shared_test(testdir,
|
||||
"""
|
||||
class test_pass(EditorSharedTest):
|
||||
import EditorTest_That_Passes as test_module
|
||||
|
||||
class test_fail(EditorSharedTest):
|
||||
import EditorTest_That_Fails as test_module
|
||||
|
||||
class test_crash(EditorSharedTest):
|
||||
import EditorTest_That_Crashes as test_module
|
||||
""", extra_cmd_line=["-k", "fail"]
|
||||
)
|
||||
# 1 Fail + 1 Success(parallel runner)
|
||||
result.assert_outcomes(failed=1, passed=1)
|
||||
outcomes = result.parseoutcomes()
|
||||
deselected = outcomes.get("deselected")
|
||||
assert deselected == 2
|
||||
@@ -0,0 +1,6 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
pytest_plugins = ["pytester"]
|
||||
-2
@@ -8,7 +8,6 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
# Test case ID : C111111
|
||||
# Test Case Title : Check that Gravity works
|
||||
|
||||
|
||||
# fmt:off
|
||||
class Tests:
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
@@ -84,7 +83,6 @@ def C111111_RigidBody_EnablingGravityWorksUsingNotificationsPoC():
|
||||
# 7) Exit game mode
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import ImportPathHelper as imports
|
||||
imports.init()
|
||||
|
||||
+4
-2
@@ -69,6 +69,9 @@ def C12712453_ScriptCanvas_MultipleRaycastNode():
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
# Disabled until Script Canvas merges the new backend
|
||||
return
|
||||
|
||||
import os
|
||||
import sys
|
||||
@@ -200,5 +203,4 @@ if __name__ == "__main__":
|
||||
imports.init()
|
||||
|
||||
from editor_python_test_tools.utils import Report
|
||||
# Disabled until Script Canvas merges the new backend
|
||||
#Report.start_test(C12712453_ScriptCanvas_MultipleRaycastNode)
|
||||
Report.start_test(C12712453_ScriptCanvas_MultipleRaycastNode)
|
||||
|
||||
@@ -20,7 +20,7 @@ class Tests():
|
||||
# fmt: on
|
||||
|
||||
|
||||
def run():
|
||||
def C17411467_AddPhysxRagdollComponent():
|
||||
"""
|
||||
Summary:
|
||||
Load level with Entity having Actor, AnimGraph and PhysX Ragdoll components.
|
||||
@@ -93,4 +93,8 @@ def run():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
import ImportPathHelper as imports
|
||||
imports.init()
|
||||
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(C17411467_AddPhysxRagdollComponent)
|
||||
|
||||
-1
@@ -133,7 +133,6 @@ def C24308873_CylinderShapeCollider_CollidesWithPhysXTerrain():
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import ImportPathHelper as imports
|
||||
imports.init()
|
||||
|
||||
@@ -22,6 +22,7 @@ class Tests():
|
||||
add_physx_shape_collider = ("Added PhysX Shape Collider", "Failed to add PhysX Shape Collider")
|
||||
add_box_shape = ("Added Box Shape", "Failed to add Box Shape")
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
exit_game_mode = ("Exited game mode", "Failed to exit game mode")
|
||||
test_collision = ("Entity collided with terrain", "Failed to collide with terrain")
|
||||
# fmt: on
|
||||
|
||||
@@ -123,7 +124,6 @@ def C4982803_Enable_PxMesh_Option():
|
||||
touched_ground = False
|
||||
|
||||
terrain_id = general.find_game_entity("Terrain")
|
||||
|
||||
def on_collision_begin(args):
|
||||
other_id = args[0]
|
||||
if other_id.Equal(terrain_id):
|
||||
@@ -137,6 +137,8 @@ def C4982803_Enable_PxMesh_Option():
|
||||
helper.wait_for_condition(lambda: Collider.touched_ground, TIMEOUT)
|
||||
Report.result(Tests.test_collision, Collider.touched_ground)
|
||||
|
||||
# 8) Exit game mode
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import ImportPathHelper as imports
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
|
||||
from ly_test_tools import LAUNCHERS
|
||||
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
|
||||
from .FileManagement import FileManagement as fm
|
||||
|
||||
# Custom test spec, it provides functionality to override files
|
||||
class EditorSingleTest_WithFileOverrides(EditorSingleTest):
|
||||
# Specify here what files to override, [(original, override), ...]
|
||||
files_to_override = [()]
|
||||
# Base directory of the files (Default path is {ProjectName})
|
||||
base_dir = None
|
||||
# True will will search sub-directories for the files in base
|
||||
search_subdirs = False
|
||||
|
||||
@classmethod
|
||||
def wrap_run(cls, instance, request, workspace, editor, editor_test_results, launcher_platform):
|
||||
root_path = cls.base_dir
|
||||
if root_path is not None:
|
||||
root_path = os.path.join(workspace.paths.engine_root(), root_path)
|
||||
else:
|
||||
# Default to project folder
|
||||
root_path = workspace.paths.project()
|
||||
|
||||
# Try to locate both target and source files
|
||||
original_file_list, override_file_list = zip(*cls.files_to_override)
|
||||
try:
|
||||
file_list = fm._find_files(original_file_list + override_file_list, root_path, cls.search_subdirs)
|
||||
except RuntimeWarning as w:
|
||||
assert False, (
|
||||
w.message
|
||||
+ " Please check use of search_subdirs; make sure you are using the correct parent directory."
|
||||
)
|
||||
|
||||
for f in original_file_list:
|
||||
fm._restore_file(f, file_list[f])
|
||||
fm._backup_file(f, file_list[f])
|
||||
|
||||
for original, override in cls.files_to_override:
|
||||
fm._copy_file(override, file_list[override], original, file_list[override])
|
||||
|
||||
yield # Run Test
|
||||
for f in original_file_list:
|
||||
fm._restore_file(f, file_list[f])
|
||||
|
||||
|
||||
@pytest.mark.SUITE_main
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
class TestAutomation(EditorTestSuite):
|
||||
|
||||
class C4044459_Material_DynamicFriction(EditorSingleTest_WithFileOverrides):
|
||||
from . import C4044459_Material_DynamicFriction as test_module
|
||||
files_to_override = [
|
||||
('physxsystemconfiguration.setreg', 'C4044459_Material_DynamicFriction.setreg_override')
|
||||
]
|
||||
base_dir = "AutomatedTesting/Registry"
|
||||
|
||||
class C4982593_PhysXCollider_CollisionLayerTest(EditorSingleTest_WithFileOverrides):
|
||||
from . import C4982593_PhysXCollider_CollisionLayerTest as test_module
|
||||
files_to_override = [
|
||||
('physxsystemconfiguration.setreg', 'C4982593_PhysXCollider_CollisionLayer.setreg_override')
|
||||
]
|
||||
base_dir = "AutomatedTesting/Registry"
|
||||
|
||||
class C111111_RigidBody_EnablingGravityWorksUsingNotificationsPoC(EditorSharedTest):
|
||||
from . import C111111_RigidBody_EnablingGravityWorksUsingNotificationsPoC as test_module
|
||||
|
||||
class C5932041_PhysXForceRegion_LocalSpaceForceOnRigidBodies(EditorSharedTest):
|
||||
from . import C5932041_PhysXForceRegion_LocalSpaceForceOnRigidBodies as test_module
|
||||
|
||||
class C15425929_Undo_Redo(EditorSharedTest):
|
||||
from . import C15425929_Undo_Redo as test_module
|
||||
|
||||
class C4976243_Collision_SameCollisionGroupDiffCollisionLayers(EditorSharedTest):
|
||||
from . import C4976243_Collision_SameCollisionGroupDiffCollisionLayers as test_module
|
||||
|
||||
class C14654881_CharacterController_SwitchLevels(EditorSharedTest):
|
||||
from . import C14654881_CharacterController_SwitchLevels as test_module
|
||||
|
||||
class C17411467_AddPhysxRagdollComponent(EditorSharedTest):
|
||||
from . import C17411467_AddPhysxRagdollComponent as test_module
|
||||
|
||||
class C12712453_ScriptCanvas_MultipleRaycastNode(EditorSharedTest):
|
||||
from . import C12712453_ScriptCanvas_MultipleRaycastNode as test_module
|
||||
|
||||
class C18243586_Joints_HingeLeadFollowerCollide(EditorSharedTest):
|
||||
from . import C18243586_Joints_HingeLeadFollowerCollide as test_module
|
||||
|
||||
class C4982803_Enable_PxMesh_Option(EditorSharedTest):
|
||||
from . import C4982803_Enable_PxMesh_Option as test_module
|
||||
|
||||
class C24308873_CylinderShapeCollider_CollidesWithPhysXTerrain(EditorSharedTest):
|
||||
from . import C24308873_CylinderShapeCollider_CollidesWithPhysXTerrain as test_module
|
||||
|
||||
@@ -46,19 +46,20 @@ def PrefabLevel_OpensLevelWithEntities():
|
||||
if entityIds[0].IsValid():
|
||||
return entityIds[0]
|
||||
return None
|
||||
#Checks for an entity called "EmptyEntity"
|
||||
|
||||
# Checks for an entity called "EmptyEntity"
|
||||
helper.wait_for_condition(lambda: find_entity("EmptyEntity").IsValid(), 5.0)
|
||||
empty_entity_id = find_entity("EmptyEntity")
|
||||
Report.result(Tests.find_empty_entity, empty_entity_id.IsValid())
|
||||
|
||||
# Checks if the EmptyEntity is in the correct position and if it fails, it will provide the expected postion and the actual postion of the entity in the Editor log
|
||||
# Checks if the EmptyEntity is in the correct position and if it fails, it will provide the expected postion and the actual postion of the entity in the Editor log
|
||||
empty_entity_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", empty_entity_id)
|
||||
is_at_position = empty_entity_pos.IsClose(EXPECTED_EMPTY_ENTITY_POS)
|
||||
Report.result(Tests.empty_entity_pos, is_at_position)
|
||||
if not is_at_position:
|
||||
Report.info(f'Expected position: {EXPECTED_EMPTY_ENTITY_POS.ToString()}, actual position: {empty_entity_pos.ToString()}')
|
||||
|
||||
#Checks for an entity called "EntityWithPxCollider" and if it has the PhysX Collider component
|
||||
# Checks for an entity called "EntityWithPxCollider" and if it has the PhysX Collider component
|
||||
pxentity = find_entity("EntityWithPxCollider")
|
||||
Report.result(Tests.find_pxentity, pxentity.IsValid())
|
||||
|
||||
@@ -69,4 +70,4 @@ def PrefabLevel_OpensLevelWithEntities():
|
||||
if __name__ == "__main__":
|
||||
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test (PrefabLevel_OpensLevelWithEntities)
|
||||
Report.start_test(PrefabLevel_OpensLevelWithEntities)
|
||||
|
||||
Reference in New Issue
Block a user