# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""shell tests."""
import mock
import os
import unittest

from pyfakefs import fake_filesystem_unittest

from system import environment
from system import shell
from tests.test_libs import helpers as test_helpers
from tests.test_libs import test_utils


class RemoveEmptyFilesTest(fake_filesystem_unittest.TestCase):
  """Tests for remove_empty_files."""

  def setUp(self):
    # FIXME: Add support for Windows.
    if not environment.is_posix():
      self.skipTest('Process tests are only applicable for posix platforms.')

    test_utils.set_up_pyfakefs(self)

  def test_remove(self):
    """Test remove."""
    self.fs.CreateFile('/test/aa/bb.txt', contents='s')
    self.fs.CreateFile('/test/aa/cc.txt', contents='')
    self.fs.CreateFile('/test/aa/aa/dd.txt', contents='s')
    self.fs.CreateFile('/test/aa/aa/aa.txt', contents='')

    shell.remove_empty_files('/test')

    self.assertTrue(os.path.exists('/test/aa/bb.txt'))
    self.assertTrue(os.path.exists('/test/aa/aa/dd.txt'))
    self.assertFalse(os.path.exists('/test/aa/cc.txt'))
    self.assertFalse(os.path.exists('/test/aa/aa/aa.txt'))

  def test_ignore_file(self):
    self.fs.CreateFile('/test/aa/cc.txt', contents='')
    shell.remove_empty_files('/test/aa/cc.txt')
    self.assertTrue(os.path.exists('/test/aa/cc.txt'))

  @mock.patch('os.remove', autospec=True)
  def test_exception(self, mock_remove):
    # bypass pyfakefs's os.remove.
    os.remove = mock_remove
    mock_remove.side_effect = OSError()

    self.fs.CreateFile('/test/aa/cc.txt', contents='')
    shell.remove_empty_files('/test')
    self.assertTrue(os.path.exists('/test/aa/cc.txt'))


