test_file_system.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876
  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 errno
  7. import logging
  8. import os
  9. import stat
  10. import psutil
  11. import subprocess
  12. import sys
  13. import tarfile
  14. import unittest.mock as mock
  15. import unittest
  16. import zipfile
  17. import pytest
  18. from ly_test_tools.environment import file_system
  19. logger = logging.getLogger(__name__)
  20. pytestmark = pytest.mark.SUITE_smoke
  21. class TestCheckFreeSpace(unittest.TestCase):
  22. def setUp(self):
  23. # sdiskusage is: (total, used, free, percent)
  24. self.disk_usage = psutil._common.sdiskusage(100 * file_system.ONE_GIB, 0, 100 * file_system.ONE_GIB, 100)
  25. def tearDown(self):
  26. self.disk_usage = None
  27. @mock.patch('psutil.disk_usage')
  28. def test_CheckFreeSpace_NoSpace_RaisesIOError(self, mock_disk_usage):
  29. total = 100 * file_system.ONE_GIB
  30. used = total
  31. mock_disk_usage.return_value = psutil._common.sdiskusage(total, used, total - used, used / total)
  32. with self.assertRaises(IOError):
  33. file_system.check_free_space('', 1, 'Raise')
  34. @mock.patch('psutil.disk_usage')
  35. def test_CheckFreeSpace_EnoughSpace_NoRaise(self, mock_disk_usage):
  36. total = 100 * file_system.ONE_GIB
  37. needed = 1
  38. used = total - needed
  39. mock_disk_usage.return_value = psutil._common.sdiskusage(total, used, total - used, used / total)
  40. dest_path = 'dest'
  41. file_system.check_free_space(dest_path, needed, 'No Raise')
  42. mock_disk_usage.assert_called_once_with(dest_path)
  43. class TestSafeMakedirs(unittest.TestCase):
  44. @mock.patch('os.makedirs')
  45. def test_SafeMakedirs_ValidCreation_Success(self, mock_makedirs):
  46. file_system.safe_makedirs('dummy')
  47. mock_makedirs.assert_called_once()
  48. @mock.patch('os.makedirs')
  49. def test_SafeMakedirs_RaisedOSErrorErrnoEEXIST_DoesNotPropagate(self, mock_makedirs):
  50. error = OSError()
  51. error.errno = errno.EEXIST
  52. mock_makedirs.side_effect = error
  53. file_system.safe_makedirs('')
  54. @mock.patch('os.makedirs')
  55. def test_SafeMakedirs_RaisedOSErrorNotErrnoEEXIST_Propagates(self, mock_makedirs):
  56. error = OSError()
  57. error.errno = errno.EINTR
  58. mock_makedirs.side_effect = error
  59. with self.assertRaises(OSError):
  60. file_system.safe_makedirs('')
  61. @mock.patch('os.makedirs')
  62. def test_SafeMakedirs_RootDir_DoesNotPropagate(self, mock_makedirs):
  63. error = OSError()
  64. error.errno = errno.EACCES
  65. if sys.platform == 'win32':
  66. mock_makedirs.side_effect = error
  67. file_system.safe_makedirs('C:\\')
  68. class TestGetNewestFileInDir(unittest.TestCase):
  69. @mock.patch('glob.iglob')
  70. def test_GetNewestFileInDir_NoResultsFound_ReturnsNone(self, mock_glob):
  71. mock_glob.return_value.iglob = None
  72. result = file_system.get_newest_file_in_dir('', '')
  73. self.assertEqual(result, None)
  74. @mock.patch('os.path.getctime')
  75. @mock.patch('glob.iglob')
  76. def test_GetNewestFileInDir_ThreeResultsTwoExts_CtimeCalledSixTimes(self, mock_glob, mock_ctime):
  77. mock_glob.return_value = ['fileA.zip', 'fileB.zip', 'fileC.zip']
  78. mock_ctime.side_effect = range(6)
  79. file_system.get_newest_file_in_dir('', ['.zip', '.tgz'])
  80. self.assertEqual(len(mock_ctime.mock_calls), 6)
  81. class TestRemovePathAndExtension(unittest.TestCase):
  82. def test_GetNewestFileInDir_ValidPath_ReturnsStripped(self):
  83. result = file_system.remove_path_and_extension(os.path.join("C", "packages", "lumberyard-XXXX.zip"))
  84. self.assertEqual(result, "lumberyard-XXXX")
  85. class TestUnZip(unittest.TestCase):
  86. decomp_obj_name = 'zipfile.ZipFile'
  87. def setUp(self):
  88. self.file_list = []
  89. for i in range(25):
  90. new_src_info = zipfile.ZipInfo('{}.txt'.format(i))
  91. new_src_info.file_size = i
  92. self.file_list.append(new_src_info)
  93. self.mock_handle = mock.MagicMock()
  94. self.mock_handle.infolist.return_value = self.file_list
  95. self.mock_handle.__enter__.return_value = self.mock_handle
  96. self.mock_decomp = mock.MagicMock()
  97. self.mock_decomp.return_value = self.mock_handle
  98. self.src_path = 'src.zip'
  99. self.dest_path = 'dest'
  100. self.exists = False
  101. def tearDown(self):
  102. self.mock_handle = None
  103. self.mock_decomp = None
  104. def call_decomp(self, dest, src, force=True, allow_exists=False):
  105. return file_system.unzip(dest, src, force, allow_exists)
  106. @mock.patch('os.path.exists')
  107. @mock.patch('ly_test_tools.environment.file_system.check_free_space')
  108. @mock.patch('os.path.join')
  109. def test_Unzip_NotEnoughSpaceInDestination_FailsWithErrorMessage(self, mock_join, mock_check_free, mock_exists):
  110. mock_exists.return_value = self.exists
  111. total_size = sum(info.file_size for info in self.file_list)
  112. with mock.patch(self.decomp_obj_name, self.mock_decomp):
  113. self.call_decomp(self.dest_path, self.src_path)
  114. mock_check_free.assert_called_once_with(self.dest_path,
  115. total_size + file_system.ONE_GIB,
  116. 'Not enough space to safely extract: ')
  117. @mock.patch('ly_test_tools.environment.file_system.check_free_space')
  118. @mock.patch('os.path.join')
  119. def test_Unzip_ReleaseBuild_ReturnsCorrectPath(self, mock_join, mock_check_free):
  120. build_name = 'lumberyard-1.2.0.3-54321-pc-1234'
  121. expected_path = self.dest_path + '\\' + build_name
  122. mock_join.return_value = expected_path
  123. path = ''
  124. self.src_path = r'C:\packages\lumberyard-1.2.0.3-54321-pc-1234.zip'
  125. with mock.patch(self.decomp_obj_name, self.mock_decomp):
  126. path = self.call_decomp(self.dest_path, self.src_path)
  127. self.assertEqual(path, expected_path)
  128. @mock.patch('ly_test_tools.environment.file_system.logger')
  129. @mock.patch('os.path.exists')
  130. @mock.patch('ly_test_tools.environment.file_system.check_free_space')
  131. @mock.patch('os.path.join')
  132. def test_Unzip_BuildDirExistsForceAndAllowExistsNotSet_CRITICALLogged(self, mock_join, mock_check_free,
  133. mock_exists, mock_log):
  134. force = False
  135. allow_exists = False
  136. self.exists = True
  137. mock_exists.return_value = self.exists
  138. level = logging.getLevelName("CRITICAL")
  139. with mock.patch(self.decomp_obj_name, self.mock_decomp):
  140. path = self.call_decomp(self.dest_path, self.src_path, force)
  141. mock_log.log.assert_called_with(level, 'Found existing {}. Will not overwrite.'.format(path))
  142. @mock.patch('ly_test_tools.environment.file_system.logger')
  143. @mock.patch('os.path.exists')
  144. @mock.patch('ly_test_tools.environment.file_system.check_free_space', mock.MagicMock())
  145. @mock.patch('os.path.join', mock.MagicMock())
  146. def test_Unzip_AllowExistsSet_INFOLogged(self, mock_exists, mock_log):
  147. force = False
  148. allow_exists = True
  149. self.exists = True
  150. mock_exists.return_value = self.exists
  151. level = logging.getLevelName("INFO")
  152. with mock.patch(self.decomp_obj_name, self.mock_decomp):
  153. path = self.call_decomp(self.dest_path, self.src_path, force, allow_exists)
  154. mock_log.log.assert_called_with(level, 'Found existing {}. Will not overwrite.'.format(path))
  155. @mock.patch('ly_test_tools.environment.file_system.logger')
  156. @mock.patch('os.path.exists')
  157. @mock.patch('ly_test_tools.environment.file_system.check_free_space', mock.MagicMock())
  158. @mock.patch('os.path.join', mock.MagicMock())
  159. def test_Unzip_BuildDirExistsForceSetTrue_INFOLogged(self, mock_exists, mock_log):
  160. path = ''
  161. self.exists = True
  162. mock_exists.return_value = self.exists
  163. with mock.patch(self.decomp_obj_name, self.mock_decomp):
  164. path = self.call_decomp(self.dest_path, self.src_path)
  165. mock_log.info.assert_called_once()
  166. class TestUnTgz(unittest.TestCase):
  167. decomp_obj_name = 'tarfile.open'
  168. def setUp(self):
  169. self.file_list = []
  170. for i in range(25):
  171. new_src_info = tarfile.TarInfo('{}.txt'.format(i))
  172. new_src_info.size = i
  173. self.file_list.append(new_src_info)
  174. self.mock_handle = mock.MagicMock()
  175. self.mock_handle.__iter__.return_value = self.file_list
  176. self.mock_handle.__enter__.return_value = self.mock_handle
  177. self.mock_decomp = mock.MagicMock()
  178. self.mock_decomp.return_value = self.mock_handle
  179. self.src_path = 'src.tgz'
  180. self.dest_path = 'dest'
  181. # os.stat_result is (mode, inode, device, hard links, owner uid, size, atime, mtime, ctime)
  182. self.src_stat = os.stat_result((0, 0, 0, 0, 0, 0, file_system.ONE_GIB, 0, 0, 0))
  183. def tearDown(self):
  184. self.mock_decomp = None
  185. self.mock_handle = None
  186. def call_decomp(self, dest, src, exact=False, force=True, allow_exists=False):
  187. return file_system.untgz(dest, src, exact, force, allow_exists)
  188. @mock.patch('ly_test_tools.environment.file_system.check_free_space')
  189. @mock.patch('os.path.join')
  190. @mock.patch('os.stat')
  191. def test_Untgz_ReleaseBuild_ReturnsCorrectPath(self, mock_stat, mock_join, mock_check_free):
  192. build_name = 'lumberyard-1.2.0.3-54321-pc-1234'
  193. expected_path = self.dest_path + '\\' + build_name
  194. mock_join.return_value = expected_path
  195. mock_stat.return_value = self.src_stat
  196. path = ''
  197. self.src_path = r'C:\packages\lumberyard-1.2.0.3-54321-pc-1234.zip'
  198. with mock.patch(self.decomp_obj_name, self.mock_decomp):
  199. path = self.call_decomp(self.dest_path, self.src_path)
  200. self.assertEqual(path, expected_path)
  201. @mock.patch('ly_test_tools.environment.file_system.logger')
  202. @mock.patch('os.path.exists')
  203. @mock.patch('ly_test_tools.environment.file_system.check_free_space', mock.MagicMock())
  204. @mock.patch('os.path.join', mock.MagicMock())
  205. @mock.patch('os.stat')
  206. def test_Untgz_BuildDirExistsForceAndAllowExistsNotSet_CRITICALLogged(self, mock_stat, mock_exists, mock_log):
  207. force = False
  208. mock_exists.return_value = True
  209. mock_stat.return_value = self.src_stat
  210. with mock.patch(self.decomp_obj_name, self.mock_decomp):
  211. path = self.call_decomp(self.dest_path, self.src_path, False, force)
  212. level = logging.getLevelName("CRITICAL")
  213. mock_log.log.assert_called_with(level, 'Found existing {}. Will not overwrite.'.format(path))
  214. @mock.patch('ly_test_tools.environment.file_system.logger')
  215. @mock.patch('os.path.exists')
  216. @mock.patch('ly_test_tools.environment.file_system.check_free_space', mock.MagicMock())
  217. @mock.patch('os.path.join', mock.MagicMock())
  218. @mock.patch('os.stat')
  219. def test_Untgz_AllowExiststSet_INFOLogged(self, mock_stat, mock_exists, mock_log):
  220. allow_exists = True
  221. force = False
  222. mock_exists.return_value = True
  223. mock_stat.return_value = self.src_stat
  224. level = logging.getLevelName("INFO")
  225. with mock.patch(self.decomp_obj_name, self.mock_decomp):
  226. path = self.call_decomp(self.dest_path, self.src_path, False, force, allow_exists)
  227. mock_log.log.assert_called_with(level, 'Found existing {}. Will not overwrite.'.format(path))
  228. @mock.patch('ly_test_tools.environment.file_system.logger')
  229. @mock.patch('os.path.exists')
  230. @mock.patch('ly_test_tools.environment.file_system.check_free_space')
  231. @mock.patch('os.path.join')
  232. @mock.patch('os.stat')
  233. def test_Untgz_BuildDirExistsForceSetTrue_INFOLogged(self, mock_stat, mock_join,
  234. mock_check_free, mock_exists, mock_log):
  235. path = ''
  236. mock_exists.return_value = True
  237. mock_stat.return_value = self.src_stat
  238. with mock.patch(self.decomp_obj_name, self.mock_decomp):
  239. path = self.call_decomp(self.dest_path, self.src_path)
  240. mock_log.info.assert_called_once()
  241. class TestChangePermissions(unittest.TestCase):
  242. def setUp(self):
  243. # Create a mock of a os.walk return iterable.
  244. self.root = 'root'
  245. self.dirs = ['dir1', 'dir2']
  246. self.files = ['file1', 'file2']
  247. self.walk_iter = iter([(self.root, self.dirs, self.files)])
  248. def tearDown(self):
  249. self.root = None
  250. self.dirs = None
  251. self.files = None
  252. self.walk_iter = None
  253. @mock.patch('os.walk')
  254. @mock.patch('os.chmod')
  255. def test_ChangePermissions_DefaultValues_ChmodCalledCorrectly(self, mock_chmod, mock_walk):
  256. os.walk.return_value = self.walk_iter
  257. file_system.change_permissions('.', 0o777)
  258. self.assertEqual(mock_chmod.mock_calls, [mock.call(os.path.join(self.root, self.dirs[0]), 0o777),
  259. mock.call(os.path.join(self.root, self.dirs[1]), 0o777),
  260. mock.call(os.path.join(self.root, self.files[0]), 0o777),
  261. mock.call(os.path.join(self.root, self.files[1]), 0o777)])
  262. @mock.patch('os.walk')
  263. @mock.patch('os.chmod')
  264. def test_ChangePermissions_DefaultValues_ReturnsTrueOnSuccess(self, mock_chmod, mock_walk):
  265. os.walk.return_value = self.walk_iter
  266. self.assertEqual(file_system.change_permissions('.', 0o777), True)
  267. @mock.patch('os.walk')
  268. @mock.patch('os.chmod')
  269. def test_ChangePermissions_OSErrorRaised_ReturnsFalse(self, mock_chmod, mock_walk):
  270. os.walk.return_value = self.walk_iter
  271. os.chmod.side_effect = OSError()
  272. self.assertEqual(file_system.change_permissions('.', 0o777), False)
  273. class MockStatResult():
  274. def __init__(self, st_mode):
  275. self.st_mode = st_mode
  276. class TestUnlockFile(unittest.TestCase):
  277. def setUp(self):
  278. self.file_name = 'file'
  279. @mock.patch('os.stat')
  280. @mock.patch('os.chmod')
  281. @mock.patch('os.access')
  282. def test_UnlockFile_WriteLocked_UnlockFile(self, mock_access, mock_chmod, mock_stat):
  283. mock_access.return_value = False
  284. os.stat.return_value = MockStatResult(stat.S_IREAD)
  285. success = file_system.unlock_file(self.file_name)
  286. mock_chmod.assert_called_once_with(self.file_name, stat.S_IREAD | stat.S_IWRITE)
  287. self.assertTrue(success)
  288. @mock.patch('os.stat')
  289. @mock.patch('os.chmod')
  290. @mock.patch('os.access')
  291. def test_UnlockFile_AlreadyUnlocked_LogAlreadyUnlocked(self, mock_access, mock_chmod, mock_stat):
  292. mock_access.return_value = True
  293. os.stat.return_value = MockStatResult(stat.S_IREAD | stat.S_IWRITE)
  294. success = file_system.unlock_file(self.file_name)
  295. self.assertFalse(success)
  296. class TestLockFile(unittest.TestCase):
  297. def setUp(self):
  298. self.file_name = 'file'
  299. @mock.patch('os.stat')
  300. @mock.patch('os.chmod')
  301. @mock.patch('os.access')
  302. def test_LockFile_UnlockedFile_FileLockedSuccessReturnsTrue(self, mock_access, mock_chmod, mock_stat):
  303. mock_access.return_value = True
  304. os.stat.return_value = MockStatResult(stat.S_IREAD | stat.S_IWRITE)
  305. success = file_system.lock_file(self.file_name)
  306. mock_chmod.assert_called_once_with(self.file_name, stat.S_IREAD)
  307. self.assertTrue(success)
  308. @mock.patch('os.stat')
  309. @mock.patch('os.chmod')
  310. @mock.patch('os.access')
  311. def test_LockFile_AlreadyLocked_FileLockedFailedReturnsFalse(self, mock_access, mock_chmod, mock_stat):
  312. mock_access.return_value = False
  313. os.stat.return_value = MockStatResult(stat.S_IREAD)
  314. success = file_system.lock_file(self.file_name)
  315. self.assertFalse(success)
  316. class TestRemoveSymlinks(unittest.TestCase):
  317. def setUp(self):
  318. # Create a mock of a os.walk return iterable.
  319. self.root = 'root'
  320. self.dirs = ['dir1', 'dir2']
  321. self.files = ['file1', 'file2']
  322. self.walk_iter = iter([(self.root, self.dirs, self.files)])
  323. @mock.patch('os.walk')
  324. @mock.patch('os.rmdir')
  325. def test_RemoveSymlinks_DefaultValues_RmdirCalledCorrectly(self, mock_rmdir, mock_walk):
  326. os.walk.return_value = self.walk_iter
  327. file_system.remove_symlinks('.')
  328. self.assertEqual(mock_rmdir.mock_calls, [mock.call(os.path.join(self.root, self.dirs[0])),
  329. mock.call(os.path.join(self.root, self.dirs[1]))])
  330. @mock.patch('os.walk')
  331. @mock.patch('os.rmdir')
  332. def test_RemoveSymlinks_OSErrnoEEXISTRaised_RaiseOSError(self, mock_rmdir, mock_walk):
  333. os.walk.return_value = self.walk_iter
  334. error = OSError()
  335. error.errno = errno.EEXIST
  336. mock_rmdir.side_effect = error
  337. with self.assertRaises(OSError):
  338. file_system.remove_symlinks('.')
  339. class TestDelete(unittest.TestCase):
  340. def setUp(self):
  341. self.path_list = ['file1', 'file2', 'dir1', 'dir2']
  342. def tearDown(self):
  343. self.path_list = None
  344. @mock.patch('os.path.isdir')
  345. @mock.patch('os.path.isfile')
  346. @mock.patch('ly_test_tools.environment.file_system.change_permissions', mock.MagicMock())
  347. @mock.patch('shutil.rmtree', mock.MagicMock())
  348. @mock.patch('os.remove', mock.MagicMock())
  349. def test_Delete_StringArg_ConvertsToList(self, mock_isfile, mock_isdir):
  350. mock_file_str = 'foo'
  351. mock_isdir.return_value = False
  352. mock_isfile.return_value = False
  353. file_system.delete(mock_file_str, del_files=True, del_dirs=True)
  354. mock_isfile.assert_called_once_with(mock_file_str)
  355. mock_isdir.assert_called_once_with(mock_file_str)
  356. @mock.patch('ly_test_tools.environment.file_system.change_permissions')
  357. @mock.patch('shutil.rmtree')
  358. @mock.patch('os.remove')
  359. @mock.patch('os.path.isdir')
  360. @mock.patch('os.path.isfile')
  361. def test_ChangePermissions_OSErrorRaised_ReturnsZero(self, mock_isfile, mock_isdir, mock_remove, mock_rmtree, mock_chper):
  362. mock_rmtree.side_effect = OSError()
  363. self.assertEqual(file_system.delete(self.path_list, del_files=True, del_dirs=True), False)
  364. @mock.patch('ly_test_tools.environment.file_system.change_permissions')
  365. @mock.patch('shutil.rmtree')
  366. @mock.patch('os.remove')
  367. @mock.patch('os.path.isdir')
  368. @mock.patch('os.path.isfile')
  369. def test_ChangePermissions_DefaultValues_ReturnsLenOfList(self,
  370. mock_isfile, mock_isdir, mock_remove, mock_rmtree, mock_chper):
  371. self.assertEqual(file_system.delete(self.path_list, del_files=True, del_dirs=True), True)
  372. @mock.patch('os.chmod')
  373. @mock.patch('ly_test_tools.environment.file_system.change_permissions')
  374. @mock.patch('shutil.rmtree')
  375. @mock.patch('os.remove')
  376. @mock.patch('os.path.isdir')
  377. @mock.patch('os.path.isfile')
  378. def test_ChangePermissions_DirsFalse_RMTreeNotCalled(self,
  379. mock_isfile, mock_isdir, mock_remove, mock_rmtree, mock_chper,
  380. mock_chmod):
  381. file_system.delete(self.path_list, del_files=True, del_dirs=False)
  382. self.assertEqual(mock_rmtree.called, False)
  383. self.assertEqual(mock_chmod.called, True)
  384. self.assertEqual(mock_remove.called, True)
  385. @mock.patch('ly_test_tools.environment.file_system.change_permissions')
  386. @mock.patch('shutil.rmtree')
  387. @mock.patch('os.remove')
  388. @mock.patch('os.path.isdir')
  389. @mock.patch('os.path.isfile')
  390. def test_ChangePermissions_NoDirs_RMTreeNotCalled(self,
  391. mock_isfile, mock_isdir, mock_remove, mock_rmtree, mock_chper):
  392. mock_isdir.return_value = False
  393. file_system.delete(self.path_list, del_files=False, del_dirs=True)
  394. self.assertEqual(mock_rmtree.called, False)
  395. @mock.patch('ly_test_tools.environment.file_system.change_permissions')
  396. @mock.patch('shutil.rmtree')
  397. @mock.patch('os.remove')
  398. @mock.patch('os.path.isdir')
  399. @mock.patch('os.path.isfile')
  400. def test_ChangePermissions_FilesFalse_RemoveNotCalled(self,
  401. mock_isfile, mock_isdir, mock_remove, mock_rmtree, mock_chper):
  402. file_system.delete(self.path_list, del_files=False, del_dirs=True)
  403. self.assertEqual(mock_rmtree.called, True)
  404. self.assertEqual(mock_remove.called, False)
  405. @mock.patch('ly_test_tools.environment.file_system.change_permissions')
  406. @mock.patch('shutil.rmtree')
  407. @mock.patch('os.remove')
  408. @mock.patch('os.path.isdir')
  409. @mock.patch('os.path.isfile')
  410. def test_ChangePermissions_NoFiles_RemoveNotCalled(self,
  411. mock_isfile, mock_isdir, mock_remove, mock_rmtree, mock_chper):
  412. mock_isfile.return_value = False
  413. file_system.delete(self.path_list, del_files=True, del_dirs=False)
  414. self.assertEqual(mock_remove.called, False)
  415. class TestRename(unittest.TestCase):
  416. def setUp(self):
  417. self.file1 = 'file1'
  418. self.file2 = 'file2'
  419. def tearDown(self):
  420. self.file1 = None
  421. self.file2 = None
  422. @mock.patch('os.path')
  423. @mock.patch('os.chmod')
  424. @mock.patch('os.rename')
  425. def test_Rename_TwoFiles_SuccessfulRename(self, mock_rename, mock_chmod, mock_path):
  426. mock_path.exists.side_effect = [True, False]
  427. self.assertTrue(file_system.rename(self.file1, self.file2))
  428. self.assertTrue(mock_rename.called)
  429. @mock.patch('os.rename')
  430. def test_Rename_SourceDoesNotExist_ErrorReported(self, mock_rename):
  431. with self.assertLogs('ly_test_tools.environment.file_system', 'ERROR') as captured_logs:
  432. with mock.patch('os.path') as mock_path:
  433. mock_path.exists.return_value = False
  434. self.assertFalse(file_system.rename(self.file1, self.file2))
  435. self.assertTrue(mock_path.exists.called)
  436. self.assertIn("No file located at:", captured_logs.records[0].getMessage())
  437. self.assertFalse(mock_rename.called)
  438. @mock.patch('os.rename')
  439. def test_Rename_DestinationExists_ErrorReported(self, mock_rename):
  440. with self.assertLogs('ly_test_tools.environment.file_system', 'ERROR') as captured_logs:
  441. with mock.patch('os.path') as mock_path:
  442. mock_path.exists.return_value = True
  443. self.assertFalse(file_system.rename(self.file1, self.file2))
  444. self.assertEqual(2, mock_path.exists.call_count)
  445. self.assertIn("File already exists at:", captured_logs.records[0].getMessage())
  446. self.assertFalse(mock_rename.called)
  447. @mock.patch('os.rename')
  448. @mock.patch('os.chmod')
  449. def test_Rename_PermissionsError_ErrorReported(self, mock_chmod, mock_rename):
  450. with self.assertLogs('ly_test_tools.environment.file_system', 'ERROR') as captured_logs:
  451. with mock.patch('os.path') as mock_path:
  452. mock_path.exists.side_effect = [True, False]
  453. mock_path.isfile.return_value = True
  454. mock_chmod.side_effect = OSError()
  455. self.assertFalse(file_system.rename(self.file1, self.file2))
  456. self.assertEqual(2, mock_path.exists.call_count)
  457. self.assertIn('Could not rename', captured_logs.records[0].getMessage())
  458. self.assertEqual(mock_rename.called, False)
  459. class TestDeleteOldest(unittest.TestCase):
  460. def setUp(self):
  461. self.path_list = ['file1', 'file2', 'dir1', 'dir2']
  462. self.age_list = [4, 3, 2, 1]
  463. def tearDown(self):
  464. self.path_list = None
  465. self.age_list = None
  466. @mock.patch('glob.iglob')
  467. @mock.patch('os.path.getctime')
  468. @mock.patch('ly_test_tools.environment.file_system.delete')
  469. def test_DeleteOldest_DefaultValuesKeepAll_DeleteCalledWithEmptyList(self, mock_delete, mock_ctime, mock_glob):
  470. mock_glob.return_value = self.path_list
  471. mock_ctime.side_effect = self.age_list
  472. # Nothing will be deleted because it's keeping everything.
  473. file_system.delete_oldest('', len(self.path_list), del_files=True, del_dirs=False)
  474. mock_delete.assert_called_once_with([], True, False)
  475. @mock.patch('glob.iglob')
  476. @mock.patch('os.path.getctime')
  477. @mock.patch('ly_test_tools.environment.file_system.delete')
  478. def test_DeleteOldest_DefaultValuesKeepNone_DeleteCalledWithList(self, mock_delete, mock_ctime, mock_glob):
  479. mock_glob.return_value = self.path_list
  480. mock_ctime.side_effect = self.age_list
  481. # Everything will be deleted because it's keeping nothing.
  482. file_system.delete_oldest('', 0, del_files=True, del_dirs=False)
  483. mock_delete.assert_called_once_with(self.path_list, True, False)
  484. @mock.patch('glob.iglob')
  485. @mock.patch('os.path.getctime')
  486. @mock.patch('ly_test_tools.environment.file_system.delete')
  487. def test_DeleteOldest_DefaultValuesKeepOne_DeleteCalledWithoutNewest(self, mock_delete, mock_ctime, mock_glob):
  488. mock_glob.return_value = self.path_list
  489. mock_ctime.side_effect = self.age_list
  490. file_system.delete_oldest('', 1, del_files=True, del_dirs=False)
  491. self.path_list.pop(0)
  492. mock_delete.assert_called_once_with(self.path_list, True, False)
  493. @mock.patch('glob.iglob')
  494. @mock.patch('os.path.getctime')
  495. @mock.patch('ly_test_tools.environment.file_system.delete')
  496. def test_DeleteOldest_UnsortedListDeleteOldest_DeleteCalledWithOldest(self, mock_delete, mock_ctime, mock_glob):
  497. mock_glob.return_value = ['newest', 'old', 'newer']
  498. mock_ctime.side_effect = [100, 0, 50]
  499. file_system.delete_oldest('', 2, del_files=True, del_dirs=False)
  500. mock_delete.assert_called_once_with(['old'], True, False)
  501. class TestMakeJunction(unittest.TestCase):
  502. def test_MakeJunction_Nondir_RaisesIOError(self):
  503. with self.assertRaises(IOError):
  504. file_system.make_junction('', '')
  505. @mock.patch('os.path.isdir')
  506. @mock.patch('sys.platform', 'linux2')
  507. def test_MakeJunction_NoSupportPlatform_RaisesIOError(self, mock_isdir):
  508. mock_isdir.return_value = True
  509. with self.assertRaises(IOError):
  510. file_system.make_junction('', '')
  511. @mock.patch('subprocess.check_call')
  512. @mock.patch('os.path.isdir')
  513. @mock.patch('sys.platform', 'win32')
  514. def test_MakeJunction_Win32_SubprocessFails_RaiseSubprocessError(self, mock_isdir, mock_sub_call):
  515. mock_isdir.return_value = True
  516. mock_sub_call.side_effect = subprocess.CalledProcessError(1, 'cmd', 'output')
  517. with self.assertRaises(subprocess.CalledProcessError):
  518. file_system.make_junction('', '')
  519. @mock.patch('subprocess.check_output')
  520. @mock.patch('os.path.isdir')
  521. @mock.patch('sys.platform', 'darwin')
  522. def test_MakeJunction_Darwin_SubprocessFails_RaiseSubprocessError(self, mock_isdir, mock_sub_call):
  523. mock_isdir.return_value = True
  524. mock_sub_call.side_effect = subprocess.CalledProcessError(1, 'cmd', 'output')
  525. with self.assertRaises(subprocess.CalledProcessError):
  526. file_system.make_junction('', '')
  527. @mock.patch('subprocess.check_output')
  528. @mock.patch('os.path.isdir')
  529. @mock.patch('sys.platform', 'win32')
  530. def test_MakeJunction_Win32_SubprocessCall_Calls(self, mock_isdir, mock_sub_call):
  531. mock_isdir.return_value = True
  532. src = 'source'
  533. dest = 'destination'
  534. file_system.make_junction(dest, src)
  535. mock_sub_call.assert_called_once_with(['mklink', '/J', dest, src], shell=True)
  536. @mock.patch('subprocess.check_output')
  537. @mock.patch('os.path.isdir')
  538. @mock.patch('sys.platform', 'darwin')
  539. def test_MakeJunction_Darwin_SubprocessCall_Calls(self, mock_isdir, mock_sub_call):
  540. mock_isdir.return_value = True
  541. src = 'source'
  542. dest = 'destination'
  543. file_system.make_junction(dest, src)
  544. mock_sub_call.assert_called_once_with(['ln', dest, src])
  545. class TestFileBackup(unittest.TestCase):
  546. def setUp(self):
  547. self._dummy_dir = os.path.join('somewhere', 'something')
  548. self._dummy_file = 'dummy.txt'
  549. self._dummy_backup_file = os.path.join(self._dummy_dir, '{}.bak'.format(self._dummy_file))
  550. @mock.patch('shutil.copy2')
  551. @mock.patch('os.path.exists')
  552. @mock.patch('os.path.isdir')
  553. def test_BackupSettings_SourceExists_BackupCreated(self, mock_path_isdir, mock_backup_exists, mock_copy):
  554. mock_path_isdir.return_value = True
  555. mock_backup_exists.side_effect = [True, False]
  556. file_system.create_backup(self._dummy_file, self._dummy_dir)
  557. mock_copy.assert_called_with(self._dummy_file, self._dummy_backup_file)
  558. @mock.patch('ly_test_tools.environment.file_system.logger.warning')
  559. @mock.patch('shutil.copy2')
  560. @mock.patch('os.path.exists')
  561. @mock.patch('os.path.isdir')
  562. def test_BackupSettings_BackupExists_WarningLogged(self, mock_path_isdir, mock_backup_exists, mock_copy, mock_logger_warning):
  563. mock_path_isdir.return_value = True
  564. mock_backup_exists.return_value = True
  565. file_system.create_backup(self._dummy_file, self._dummy_dir)
  566. mock_copy.assert_called_with(self._dummy_file, self._dummy_backup_file)
  567. mock_logger_warning.assert_called_once()
  568. @mock.patch('ly_test_tools.environment.file_system.logger.warning')
  569. @mock.patch('shutil.copy2')
  570. @mock.patch('os.path.exists')
  571. @mock.patch('os.path.isdir')
  572. def test_BackupSettings_SourceNotExists_WarningLogged(self, mock_path_isdir, mock_backup_exists, mock_copy, mock_logger_warning):
  573. mock_path_isdir.return_value = True
  574. mock_backup_exists.return_value = False
  575. file_system.create_backup(self._dummy_file, self._dummy_dir)
  576. mock_copy.assert_not_called()
  577. mock_logger_warning.assert_called_once()
  578. @mock.patch('ly_test_tools.environment.file_system.logger.warning')
  579. @mock.patch('shutil.copy2')
  580. @mock.patch('os.path.exists')
  581. @mock.patch('os.path.isdir')
  582. def test_BackupSettings_CannotCopy_WarningLogged(self, mock_path_isdir, mock_backup_exists, mock_copy, mock_logger_warning):
  583. mock_path_isdir.return_value = True
  584. mock_backup_exists.side_effect = [True, False]
  585. mock_copy.side_effect = Exception('some error')
  586. file_system.create_backup(self._dummy_file, self._dummy_dir)
  587. mock_copy.assert_called_with(self._dummy_file, self._dummy_backup_file)
  588. mock_logger_warning.assert_called_once()
  589. @mock.patch('ly_test_tools.environment.file_system.logger.error')
  590. @mock.patch('os.path.exists')
  591. @mock.patch('os.path.isdir')
  592. def test_BackupSettings_InvalidDir_ErrorLogged(self, mock_path_isdir, mock_backup_exists, mock_logger_error):
  593. mock_path_isdir.return_value = False
  594. mock_backup_exists.return_value = False
  595. file_system.create_backup(self._dummy_file, None)
  596. mock_logger_error.assert_called_once()
  597. class TestFileBackupRestore(unittest.TestCase):
  598. def setUp(self):
  599. self._dummy_dir = os.path.join('somewhere', 'something')
  600. self._dummy_file = 'dummy.txt'
  601. self._dummy_backup_file = os.path.join(self._dummy_dir, '{}.bak'.format(self._dummy_file))
  602. @mock.patch('shutil.copy2')
  603. @mock.patch('os.path.exists')
  604. @mock.patch('os.path.isdir')
  605. def test_RestoreSettings_BackupRestore_Success(self, mock_path_isdir, mock_exists, mock_copy):
  606. mock_path_isdir.return_value = True
  607. mock_exists.return_value = True
  608. file_system.restore_backup(self._dummy_file, self._dummy_dir)
  609. mock_copy.assert_called_with(self._dummy_backup_file, self._dummy_file)
  610. @mock.patch('ly_test_tools.environment.file_system.logger.warning')
  611. @mock.patch('shutil.copy2')
  612. @mock.patch('os.path.exists')
  613. @mock.patch('os.path.isdir')
  614. def test_RestoreSettings_CannotCopy_WarningLogged(self, mock_path_isdir, mock_exists, mock_copy, mock_logger_warning):
  615. mock_path_isdir.return_value = True
  616. mock_exists.return_value = True
  617. mock_copy.side_effect = Exception('some error')
  618. file_system.restore_backup(self._dummy_file, self._dummy_dir)
  619. mock_copy.assert_called_with(self._dummy_backup_file, self._dummy_file)
  620. mock_logger_warning.assert_called_once()
  621. @mock.patch('ly_test_tools.environment.file_system.logger.warning')
  622. @mock.patch('shutil.copy2')
  623. @mock.patch('os.path.exists')
  624. @mock.patch('os.path.isdir')
  625. def test_RestoreSettings_BackupNotExists_WarningLogged(self, mock_path_isdir, mock_exists, mock_copy, mock_logger_warning):
  626. mock_path_isdir.return_value = True
  627. mock_exists.return_value = False
  628. file_system.restore_backup(self._dummy_file, self._dummy_dir)
  629. mock_copy.assert_not_called()
  630. mock_logger_warning.assert_called_once()
  631. @mock.patch('ly_test_tools.environment.file_system.logger.error')
  632. def test_RestoreSettings_InvalidDir_ErrorLogged(self, mock_logger_error):
  633. file_system.restore_backup(self._dummy_file, None)
  634. mock_logger_error.assert_called_once()
  635. @mock.patch('ly_test_tools.environment.file_system.logger.error')
  636. @mock.patch('os.path.isdir')
  637. def test_RestoreSettings_InvalidDir_ErrorLogged(self, mock_path_isdir, mock_logger_error):
  638. mock_path_isdir.return_value = False
  639. file_system.restore_backup(self._dummy_file, self._dummy_dir)
  640. mock_logger_error.assert_called_once()
  641. class TestReduceFileName(unittest.TestCase):
  642. def test_Reduce_LongString_ReturnsReducedString(self):
  643. target_name = 'really_long_string_that_needs_reduction' # len(mock_file_name) == 39
  644. max_length = 25
  645. under_test = file_system.reduce_file_name_length(target_name, max_length)
  646. assert len(under_test) == max_length
  647. def test_Reduce_ShortString_ReturnsSameString(self):
  648. target_name = 'less_than_max' # len(mock_file_name) == 13
  649. max_length = 25
  650. under_test = file_system.reduce_file_name_length(target_name, max_length)
  651. assert under_test == target_name
  652. def test_Reduce_NoString_RaisesTypeError(self):
  653. with pytest.raises(TypeError):
  654. file_system.reduce_file_name_length(max_length=25)
  655. def test_Reduce_NoMaxLength_RaisesTypeError(self):
  656. target_name = 'raises_type_error'
  657. with pytest.raises(TypeError):
  658. file_system.reduce_file_name_length(file_name=target_name)
  659. class TestFindAncestorFile(unittest.TestCase):
  660. @mock.patch('os.path.exists', mock.MagicMock(side_effect=[False, False, True, True]))
  661. def test_Find_OneLevel_ReturnsPath(self):
  662. mock_file = 'mock_file.txt'
  663. mock_start_path = os.path.join('foo1', 'foo2', 'foo3')
  664. expected = os.path.abspath(os.path.join('foo1', mock_file))
  665. actual = file_system.find_ancestor_file(mock_file, mock_start_path)
  666. assert actual == expected