file_utils.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. """
  2. All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
  3. its licensors.
  4. For complete copyright and license terms please see the LICENSE at the root of this
  5. distribution (the "License"). All use of this software is governed by the License,
  6. or, if provided, by the license below or the license accompanying this file. Do not
  7. remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. """
  10. import os
  11. import shutil
  12. import logging
  13. import stat
  14. import ly_test_tools.environment.file_system as file_system
  15. import ly_test_tools.environment.waiter as waiter
  16. logger = logging.getLogger(__name__)
  17. def clear_out_file(file_path):
  18. """
  19. Clears out the specified config file to be empty.
  20. :param file_path: The full path to the file.
  21. """
  22. if os.path.exists(file_path):
  23. file_system.unlock_file(file_path)
  24. with open(file_path, 'w') as file_to_write:
  25. file_to_write.write('')
  26. else:
  27. logger.debug(f'{file_path} not found while attempting to clear out file.')
  28. def add_commands_to_config_file(config_file_dir, config_file_name, command_list):
  29. """
  30. From the command list, appends each command to the specified config file.
  31. :param config_file_dir: The directory the config file is contained in.
  32. :param config_file_name: The config file name.
  33. :param command_list: The commands to add to the file.
  34. :return:
  35. """
  36. config_file_path = os.path.join(config_file_dir, config_file_name)
  37. os.chmod(config_file_path, stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC)
  38. with open(config_file_path, 'w') as launch_config_file:
  39. for command in command_list:
  40. launch_config_file.write("{}\n".format(command))
  41. def gather_error_logs(workspace):
  42. """
  43. Grabs all error logs (if there are any) and puts them into the specified logs path.
  44. :param workspace: The AbstractWorkspaceManager object that contains all of the paths
  45. """
  46. error_log_path = os.path.join(workspace.paths.project_log(), 'error.log')
  47. error_dump_path = os.path.join(workspace.paths.project_log(), 'error.dmp')
  48. if os.path.exists(error_dump_path):
  49. workspace.artifact_manager.save_artifact(error_dump_path)
  50. if os.path.exists(error_log_path):
  51. workspace.artifact_manager.save_artifact(error_log_path)
  52. def delete_screenshot_folder(workspace):
  53. """
  54. Deletes screenshot folder from platform path
  55. :param workspace: The AbstractWorkspaceManager object that contains all of the paths
  56. """
  57. shutil.rmtree(workspace.paths.project_screenshots(), ignore_errors=True)
  58. def move_file(src_dir, dest_dir, file_name, timeout=120):
  59. """
  60. Attempts to move a file from the source directory to the destination directory. Raises an IOError if
  61. the file is in use.
  62. :param src_dir: Directory of the file to be moved.
  63. :param dest_dir: Directory where the file will be moved to.
  64. :param file_name: Name of the file to be moved.
  65. :param timeout: Number of seconds to wait for the file to be released.
  66. """
  67. file_path = os.path.join(src_dir, file_name)
  68. if os.path.exists(file_path):
  69. waiter.wait_for(lambda: move_file_check(src_dir, dest_dir, file_name), timeout=timeout,
  70. exc=IOError('Cannot move file {} while in use'.format(file_path)))
  71. def move_file_check(src_dir, dest_dir, file_name):
  72. """
  73. Moves file and checks if the file has been moved from the source to the destination directory.
  74. :param src_dir: Source directory of the file to be moved
  75. :param dest_dir: Destination directory where the file should move to
  76. :param file_name: The name of the file to be moved
  77. :return:
  78. """
  79. try:
  80. shutil.move(os.path.join(src_dir, file_name), os.path.join(dest_dir, file_name))
  81. except OSError as e:
  82. logger.info(e)
  83. return False
  84. return True
  85. def rename_file(file_path, dest_path, timeout=10):
  86. # type: (str, str, int) -> None
  87. """
  88. Renames a file by moving it. Waits for file to become available and raises and exception if timeout occurs.
  89. :param file_path: absolute path to the source file
  90. :param dest_path: absolute path to the new file
  91. :param timeout: timeout to wait for function to complete
  92. :return: None
  93. """
  94. def _rename_file_check():
  95. try:
  96. shutil.move(file_path, dest_path)
  97. except OSError as e:
  98. logger.debug(f'Attempted to rename file: {file_path} but an error occurred, retrying.'
  99. f'\nError: {e}',
  100. stackinfo=True)
  101. return False
  102. return True
  103. if os.path.exists(file_path):
  104. waiter.wait_for(lambda: _rename_file_check(), timeout=timeout,
  105. exc=OSError('Cannot rename file {} while in use'.format(file_path)))
  106. def delete_level(workspace, level_dir, timeout=120):
  107. """
  108. Attempts to delete an entire level folder from the project.
  109. :param workspace: The workspace instance to delete the level from.
  110. :param level_dir: The level folder to delete
  111. """
  112. if not level_dir:
  113. logger.warning("level_dir is empty, nothing to delete.")
  114. return
  115. full_level_dir = os.path.join(workspace.paths.project(), 'Levels', level_dir)
  116. if not os.path.isdir(full_level_dir):
  117. if os.path.exists(full_level_dir):
  118. logger.error("level '{}' isn't a directory, it won't be deleted.".format(full_level_dir))
  119. else:
  120. logger.info("level '{}' doesn't exist, nothing to delete.".format(full_level_dir))
  121. return
  122. waiter.wait_for(lambda: delete_check(full_level_dir),
  123. timeout=timeout,
  124. exc=IOError('Cannot delete directory {} while in use'.format(full_level_dir)))
  125. def delete_check(src_dir):
  126. """
  127. Deletes directory and verifies that it's been deleted.
  128. :param src_dir: The directory to delete
  129. """
  130. try:
  131. def handle_delete_error(action, path, exception_info):
  132. # The deletion could fail if the file is read-only, so set the permissions to writeable and try again
  133. os.chmod(path, stat.S_IWRITE)
  134. # Try the passed-in action (delete) again
  135. action(path)
  136. shutil.rmtree(src_dir, onerror=handle_delete_error)
  137. except OSError as e:
  138. logger.debug("Delete for '{}' failed: {}".format(src_dir, e))
  139. return False
  140. return not os.path.exists(src_dir)