test_process_utils.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  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. """
  6. import unittest.mock as mock
  7. import psutil
  8. import pytest
  9. import subprocess
  10. import unittest
  11. import ly_test_tools.environment.process_utils as process_utils
  12. from ly_test_tools import WINDOWS
  13. pytestmark = pytest.mark.SUITE_smoke
  14. class TestSubprocessCheckOutputWrapper(unittest.TestCase):
  15. @mock.patch('subprocess.check_output')
  16. @mock.patch('logging.Logger.error')
  17. def test_CheckOutput_FailingCommand_UsesCorrectLoggingLevel(self, mock_log_err, mock_sub_output):
  18. mock_sub_output.side_effect = subprocess.CalledProcessError(1, 'cmd', 'output')
  19. cmd = ['test', 'cmd']
  20. with pytest.raises(subprocess.CalledProcessError):
  21. process_utils.check_output(cmd)
  22. mock_log_err.assert_called_once()
  23. @mock.patch('subprocess.check_output')
  24. @mock.patch('logging.Logger.info')
  25. def test_CheckOutput_SuccessfulCommand_UsesCorrectLoggingLevel(self, mock_log_info, mock_sub_output):
  26. mock_sub_output.return_value = 'Output returned successfully'.encode()
  27. cmd = ['test', 'cmd']
  28. expected_logger_info_calls = 2
  29. process_utils.check_output(cmd)
  30. self.assertEqual(expected_logger_info_calls, mock_log_info.call_count)
  31. @mock.patch('subprocess.check_output')
  32. def test_CheckOutput_FailingCommand_RaisesCalledProcessError(self, mock_sub_output):
  33. mock_sub_output.side_effect = subprocess.CalledProcessError(1, 'cmd', 'output')
  34. cmd = ['test', 'cmd']
  35. with self.assertRaises(subprocess.CalledProcessError):
  36. process_utils.check_output(cmd)
  37. @mock.patch('subprocess.check_output')
  38. def test_CheckOutput_CmdPassedAsString_ReturnsOutput(self, mock_sub_output):
  39. expected_output = 'Output returned successfully'
  40. mock_sub_output.return_value = expected_output.encode()
  41. cmd = 'test cmd'
  42. actual_output = process_utils.check_output(cmd)
  43. mock_sub_output.assert_called_once()
  44. self.assertEqual(expected_output, actual_output)
  45. class TestSubprocessCheckOutputWrapperSafe(unittest.TestCase):
  46. @mock.patch('subprocess.check_output')
  47. @mock.patch('logging.Logger.warning')
  48. def test_SafeCheckOutput_FailingCommand_UsesCorrectLoggingLevel(self, mock_log_warn, mock_sub_output):
  49. mock_sub_output.side_effect = subprocess.CalledProcessError(1, 'cmd', 'output')
  50. cmd = ['test', 'cmd']
  51. process_utils.safe_check_output(cmd)
  52. mock_log_warn.assert_called_once()
  53. @mock.patch('subprocess.check_output')
  54. @mock.patch('logging.Logger.info')
  55. def test_SafeCheckOutput_SuccessfulCommand_UsesCorrectLoggingLevel(self, mock_log_info, mock_sub_output):
  56. mock_sub_output.return_value = 'Output returned successfully'.encode()
  57. cmd = ['test', 'cmd']
  58. expected_logger_info_calls = 2
  59. process_utils.check_output(cmd)
  60. self.assertEqual(expected_logger_info_calls, mock_log_info.call_count)
  61. @mock.patch('subprocess.check_output')
  62. def test_SafeCheckOutput_FailingCommand_ReturnsOutput(self, mock_sub_output):
  63. expected_output = 'Output returned successfully'
  64. mock_sub_output.side_effect = subprocess.CalledProcessError(1, 'cmd', expected_output)
  65. cmd = ['test', 'cmd']
  66. actual_output = process_utils.safe_check_output(cmd)
  67. mock_sub_output.assert_called_once()
  68. self.assertEqual(expected_output, actual_output)
  69. @mock.patch('subprocess.check_output')
  70. def test_SafeCheckOutput_CmdPassedAsString_ReturnsOutput(self, mock_sub_output):
  71. expected_output = 'Output returned successfully'
  72. mock_sub_output.return_value = expected_output.encode()
  73. cmd = 'test cmd'
  74. actual_output = process_utils.safe_check_output(cmd)
  75. mock_sub_output.assert_called_once()
  76. self.assertEqual(expected_output, actual_output)
  77. class TestSubprocessCheckCallWrapper(unittest.TestCase):
  78. @mock.patch('subprocess.check_call')
  79. @mock.patch('logging.Logger.error')
  80. def test_CheckCall_FailingCommand_UsesCorrectLoggingLevel(self, mock_log_err, mock_sub_call):
  81. mock_sub_call.side_effect = subprocess.CalledProcessError(1, 'cmd', 'output')
  82. cmd = ['test', 'cmd']
  83. with pytest.raises(subprocess.CalledProcessError):
  84. process_utils.check_call(cmd)
  85. mock_log_err.assert_called_once()
  86. @mock.patch('subprocess.check_call')
  87. @mock.patch('logging.Logger.info')
  88. def test_CheckOutput_SuccessfulCommand_UsesCorrectLoggingLevel(self, mock_log_info, mock_sub_call):
  89. mock_sub_call.return_value = 0
  90. cmd = ['test', 'cmd']
  91. expected_logger_info_calls = 2
  92. process_utils.check_call(cmd)
  93. self.assertEqual(expected_logger_info_calls, mock_log_info.call_count)
  94. @mock.patch('subprocess.check_call')
  95. def test_CheckCall_FailingCommand_RaisesCalledProcessError(self, mock_sub_call):
  96. mock_sub_call.side_effect = subprocess.CalledProcessError(1, 'cmd', 'output')
  97. cmd = ['test', 'cmd']
  98. with self.assertRaises(subprocess.CalledProcessError):
  99. process_utils.check_call(cmd)
  100. mock_sub_call.assert_called_once()
  101. @mock.patch('subprocess.check_call')
  102. def test_CheckCall_CmdPassedAsString_ReturnsSuccess(self, mock_sub_call):
  103. expected_retcode = 0
  104. mock_sub_call.returncode = expected_retcode
  105. cmd = 'test cmd'
  106. actual_retcode = process_utils.check_call(cmd)
  107. mock_sub_call.assert_called_once()
  108. self.assertEqual(expected_retcode, actual_retcode)
  109. class TestSubprocessCheckCallWrapperSafe(unittest.TestCase):
  110. @mock.patch('subprocess.check_call')
  111. @mock.patch('logging.Logger.warning')
  112. def test_SafeCheckCall_FailingCommand_UsesCorrectLoggingLevel(self, mock_log_warn, mock_sub_call):
  113. mock_sub_call.side_effect = subprocess.CalledProcessError(1, 'cmd', 'output')
  114. cmd = ['test', 'cmd']
  115. process_utils.safe_check_call(cmd)
  116. mock_log_warn.assert_called_once()
  117. @mock.patch('subprocess.check_call')
  118. @mock.patch('logging.Logger.info')
  119. def test_CheckOutput_SuccessfulCommand_UsesCorrectLoggingLevel(self, mock_log_info, mock_sub_call):
  120. mock_sub_call.return_value = 0
  121. cmd = ['test', 'cmd']
  122. expected_logger_info_calls = 2
  123. process_utils.safe_check_call(cmd)
  124. self.assertEqual(expected_logger_info_calls, mock_log_info.call_count)
  125. @mock.patch('subprocess.check_call')
  126. def test_SafeCheckCall_FailingCommand_ReturnsFailureCode(self, mock_sub_call):
  127. expected_retcode = 1
  128. mock_sub_call.side_effect = subprocess.CalledProcessError(expected_retcode, 'cmd', 'output')
  129. cmd = ['test', 'cmd']
  130. actual_retcode = process_utils.safe_check_call(cmd)
  131. mock_sub_call.assert_called_once()
  132. self.assertEqual(expected_retcode, actual_retcode)
  133. @mock.patch('subprocess.check_call')
  134. def test_SafeCheckCall_CmdPassedAsString_ReturnsSuccess(self, mock_sub_call):
  135. expected_retcode = 0
  136. mock_sub_call.returncode = expected_retcode
  137. cmd = 'test cmd'
  138. actual_retcode = process_utils.safe_check_call(cmd)
  139. mock_sub_call.assert_called_once()
  140. self.assertEqual(expected_retcode, actual_retcode)
  141. @pytest.mark.skipif(
  142. not WINDOWS,
  143. reason="tests.unit.test_process_utils is restricted to the Windows platform.")
  144. class TestCloseWindowsProcess(unittest.TestCase):
  145. @mock.patch('ly_test_tools.WINDOWS', False)
  146. def test_CloseWindowsProccess_NotOnWindows_Error(self):
  147. with pytest.raises(NotImplementedError):
  148. process_utils.close_windows_process(1)
  149. def test_CloseWindowsProccess_IdNone_Error(self):
  150. with pytest.raises(TypeError):
  151. process_utils.close_windows_process(None)
  152. @mock.patch('psutil.Process')
  153. def test_CloseWindowsProccess_ProcDNE_Error(self, mock_psutil):
  154. mock_proc = mock.MagicMock()
  155. mock_proc.is_running.return_value = False
  156. mock_psutil.return_value = mock_proc
  157. with pytest.raises(TypeError):
  158. process_utils.close_windows_process(None)
  159. @mock.patch('psutil.Process')
  160. @mock.patch('ctypes.windll.user32.EnumWindows')
  161. @mock.patch('ctypes.windll', mock.MagicMock())
  162. @mock.patch('ly_test_tools.environment.waiter.wait_for', mock.MagicMock())
  163. def test_CloseProcess_MockedWindll_VerifyMock(self, mock_enum, mock_psutil):
  164. mock_proc = mock.MagicMock()
  165. mock_proc.is_running.return_value = True
  166. mock_psutil.return_value = mock_proc
  167. process_utils.close_windows_process(3)
  168. mock_enum.assert_called_once()
  169. class TestProcessMatching(unittest.TestCase):
  170. @mock.patch("ly_test_tools.environment.process_utils._safe_get_processes")
  171. def test_ProcExists_HasExtension_Found(self, mock_get_proc):
  172. name = "dummy.exe"
  173. proc_mock = mock.MagicMock()
  174. proc_mock.name.return_value = name
  175. mock_get_proc.return_value = [proc_mock]
  176. result = process_utils.process_exists(name)
  177. self.assertTrue(result)
  178. proc_mock.name.assert_called()
  179. @mock.patch("ly_test_tools.environment.process_utils._safe_get_processes")
  180. def test_ProcExists_NoExtension_Ignored(self, mock_get_proc):
  181. name = "dummy.exe"
  182. proc_mock = mock.MagicMock()
  183. proc_mock.name.return_value = name
  184. mock_get_proc.return_value = [proc_mock]
  185. result = process_utils.process_exists("dummy")
  186. self.assertFalse(result)
  187. proc_mock.name.assert_called()
  188. @mock.patch("ly_test_tools.environment.process_utils._safe_get_processes")
  189. def test_ProcExistsIgnoreExtension_NoExtension_Found(self, mock_get_proc):
  190. name = "dummy.exe"
  191. proc_mock = mock.MagicMock()
  192. proc_mock.name.return_value = name
  193. mock_get_proc.return_value = [proc_mock]
  194. result = process_utils.process_exists("dummy", ignore_extensions=True)
  195. self.assertTrue(result)
  196. proc_mock.name.assert_called()
  197. @mock.patch('ly_test_tools.environment.process_utils._safe_kill_processes')
  198. @mock.patch('ly_test_tools.environment.process_utils._safe_get_processes')
  199. def test_KillProcNamed_ExactMatch_Killed(self, mock_get_proc, mock_kill_proc):
  200. name = "dummy.exe"
  201. proc_mock = mock.MagicMock()
  202. proc_mock.name.return_value = name
  203. mock_get_proc.return_value = [proc_mock]
  204. process_utils.kill_processes_named("dummy.exe", ignore_extensions=False)
  205. mock_kill_proc.assert_called()
  206. proc_mock.name.assert_called()
  207. @mock.patch('ly_test_tools.environment.process_utils._safe_kill_processes')
  208. @mock.patch('ly_test_tools.environment.process_utils._safe_get_processes')
  209. def test_KillProcNamed_NearMatch_Ignore(self, mock_get_proc, mock_kill_proc):
  210. name = "dummy.exe"
  211. proc_mock = mock.MagicMock()
  212. proc_mock.name.return_value = name
  213. mock_get_proc.return_value = [proc_mock]
  214. process_utils.kill_processes_named("dummy", ignore_extensions=False)
  215. mock_kill_proc.assert_not_called()
  216. proc_mock.name.assert_called()
  217. @mock.patch('ly_test_tools.environment.process_utils._safe_kill_processes')
  218. @mock.patch('ly_test_tools.environment.process_utils._safe_get_processes')
  219. def test_KillProcNamed_NearMatchIgnoreExtension_Kill(self, mock_get_proc, mock_kill_proc):
  220. name = "dummy.exe"
  221. proc_mock = mock.MagicMock()
  222. proc_mock.name.return_value = name
  223. mock_get_proc.return_value = [proc_mock]
  224. process_utils.kill_processes_named("dummy", ignore_extensions=True)
  225. mock_kill_proc.assert_called()
  226. proc_mock.name.assert_called()
  227. @mock.patch('ly_test_tools.environment.process_utils._safe_kill_processes')
  228. @mock.patch('ly_test_tools.environment.process_utils._safe_get_processes')
  229. def test_KillProcNamed_ExactMatchIgnoreExtension_Killed(self, mock_get_proc, mock_kill_proc):
  230. name = "dummy.exe"
  231. proc_mock = mock.MagicMock()
  232. proc_mock.name.return_value = name
  233. mock_get_proc.return_value = [proc_mock]
  234. process_utils.kill_processes_named("dummy.exe", ignore_extensions=True)
  235. mock_kill_proc.assert_called()
  236. proc_mock.name.assert_called()
  237. @mock.patch('ly_test_tools.environment.process_utils._safe_kill_processes', mock.MagicMock)
  238. @mock.patch('ly_test_tools.environment.process_utils._safe_get_processes')
  239. @mock.patch('os.path.exists')
  240. def test_KillProcFrom_MockKill_SilentSuccess(self, mock_path, mock_get_proc):
  241. mock_path.return_value = True
  242. proc_mock = mock.MagicMock()
  243. mock_get_proc.return_value = [proc_mock]
  244. process_utils.kill_processes_started_from("dummy_path")
  245. @mock.patch('ly_test_tools.environment.process_utils._safe_kill_process')
  246. @mock.patch('psutil.Process')
  247. def test_KillProcPid_ProcRunning_Killed(self, mock_psutil, mock_kill):
  248. mock_proc = mock.MagicMock()
  249. mock_proc.is_running.return_value = True
  250. mock_psutil.return_value = mock_proc
  251. process_utils.kill_process_with_pid(1)
  252. mock_kill.assert_called()
  253. @mock.patch('ly_test_tools.environment.process_utils._safe_kill_processes', mock.MagicMock)
  254. @mock.patch('psutil.Process')
  255. def test_KillProcPid_NoProc_SilentPass(self, mock_psutil):
  256. mock_proc = mock.MagicMock()
  257. mock_proc.is_running.return_value = False
  258. mock_psutil.return_value = mock_proc
  259. process_utils.kill_process_with_pid(1)
  260. @mock.patch('ly_test_tools.environment.process_utils._safe_kill_processes', mock.MagicMock)
  261. @mock.patch('psutil.Process')
  262. def test_KillProcPidRaiseOnMissing_NoProc_Raises(self, mock_psutil):
  263. mock_proc = mock.MagicMock()
  264. mock_proc.is_running.return_value = False
  265. mock_psutil.return_value = mock_proc
  266. with self.assertRaises(RuntimeError):
  267. process_utils.kill_process_with_pid(1, raise_on_missing=True)
  268. def test_SafeKillProc_HappyPath_Success(self):
  269. proc_mock = mock.MagicMock()
  270. proc_mock.is_running.return_value = False
  271. process_utils._safe_kill_process(proc_mock)
  272. proc_mock.kill.assert_called()
  273. proc_mock.is_running.assert_called()
  274. @mock.patch('ly_test_tools.environment.waiter.wait_for')
  275. @mock.patch('logging.Logger.warning')
  276. def test_SafeKillProc_KillRetriesFail_LogsFailure(self, mock_log_warn, mock_wait):
  277. mock_wait.side_effect = psutil.AccessDenied()
  278. proc_mock = mock.MagicMock()
  279. process_utils._safe_kill_process(proc_mock)
  280. proc_mock.kill.assert_called()
  281. mock_wait.assert_called()
  282. mock_log_warn.assert_called()
  283. @mock.patch('psutil.wait_procs')
  284. @mock.patch('logging.Logger.debug')
  285. def test_SafeKillProcList_RaisesError_NoRaiseAndLogsError(self, mock_log, mock_wait_procs):
  286. mock_wait_procs.side_effect = psutil.PermissionError()
  287. proc_mock = mock.MagicMock()
  288. process_utils._safe_kill_processes(proc_mock)
  289. mock_wait_procs.assert_called()
  290. mock_log.assert_called()
  291. @mock.patch('psutil.process_iter')
  292. @mock.patch('logging.Logger.debug')
  293. def test_SafeGetProc_CannotAccess_LogAndReturnNone(self, mock_log_debug, mock_psiter):
  294. mock_psiter.side_effect = psutil.Error()
  295. under_test = process_utils._safe_get_processes()
  296. self.assertIsNone(under_test)
  297. mock_log_debug.assert_called()
  298. @mock.patch('psutil.process_iter')
  299. @mock.patch('logging.Logger.debug')
  300. def test_SafeGetProc_HappyPathDummy_ReturnDummy(self, mock_log_debug, mock_psiter):
  301. dummy = object()
  302. mock_psiter.return_value = dummy
  303. under_test = process_utils._safe_get_processes()
  304. self.assertIs(under_test, dummy)
  305. mock_log_debug.assert_not_called()