class RemoveDirectoryTest(unittest.TestCase):
  """Tests for remove_directory."""

  def setUp(self):
    test_helpers.patch(self, [
        'os.chmod',
        'os.mkdir',
        'os.path.exists',
        'os.system',
        'system.environment.platform',
        'metrics.logs.log_error',
        'metrics.logs.log_warn',
        'shutil.rmtree',
    ])

  def _test_remove_os_specific(self, platform, recreate, raise_mkdir_error):
    """Helper for testing removing dir with os-specific command."""
    self.mock.platform.return_value = platform
    self.mock.exists.side_effect = [True, False, False]
    if raise_mkdir_error:
      self.mock.mkdir.side_effect = OSError()

    result = shell.remove_directory('dir', recreate=recreate)

    if recreate:
      self.assertEqual(not raise_mkdir_error, result)
    else:
      self.assertTrue(result)

    self.mock.rmtree.assert_has_calls([])
    if recreate:
      self.mock.mkdir.assert_has_calls([mock.call('dir')])
    else:
      self.mock.mkdir.assert_has_calls([])

  def test_remove_os_specific_windows(self):
    """Test remove with os-specific command on windows."""
    self._test_remove_os_specific('WINDOWS', True, False)
    self.mock.system.assert_has_calls([mock.call('rd /s /q "dir" > nul 2>&1')])

  def test_remove_os_specific_non_windows(self):
    """Test remove with os-specific command on non-windows."""
    self._test_remove_os_specific('LINUX', True, False)
    self.mock.system.assert_has_calls(
        [mock.call('rm -rf "dir" > /dev/null 2>&1')])

  def test_remove_without_recreate(self):
    """Test remove without recreate."""
    self._test_remove_os_specific('LINUX', False, True)

  def test_remove_with_mkdir_error(self):
    """Test remove when mkdir errors."""
    self._test_remove_os_specific('LINUX', True, True)

  def test_remove_shutil_success(self):
    """Test remove with shutil."""
    self.mock.exists.side_effect = [True, True, False]
    self.assertTrue(shell.remove_directory('dir'))
    self.mock.system.assert_has_calls(
        [mock.call('rm -rf "dir" > /dev/null 2>&1')])
    self.mock.rmtree.assert_has_calls([mock.call('dir', onerror=mock.ANY)])

  def test_remove_shutil_failure(self):
    """Test remove with shutil but fails."""
    self.mock.exists.side_effect = [True, True, True]
    self.assertFalse(shell.remove_directory('dir'))
    self.mock.log_error.assert_has_calls(
        [mock.call('Failed to clear directory dir.')])
    self.assertEqual(0, self.mock.log_warn.call_count)
    self.mock.system.assert_has_calls(
        [mock.call('rm -rf "dir" > /dev/null 2>&1')])
    self.mock.rmtree.assert_has_calls([mock.call('dir', onerror=mock.ANY)])

  def test_remove_shutil_failure_ignore_errors(self):
    self.mock.exists.side_effect = [True, True, True]
    self.assertFalse(shell.remove_directory('dir', ignore_errors=True))
    self.mock.log_warn.assert_has_calls(
        [mock.call('Failed to clear directory dir.')])
    self.assertEqual(0, self.mock.log_error.call_count)
    self.mock.system.assert_has_calls(
        [mock.call('rm -rf "dir" > /dev/null 2>&1')])
    self.mock.rmtree.assert_has_calls([mock.call('dir', onerror=mock.ANY)])

  def test_remove_shutil_onerror(self):
    """Test shutil invoking onerror."""
    self.mock.exists.side_effect = [True, True, False]
    self.assertTrue(shell.remove_directory('dir'))
    self.mock.system.assert_has_calls(
        [mock.call('rm -rf "dir" > /dev/null 2>&1')])
    self.mock.rmtree.assert_has_calls([mock.call('dir', onerror=mock.ANY)])

    onerror = self.mock.rmtree.call_args[1]['onerror']
    fake_fn = mock.MagicMock()
    fake_fn.side_effect = OSError()

    onerror(fake_fn, 'dir/child', ImportError())

    self.mock.chmod.assert_has_calls([mock.call('dir/child', 0o750)])
    fake_fn.assert_has_calls([mock.call('dir/child')])


class GetDirectoryFileCount(fake_filesystem_unittest.TestCase):
  """Tests for get_directory_file_count."""

  def setUp(self):
    test_utils.set_up_pyfakefs(self)

  def test(self):
    """Test get_directory_file_count."""
    self.fs.CreateFile('/test/aa/bb.txt', contents='abc')
    self.fs.CreateFile('/test/aa/cc.txt', contents='def')
    self.fs.CreateFile('/test/aa/aa/aa.txt', contents='ghi')
    self.fs.CreateFile('/test/aa/aa/dd.txt', contents='t')

    self.assertEqual(shell.get_directory_file_count('/test/aa'), 4)


class GetDirectorySizeTest(fake_filesystem_unittest.TestCase):
  """Tests for get_directory_size."""

  def setUp(self):
    test_utils.set_up_pyfakefs(self)

  def test(self):
    """Test get_directory_size."""
    self.fs.CreateFile('/test/aa/bb.txt', contents='abc')
    self.fs.CreateFile('/test/aa/cc.txt', contents='def')
    self.fs.CreateFile('/test/aa/aa/aa.txt', contents='ghi')
    self.fs.CreateFile('/test/aa/aa/dd.txt', contents='t')

    self.assertEqual(shell.get_directory_size('/test/aa'), 10)


class WhichTest(fake_filesystem_unittest.TestCase):
  """Tests for which (shutil.which)."""

  def setUp(self):
    # FIXME: Add support for Windows.
    if not environment.is_posix():
      self.skipTest('Which test is only supported on posix platforms.')

  def test(self):
    self.assertEqual('/bin/ls', shell.which('ls'))


