Merge branch 'development' into Atom/jromnoa/assert-for-screenshot-comparisons

This commit is contained in:
jromnoa
2021-11-01 21:10:16 -07:00
318 changed files with 6249 additions and 1783 deletions
@@ -37,6 +37,10 @@ class TestAutomation(EditorTestSuite):
@pytest.mark.test_case_id("C32078115")
class AtomEditorComponents_GlobalSkylightIBLAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_GlobalSkylightIBLAdded as test_module
@pytest.mark.test_case_id("C32078122")
class AtomEditorComponents_GridAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_GridAdded as test_module
@pytest.mark.test_case_id("C32078117")
class AtomEditorComponents_LightAdded(EditorSharedTest):
@@ -0,0 +1,158 @@
"""
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
"""
class Tests:
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
grid_entity_creation = (
"Grid Entity successfully created",
"Grid Entity failed to be created")
grid_component_added = (
"Entity has a Grid component",
"Entity failed to find Grid component")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
def AtomEditorComponents_Grid_AddedToEntity():
"""
Summary:
Tests the Grid component can be added to an entity and has the expected functionality.
Test setup:
- Wait for Editor idle loop.
- Open the "Base" level.
Expected Behavior:
The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Create a Grid entity with no components.
2) Add a Grid component to Grid entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Enter/Exit game mode.
6) Test IsHidden.
7) Test IsVisible.
8) Delete Grid entity.
9) UNDO deletion.
10) REDO deletion.
11) Look for errors.
:return: None
"""
import os
import azlmbr.legacy.general as general
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper
from Atom.atom_utils.atom_constants import AtomComponentProperties
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
TestHelper.init_idle()
TestHelper.open_level("", "Base")
# Test steps begin.
# 1. Create a Grid entity with no components.
grid_entity = EditorEntity.create_editor_entity(AtomComponentProperties.grid())
Report.critical_result(Tests.grid_entity_creation, grid_entity.exists())
# 2. Add a Grid component to Grid entity.
grid_component = grid_entity.add_component(AtomComponentProperties.grid())
Report.critical_result(
Tests.grid_component_added,
grid_entity.has_component(AtomComponentProperties.grid()))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
general.undo()
# -> UNDO naming entity.
general.undo()
# -> UNDO selecting entity.
general.undo()
# -> UNDO entity creation.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo, not grid_entity.exists())
# 4. REDO the entity creation and component addition.
# -> REDO entity creation.
general.redo()
# -> REDO selecting entity.
general.redo()
# -> REDO naming entity.
general.redo()
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, grid_entity.exists())
# 5. Enter/Exit game mode.
TestHelper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
TestHelper.exit_game_mode(Tests.exit_game_mode)
# 6. Test IsHidden.
grid_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, grid_entity.is_hidden() is True)
# 7. Test IsVisible.
grid_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, grid_entity.is_visible() is True)
# 8. Delete Grid entity.
grid_entity.delete()
Report.result(Tests.entity_deleted, not grid_entity.exists())
# 9. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, grid_entity.exists())
# 10. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not grid_entity.exists())
# 11. Look for errors or asserts.
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
for error_info in error_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
for assert_info in error_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_Grid_AddedToEntity)
@@ -70,8 +70,6 @@ def AtomEditorComponents_postfx_layer_AddedToEntity():
:return: None
"""
import os
import azlmbr.legacy.general as general
from editor_python_test_tools.editor_entity_utils import EditorEntity
@@ -107,6 +107,19 @@ class EditorComponent:
return type_ids
def convert_to_azvector3(xyz) -> azlmbr.math.Vector3:
"""
Converts a vector3-like element into a azlmbr.math.Vector3
"""
if isinstance(xyz, Tuple) or isinstance(xyz, List):
assert len(xyz) == 3, ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3")
return math.Vector3(float(xyz[0]), float(xyz[1]), float(xyz[2]))
elif isinstance(xyz, type(math.Vector3())):
return xyz
else:
raise ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3")
class EditorEntity:
"""
Entity class is used to create and interact with Editor Entities.
@@ -183,15 +196,6 @@ class EditorEntity:
:return: EditorEntity class object
"""
def convert_to_azvector3(xyz) -> math.Vector3:
if isinstance(xyz, Tuple) or isinstance(xyz, List):
assert len(xyz) == 3, ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3")
return math.Vector3(*xyz)
elif isinstance(xyz, type(math.Vector3())):
return xyz
else:
raise ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3")
if parent_id is None:
parent_id = azlmbr.entity.EntityId()
@@ -206,7 +210,7 @@ class EditorEntity:
return entity
# Methods
def set_name(self, entity_name: str):
def set_name(self, entity_name: str) -> None:
"""
Given entity_name, sets name to Entity
:param: entity_name: Name of the entity to set
@@ -324,7 +328,7 @@ class EditorEntity:
self.start_status = status
return status
def set_start_status(self, desired_start_status: str):
def set_start_status(self, desired_start_status: str) -> None:
"""
Set an entity as active/inactive at beginning of runtime or it is editor-only,
given its entity id and the start status then return set success
@@ -382,18 +386,75 @@ class EditorEntity:
"""
return editor.EditorEntityInfoRequestBus(bus.Event, "IsVisible", self.id)
# World Transform Functions
def get_world_translation(self) -> azlmbr.math.Vector3:
"""
Gets the world translation of the entity
"""
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
def set_world_translation(self, new_translation) -> None:
"""
Sets the new world translation of the current entity
"""
new_translation = convert_to_azvector3(new_translation)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetWorldTranslation", self.id, new_translation)
def get_world_rotation(self) -> azlmbr.math.Quaternion:
"""
Gets the world rotation of the entity
"""
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldRotation", self.id)
def set_world_rotation(self, new_rotation):
"""
Sets the new world rotation of the current entity
"""
new_rotation = convert_to_azvector3(new_rotation)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetWorldRotation", self.id, new_rotation)
# Local Transform Functions
def get_local_uniform_scale(self) -> float:
"""
Gets the local uniform scale of the entity
"""
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetLocalUniformScale", self.id)
def set_local_uniform_scale(self, scale_float) -> None:
"""
Sets the "SetLocalUniformScale" value on the entity.
Sets the local uniform scale value(relative to the parent) on the entity.
:param scale_float: value for "SetLocalUniformScale" to set to.
:return: None
"""
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalUniformScale", self.id, scale_float)
def set_local_rotation(self, vector3_rotation) -> None:
def get_local_rotation(self) -> azlmbr.math.Quaternion:
"""
Sets the "SetLocalRotation" value on the entity.
Gets the local rotation of the entity
"""
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetLocalRotation", self.id)
def set_local_rotation(self, new_rotation) -> None:
"""
Sets the set the local rotation(relative to the parent) of the current entity.
:param vector3_rotation: The math.Vector3 value to use for rotation on the entity (uses radians).
:return: None
"""
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", self.id, vector3_rotation)
new_rotation = convert_to_azvector3(new_rotation)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", self.id, new_rotation)
def get_local_translation(self) -> azlmbr.math.Vector3:
"""
Gets the local translation of the current entity.
:return: The math.Vector3 value of the local translation.
"""
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetLocalTranslation", self.id)
def set_local_translation(self, new_translation) -> None:
"""
Sets the local translation(relative to the parent) of the current entity.
:param vector3_translation: The math.Vector3 value to use for translation on the entity.
:return: None
"""
new_translation = convert_to_azvector3(new_translation)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalTranslation", self.id, new_translation)
@@ -138,6 +138,14 @@ class PrefabInstance:
self.container_entity = reparented_container_entity
current_instance_prefab.instances.add(self)
def get_direct_child_entities(self):
"""
Returns the entities only contained in the current prefab instance.
This function does not return entities contained in other child instances
"""
return self.container_entity.get_children()
# This is a helper class which contains some of the useful information about a prefab template.
class Prefab:
@@ -55,3 +55,11 @@ class TestAutomation(TestAutomationBase):
def test_PrefabBasicWorkflow_CreateAndDuplicatePrefab(self, request, workspace, editor, launcher_platform):
from .tests import PrefabBasicWorkflow_CreateAndDuplicatePrefab as test_module
self._run_prefab_test(request, workspace, editor, test_module)
def test_PrefabComplexWorflow_CreatePrefabOfChildEntity(self, request, workspace, editor, launcher_platform):
from .tests import PrefabComplexWorflow_CreatePrefabOfChildEntity as test_module
self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False)
def test_PrefabComplexWorflow_CreatePrefabInsidePrefab(self, request, workspace, editor, launcher_platform):
from .tests import PrefabComplexWorflow_CreatePrefabInsidePrefab as test_module
self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False)
@@ -0,0 +1,57 @@
"""
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 PrefabComplexWorflow_CreatePrefabInsidePrefab():
"""
Test description:
- Creates an entity with a physx collider
- Creates a prefab "Outer_prefab" and an instance based of that entity
- Creates a prefab "Inner_prefab" inside "Outer_prefab" based the entity contained inside of it
Checks that the entity is correctly handlded by the prefab system checking the name and that it contains the physx collider
"""
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.prefab_utils import Prefab
import PrefabTestUtils as prefab_test_utils
prefab_test_utils.open_base_tests_level()
# Creates a new Entity at the root level
# Asserts if creation didn't succeed
entity = EditorEntity.create_editor_entity_at((100.0, 100.0, 100.0), name = "TestEntity")
assert entity.id.IsValid(), "Couldn't create entity"
entity.add_component("PhysX Collider")
assert entity.has_component("PhysX Collider"), "Attempted to add a PhysX Collider but no physx collider collider was found afterwards"
# Create a prefab based on that entity
outer_prefab, outer_instance = Prefab.create_prefab([entity], "Outer_prefab")
# The test should be now inside the outer prefab instance.
entity = outer_instance.get_direct_child_entities()[0]
# We track if that is the same entity by checking the name and if it still contains the component that we created before
assert entity.get_name() == "TestEntity", f"Entity name inside outer_prefab doesn't match the original name, original:'TestEntity' current:'{entity.get_name()}'"
assert entity.has_component("PhysX Collider"), "Entity name inside outer_prefab doesn't have the collider component it should"
# Now, create another prefab, based on the entity that is inside outer_prefab
inner_prefab, inner_instance = Prefab.create_prefab([entity], "Inner_prefab")
# The test entity should now be inside the inner prefab instance
entity = inner_instance.get_direct_child_entities()[0]
# We track if that is the same entity by checking the name and if it still contains the component that we created before
assert entity.get_name() == "TestEntity", f"Entity name inside inner_prefab doesn't match the original name, original:'TestEntity' current:'{entity.get_name()}'"
assert entity.has_component("PhysX Collider"), "Entity name inside inner_prefab doesn't have the collider component it should"
# Verify hierarchy of entities:
# Outer_prefab
# |- Inner_prefab
# | |- TestEntity
assert entity.get_parent_id() == inner_instance.container_entity.id
assert inner_instance.container_entity.get_parent_id() == outer_instance.container_entity.id
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(PrefabComplexWorflow_CreatePrefabInsidePrefab)
@@ -0,0 +1,52 @@
"""
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 PrefabComplexWorflow_CreatePrefabOfChildEntity():
"""
Test description:
- Creates two entities, parent and child. Child entity has Parent entity as its parent.
- Creates a prefab of the child entity.
Test is successful if the new instanced prefab of the child has the parent entity id
"""
CAR_PREFAB_FILE_NAME = 'car_prefab'
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.prefab_utils import Prefab
import PrefabTestUtils as prefab_test_utils
prefab_test_utils.open_base_tests_level()
# Creates a new Entity at the root level
# Asserts if creation didn't succeed
parent_entity = EditorEntity.create_editor_entity_at((100.0, 100.0, 100.0))
assert parent_entity.id.IsValid(), "Couldn't create parent entity"
child_entity = EditorEntity.create_editor_entity(parent_id=parent_entity.id)
assert child_entity.id.IsValid(), "Couldn't create child entity"
assert child_entity.get_world_translation().IsClose(parent_entity.get_world_translation()), f"Child entity position{child_entity.get_world_translation().ToString()}" \
f" is not located at the same position as the parent{parent_entity.get_world_translation().ToString()}"
# Asserts if prefab creation doesn't succeed
child_prefab, child_instance = Prefab.create_prefab([child_entity], CAR_PREFAB_FILE_NAME)
child_entity_on_child_instance = child_instance.get_direct_child_entities()[0]
assert child_instance.container_entity.get_parent_id().IsValid(), "Newly instanced entity has no parent"
assert child_instance.container_entity.get_parent_id() == parent_entity.id, "Newly instanced entity parent does not match the expected parent"
assert child_instance.container_entity.get_world_translation().IsClose(parent_entity.get_world_translation()), "Newly instanced entity position is not located at the same position as the parent"
# Move the parent position, it should update the child position
parent_entity.set_world_translation((200.0, 200.0, 200.0))
child_instance_translation = child_instance.container_entity.get_world_translation()
assert child_instance_translation.IsClose(azlmbr.math.Vector3(200.0, 200.0, 200.0)), f"Instance position position{child_instance_translation.ToString()} didn't get updated" \
f" to the same position as the parent{parent_entity.get_world_translation().ToString()}"
child_translation = child_entity_on_child_instance.get_world_translation()
assert child_translation.IsClose(azlmbr.math.Vector3(200.0, 200.0, 200.0)), f"Entity position{child_translation.ToString()} of the instance didn't get updated" \
f" to the same position as the parent{parent_entity.get_world_translation().ToString()}"
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(PrefabComplexWorflow_CreatePrefabOfChildEntity)
@@ -23,7 +23,7 @@ def create_jobs(request):
jobDescriptorList = []
for platformInfo in request.enabledPlatforms:
jobDesc = azlmbr.asset.builder.JobDescriptor()
jobDesc.jobKey = jobKeyName
jobDesc.jobKey = f'{jobKeyName}-{platformInfo.identifier}'
jobDesc.set_platform_identifier(platformInfo.identifier)
jobDescriptorList.append(jobDesc)
@@ -38,7 +38,7 @@ def on_create_jobs(args):
return create_jobs(request)
except:
log_exception_traceback()
# returing back a default CreateJobsResponse() records an asset error
# returning back a default CreateJobsResponse() records an asset error
return azlmbr.asset.builder.CreateJobsResponse()
def process_file(request):
@@ -58,6 +58,7 @@ def process_file(request):
fileOutput = open(tempFilename, "w")
fileOutput.write('{}')
fileOutput.close()
print(f'Wrote mock asset file: {tempFilename}')
# generate a product asset file entry
subId = binascii.crc32(mockFilename.encode())
@@ -29,6 +29,9 @@ def ap_fast_scan_setting_backup_fixture(request, workspace) -> PlatformSetting:
if workspace.asset_processor_platform == 'mac':
pytest.skip("Mac plist file editing not implemented yet")
if workspace.asset_processor_platform == 'linux':
pytest.skip("Linux system settings not implemented yet")
key = fast_scan_key
subkey = fast_scan_subkey
@@ -79,7 +79,7 @@ class TestsAssetProcessorBatch_DependenycyTests(object):
env = ap_setup_fixture
BATCH_LOG_PATH = env["ap_batch_log_file"]
asset_processor.create_temp_asset_root()
asset_processor.add_relative_source_asset(os.path.join("Assets", "Engine", "engine_dependencies.xml"))
asset_processor.add_relative_source_asset(os.path.join("Assets", "Engine", "Engine_Dependencies.xml"))
asset_processor.add_scan_folder(os.path.join("Assets", "Engine"))
asset_processor.add_relative_source_asset(os.path.join("Assets", "Engine", "Libs", "MaterialEffects", "surfacetypes.xml"))
@@ -15,6 +15,7 @@ import sys
import importlib
import re
import ly_test_tools
from ly_test_tools import LAUNCHERS
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
@@ -25,8 +26,15 @@ import ly_test_tools.environment.process_utils as process_utils
import argparse, sys
@pytest.mark.SUITE_main
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
def get_editor_launcher_platform():
if ly_test_tools.WINDOWS:
return "windows_editor"
elif ly_test_tools.LINUX:
return "linux_editor"
else:
return None
@pytest.mark.parametrize("launcher_platform", [get_editor_launcher_platform()])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestEditorTest:
@@ -69,7 +77,7 @@ class TestEditorTest:
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("launcher_platform", [{get_editor_launcher_platform()}])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomation(EditorTestSuite):
class test_single(EditorSingleTest):
@@ -123,7 +131,7 @@ class TestEditorTest:
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("launcher_platform", [{get_editor_launcher_platform()}])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomation(EditorTestSuite):
{module_class_code}
@@ -13,18 +13,26 @@ import os
import pytest
import subprocess
import ly_test_tools
@pytest.mark.SUITE_smoke
class TestCLIToolAzTestRunnerWorks(object):
def test_CLITool_AzTestRunner_Works(self, build_directory):
def test_CLITool_AzTestRunner_ListSelfTests(self, build_directory):
file_path = os.path.join(build_directory, "AzTestRunner")
help_message = "OKAY Symbol found: AzRunUnitTests"
# Launch AzTestRunner
if ly_test_tools.WINDOWS:
target_lib = "AzTestRunner.Tests"
else:
target_lib = "libAzTestRunner.Tests"
# Launch AzTestRunner, load self-tests, print test names
output = subprocess.run(
[file_path, "AzTestRunner.Tests", "AzRunUnitTests", "--gtest_list_tests"], capture_output=True, timeout=10
[file_path, target_lib, "AzRunUnitTests", "--gtest_list_tests"], capture_output=True, timeout=10
)
assert (
len(output.stderr) == 0 and output.returncode == 0
), f"Error occurred while launching {file_path}: {output.stderr}"
# Verify help message
assert help_message in str(output.stdout), f"Help Message: {help_message} is not present"
assert help_message in str(output.stdout), f"Help Message: '{help_message}' unexpectedly not present"