3
0

workspace.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. """
  2. Copyright (c) Contributors to the Open 3D Engine Project.
  3. For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. SPDX-License-Identifier: Apache-2.0 OR MIT
  5. Workspace Manager: Provides an API for managing lumberyard installations and saving of files
  6. """
  7. import abc
  8. import datetime
  9. import logging
  10. import os
  11. import subprocess
  12. import tempfile
  13. import ly_test_tools.environment.file_system
  14. import ly_test_tools.environment.process_utils as process_utils
  15. import ly_test_tools.o3de.asset_processor
  16. import ly_test_tools.o3de.settings as settings
  17. import ly_test_tools.o3de.shader_compiler
  18. import ly_test_tools._internal.managers.artifact_manager as artifact_manager
  19. import ly_test_tools._internal.managers.abstract_resource_locator as arl
  20. logger = logging.getLogger(__name__)
  21. class AbstractWorkspaceManager:
  22. __metaclass__ = abc.ABCMeta
  23. """
  24. Base workspace manager: provides a simple managed setup/teardown.
  25. All workspace managers are subclasses of this.
  26. """
  27. def __init__(self, # type: AbstractWorkspaceManager
  28. resource_locator, # type: arl.AbstractResourceLocator
  29. project, # type: str
  30. tmp_path=None, # type: str or None
  31. output_path=None, # type: str or None
  32. ): # type: (...) -> None
  33. """
  34. Create a workspace manager with an associated AbstractResourceLocator object and initialize temp and logs dirs.
  35. The workspace contains information about the workspace being used and the running pytest test.
  36. :param resource_locator: A resource locator to create paths for the workspace
  37. :param project: O3DE project to use for the LumberyardRelease object
  38. :param tmp_path: A path to use for storing temp files, if not specified default to the system's tmp
  39. :param output_path: A path used to store artifacts, if not specified defaults to
  40. "<build>\\dev\\TestResults\\<timestamp>"
  41. """
  42. self.paths = resource_locator
  43. self.project = project
  44. self.artifact_manager = artifact_manager.NullArtifactManager()
  45. self.asset_processor = ly_test_tools.o3de.asset_processor.AssetProcessor(self)
  46. self.shader_compiler = ly_test_tools.o3de.shader_compiler.ShaderCompiler(self)
  47. self._original_cwd = os.getcwd()
  48. self.tmp_path = tmp_path
  49. self.output_path = output_path
  50. if not self.tmp_path:
  51. self.tmp_path = tempfile.mkdtemp()
  52. self.settings = settings.LySettings(self.tmp_path, self.paths)
  53. if not self.output_path:
  54. self.output_path = os.path.join(self.paths.test_results(),
  55. datetime.datetime.now().strftime('%Y-%m-%dT%H_%M_%S_%f'))
  56. logger.info(f"No logs path set, using default based on timestamp: {self.output_path}")
  57. def setup(self):
  58. """
  59. Perform default setup for this workspace. Configures tmp and logs path, loggers
  60. Derived classes should call this before calling its own setup code (Unless you really know what you are doing).
  61. :return: None
  62. """
  63. if self.tmp_path:
  64. print(f"Checking for tmp path {self.tmp_path}")
  65. if os.path.exists(self.tmp_path):
  66. print("Found existing tmp path, deleting")
  67. ly_test_tools.environment.file_system.delete([self.tmp_path], True, True)
  68. print("Creating tmp path")
  69. os.makedirs(self.tmp_path, exist_ok=True)
  70. if not os.path.exists(self.output_path):
  71. print(f"Creating logs path at {self.output_path}")
  72. os.makedirs(self.output_path, exist_ok=True)
  73. print(f"Configuring Artifact Manager with path {self.output_path}")
  74. self.artifact_manager = artifact_manager.ArtifactManager(self.output_path)
  75. def teardown(self):
  76. """
  77. Perform teardown on this workspace: call teardown() on the LumberyardRelease object and delete tmp_path.
  78. Derived classes should call this after calling its own teardown code (Unless you really know what you are doing)
  79. :return: None
  80. """
  81. logger.debug("Deleting tmp path")
  82. os.chdir(self._original_cwd)
  83. if self.tmp_path:
  84. ly_test_tools.environment.file_system.delete([self.tmp_path], True, True)
  85. def clear_cache(self):
  86. """
  87. Clears the Cache folder located at dev/Cache
  88. :return: None
  89. """
  90. if os.path.exists(self.paths.cache()):
  91. logger.debug(f"Clearing {self.paths.cache()}")
  92. ly_test_tools.environment.file_system.delete([self.paths.cache()], True, True)
  93. return
  94. logger.debug(f"Cache directory: {self.paths.cache()} could not be found.")
  95. def clear_bin(self):
  96. """
  97. Clears the relative Bin folder (i.e. engine_root/dev/windows/bin/profile/)
  98. :return: None
  99. """
  100. if os.path.exists(self.paths.build_directory()):
  101. logger.debug(f"Clearing {self.paths.build_directory()}")
  102. ly_test_tools.environment.file_system.delete([self.paths.build_directory()], True, True)
  103. return
  104. logger.debug(f"build_directory directory: {self.paths.build_directory()} could not be found.")
  105. def _execute_and_save_log(self, command, log_file_name):
  106. """
  107. Executes a subprocess command and saves its output with the artifacts of current test
  108. :param command: command to execute
  109. :param log_file_name: artifact name to save
  110. :raises subprocess.CalledProcessError, OSError: on command failure
  111. """
  112. temp_file_dir = os.path.join(tempfile.gettempdir(), "LyTestTools")
  113. temp_file_path = os.path.join(temp_file_dir, log_file_name)
  114. if not os.path.exists(temp_file_dir):
  115. os.makedirs(temp_file_dir, exist_ok=True)
  116. if os.path.exists(temp_file_path):
  117. # assume temp is not being used for long-term storage, and safe to delete
  118. os.remove(temp_file_path)
  119. try:
  120. with open(temp_file_path, "w+") as logfile:
  121. try:
  122. output = process_utils.check_output(command, stderr=subprocess.STDOUT)
  123. logfile.write(output)
  124. except subprocess.CalledProcessError as err:
  125. failure_output = err.output if err.output else "<no output>"
  126. logger.exception(
  127. f'Command "{command}" failed with exit code "{err.returncode}" '
  128. f'and output: {failure_output.decode()}')
  129. logfile.write(failure_output.decode())
  130. raise
  131. finally:
  132. self.artifact_manager.save_artifact(temp_file_path)
  133. except OSError:
  134. logger.exception(f'Command "{command}" failed due to a filesystem error.')
  135. raise
  136. finally:
  137. if os.path.exists(temp_file_path):
  138. try:
  139. os.remove(temp_file_path)
  140. except Exception: # purposefully broad
  141. logger.warning(
  142. f"Ignored exception while cleaning up file: {temp_file_dir}", exc_info=True)