(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:
AMZN-AlexOteiza
2021-07-22 12:57:23 +02:00
committed by GitHub
parent 231f09d899
commit b815c203da
32 changed files with 1653 additions and 111 deletions
@@ -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"]
@@ -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()
@@ -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)
@@ -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)