class ClearSystemTempDirectoryTest(fake_filesystem_unittest.TestCase):
  """Tests for clear_system_temp_directory."""

  def setUp(self):
    test_helpers.patch(self, [
        'tempfile.gettempdir',
    ])
    self.mock.gettempdir.return_value = '/tmp'

    test_utils.set_up_pyfakefs(self)

  def test(self):
    """Test clear_system_temp_directory works as expected."""
    self.fs.CreateFile('/tmp/aa/bb.txt', contents='abc')
    self.fs.CreateFile('/tmp/cc/dd/ee.txt', contents='def')
    self.fs.CreateDirectory('/tmp/ff/gg')
    self.fs.CreateDirectory('/tmp/hh')
    self.fs.CreateDirectory('/unrelated')
    self.fs.CreateFile('/unrelated/zz.txt', contents='zzz')
    os.symlink('/unrelated/zz.txt', '/tmp/hh/gg.txt')
    os.symlink('/unrelated', '/tmp/ii')

    shell.clear_system_temp_directory()

    self.assertTrue(os.path.exists('/tmp'))
    self.assertTrue(os.path.exists('/unrelated'))
    self.assertEqual(shell.get_directory_file_count('/tmp'), 0)
    self.assertEqual(shell.get_directory_file_count('/unrelated'), 1)
    self.assertFalse(os.path.exists('/tmp/aa/bb.txt'))
    self.assertFalse(os.path.exists('/tmp/cc/dd/ee.txt'))
    self.assertFalse(os.path.exists('/tmp/ff/gg'))
    self.assertFalse(os.path.exists('/tmp/hh'))


class GetExecuteCommand(unittest.TestCase):
  """Test that the correct commands to run files are returned."""

  def call_and_assert_helper(self, expected_command, file_to_execute):
    """Call get_execute_command on |file_to_execute| and assert result equal to
    |expected_command|."""
    self.assertEqual(expected_command,
                     shell.get_execute_command(file_to_execute))

  def test_standard_script(self):
    """Test correct command returned for python script."""
    script_name = 'script.py'
    expected_command = 'python %s' % script_name
    self.call_and_assert_helper(expected_command, script_name)

  def test_java(self):
    """Test correct launch command returned for Java class."""
    script_name = 'javaclassfile.class'
    expected_command = 'java javaclassfile'
    self.call_and_assert_helper(expected_command, script_name)

  def test_binary(self):
    """Test correct launch command returned for a binary (executable) file."""
    executable_name = 'executable'
    self.call_and_assert_helper(executable_name, executable_name)

    executable_name += '.exe'
    self.call_and_assert_helper(executable_name, executable_name)


class GetInterpreter(object):
  """Test that the correct interpreters to execute a file are returned."""

  def get_interpreted_file_test(self):
    """Test correct interpreter is returned for a file that needs one."""
    self.assertEqual('python', shell.get_interpreter('run.py'))

  def get_non_interpreter_file_test(self):
    """Test that None is returned for a file that doesn't need one. We don't
    want empty string since this is easier to than None. """
    self.assertIsNone(shell.get_interpreter('executable'))

