You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
903 lines
35 KiB
Python
903 lines
35 KiB
Python
"""
|
|
Copyright (c) Contributors to the Open 3D Engine Project.
|
|
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
|
|
SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
"""
|
|
|
|
import errno
|
|
import logging
|
|
import os
|
|
import stat
|
|
import psutil
|
|
import subprocess
|
|
import sys
|
|
import tarfile
|
|
import unittest.mock as mock
|
|
import unittest
|
|
import zipfile
|
|
|
|
import pytest
|
|
|
|
from ly_test_tools.environment import file_system
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
pytestmark = pytest.mark.SUITE_smoke
|
|
|
|
|
|
class TestCheckFreeSpace(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
# sdiskusage is: (total, used, free, percent)
|
|
self.disk_usage = psutil._common.sdiskusage(100 * file_system.ONE_GIB, 0, 100 * file_system.ONE_GIB, 100)
|
|
|
|
def tearDown(self):
|
|
self.disk_usage = None
|
|
|
|
@mock.patch('psutil.disk_usage')
|
|
def test_CheckFreeSpace_NoSpace_RaisesIOError(self, mock_disk_usage):
|
|
|
|
total = 100 * file_system.ONE_GIB
|
|
used = total
|
|
mock_disk_usage.return_value = psutil._common.sdiskusage(total, used, total - used, used / total)
|
|
|
|
with self.assertRaises(IOError):
|
|
file_system.check_free_space('', 1, 'Raise')
|
|
|
|
@mock.patch('psutil.disk_usage')
|
|
def test_CheckFreeSpace_EnoughSpace_NoRaise(self, mock_disk_usage):
|
|
total = 100 * file_system.ONE_GIB
|
|
needed = 1
|
|
used = total - needed
|
|
mock_disk_usage.return_value = psutil._common.sdiskusage(total, used, total - used, used / total)
|
|
|
|
dest_path = 'dest'
|
|
file_system.check_free_space(dest_path, needed, 'No Raise')
|
|
mock_disk_usage.assert_called_once_with(dest_path)
|
|
|
|
|
|
class TestSafeMakedirs(unittest.TestCase):
|
|
|
|
@mock.patch('os.makedirs')
|
|
def test_SafeMakedirs_RaisedOSErrorErrnoEEXIST_DoesNotPropagate(self, mock_makedirs):
|
|
error = OSError()
|
|
error.errno = errno.EEXIST
|
|
mock_makedirs.side_effect = error
|
|
file_system.safe_makedirs('')
|
|
|
|
@mock.patch('os.makedirs')
|
|
def test_SafeMakedirs_RaisedOSErrorNotErrnoEEXIST_Propagates(self, mock_makedirs):
|
|
error = OSError()
|
|
error.errno = errno.EINTR
|
|
mock_makedirs.side_effect = error
|
|
with self.assertRaises(OSError):
|
|
file_system.safe_makedirs('')
|
|
|
|
@mock.patch('os.makedirs')
|
|
def test_SafeMakedirs_RootDir_DoesNotPropagate(self, mock_makedirs):
|
|
error = OSError()
|
|
error.errno = errno.EACCES
|
|
if sys.platform == 'win32':
|
|
mock_makedirs.side_effect = error
|
|
file_system.safe_makedirs('C:\\')
|
|
|
|
|
|
class TestGetNewestFileInDir(unittest.TestCase):
|
|
|
|
@mock.patch('glob.iglob')
|
|
def test_GetNewestFileInDir_NoResultsFound_ReturnsNone(self, mock_glob):
|
|
mock_glob.return_value.iglob = None
|
|
result = file_system.get_newest_file_in_dir('', '')
|
|
self.assertEqual(result, None)
|
|
|
|
@mock.patch('os.path.getctime')
|
|
@mock.patch('glob.iglob')
|
|
def test_GetNewestFileInDir_TwoResultsFound_ReturnsNewer(self, mock_glob, mock_ctime):
|
|
mock_glob.return_value = ['fileA.zip', 'fileB.zip']
|
|
mock_ctime.side_effect = [1, 2]
|
|
result = file_system.get_newest_file_in_dir('', [''])
|
|
|
|
self.assertEqual(result, 'fileB.zip')
|
|
|
|
@mock.patch('os.path.getctime')
|
|
@mock.patch('glob.iglob')
|
|
def test_GetNewestFileInDir_ThreeResultsTwoExts_CtimeCalledSixTimes(self, mock_glob, mock_ctime):
|
|
mock_glob.return_value = ['fileA.zip', 'fileB.zip', 'fileC.zip']
|
|
mock_ctime.side_effect = range(6)
|
|
file_system.get_newest_file_in_dir('', ['.zip', '.tgz'])
|
|
|
|
self.assertEqual(len(mock_ctime.mock_calls), 6)
|
|
|
|
|
|
class TestUnZip(unittest.TestCase):
|
|
|
|
decomp_obj_name = 'zipfile.ZipFile'
|
|
|
|
def setUp(self):
|
|
self.file_list = []
|
|
for i in range(25):
|
|
new_src_info = zipfile.ZipInfo('{}.txt'.format(i))
|
|
new_src_info.file_size = i
|
|
self.file_list.append(new_src_info)
|
|
|
|
self.mock_handle = mock.MagicMock()
|
|
self.mock_handle.infolist.return_value = self.file_list
|
|
self.mock_handle.__enter__.return_value = self.mock_handle
|
|
|
|
self.mock_decomp = mock.MagicMock()
|
|
self.mock_decomp.return_value = self.mock_handle
|
|
|
|
self.src_path = 'src.zip'
|
|
self.dest_path = 'dest'
|
|
self.exists = False
|
|
|
|
def tearDown(self):
|
|
self.mock_handle = None
|
|
self.mock_decomp = None
|
|
|
|
def call_decomp(self, dest, src, force=True, allow_exists=False):
|
|
return file_system.unzip(dest, src, force, allow_exists)
|
|
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
@mock.patch(decomp_obj_name)
|
|
def test_Unzip_DefaultArgs_CallsDecompressorWithSrc(self, mock_decomp, mock_join, mock_check_free, mock_exists):
|
|
|
|
mock_exists.return_value = self.exists
|
|
|
|
self.call_decomp(self.dest_path, self.src_path)
|
|
|
|
mock_decomp.assert_called_once_with(self.src_path, 'r')
|
|
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
def test_Unzip_DefaultArgs_CheckFreeSpaceCalledOnceWithOneGiBAdded(self, mock_join,
|
|
mock_check_free, mock_exists):
|
|
|
|
mock_exists.return_value = self.exists
|
|
total_size = sum(info.file_size for info in self.file_list)
|
|
|
|
with mock.patch(self.decomp_obj_name, self.mock_decomp):
|
|
self.call_decomp(self.dest_path, self.src_path)
|
|
|
|
mock_check_free.assert_called_once_with(self.dest_path,
|
|
total_size + file_system.ONE_GIB,
|
|
'Not enough space to safely extract: ')
|
|
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
def test_Unzip_DefaultArgs_JoinCalledWithNoPathNoExtension(self, mock_join, mock_check_free, mock_exists):
|
|
expected_name, _ = os.path.splitext(self.src_path)
|
|
|
|
mock_exists.return_value = self.exists
|
|
|
|
with mock.patch(self.decomp_obj_name, self.mock_decomp):
|
|
self.call_decomp(self.dest_path, self.src_path)
|
|
|
|
mock_join.assert_called_once_with(self.dest_path, expected_name)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
def test_Unzip_DefaultArgs_ReturnsCorrectPath(self, mock_join, mock_check_free):
|
|
|
|
build_name = 'build_name'
|
|
expected_path = self.dest_path+'\\'+build_name
|
|
mock_join.return_value = expected_path
|
|
|
|
path = ''
|
|
with mock.patch(self.decomp_obj_name, self.mock_decomp):
|
|
path = self.call_decomp(self.dest_path, self.src_path)
|
|
|
|
self.assertEqual(path, expected_path)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
def test_Unzip_ReleaseBuild_ReturnsCorrectPath(self, mock_join, mock_check_free):
|
|
|
|
build_name = 'lumberyard-1.2.0.3-54321-pc-1234'
|
|
expected_path = self.dest_path + '\\' + build_name
|
|
mock_join.return_value = expected_path
|
|
|
|
path = ''
|
|
self.src_path = r'C:\packages\lumberyard-1.2.0.3-54321-pc-1234.zip'
|
|
with mock.patch(self.decomp_obj_name, self.mock_decomp):
|
|
path = self.call_decomp(self.dest_path, self.src_path)
|
|
|
|
self.assertEqual(path, expected_path)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.logger')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
def test_Unzip_BuildDirExistsForceAndAllowExistsNotSet_CRITICALLogged(self, mock_join, mock_check_free,
|
|
mock_exists, mock_log):
|
|
|
|
force = False
|
|
allow_exists = False
|
|
self.exists = True
|
|
mock_exists.return_value = self.exists
|
|
level = logging.getLevelName("CRITICAL")
|
|
|
|
with mock.patch(self.decomp_obj_name, self.mock_decomp):
|
|
path = self.call_decomp(self.dest_path, self.src_path, force)
|
|
|
|
mock_log.log.assert_called_with(level, 'Found existing {}. Will not overwrite.'.format(path))
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.logger')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
def test_Unzip_AllowExistsSet_INFOLogged(self, mock_join, mock_check_free, mock_exists, mock_log):
|
|
|
|
force = False
|
|
allow_exists = True
|
|
self.exists = True
|
|
mock_exists.return_value = self.exists
|
|
level = logging.getLevelName("INFO")
|
|
|
|
with mock.patch(self.decomp_obj_name, self.mock_decomp):
|
|
path = self.call_decomp(self.dest_path, self.src_path, force, allow_exists)
|
|
|
|
mock_log.log.assert_called_with(level, 'Found existing {}. Will not overwrite.'.format(path))
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.logger')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
def test_Unzip_BuildDirExistsForceSetTrue_INFOLogged(self, mock_join, mock_check_free, mock_exists, mock_log):
|
|
|
|
path = ''
|
|
self.exists = True
|
|
mock_exists.return_value = self.exists
|
|
|
|
with mock.patch(self.decomp_obj_name, self.mock_decomp):
|
|
path = self.call_decomp(self.dest_path, self.src_path)
|
|
|
|
mock_log.info.assert_called_once()
|
|
|
|
|
|
class TestUnTgz(unittest.TestCase):
|
|
|
|
decomp_obj_name = 'tarfile.open'
|
|
|
|
def setUp(self):
|
|
self.file_list = []
|
|
for i in range(25):
|
|
new_src_info = tarfile.TarInfo('{}.txt'.format(i))
|
|
new_src_info.size = i
|
|
self.file_list.append(new_src_info)
|
|
|
|
self.mock_handle = mock.MagicMock()
|
|
self.mock_handle.__iter__.return_value = self.file_list
|
|
self.mock_handle.__enter__.return_value = self.mock_handle
|
|
|
|
self.mock_decomp = mock.MagicMock()
|
|
self.mock_decomp.return_value = self.mock_handle
|
|
|
|
self.src_path = 'src.tgz'
|
|
self.dest_path = 'dest'
|
|
|
|
# os.stat_result is (mode, inode, device, hard links, owner uid, size, atime, mtime, ctime)
|
|
self.src_stat = os.stat_result((0, 0, 0, 0, 0, 0, file_system.ONE_GIB, 0, 0, 0))
|
|
|
|
def tearDown(self):
|
|
self.mock_decomp = None
|
|
self.mock_handle = None
|
|
|
|
def call_decomp(self, dest, src, exact=False, force=True, allow_exists=False):
|
|
return file_system.untgz(dest, src, exact, force, allow_exists)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
@mock.patch(decomp_obj_name)
|
|
@mock.patch('os.stat')
|
|
def test_UnTgz_DefaultArgs_CallsDecompressorWithSrc(self, mock_stat, mock_decomp, mock_join, mock_check_free):
|
|
|
|
mock_stat.return_value = self.src_stat
|
|
self.call_decomp(self.dest_path, self.src_path)
|
|
|
|
mock_decomp.assert_called_once_with(self.src_path)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
@mock.patch('os.stat')
|
|
def test_UnTgz_DefaultArgs_CheckFreeSpaceCalledOnceWithOneGiBAdded(self, mock_stat, mock_join, mock_check_free):
|
|
|
|
mock_stat.return_value = self.src_stat
|
|
total_size = sum(info.size for info in self.file_list)
|
|
|
|
with mock.patch(self.decomp_obj_name, self.mock_decomp):
|
|
self.call_decomp(self.dest_path, self.src_path, True)
|
|
|
|
mock_check_free.assert_called_once_with(self.dest_path,
|
|
total_size + file_system.ONE_GIB,
|
|
'Not enough space to safely extract: ')
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
@mock.patch('os.stat')
|
|
def test_UnTgz_DefaultArgs_JoinCalledWithNoPathNoExtension(self, mock_stat, mock_join, mock_check_free):
|
|
expected_path, _ = os.path.splitext(os.path.basename(self.src_path))
|
|
|
|
mock_stat.return_value = self.src_stat
|
|
with mock.patch(self.decomp_obj_name, self.mock_decomp):
|
|
self.call_decomp(self.dest_path, self.src_path)
|
|
|
|
mock_join.assert_called_once_with(self.dest_path, expected_path)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
@mock.patch('os.stat')
|
|
def test_Untgz_DefaultArgs_ReturnsCorrectPath(self, mock_stat, mock_join, mock_check_free):
|
|
|
|
build_name = 'build_name'
|
|
expected_path = self.dest_path+'\\'+build_name
|
|
mock_join.return_value = expected_path
|
|
mock_stat.return_value = self.src_stat
|
|
|
|
path = ''
|
|
with mock.patch(self.decomp_obj_name, self.mock_decomp):
|
|
path = self.call_decomp(self.dest_path, self.src_path)
|
|
|
|
self.assertEqual(path, expected_path)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
@mock.patch('os.stat')
|
|
def test_Untgz_ReleaseBuild_ReturnsCorrectPath(self, mock_stat, mock_join, mock_check_free):
|
|
|
|
build_name = 'lumberyard-1.2.0.3-54321-pc-1234'
|
|
expected_path = self.dest_path + '\\' + build_name
|
|
mock_join.return_value = expected_path
|
|
mock_stat.return_value = self.src_stat
|
|
|
|
path = ''
|
|
self.src_path = r'C:\packages\lumberyard-1.2.0.3-54321-pc-1234.zip'
|
|
with mock.patch(self.decomp_obj_name, self.mock_decomp):
|
|
path = self.call_decomp(self.dest_path, self.src_path)
|
|
|
|
self.assertEqual(path, expected_path)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.logger')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
@mock.patch('os.stat')
|
|
def test_Untgz_BuildDirExistsForceAndAllowExistsNotSet_CRITICALLogged(self, mock_stat, mock_join,
|
|
mock_check_free, mock_exists, mock_log):
|
|
|
|
force = False
|
|
mock_exists.return_value = True
|
|
mock_stat.return_value = self.src_stat
|
|
|
|
with mock.patch(self.decomp_obj_name, self.mock_decomp):
|
|
path = self.call_decomp(self.dest_path, self.src_path, False, force)
|
|
|
|
level = logging.getLevelName("CRITICAL")
|
|
mock_log.log.assert_called_with(level, 'Found existing {}. Will not overwrite.'.format(path))
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.logger')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
@mock.patch('os.stat')
|
|
def test_Untgz_AllowExiststSet_INFOLogged(self, mock_stat, mock_join, mock_check_free, mock_exists, mock_log):
|
|
|
|
allow_exists = True
|
|
force = False
|
|
mock_exists.return_value = True
|
|
mock_stat.return_value = self.src_stat
|
|
level = logging.getLevelName("INFO")
|
|
|
|
with mock.patch(self.decomp_obj_name, self.mock_decomp):
|
|
path = self.call_decomp(self.dest_path, self.src_path, False, force, allow_exists)
|
|
|
|
mock_log.log.assert_called_with(level, 'Found existing {}. Will not overwrite.'.format(path))
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.logger')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('ly_test_tools.environment.file_system.check_free_space')
|
|
@mock.patch('os.path.join')
|
|
@mock.patch('os.stat')
|
|
def test_Untgz_BuildDirExistsForceSetTrue_INFOLogged(self, mock_stat, mock_join,
|
|
mock_check_free, mock_exists, mock_log):
|
|
|
|
path = ''
|
|
mock_exists.return_value = True
|
|
mock_stat.return_value = self.src_stat
|
|
|
|
with mock.patch(self.decomp_obj_name, self.mock_decomp):
|
|
path = self.call_decomp(self.dest_path, self.src_path)
|
|
|
|
mock_log.info.assert_called_once()
|
|
|
|
|
|
class TestChangePermissions(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
# Create a mock of a os.walk return iterable.
|
|
self.root = 'root'
|
|
self.dirs = ['dir1', 'dir2']
|
|
self.files = ['file1', 'file2']
|
|
self.walk_iter = iter([(self.root, self.dirs, self.files)])
|
|
|
|
def tearDown(self):
|
|
self.root = None
|
|
self.dirs = None
|
|
self.files = None
|
|
self.walk_iter = None
|
|
|
|
@mock.patch('os.walk')
|
|
@mock.patch('os.chmod')
|
|
def test_ChangePermissions_DefaultValues_ChmodCalledCorrectly(self, mock_chmod, mock_walk):
|
|
os.walk.return_value = self.walk_iter
|
|
file_system.change_permissions('.', 0o777)
|
|
self.assertEqual(mock_chmod.mock_calls, [mock.call(os.path.join(self.root, self.dirs[0]), 0o777),
|
|
mock.call(os.path.join(self.root, self.dirs[1]), 0o777),
|
|
mock.call(os.path.join(self.root, self.files[0]), 0o777),
|
|
mock.call(os.path.join(self.root, self.files[1]), 0o777)])
|
|
|
|
@mock.patch('os.walk')
|
|
@mock.patch('os.chmod')
|
|
def test_ChangePermissions_DefaultValues_ReturnsTrueOnSuccess(self, mock_chmod, mock_walk):
|
|
os.walk.return_value = self.walk_iter
|
|
self.assertEqual(file_system.change_permissions('.', 0o777), True)
|
|
|
|
@mock.patch('os.walk')
|
|
@mock.patch('os.chmod')
|
|
def test_ChangePermissions_OSErrorRaised_ReturnsFalse(self, mock_chmod, mock_walk):
|
|
os.walk.return_value = self.walk_iter
|
|
os.chmod.side_effect = OSError()
|
|
self.assertEqual(file_system.change_permissions('.', 0o777), False)
|
|
|
|
|
|
class MockStatResult():
|
|
def __init__(self, st_mode):
|
|
self.st_mode = st_mode
|
|
|
|
class TestUnlockFile(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.file_name = 'file'
|
|
|
|
@mock.patch('os.stat')
|
|
@mock.patch('os.chmod')
|
|
@mock.patch('os.access')
|
|
def test_UnlockFile_WriteLocked_UnlockFile(self, mock_access, mock_chmod, mock_stat):
|
|
mock_access.return_value = False
|
|
os.stat.return_value = MockStatResult(stat.S_IREAD)
|
|
|
|
success = file_system.unlock_file(self.file_name)
|
|
mock_chmod.assert_called_once_with(self.file_name, stat.S_IREAD | stat.S_IWRITE)
|
|
|
|
self.assertTrue(success)
|
|
|
|
@mock.patch('os.stat')
|
|
@mock.patch('os.chmod')
|
|
@mock.patch('os.access')
|
|
def test_UnlockFile_AlreadyUnlocked_LogAlreadyUnlocked(self, mock_access, mock_chmod, mock_stat):
|
|
mock_access.return_value = True
|
|
os.stat.return_value = MockStatResult(stat.S_IREAD | stat.S_IWRITE)
|
|
|
|
success = file_system.unlock_file(self.file_name)
|
|
|
|
self.assertFalse(success)
|
|
|
|
|
|
class TestLockFile(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.file_name = 'file'
|
|
|
|
@mock.patch('os.stat')
|
|
@mock.patch('os.chmod')
|
|
@mock.patch('os.access')
|
|
def test_LockFile_UnlockedFile_FileLockedSuccessReturnsTrue(self, mock_access, mock_chmod, mock_stat):
|
|
mock_access.return_value = True
|
|
os.stat.return_value = MockStatResult(stat.S_IREAD | stat.S_IWRITE)
|
|
|
|
success = file_system.lock_file(self.file_name)
|
|
mock_chmod.assert_called_once_with(self.file_name, stat.S_IREAD)
|
|
|
|
self.assertTrue(success)
|
|
|
|
@mock.patch('os.stat')
|
|
@mock.patch('os.chmod')
|
|
@mock.patch('os.access')
|
|
def test_LockFile_AlreadyLocked_FileLockedFailedReturnsFalse(self, mock_access, mock_chmod, mock_stat):
|
|
mock_access.return_value = False
|
|
os.stat.return_value = MockStatResult(stat.S_IREAD)
|
|
|
|
success = file_system.lock_file(self.file_name)
|
|
|
|
self.assertFalse(success)
|
|
|
|
|
|
class TestRemoveSymlinks(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
# Create a mock of a os.walk return iterable.
|
|
self.root = 'root'
|
|
self.dirs = ['dir1', 'dir2']
|
|
self.files = ['file1', 'file2']
|
|
self.walk_iter = iter([(self.root, self.dirs, self.files)])
|
|
|
|
@mock.patch('os.walk')
|
|
@mock.patch('os.rmdir')
|
|
def test_RemoveSymlinks_DefaultValues_RmdirCalledCorrectly(self, mock_rmdir, mock_walk):
|
|
os.walk.return_value = self.walk_iter
|
|
|
|
file_system.remove_symlinks('.')
|
|
|
|
self.assertEqual(mock_rmdir.mock_calls, [mock.call(os.path.join(self.root, self.dirs[0])),
|
|
mock.call(os.path.join(self.root, self.dirs[1]))])
|
|
|
|
@mock.patch('os.walk')
|
|
@mock.patch('os.rmdir')
|
|
def test_RemoveSymlinks_OSErrnoEEXISTRaised_RaiseOSError(self, mock_rmdir, mock_walk):
|
|
os.walk.return_value = self.walk_iter
|
|
error = OSError()
|
|
error.errno = errno.EEXIST
|
|
mock_rmdir.side_effect = error
|
|
|
|
with self.assertRaises(OSError):
|
|
file_system.remove_symlinks('.')
|
|
|
|
|
|
class TestDelete(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.path_list = ['file1', 'file2', 'dir1', 'dir2']
|
|
|
|
def tearDown(self):
|
|
self.path_list = None
|
|
|
|
@mock.patch('os.path.isdir')
|
|
@mock.patch('os.path.isfile')
|
|
@mock.patch('ly_test_tools.environment.file_system.change_permissions', mock.MagicMock())
|
|
@mock.patch('shutil.rmtree', mock.MagicMock())
|
|
@mock.patch('os.remove', mock.MagicMock())
|
|
def test_Delete_StringArg_ConvertsToList(self, mock_isfile, mock_isdir):
|
|
mock_file_str = 'foo'
|
|
mock_isdir.return_value = False
|
|
mock_isfile.return_value = False
|
|
file_system.delete(mock_file_str, del_files=True, del_dirs=True)
|
|
|
|
mock_isfile.assert_called_once_with(mock_file_str)
|
|
mock_isdir.assert_called_once_with(mock_file_str)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.change_permissions')
|
|
@mock.patch('shutil.rmtree')
|
|
@mock.patch('os.remove')
|
|
@mock.patch('os.path.isdir')
|
|
@mock.patch('os.path.isfile')
|
|
def test_ChangePermissions_OSErrorRaised_ReturnsZero(self, mock_isfile, mock_isdir, mock_remove, mock_rmtree, mock_chper):
|
|
mock_rmtree.side_effect = OSError()
|
|
self.assertEqual(file_system.delete(self.path_list, del_files=True, del_dirs=True), False)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.change_permissions')
|
|
@mock.patch('shutil.rmtree')
|
|
@mock.patch('os.remove')
|
|
@mock.patch('os.path.isdir')
|
|
@mock.patch('os.path.isfile')
|
|
def test_ChangePermissions_DefaultValues_ReturnsLenOfList(self,
|
|
mock_isfile, mock_isdir, mock_remove, mock_rmtree, mock_chper):
|
|
self.assertEqual(file_system.delete(self.path_list, del_files=True, del_dirs=True), True)
|
|
|
|
@mock.patch('os.chmod')
|
|
@mock.patch('ly_test_tools.environment.file_system.change_permissions')
|
|
@mock.patch('shutil.rmtree')
|
|
@mock.patch('os.remove')
|
|
@mock.patch('os.path.isdir')
|
|
@mock.patch('os.path.isfile')
|
|
def test_ChangePermissions_DirsFalse_RMTreeNotCalled(self,
|
|
mock_isfile, mock_isdir, mock_remove, mock_rmtree, mock_chper,
|
|
mock_chmod):
|
|
file_system.delete(self.path_list, del_files=True, del_dirs=False)
|
|
self.assertEqual(mock_rmtree.called, False)
|
|
self.assertEqual(mock_chmod.called, True)
|
|
self.assertEqual(mock_remove.called, True)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.change_permissions')
|
|
@mock.patch('shutil.rmtree')
|
|
@mock.patch('os.remove')
|
|
@mock.patch('os.path.isdir')
|
|
@mock.patch('os.path.isfile')
|
|
def test_ChangePermissions_NoDirs_RMTreeNotCalled(self,
|
|
mock_isfile, mock_isdir, mock_remove, mock_rmtree, mock_chper):
|
|
mock_isdir.return_value = False
|
|
file_system.delete(self.path_list, del_files=False, del_dirs=True)
|
|
self.assertEqual(mock_rmtree.called, False)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.change_permissions')
|
|
@mock.patch('shutil.rmtree')
|
|
@mock.patch('os.remove')
|
|
@mock.patch('os.path.isdir')
|
|
@mock.patch('os.path.isfile')
|
|
def test_ChangePermissions_FilesFalse_RemoveNotCalled(self,
|
|
mock_isfile, mock_isdir, mock_remove, mock_rmtree, mock_chper):
|
|
file_system.delete(self.path_list, del_files=False, del_dirs=True)
|
|
self.assertEqual(mock_rmtree.called, True)
|
|
self.assertEqual(mock_remove.called, False)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.change_permissions')
|
|
@mock.patch('shutil.rmtree')
|
|
@mock.patch('os.remove')
|
|
@mock.patch('os.path.isdir')
|
|
@mock.patch('os.path.isfile')
|
|
def test_ChangePermissions_NoFiles_RemoveNotCalled(self,
|
|
mock_isfile, mock_isdir, mock_remove, mock_rmtree, mock_chper):
|
|
mock_isfile.return_value = False
|
|
file_system.delete(self.path_list, del_files=True, del_dirs=False)
|
|
self.assertEqual(mock_remove.called, False)
|
|
|
|
|
|
class TestDeleteOldest(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.path_list = ['file1', 'file2', 'dir1', 'dir2']
|
|
self.age_list = [4, 3, 2, 1]
|
|
|
|
def tearDown(self):
|
|
self.path_list = None
|
|
self.age_list = None
|
|
|
|
@mock.patch('glob.iglob')
|
|
@mock.patch('os.path.getctime')
|
|
@mock.patch('ly_test_tools.environment.file_system.delete')
|
|
def test_DeleteOldest_DefaultValuesKeepAll_DeleteCalledWithEmptyList(self, mock_delete, mock_ctime, mock_glob):
|
|
mock_glob.return_value = self.path_list
|
|
mock_ctime.side_effect = self.age_list
|
|
# Nothing will be deleted because it's keeping everything.
|
|
file_system.delete_oldest('', len(self.path_list), del_files=True, del_dirs=False)
|
|
mock_delete.assert_called_once_with([], True, False)
|
|
|
|
@mock.patch('glob.iglob')
|
|
@mock.patch('os.path.getctime')
|
|
@mock.patch('ly_test_tools.environment.file_system.delete')
|
|
def test_DeleteOldest_DefaultValuesKeepNone_DeleteCalledWithList(self, mock_delete, mock_ctime, mock_glob):
|
|
mock_glob.return_value = self.path_list
|
|
mock_ctime.side_effect = self.age_list
|
|
# Everything will be deleted because it's keeping nothing.
|
|
file_system.delete_oldest('', 0, del_files=True, del_dirs=False)
|
|
mock_delete.assert_called_once_with(self.path_list, True, False)
|
|
|
|
@mock.patch('glob.iglob')
|
|
@mock.patch('os.path.getctime')
|
|
@mock.patch('ly_test_tools.environment.file_system.delete')
|
|
def test_DeleteOldest_DefaultValuesKeepOne_DeleteCalledWithoutNewest(self, mock_delete, mock_ctime, mock_glob):
|
|
mock_glob.return_value = self.path_list
|
|
mock_ctime.side_effect = self.age_list
|
|
file_system.delete_oldest('', 1, del_files=True, del_dirs=False)
|
|
self.path_list.pop(0)
|
|
mock_delete.assert_called_once_with(self.path_list, True, False)
|
|
|
|
@mock.patch('glob.iglob')
|
|
@mock.patch('os.path.getctime')
|
|
@mock.patch('ly_test_tools.environment.file_system.delete')
|
|
def test_DeleteOldest_UnsortedListDeleteOldest_DeleteCalledWithOldest(self, mock_delete, mock_ctime, mock_glob):
|
|
mock_glob.return_value = ['newest', 'old', 'newer']
|
|
mock_ctime.side_effect = [100, 0, 50]
|
|
file_system.delete_oldest('', 2, del_files=True, del_dirs=False)
|
|
mock_delete.assert_called_once_with(['old'], True, False)
|
|
|
|
|
|
class TestMakeJunction(unittest.TestCase):
|
|
|
|
def test_MakeJunction_Nondir_RaisesIOError(self):
|
|
with self.assertRaises(IOError):
|
|
file_system.make_junction('', '')
|
|
|
|
@mock.patch('os.path.isdir')
|
|
@mock.patch('sys.platform', 'linux2')
|
|
def test_MakeJunction_NoSupportPlatform_RaisesIOError(self, mock_isdir):
|
|
mock_isdir.return_value = True
|
|
with self.assertRaises(IOError):
|
|
file_system.make_junction('', '')
|
|
|
|
@mock.patch('subprocess.check_call')
|
|
@mock.patch('os.path.isdir')
|
|
@mock.patch('sys.platform', 'win32')
|
|
def test_MakeJunction_Win32_SubprocessFails_RaiseSubprocessError(self, mock_isdir, mock_sub_call):
|
|
mock_isdir.return_value = True
|
|
mock_sub_call.side_effect = subprocess.CalledProcessError(1, 'cmd', 'output')
|
|
|
|
with self.assertRaises(subprocess.CalledProcessError):
|
|
file_system.make_junction('', '')
|
|
|
|
@mock.patch('subprocess.check_output')
|
|
@mock.patch('os.path.isdir')
|
|
@mock.patch('sys.platform', 'darwin')
|
|
def test_MakeJunction_Darwin_SubprocessFails_RaiseSubprocessError(self, mock_isdir, mock_sub_call):
|
|
mock_isdir.return_value = True
|
|
mock_sub_call.side_effect = subprocess.CalledProcessError(1, 'cmd', 'output')
|
|
|
|
with self.assertRaises(subprocess.CalledProcessError):
|
|
file_system.make_junction('', '')
|
|
|
|
@mock.patch('subprocess.check_output')
|
|
@mock.patch('os.path.isdir')
|
|
@mock.patch('sys.platform', 'win32')
|
|
def test_MakeJunction_Win32_SubprocessCall_Calls(self, mock_isdir, mock_sub_call):
|
|
mock_isdir.return_value = True
|
|
src = 'source'
|
|
dest = 'destination'
|
|
|
|
file_system.make_junction(dest, src)
|
|
|
|
mock_sub_call.assert_called_once_with(['mklink', '/J', dest, src], shell=True)
|
|
|
|
@mock.patch('subprocess.check_output')
|
|
@mock.patch('os.path.isdir')
|
|
@mock.patch('sys.platform', 'darwin')
|
|
def test_MakeJunction_Darwin_SubprocessCall_Calls(self, mock_isdir, mock_sub_call):
|
|
mock_isdir.return_value = True
|
|
src = 'source'
|
|
dest = 'destination'
|
|
|
|
file_system.make_junction(dest, src)
|
|
|
|
mock_sub_call.assert_called_once_with(['ln', dest, src])
|
|
|
|
|
|
class TestFileBackup(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self._dummy_dir = os.path.join('somewhere', 'something')
|
|
self._dummy_file = 'dummy.txt'
|
|
self._dummy_backup_file = os.path.join(self._dummy_dir, '{}.bak'.format(self._dummy_file))
|
|
|
|
@mock.patch('shutil.copy')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('os.path.isdir')
|
|
def test_BackupSettings_SourceExists_BackupCreated(self, mock_path_isdir, mock_backup_exists, mock_copy):
|
|
mock_path_isdir.return_value = True
|
|
mock_backup_exists.side_effect = [True, False]
|
|
|
|
file_system.create_backup(self._dummy_file, self._dummy_dir)
|
|
|
|
mock_copy.assert_called_with(self._dummy_file, self._dummy_backup_file)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.logger.warning')
|
|
@mock.patch('shutil.copy')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('os.path.isdir')
|
|
def test_BackupSettings_BackupExists_WarningLogged(self, mock_path_isdir, mock_backup_exists, mock_copy, mock_logger_warning):
|
|
mock_path_isdir.return_value = True
|
|
mock_backup_exists.return_value = True
|
|
|
|
file_system.create_backup(self._dummy_file, self._dummy_dir)
|
|
|
|
mock_copy.assert_called_with(self._dummy_file, self._dummy_backup_file)
|
|
mock_logger_warning.assert_called_once()
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.logger.warning')
|
|
@mock.patch('shutil.copy')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('os.path.isdir')
|
|
def test_BackupSettings_SourceNotExists_WarningLogged(self, mock_path_isdir, mock_backup_exists, mock_copy, mock_logger_warning):
|
|
mock_path_isdir.return_value = True
|
|
mock_backup_exists.return_value = False
|
|
|
|
file_system.create_backup(self._dummy_file, self._dummy_dir)
|
|
|
|
mock_copy.assert_not_called()
|
|
mock_logger_warning.assert_called_once()
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.logger.warning')
|
|
@mock.patch('shutil.copy')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('os.path.isdir')
|
|
def test_BackupSettings_CannotCopy_WarningLogged(self, mock_path_isdir, mock_backup_exists, mock_copy, mock_logger_warning):
|
|
mock_path_isdir.return_value = True
|
|
mock_backup_exists.side_effect = [True, False]
|
|
mock_copy.side_effect = Exception('some error')
|
|
|
|
file_system.create_backup(self._dummy_file, self._dummy_dir)
|
|
|
|
mock_copy.assert_called_with(self._dummy_file, self._dummy_backup_file)
|
|
mock_logger_warning.assert_called_once()
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.logger.error')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('os.path.isdir')
|
|
def test_BackupSettings_InvalidDir_ErrorLogged(self, mock_path_isdir, mock_backup_exists, mock_logger_error):
|
|
mock_path_isdir.return_value = False
|
|
mock_backup_exists.return_value = False
|
|
|
|
file_system.create_backup(self._dummy_file, None)
|
|
|
|
mock_logger_error.assert_called_once()
|
|
|
|
|
|
class TestFileBackupRestore(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self._dummy_dir = os.path.join('somewhere', 'something')
|
|
self._dummy_file = 'dummy.txt'
|
|
self._dummy_backup_file = os.path.join(self._dummy_dir, '{}.bak'.format(self._dummy_file))
|
|
|
|
@mock.patch('shutil.copy')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('os.path.isdir')
|
|
def test_RestoreSettings_BackupRestore_Success(self, mock_path_isdir, mock_exists, mock_copy):
|
|
mock_path_isdir.return_value = True
|
|
mock_exists.return_value = True
|
|
file_system.restore_backup(self._dummy_file, self._dummy_dir)
|
|
|
|
mock_copy.assert_called_with(self._dummy_backup_file, self._dummy_file)
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.logger.warning')
|
|
@mock.patch('shutil.copy')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('os.path.isdir')
|
|
def test_RestoreSettings_CannotCopy_WarningLogged(self, mock_path_isdir, mock_exists, mock_copy, mock_logger_warning):
|
|
mock_path_isdir.return_value = True
|
|
mock_exists.return_value = True
|
|
mock_copy.side_effect = Exception('some error')
|
|
|
|
file_system.restore_backup(self._dummy_file, self._dummy_dir)
|
|
|
|
mock_copy.assert_called_with(self._dummy_backup_file, self._dummy_file)
|
|
mock_logger_warning.assert_called_once()
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.logger.warning')
|
|
@mock.patch('shutil.copy')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('os.path.isdir')
|
|
def test_RestoreSettings_BackupNotExists_WarningLogged(self, mock_path_isdir, mock_exists, mock_copy, mock_logger_warning):
|
|
mock_path_isdir.return_value = True
|
|
mock_exists.return_value = False
|
|
|
|
file_system.restore_backup(self._dummy_file, self._dummy_dir)
|
|
|
|
mock_copy.assert_not_called()
|
|
mock_logger_warning.assert_called_once()
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.logger.error')
|
|
def test_RestoreSettings_InvalidDir_ErrorLogged(self, mock_logger_error):
|
|
file_system.restore_backup(self._dummy_file, None)
|
|
|
|
mock_logger_error.assert_called_once()
|
|
|
|
@mock.patch('ly_test_tools.environment.file_system.logger.error')
|
|
@mock.patch('os.path.isdir')
|
|
def test_RestoreSettings_InvalidDir_ErrorLogged(self, mock_path_isdir, mock_logger_error):
|
|
mock_path_isdir.return_value = False
|
|
file_system.restore_backup(self._dummy_file, self._dummy_dir)
|
|
|
|
mock_logger_error.assert_called_once()
|
|
|
|
|
|
class TestReduceFileName(unittest.TestCase):
|
|
|
|
def test_Reduce_LongString_ReturnsReducedString(self):
|
|
target_name = 'really_long_string_that_needs_reduction' # len(mock_file_name) == 39
|
|
max_length = 25
|
|
|
|
under_test = file_system.reduce_file_name_length(target_name, max_length)
|
|
|
|
assert len(under_test) == max_length
|
|
|
|
def test_Reduce_ShortString_ReturnsSameString(self):
|
|
target_name = 'less_than_max' # len(mock_file_name) == 13
|
|
max_length = 25
|
|
|
|
under_test = file_system.reduce_file_name_length(target_name, max_length)
|
|
|
|
assert under_test == target_name
|
|
|
|
def test_Reduce_NoString_RaisesTypeError(self):
|
|
with pytest.raises(TypeError):
|
|
file_system.reduce_file_name_length(max_length=25)
|
|
|
|
def test_Reduce_NoMaxLength_RaisesTypeError(self):
|
|
target_name = 'raises_type_error'
|
|
|
|
with pytest.raises(TypeError):
|
|
file_system.reduce_file_name_length(file_name=target_name)
|