123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- """
- 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.
- """
- import os
- import time
- import math
- import azlmbr
- import azlmbr.legacy.general as general
- import azlmbr.debug
- import traceback
- class FailFast(Exception):
- """
- Raise to stop proceeding through test steps.
- """
- pass
- class TestHelper:
- @staticmethod
- def init_idle():
- general.idle_enable(True)
- # JIRA: SPEC-2880
- # general.idle_wait_frames(1)
- @staticmethod
- def open_level(directory, level):
- # type: (str, ) -> None
- """
- :param level: the name of the level folder in AutomatedTesting\\Physics\\
- :return: None
- """
- Report.info("Open level {}/{}".format(directory, level))
- success = general.open_level_no_prompt(os.path.join(directory, level))
- if not success:
- open_level_name = general.get_current_level_name()
- if open_level_name == level:
- Report.info("{} was already opened".format(level))
- else:
- assert False, "Failed to open level: {} does not exist or is invalid".format(level)
-
- # FIX-ME: Expose call for checking when has been finished loading and change this frame waiting
- # Jira: LY-113761
- general.idle_wait_frames(200)
- @staticmethod
- def enter_game_mode(msgtuple_success_fail):
- # type: (tuple) -> None
- """
- :param msgtuple_success_fail: The tuple with the expected/unexpected messages for entering game mode.
- :return: None
- """
- Report.info("Entering game mode")
- general.enter_game_mode()
-
- TestHelper.wait_for_condition(lambda : general.is_in_game_mode(), 1.0)
- Report.critical_result(msgtuple_success_fail, general.is_in_game_mode())
- @staticmethod
- def exit_game_mode(msgtuple_success_fail):
- # type: (tuple) -> None
- """
- :param msgtuple_success_fail: The tuple with the expected/unexpected messages for exiting game mode.
- :return: None
- """
- Report.info("Exiting game mode")
- general.exit_game_mode()
- TestHelper.wait_for_condition(lambda : not general.is_in_game_mode(), 1.0)
- Report.critical_result(msgtuple_success_fail, not general.is_in_game_mode())
- @staticmethod
- def close_editor():
- general.exit_no_prompt()
- @staticmethod
- def fail_fast(message=None):
- # type: (str) -> None
- """
- A state has been reached where progressing in the test is not viable.
- raises FailFast
- :return: None
- """
- Report.info("Failing fast. Raising an exception and shutting down the editor.")
- if message:
- Report.info("Fail fast message: {}".format(message))
- TestHelper.close_editor()
- raise FailFast()
- @staticmethod
- def wait_for_condition(function, timeout_in_seconds):
- # type: (function, float) -> bool
- """
- **** Will be replaced by a function of the same name exposed in the Engine*****
- a function to run until it returns True or timeout is reached
- the function can have no parameters and
- waiting idle__wait_* is handled here not in the function
- :param function: a function that returns a boolean indicating a desired condition is achieved
- :param timeout_in_seconds: when reached, function execution is abandoned and False is returned
- """
- with Timeout(timeout_in_seconds) as t:
- while True:
- try:
- general.idle_wait_frames(1)
- except:
- Report.info("WARNING: Couldn't wait for frame")
-
- if t.timed_out:
- return False
- ret = function()
- if not isinstance(ret, bool):
- raise TypeError("return value for wait_for_condition function must be a bool")
- if ret:
- return True
- class Timeout:
- # type: (float) -> None
- """
- contextual timeout
- :param seconds: float seconds to allow before timed_out is True
- """
- def __init__(self, seconds):
- self.seconds = seconds
- def __enter__(self):
- self.die_after = time.time() + self.seconds
- return self
- def __exit__(self, type, value, traceback):
- pass
- @property
- def timed_out(self):
- return time.time() > self.die_after
- class Report:
- _results = []
- _exception = None
- @staticmethod
- def start_test(test_function):
- try:
- test_function()
- except Exception as ex:
- Report._exception = traceback.format_exc()
- Report.report_results(test_function)
- @staticmethod
- def report_results(test_function):
- success = True
- report = f"Report for {test_function.__name__}:\n"
- for result in Report._results:
- passed, info = result
- success = success and passed
- if passed:
- report += f"[SUCCESS] {info}\n"
- else:
- report += f"[FAILED ] {info}\n"
- if Report._exception:
- report += "EXCEPTION raised:\n %s\n" % Report._exception[:-1].replace("\n", "\n ")
- success = False
- report += "Test result: "
- report += "SUCCESS" if success else "FAILURE"
- print(report)
- general.report_test_result(success, report)
- @staticmethod
- def info(msg):
- print("Info: {}".format(msg))
- @staticmethod
- def success(msgtuple_success_fail):
- outcome = "Success: {}".format(msgtuple_success_fail[0])
- print(outcome)
- Report._results.append((True, outcome))
- @staticmethod
- def failure(msgtuple_success_fail):
- 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")
- if condition:
- Report.success(msgtuple_success_fail)
- else:
- Report.failure(msgtuple_success_fail)
- return condition
- @staticmethod
- def critical_result(msgtuple_success_fail, condition, fast_fail_message=None):
- # type: (tuple, bool, str) -> None
- """
- if condition 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 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 Report.result(msgtuple_success_fail, condition):
- TestHelper.fail_fast(fast_fail_message)
- # DEPRECATED: Use vector3_str()
- @staticmethod
- def info_vector3(vector3, label="", magnitude=None):
- # type: (azlmbr.math.Vector3, str, float) -> None
- """
- prints the vector to the Report.info log. If applied, label will print first,
- followed by the vector's values (x, y, z,) to 2 decimal places. Lastly if the
- magnitude is supplied, it will print on the third line.
- :param vector3: a azlmbr.math.Vector3 object to print
- prints in [x: , y: , z: ] format.
- :param label: [optional] A string to print before printing the vector3's contents
- :param magnitude: [optional] the vector's magnitude to print after the vector's contents
- :return: None
- """
- if label != "":
- Report.info(label)
- Report.info(" x: {:.2f}, y: {:.2f}, z: {:.2f}".format(vector3.x, vector3.y, vector3.z))
- if magnitude is not None:
- Report.info(" magnitude: {:.2f}".format(magnitude))
-
-
- '''
- Utility for scope tracing errors and warnings.
- Usage:
- ...
- with Tracer() as section_tracer:
- # section were we are interested in capturing errors/warnings/asserts
- ...
- Report.result(Tests.warnings_not_found_in_section, not section_tracer.has_warnings)
- '''
- class Tracer:
- def __init__(self):
- self.warnings = []
- self.errors = []
- self.asserts = []
- self.has_warnings = False
- self.has_errors = False
- self.has_asserts = False
- self.handler = None
-
- class WarningInfo:
- def __init__(self, args):
- self.window = args[0]
- self.filename = args[1]
- self.line = args[2]
- self.function = args[3]
- self.message = args[4]
-
- class ErrorInfo:
- def __init__(self, args):
- self.window = args[0]
- self.filename = args[1]
- self.line = args[2]
- self.function = args[3]
- self.message = args[4]
-
- class AssertInfo:
- def __init__(self, args):
- self.filename = args[0]
- self.line = args[1]
- self.function = args[2]
- self.message = args[3]
-
- def _on_warning(self, args):
- warningInfo = Tracer.WarningInfo(args)
- self.warnings.append(warningInfo)
- Report.info("Tracer caught Warning: %s" % warningInfo.message)
- self.has_warnings = True
- return False
-
- def _on_error(self, args):
- errorInfo = Tracer.ErrorInfo(args)
- self.errors.append(errorInfo)
- Report.info("Tracer caught Error: %s" % errorInfo.message)
- self.has_errors = True
- return False
-
- def _on_assert(self, args):
- assertInfo = Tracer.AssertInfo(args)
- self.asserts.append(assertInfo)
- Report.info("Tracer caught Assert: %s:%i[%s] \"%s\"" % (assertInfo.filename, assertInfo.line, assertInfo.function, assertInfo.message))
- self.has_asserts = True
- return False
-
- def __enter__(self):
- self.handler = azlmbr.debug.TraceMessageBusHandler()
- self.handler.connect(None)
- self.handler.add_callback("OnPreAssert", self._on_assert)
- self.handler.add_callback("OnPreWarning", self._on_warning)
- self.handler.add_callback("OnPreError", self._on_error)
- return self
-
- def __exit__(self, type, value, traceback):
- self.handler.disconnect()
- self.handler = None
- return False
- class AngleHelper:
- @staticmethod
- def is_angle_close(x_rad, y_rad, tolerance):
- # type: (float, float , float) -> bool
- """
- compare if 2 angles measured in radians are close
- :param x_rad: angle in radians
- :param y_rad: angle in radians
- :param tolerance: the tolerance to define close
- :return: bool
- """
- sinx_sub_siny = math.sin(x_rad) - math.sin(y_rad)
- cosx_sub_cosy = math.cos(x_rad) - math.cos(y_rad)
- r = sinx_sub_siny * sinx_sub_siny + cosx_sub_cosy * cosx_sub_cosy
- diff = math.acos((2.0 - r) / 2.0)
- return abs(diff) <= tolerance
- @staticmethod
- def is_angle_close_deg(x_deg, y_deg, tolerance):
- # type: (float, float , float) -> bool
- """
- compare if 2 angles measured in degrees are close
- :param x_deg: angle in degrees
- :param y_deg: angle in degrees
- :param tolerance: the tolerance to define close
- :return: bool
- """
- return AngleHelper.is_angle_close(math.radians(x_deg), math.radians(y_deg), tolerance)
- 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))
|