Integrating latest 47acbe8

This commit is contained in:
alexpete
2021-03-25 13:57:57 -07:00
parent 448c549698
commit 75dc720198
10312 changed files with 2711566 additions and 671451 deletions
@@ -0,0 +1,60 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# Test case ID : C28798177
# Test Case Title : White Box Tool Component can be added to an Entity
# URL of the test case : https://testrail.agscollab.com/index.php?/cases/view/28798177
# fmt:off
class Tests():
white_box_entity_created = ("White box entity created", "Failed to create white box entity")
white_box_component_enabled = ("White box component enabled", "Failed to enable white box component")
# fmt:on
def run():
import os
import sys
import WhiteBoxInit as init
import ImportPathHelper as imports
imports.init()
import azlmbr.bus as bus
import azlmbr.editor as editor
import azlmbr.legacy.general as general
from utils import Report
from utils import TestHelper as helper
# open level
helper.init_idle()
general.open_level("EmptyLevel")
# create white box entity and attach component
white_box_entity = init.create_white_box_entity()
white_box_mesh_component = init.create_white_box_component(white_box_entity)
init.create_white_box_handle(white_box_mesh_component)
# verify results
entity_id = general.find_editor_entity('WhiteBox')
Report.result(Tests.white_box_entity_created, entity_id.IsValid())
component_enabled = editor.EditorComponentAPIBus(bus.Broadcast, 'IsComponentEnabled', white_box_mesh_component)
Report.result(Tests.white_box_component_enabled, component_enabled)
# close editor
helper.close_editor()
if __name__ == "__main__":
run()
@@ -0,0 +1,70 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# Test case ID : C28798205
# Test Case Title : From the White Box Component Card the White Box Mesh can be set to be invisible in Game View
# URL of the test case : https://testrail.agscollab.com/index.php?/cases/view/28798205
# fmt:off
class Tests():
white_box_initially_visible = ("White box initially invisible", "White box not initially invisible")
white_box_set_to_invisible = ("White box set to invisible", "Failed to set white box to invisible")
# fmt:on
def run():
# note: This automated test does not fully replicate the test case in Test Rail as it's
# not currently possible using the Hydra API to get an EntityComponentIdPair at runtime,
# in future game_mode will be activated and a runtime White Box Component queried
import os
import sys
import WhiteBoxInit as init
import ImportPathHelper as imports
import Tests.ly_shared.hydra_editor_utils as hydra
imports.init()
import azlmbr.whitebox.api as api
import azlmbr.whitebox
import azlmbr.bus as bus
import azlmbr.editor as editor
import azlmbr.entity as entity
import azlmbr.legacy.general as general
from utils import Report
from utils import TestHelper as helper
# open level
helper.init_idle()
general.open_level("EmptyLevel")
white_box_entity_name = 'WhiteBox-Visibility'
white_box_visibility_path = 'White Box Material|Visible'
# create white box entity and attach component
white_box_entity = init.create_white_box_entity(white_box_entity_name)
white_box_mesh_component = init.create_white_box_component(white_box_entity)
# verify preconditions
visible_property = hydra.get_component_property_value(white_box_mesh_component, white_box_visibility_path)
Report.result(Tests.white_box_initially_visible, visible_property)
# verify setting visibility
editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", white_box_mesh_component, white_box_visibility_path, False)
visible_property = hydra.get_component_property_value(white_box_mesh_component, white_box_visibility_path)
Report.result(Tests.white_box_set_to_invisible, not visible_property)
helper.close_editor()
if __name__ == "__main__":
run()
@@ -0,0 +1,109 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# Test case ID : C29279329
# Test Case Title : White Box mesh shape can be changed with the Default Shape dropdown on the Component
# URL of the test case : https://testrail.agscollab.com/index.php?/cases/view/29279329
# fmt:off
class Tests():
default_shape_cube = ("Default shape set to cube", "Default shape is not set to cube")
default_shape_tetrahedron = ("Default shape changed to tetrahedron", "Failed to change default shape to tetrahedron")
default_shape_icosahedron = ("Default shape changed to icosahedron", "Failed to change default shape to icosahedron")
default_shape_cylinder = ("Default shape changed to cylinder", "Failed to change default shape to cylinder")
default_shape_sphere = ("Default shape changed to sphere", "Failed to change default shape to sphere")
# fmt:on
critical_shape_check = ("Default shape has more than 0 sides", "default shape has 0 sides")
def run():
import os
import sys
import WhiteBoxInit as init
import ImportPathHelper as imports
imports.init()
import azlmbr.whitebox.api as api
import azlmbr.bus as bus
import azlmbr.legacy.general as general
from utils import Report
from utils import TestHelper as helper
def check_shape_result(success_fail_tuple, condition):
result = Report.result(success_fail_tuple, condition)
if not result:
face_count = white_box_handle.MeshFaceCount()
Report.critical_result(critical_shape_check, face_count > 0)
Report.info(f"Face count = {face_count}")
shape_types = azlmbr.globals.property
# expected results
expected_results = {
shape_types.CUBE: 12,
shape_types.TETRAHEDRON: 4,
shape_types.ICOSAHEDRON: 20,
shape_types.CYLINDER: 64,
shape_types.SPHERE: 320
}
# open level
helper.init_idle()
general.open_level("EmptyLevel")
# create white box entity and attach component
white_box_entity = init.create_white_box_entity()
white_box_mesh_component = init.create_white_box_component(white_box_entity)
white_box_handle = init.create_white_box_handle(white_box_mesh_component)
# verify results
# cube
check_shape_result(
Tests.default_shape_cube,
white_box_handle.MeshFaceCount() == expected_results.get(shape_types.CUBE))
# tetrahedron
azlmbr.whitebox.request.bus.EditorWhiteBoxComponentRequestBus(
bus.Event, 'SetDefaultShape', white_box_mesh_component, shape_types.TETRAHEDRON)
check_shape_result(
Tests.default_shape_tetrahedron,
white_box_handle.MeshFaceCount() == expected_results.get(shape_types.TETRAHEDRON))
# icosahedron
azlmbr.whitebox.request.bus.EditorWhiteBoxComponentRequestBus(
bus.Event, 'SetDefaultShape', white_box_mesh_component, shape_types.ICOSAHEDRON)
check_shape_result(
Tests.default_shape_icosahedron,
white_box_handle.MeshFaceCount() == expected_results.get(shape_types.ICOSAHEDRON))
# cylinder
azlmbr.whitebox.request.bus.EditorWhiteBoxComponentRequestBus(
bus.Event, 'SetDefaultShape', white_box_mesh_component, shape_types.CYLINDER)
check_shape_result(
Tests.default_shape_cylinder,
white_box_handle.MeshFaceCount() == expected_results.get(shape_types.CYLINDER))
# sphere
azlmbr.whitebox.request.bus.EditorWhiteBoxComponentRequestBus(
bus.Event, 'SetDefaultShape', white_box_mesh_component, shape_types.SPHERE)
check_shape_result(
Tests.default_shape_sphere,
white_box_handle.MeshFaceCount() == expected_results.get(shape_types.SPHERE))
# close editor
helper.close_editor()
if __name__ == "__main__":
run()
@@ -0,0 +1,354 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# Imports
import os
import shutil
import json
import logging
import ly_test_tools.environment.file_system as fs
class FileManagement:
"""
File Management static class: Handles file backup and restoration.
Organizes backing up files into a single directory and
mapping the back up files to their original files.
Features accessible via the exposed decorator functions.
"""
# Static variables
MAX_BACKUPS = 10000 # Arbitrary number to cap same-file-name name generating
backup_folder_name = ".backup"
backup_folder_path = os.path.join(os.path.split(__file__)[0], backup_folder_name) # CWD plus backup folder name
file_map_name = "_filebackup_map.json" # JSON file to store original to back up file mappings
@staticmethod
def _load_file_map():
# type: () -> {str: str}
"""
Loads the FileManagement's json file map.
The returned dictionary has key: value sets such that:
keys: full path to an original file currently being backed up
values: name of the original file's backup file. Uses a numbering system for
storing multiple file names that are identical but located in different directories.
If there is no current json file or the file cannot be parsed, an empty dictionary is returned.
:return: Dictionary to map original file's to their back up file names.
"""
json_file_path = os.path.join(FileManagement.backup_folder_path, FileManagement.file_map_name)
if os.path.exists(json_file_path):
try:
with open(json_file_path, "r") as f:
return json.load(f)
except ValueError:
logging.info("Decoding JSON file {} failed. Empty dictionary used".format(json_file_path))
return {}
@staticmethod
def _save_file_map(file_map):
# type: ({str: str}) -> None
"""
Saves the [file_map] dictionary as a json file.
If [file_map] is empty, deletes the json file.
:param file_map: A dictionary mapping original file paths to their back up file names.
:return: None
"""
file_path = os.path.join(FileManagement.backup_folder_path, FileManagement.file_map_name)
if os.path.exists(file_path):
fs.unlock_file(file_path)
if len(file_map) > 0:
with open(file_path, "w") as f:
json.dump(file_map, f)
fs.lock_file(file_path)
else:
fs.delete([file_path], True, False)
@staticmethod
def _next_available_name(file_name, file_map):
# type: (str, {str: str}) -> str or None
"""
Generates the next available backup file name using a FILE_NAME_x.ext naming convention
where x is a number. Returns None if we've maxed out the numbering system.
:param file_name: The base file to generate a back up file name for.
:param file_map: The file_map for the FileManagement system
:return: str
"""
suffix, extension = file_name.split(".", 1)
for i in range(FileManagement.MAX_BACKUPS):
potential_name = suffix + "_" + str(i) + "." + extension
if potential_name not in file_map.values():
return potential_name
return None
@staticmethod
def _backup_file(file_name, file_path):
# type: (str, str) -> None
"""
Backs up the [file_name] located at [file_path] into the FileManagement's backup folder.
Leaves the backup file locked from writing privileges.
:param file_name: The file's name that will be backed up
:param file_path: The path to the file to back up
:return: None
"""
file_map = FileManagement._load_file_map()
backup_path = FileManagement.backup_folder_path
backup_file_name = "{}.bak".format(file_name)
backup_file = os.path.join(backup_path, backup_file_name)
# If backup directory DNE, make one
if not os.path.exists(backup_path):
os.mkdir(backup_path)
# If "traditional" backup file exists, delete it (myFile.txt.bak)
if os.path.exists(backup_file):
fs.delete([backup_file], True, False)
# Find my next storage name (myFile_1.txt.bak)
backup_storage_file_name = FileManagement._next_available_name(backup_file_name, file_map)
if backup_storage_file_name is None:
# If _next_available_name returns None, we have backed up MAX_BACKUPS of files name [file_name]
raise Exception(
"FileManagement class ran out of backups per name. Max: {}".format(FileManagement.MAX_BACKUPS)
)
backup_storage_file = os.path.join(backup_path, backup_storage_file_name)
if os.path.exists(backup_storage_file):
# This file should not exists, but if it does it's about to get clobbered!
fs.unlock_file(backup_storage_file)
# Create "traditional" backup file (myFile.txt.bak)
fs.create_backup(os.path.join(file_path, file_name), backup_path)
# Copy "traditional" backup file into storage backup (myFile_1.txt.bak)
FileManagement._copy_file(backup_file_name, backup_path, backup_storage_file_name, backup_path)
fs.lock_file(backup_storage_file)
# Delete "traditional" back up file
fs.unlock_file(backup_file)
fs.delete([backup_file], True, False)
# Update file map with new file
file_map[os.path.join(file_path, file_name)] = backup_storage_file_name
FileManagement._save_file_map(file_map)
# Unlock original file to get it ready to be edited by the test
fs.unlock_file(os.path.join(file_path, file_name))
@staticmethod
def _restore_file(file_name, file_path):
# type: (str, str) -> None
"""
Restores the [file_name] located at [file_path] which is backed up in FileManagement's backup folder.
Locks [file_name] from writing privileges when done restoring.
:param file_name: The Original file that was backed up, and now restored
:param file_path: The location of the original file that was backed up
:return: None
"""
file_map = FileManagement._load_file_map()
backup_path = FileManagement.backup_folder_path
src_file = os.path.join(file_path, file_name)
if src_file in file_map:
backup_file = os.path.join(backup_path, file_map[src_file])
if os.path.exists(backup_file):
fs.unlock_file(backup_file)
fs.unlock_file(src_file)
# Make temporary copy of backed up file to restore from
temp_file = "{}.bak".format(file_name)
FileManagement._copy_file(file_map[src_file], backup_path, temp_file, backup_path)
fs.restore_backup(src_file, backup_path)
fs.lock_file(src_file)
# Delete backup file
fs.delete([os.path.join(backup_path, temp_file)], True, False)
fs.delete([backup_file], True, False)
# Remove from file map
del file_map[src_file]
FileManagement._save_file_map(file_map)
if not os.listdir(backup_path):
# Delete backup folder if it's empty now
os.rmdir(backup_path)
@staticmethod
def _find_files(file_names, root_dir, search_subdirs=False):
# type: ([str], str, bool) -> {str:str}
"""
Searches the [root_dir] directory tree for each of the file names in [file_names]. Returns a dictionary
where keys = the entries in file_names, and their values are the paths they were found at.
Raises a runtime warning if not exactly one copy of each file is found.
:param file_names: A list of file_names to look for
:param root_dir: The root directory to begin the search
collect all file paths matching that file_name
:param search_subdirs: Optional boolean flag for searching sub-directories of [parent_path]
:return: A dictionary where keys are file names, and values are the file's path.
"""
file_list = {}
found_count = 0
for file_name in file_names:
file_list[file_name] = None
if search_subdirs:
# Search parent path for all file name arguments
for dir_path, _, dir_files in os.walk(root_dir):
for dir_file in dir_files:
if dir_file in file_list.keys():
if file_list[dir_file] is None:
file_list[dir_file] = dir_path
found_count += 1
else:
# Found multiple files with same name. Raise warning.
raise RuntimeWarning("Found multiple files with the name {}.".format(dir_file))
else:
for dir_file in os.listdir(root_dir):
if os.path.isfile(os.path.join(root_dir, dir_file)) and dir_file in file_names:
file_list[dir_file] = root_dir
not_found = [file_name for file_name in file_names if file_list[file_name] is None]
if not_found:
raise RuntimeWarning("Could not find the following files: {}".format(not_found))
return file_list
@staticmethod
def _copy_file(src_file, src_path, target_file, target_path):
# type: (str, str, str, str) -> None
"""
Copies the [src_file] at located in [src_path] to the [target_file] located at [target_path].
Leaves the [target_file] unlocked for reading and writing privileges
:param src_file: The source file to copy (file name)
:param src_path: The source file's path
:param target_file: The target file to copy into (file name)
:param target_path: The target file's path
:return: None
"""
target_file_path = os.path.join(target_path, target_file)
src_file_path = os.path.join(src_path, src_file)
if os.path.exists(target_file_path):
fs.unlock_file(target_file_path)
shutil.copyfile(src_file_path, target_file_path)
@staticmethod
def file_revert(file_name, parent_path=None, search_subdirs=False):
# type: (str, str, bool) -> function
"""
Function decorator:
Convenience version of file_revert_list for passing a single file
Calls file_revert_list on a list one [file_name]
:param file_name: The name of a file to backup (not path)
:param parent_path: The root directory to search for the [file_names] (relative to the dev folder).
Defaults to the project folder described by the inner function's workspace fixture
:param search_subdirs: A boolean option for searching sub-directories for the files in [file_names]
:return: function as per the function decorator pattern
"""
return FileManagement.file_revert_list([file_name], parent_path, search_subdirs)
@staticmethod
def file_revert_list(file_names, parent_path=None, search_subdirs=False):
# type: ([str], str, bool) -> function
"""
Function decorator:
Collects file names specified in [file_names] in the [parent_path] directory.
Backs up these files before executing the parameter to the "wrap" function.
If the search_subdirs flag is True, searches all subdirectories of [parent_path] for files.
:param file_names: A list of file names (not paths)
:param parent_path: The root directory to search for the [file_names] (relative to the dev folder).
Defaults to the project folder described by the inner function's workspace fixture
:param search_subdirs: A boolean option for searching sub-directories for the files in [file_names]
:return: function as per the function decorator pattern
"""
def wrap(func):
# type: (function) -> function
def inner(self, request, workspace, editor, launcher_platform):
# type: (...) -> None
"""
Inner function to wrap around decorated function. Function being decorated must match this
functions parameter order.
"""
root_path = parent_path
if root_path is not None:
root_path = os.path.join(workspace.paths.dev(), root_path)
else:
# Default to project folder (AutomatedTesting)
root_path = workspace.paths.project()
# Try to locate files
try:
file_list = FileManagement._find_files(file_names, root_path, 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."
)
# Restore from backups
for file_name, file_path in file_list.items():
FileManagement._restore_file(file_name, file_path)
# Create backups for each discovered path for each specified filename
for file_name, file_path in file_list.items():
FileManagement._backup_file(file_name, file_path)
try:
# Run the test
func(self, request, workspace, editor, launcher_platform)
finally:
# Restore backups for each discovered path for each specified filename if they exist
for file_name, file_path in file_list.items():
FileManagement._restore_file(file_name, file_path)
return inner
return wrap
@staticmethod
def file_override(target_file, src_file, parent_path=None, search_subdirs=False):
# type: (str, str, str, bool) -> function
"""
Function decorator:
Searches for [target_file] and [src_file] in [parent_path](relative to the project dev folder)
and performs backup and file swapping. [target_file] is backed up and replaced with [src_file] before the
decorated function executes.
After the decorated function is executed [target_file] is restored to the state before being swapped.
If [parent_path] is not specified, the project folder described by the current workspace will be used.
If the search_subdirs flag is True, searches all subdirectories of [parent_path] for files.
:param target_file: The name of the file being backed up and swapped into
:param src_file: The name of the file to swap into [target_file]
:param parent_path: The root directory to search for the [file_names] (relative to the dev folder).
Defaults to the project folder described by the inner function's workspace fixture
:param search_subdirs: A boolean option for searching sub-directories for the files in [file_names]
:return: The decorated function
"""
def wrap(func):
def inner(self, request, workspace, editor, launcher_platform):
"""
Inner function to wrap around decorated function. Function being decorated must match this
functions parameter order.
"""
root_path = parent_path
if root_path is not None:
root_path = os.path.join(workspace.paths.dev(), root_path)
else:
# Default to project folder (AutomatedTesting)
root_path = workspace.paths.project()
# Try to locate both target and source files
try:
file_list = FileManagement._find_files([target_file, src_file], root_path, 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."
)
FileManagement._restore_file(target_file, file_list[target_file])
FileManagement._backup_file(target_file, file_list[target_file])
FileManagement._copy_file(src_file, file_list[src_file], target_file, file_list[target_file])
try:
# Run Test
func(self, request, workspace, editor, launcher_platform)
finally:
FileManagement._restore_file(target_file, file_list[target_file])
return inner
return wrap
@@ -0,0 +1,15 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
def init():
import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared')
@@ -0,0 +1,40 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# This suite consists of all test cases that are passing and have been verified.
import pytest
import sys
import os
from .FileManagement import FileManagement as fm
from ly_test_tools import LAUNCHERS
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared')
from base import TestAutomationBase
@pytest.mark.SUITE_main
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomation(TestAutomationBase):
def test_C28798177_WhiteBox_AddComponentToEntity(self, request, workspace, editor, launcher_platform):
from . import C28798177_WhiteBox_AddComponentToEntity as test_module
self._run_test(request, workspace, editor, test_module)
def test_C29279329_WhiteBox_SetDefaultShape(self, request, workspace, editor, launcher_platform):
from . import C29279329_WhiteBox_SetDefaultShape as test_module
self._run_test(request, workspace, editor, test_module)
def test_C28798205_WhiteBox_SetInvisible(self, request, workspace, editor, launcher_platform):
from . import C28798205_WhiteBox_SetInvisible as test_module
self._run_test(request, workspace, editor, test_module)
@@ -0,0 +1,10 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""