[((1877, 1915), 'mock.patch', 'mock.patch', (['"""os.remove"""'], {'autospec': '(True)'}), "('os.remove', autospec=True)\n", (1887, 1915), False, 'import mock\n'), ((1106, 1138), 'tests.test_libs.test_utils.set_up_pyfakefs', 'test_utils.set_up_pyfakefs', (['self'], {}), '(self)\n', (1132, 1138), False, 'from tests.test_libs import test_utils\n'), ((1421, 1454), 'system.shell.remove_empty_files', 'shell.remove_empty_files', (['"""/test"""'], {}), "('/test')\n", (1445, 1454), False, 'from system import shell\n'), ((1774, 1817), 'system.shell.remove_empty_files', 'shell.remove_empty_files', (['"""/test/aa/cc.txt"""'], {}), "('/test/aa/cc.txt')\n", (1798, 1817), False, 'from system import shell\n'), ((2120, 2153), 'system.shell.remove_empty_files', 'shell.remove_empty_files', (['"""/test"""'], {}), "('/test')\n", (2144, 2153), False, 'from system import shell\n'), ((2317, 2505), 'tests.test_libs.helpers.patch', 'test_helpers.patch', (['self', "['os.chmod', 'os.mkdir', 'os.path.exists', 'os.system',\n    'system.environment.platform', 'metrics.logs.log_error',\n    'metrics.logs.log_warn', 'shutil.rmtree']"], {}), "(self, ['os.chmod', 'os.mkdir', 'os.path.exists',\n    'os.system', 'system.environment.platform', 'metrics.logs.log_error',\n    'metrics.logs.log_warn', 'shutil.rmtree'])\n", (2335, 2505), True, 'from tests.test_libs import helpers as test_helpers\n'), ((2904, 2952), 'system.shell.remove_directory', 'shell.remove_directory', (['"""dir"""'], {'recreate': 'recreate'}), "('dir', recreate=recreate)\n", (2926, 2952), False, 'from system import shell\n'), ((5812, 5828), 'mock.MagicMock', 'mock.MagicMock', ([], {}), '()\n', (5826, 5828), False, 'import mock\n'), ((6175, 6207), 'tests.test_libs.test_utils.set_up_pyfakefs', 'test_utils.set_up_pyfakefs', (['self'], {}), '(self)\n', (6201, 6207), False, 'from tests.test_libs import test_utils\n'), ((6700, 6732), 'tests.test_libs.test_utils.set_up_pyfakefs', 'test_utils.set_up_pyfakefs', (['self'], {}), '(self)\n', (6726, 6732), False, 'from tests.test_libs import test_utils\n'), ((7560, 7609), 'tests.test_libs.helpers.patch', 'test_helpers.patch', (['self', "['tempfile.gettempdir']"], {}), "(self, ['tempfile.gettempdir'])\n", (7578, 7609), True, 'from tests.test_libs import helpers as test_helpers\n'), ((7677, 7709), 'tests.test_libs.test_utils.set_up_pyfakefs', 'test_utils.set_up_pyfakefs', (['self'], {}), '(self)\n', (7703, 7709), False, 'from tests.test_libs import test_utils\n'), ((8095, 8144), 'os.symlink', 'os.symlink', (['"""/unrelated/zz.txt"""', '"""/tmp/hh/gg.txt"""'], {}), "('/unrelated/zz.txt', '/tmp/hh/gg.txt')\n", (8105, 8144), False, 'import os\n'), ((8149, 8184), 'os.symlink', 'os.symlink', (['"""/unrelated"""', '"""/tmp/ii"""'], {}), "('/unrelated', '/tmp/ii')\n", (8159, 8184), False, 'import os\n'), ((8190, 8225), 'system.shell.clear_system_temp_directory', 'shell.clear_system_temp_directory', ([], {}), '()\n', (8223, 8225), False, 'from system import shell\n'), ((999, 1021), 'system.environment.is_posix', 'environment.is_posix', ([], {}), '()\n', (1019, 1021), False, 'from system import environment\n'), ((1476, 1509), 'os.path.exists', 'os.path.exists', (['"""/test/aa/bb.txt"""'], {}), "('/test/aa/bb.txt')\n", (1490, 1509), False, 'import os\n'), ((1531, 1567), 'os.path.exists', 'os.path.exists', (['"""/test/aa/aa/dd.txt"""'], {}), "('/test/aa/aa/dd.txt')\n", (1545, 1567), False, 'import os\n'), ((1590, 1623), 'os.path.exists', 'os.path.exists', (['"""/test/aa/cc.txt"""'], {}), "('/test/aa/cc.txt')\n", (1604, 1623), False, 'import os\n'), ((1646, 1682), 'os.path.exists', 'os.path.exists', (['"""/test/aa/aa/aa.txt"""'], {}), "('/test/aa/aa/aa.txt')\n", (1660, 1682), False, 'import os\n'), ((1838, 1871), 'os.path.exists', 'os.path.exists', (['"""/test/aa/cc.txt"""'], {}), "('/test/aa/cc.txt')\n", (1852, 1871), False, 'import os\n'), ((2174, 2207), 'os.path.exists', 'os.path.exists', (['"""/test/aa/cc.txt"""'], {}), "('/test/aa/cc.txt')\n", (2188, 2207), False, 'import os\n'), ((4171, 4200), 'system.shell.remove_directory', 'shell.remove_directory', (['"""dir"""'], {}), "('dir')\n", (4193, 4200), False, 'from system import shell\n'), ((4532, 4561), 'system.shell.remove_directory', 'shell.remove_directory', (['"""dir"""'], {}), "('dir')\n", (4554, 4561), False, 'from system import shell\n'), ((5014, 5063), 'system.shell.remove_directory', 'shell.remove_directory', (['"""dir"""'], {'ignore_errors': '(True)'}), "('dir', ignore_errors=True)\n", (5036, 5063), False, 'from system import shell\n'), ((5542, 5571), 'system.shell.remove_directory', 'shell.remove_directory', (['"""dir"""'], {}), "('dir')\n", (5564, 5571), False, 'from system import shell\n'), ((6526, 6568), 'system.shell.get_directory_file_count', 'shell.get_directory_file_count', (['"""/test/aa"""'], {}), "('/test/aa')\n", (6556, 6568), False, 'from system import shell\n'), ((7045, 7081), 'system.shell.get_directory_size', 'shell.get_directory_size', (['"""/test/aa"""'], {}), "('/test/aa')\n", (7069, 7081), False, 'from system import shell\n'), ((7250, 7272), 'system.environment.is_posix', 'environment.is_posix', ([], {}), '()\n', (7270, 7272), False, 'from system import environment\n'), ((7397, 7414), 'system.shell.which', 'shell.which', (['"""ls"""'], {}), "('ls')\n", (7408, 7414), False, 'from system import shell\n'), ((8247, 8269), 'os.path.exists', 'os.path.exists', (['"""/tmp"""'], {}), "('/tmp')\n", (8261, 8269), False, 'import os\n'), ((8291, 8319), 'os.path.exists', 'os.path.exists', (['"""/unrelated"""'], {}), "('/unrelated')\n", (8305, 8319), False, 'import os\n'), ((8342, 8380), 'system.shell.get_directory_file_count', 'shell.get_directory_file_count', (['"""/tmp"""'], {}), "('/tmp')\n", (8372, 8380), False, 'from system import shell\n'), ((8406, 8450), 'system.shell.get_directory_file_count', 'shell.get_directory_file_count', (['"""/unrelated"""'], {}), "('/unrelated')\n", (8436, 8450), False, 'from system import shell\n'), ((8476, 8508), 'os.path.exists', 'os.path.exists', (['"""/tmp/aa/bb.txt"""'], {}), "('/tmp/aa/bb.txt')\n", (8490, 8508), False, 'import os\n'), ((8531, 8566), 'os.path.exists', 'os.path.exists', (['"""/tmp/cc/dd/ee.txt"""'], {}), "('/tmp/cc/dd/ee.txt')\n", (8545, 8566), False, 'import os\n'), ((8589, 8617), 'os.path.exists', 'os.path.exists', (['"""/tmp/ff/gg"""'], {}), "('/tmp/ff/gg')\n", (8603, 8617), False, 'import os\n'), ((8640, 8665), 'os.path.exists', 'os.path.exists', (['"""/tmp/hh"""'], {}), "('/tmp/hh')\n", (8654, 8665), False, 'import os\n'), ((9018, 9060), 'system.shell.get_execute_command', 'shell.get_execute_command', (['file_to_execute'], {}), '(file_to_execute)\n', (9043, 9060), False, 'from system import shell\n'), ((10087, 10118), 'system.shell.get_interpreter', 'shell.get_interpreter', (['"""run.py"""'], {}), "('run.py')\n", (10108, 10118), False, 'from system import shell\n'), ((10324, 10359), 'system.shell.get_interpreter', 'shell.get_interpreter', (['"""executable"""'], {}), "('executable')\n", (10345, 10359), False, 'from system import shell\n'), ((3439, 3477), 'mock.call', 'mock.call', (['"""rd /s /q "dir" > nul 2>&1"""'], {}), '(\'rd /s /q "dir" > nul 2>&1\')\n', (3448, 3477), False, 'import mock\n'), ((3697, 3739), 'mock.call', 'mock.call', (['"""rm -rf "dir" > /dev/null 2>&1"""'], {}), '(\'rm -rf "dir" > /dev/null 2>&1\')\n', (3706, 3739), False, 'import mock\n'), ((4250, 4292), 'mock.call', 'mock.call', (['"""rm -rf "dir" > /dev/null 2>&1"""'], {}), '(\'rm -rf "dir" > /dev/null 2>&1\')\n', (4259, 4292), False, 'import mock\n'), ((4334, 4368), 'mock.call', 'mock.call', (['"""dir"""'], {'onerror': 'mock.ANY'}), "('dir', onerror=mock.ANY)\n", (4343, 4368), False, 'import mock\n'), ((4614, 4657), 'mock.call', 'mock.call', (['"""Failed to clear directory dir."""'], {}), "('Failed to clear directory dir.')\n", (4623, 4657), False, 'import mock\n'), ((4763, 4805), 'mock.call', 'mock.call', (['"""rm -rf "dir" > /dev/null 2>&1"""'], {}), '(\'rm -rf "dir" > /dev/null 2>&1\')\n', (4772, 4805), False, 'import mock\n'), ((4847, 4881), 'mock.call', 'mock.call', (['"""dir"""'], {'onerror': 'mock.ANY'}), "('dir', onerror=mock.ANY)\n", (4856, 4881), False, 'import mock\n'), ((5115, 5158), 'mock.call', 'mock.call', (['"""Failed to clear directory dir."""'], {}), "('Failed to clear directory dir.')\n", (5124, 5158), False, 'import mock\n'), ((5265, 5307), 'mock.call', 'mock.call', (['"""rm -rf "dir" > /dev/null 2>&1"""'], {}), '(\'rm -rf "dir" > /dev/null 2>&1\')\n', (5274, 5307), False, 'import mock\n'), ((5349, 5383), 'mock.call', 'mock.call', (['"""dir"""'], {'onerror': 'mock.ANY'}), "('dir', onerror=mock.ANY)\n", (5358, 5383), False, 'import mock\n'), ((5621, 5663), 'mock.call', 'mock.call', (['"""rm -rf "dir" > /dev/null 2>&1"""'], {}), '(\'rm -rf "dir" > /dev/null 2>&1\')\n', (5630, 5663), False, 'import mock\n'), ((5705, 5739), 'mock.call', 'mock.call', (['"""dir"""'], {'onerror': 'mock.ANY'}), "('dir', onerror=mock.ANY)\n", (5714, 5739), False, 'import mock\n'), ((5954, 5981), 'mock.call', 'mock.call', (['"""dir/child"""', '(488)'], {}), "('dir/child', 488)\n", (5963, 5981), False, 'import mock\n'), ((6016, 6038), 'mock.call', 'mock.call', (['"""dir/child"""'], {}), "('dir/child')\n", (6025, 6038), False, 'import mock\n'), ((3165, 3181), 'mock.call', 'mock.call', (['"""dir"""'], {}), "('dir')\n", (3174, 3181), False, 'import mock\n')]