# Copyright 2015 The Bazel Authors. All rights reserved.
#
# 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.
"""Testing for pkg_tar."""

import tarfile
import unittest

from bazel_tools.tools.python.runfiles import runfiles
from pkg.private.tar import tar_writer

PORTABLE_MTIME = 946684800  # 2000-01-01 00:00:00.000 UTC


class PkgTarTest(unittest.TestCase):
  """Testing for pkg_tar rule."""

  def assertTarFileContent(self, file_name, content, verbose=False):
    """Assert that tarfile contains exactly the entry described by `content`.

    Args:
        file_name: the path to the TAR file to test.
        content: an array describing the expected content of the TAR file.
            Each entry in that list should be a dictionary where each field
            is a field to test in the corresponding TarInfo. For
            testing the presence of a file "x", then the entry could simply
            be `{'name': 'x'}`, the missing field will be ignored. To match
            the content of a file entry, use the key 'data'.
    """
    # NOTE: This is portable to Windows. os.path.join('rules_pkg', 'tests',
    # filename) is not.
    file_path = runfiles.Create().Rlocation('rules_pkg/tests/tar/' + file_name)
    got = []
    with tarfile.open(file_path, 'r:*') as f:
      i = 0
      for info in f:
        if verbose:
          print('  >> from tar file:', info.name)
        error_msg = 'Extraneous file at end of archive %s: %s' % (
            file_path,
            info.name
            )
        self.assertLess(i, len(content), error_msg)
        for k, v in content[i].items():
          if k == 'halt':
            return
          elif k == 'data':
            value = f.extractfile(info).read()
          elif k == 'isdir':
            value = info.isdir()
          else:
            value = getattr(info, k)
          if k == 'mode':
            p_value = '0o%o' % value
            p_v = '0o%o' % v
          else:
            p_value = str(value)
            p_v = str(v)
          error_msg = ' '.join([
              'Value `%s` for key `%s` of file' % (p_value, k),
              '%s in archive %s does' % (info.name, file_path),
              'not match expected value `%s`' % p_v
              ])
          self.assertEqual(value, v, error_msg)
          if value != v:
            print(error_msg)
        i += 1
      if i < len(content):
        self.fail('Missing file %s in archive %s of [%s]' % (
            content[i], file_path, ',\n    '.join(got)))

  def test_strip_prefix_empty(self):
    content = [
        {'name': 'nsswitch.conf'},
    ]
    self.assertTarFileContent('test-tar-strip_prefix-empty.tar', content)

  def test_strip_prefix_none(self):
    content = [
        {'name': 'nsswitch.conf'},
    ]
    self.assertTarFileContent('test-tar-strip_prefix-none.tar', content)

  def test_strip_prefix_etc(self):
    content = [
        {'name': 'nsswitch.conf'},
    ]
    self.assertTarFileContent('test-tar-strip_prefix-etc.tar', content)

  def test_strip_prefix_substring(self):
    content = [
        {'name': 'etc', 'isdir': True},
        {'name': 'etc/nsswitch.conf'},
    ]
    self.assertTarFileContent('test-tar-strip_prefix-substring.tar', content)

  def test_strip_prefix_dot(self):
    content = [
        {'name': 'etc'},
        {'name': 'etc/nsswitch.conf'},
        {'name': 'external'},
        {'name': 'external/bazel_tools'},
        {'name': 'external/bazel_tools/tools'},
        {'name': 'external/bazel_tools/tools/python'},
        {'name': 'external/bazel_tools/tools/python/runfiles'},
        # This is brittle. In old bazel the next file would be
        # external/bazel_tools/tools/python/runfiles/runfiles.py, but there
        # is now _runfiles_constants.py, first. So this is too brittle.
        {'halt': None},
    ]
    self.assertTarFileContent('test-tar-strip_prefix-dot.tar', content)

  def test_strip_files_dict(self):
    content = [
        {'name': 'not-etc'},
        {'name': 'not-etc/mapped-filename.conf'},
    ]
    self.assertTarFileContent('test-tar-files_dict.tar', content)

  def test_empty_files(self):
    content = [
        {'name': 'a', 'size': 0, 'uid': 0},
        {'name': 'b', 'size': 0, 'uid': 0, 'mtime': PORTABLE_MTIME},
    ]
    self.assertTarFileContent('test-tar-empty_files.tar', content)

  def test_empty_dirs(self):
    content = [
        {'name': 'pmt', 'isdir': True, 'size': 0, 'uid': 0,
         'mtime': PORTABLE_MTIME},
        {'name': 'tmp', 'isdir': True, 'size': 0, 'uid': 0,
         'mtime': PORTABLE_MTIME},
    ]
    self.assertTarFileContent('test-tar-empty_dirs.tar', content)

  def test_mtime(self):
    # Note strange mtime. It is specified in the BUILD file.
    content = [
        {'name': 'nsswitch.conf', 'mtime': 946684740},
    ]
    self.assertTarFileContent('test-tar-mtime.tar', content)

  def test_basic(self):
    # Check the set of 'test-tar-basic-*' smoke test.
    content = [
        {'name': 'etc',
         'uid': 24, 'gid': 42, 'uname': 'tata', 'gname': 'titi'},
        {'name': 'etc/nsswitch.conf',
         'mode': 0o644,
         'uid': 24, 'gid': 42, 'uname': 'tata', 'gname': 'titi'
         },
        {'name': 'usr',
         'uid': 42, 'gid': 24, 'uname': 'titi', 'gname': 'tata'},
        {'name': 'usr/bin'},
        {'name': 'usr/bin/java', 'linkname': '/path/to/bin/java'},
        {'name': 'usr/titi',
         'mode': 0o755,
         'uid': 42, 'gid': 24, 'uname': 'titi', 'gname': 'tata'},
    ]
    for ext in [('.' + comp if comp else '')
                for comp in tar_writer.COMPRESSIONS]:
      with self.subTest(ext=ext):
        self.assertTarFileContent('test-tar-basic-%s.tar%s' % (ext[1:], ext),
                                  content)

  def test_file_inclusion(self):
    content = [
        {'name': 'etc', 'uid': 24, 'gid': 42},
        {'name': 'etc/nsswitch.conf', 'mode': 0o644, 'uid': 24, 'gid': 42},
        {'name': 'usr', 'uid': 42, 'gid': 24},
        {'name': 'usr/bin'},
        {'name': 'usr/bin/java', 'linkname': '/path/to/bin/java'},
        {'name': 'usr/titi', 'mode': 0o755, 'uid': 42, 'gid': 24},
        {'name': 'BUILD'},
    ]
    for ext in [('.' + comp if comp else '')
                for comp in tar_writer.COMPRESSIONS]:
      with self.subTest(ext=ext):
        self.assertTarFileContent('test-tar-inclusion-%s.tar' % ext[1:],
                                  content)

  def test_strip_prefix_empty(self):
    content = [
        {'name': 'level1'},
        {'name': 'level1/some_value'},
        {'name': 'level1/some_value/level3'},
        {'name': 'level1/some_value/level3/BUILD'},
    ]
    self.assertTarFileContent('test_tar_package_dir_substitution.tar', content)

  def test_tar_with_long_file_name(self):
    content = [
      {'name': 'file_with_a_ridiculously_long_name_consectetur_adipiscing_elit_fusce_laoreet_lorem_neque_sed_pharetra_erat.txt'}
    ]
    self.assertTarFileContent('test-tar-long-filename.tar', content)

  def test_repackage_file_with_long_name(self):
    content = [
      {'name': 'can_i_repackage_a_file_with_a_long_name'},
      {'name': 'can_i_repackage_a_file_with_a_long_name/file_with_a_ridiculously_long_name_consectetur_adipiscing_elit_fusce_laoreet_lorem_neque_sed_pharetra_erat.txt'}
    ]
    self.assertTarFileContent('test-tar-repackaging-long-filename.tar', content)

  def test_tar_with_tree_artifact(self):
    # (sorted) list of files:
    #  "a/a"
    #  "a/b/c"
    #  "b/c/d"
    #  "b/d"
    #  "b/e"

    content = [
      {'name': 'a_tree', 'isdir': True},
      {'name': 'a_tree/generate_tree', 'isdir': True, 'mode': 0o755},
      {'name': 'a_tree/generate_tree/a', 'isdir': True, 'mode': 0o755},
      {'name': 'a_tree/generate_tree/a/a'},
      {'name': 'a_tree/generate_tree/a/b', 'isdir': True, 'mode': 0o755},
      {'name': 'a_tree/generate_tree/a/b/c'},
      {'name': 'a_tree/generate_tree/b', 'isdir': True, 'mode': 0o755},
      {'name': 'a_tree/generate_tree/b/c', 'isdir': True, 'mode': 0o755},
      {'name': 'a_tree/generate_tree/b/c/d'},
      {'name': 'a_tree/generate_tree/b/d'},
      {'name': 'a_tree/generate_tree/b/e'},
      {'name': 'a_tree/loremipsum.txt'},
    ]
    self.assertTarFileContent('test-tar-tree-artifact.tar', content)

    # Now test against the tree artifact with the dir name stripped.
    noroot_content = []
    for c in content[1:]:  # one less level in tree. Skip first element.
      nc = dict(c)
      nc['name'] = c['name'].replace('/generate_tree', '')
      noroot_content.append(nc)
    self.assertTarFileContent('test-tar-tree-artifact-noroot.tar',
                              noroot_content)

  def test_tar_with_runfiles(self):
    content = [
      {'name': 'BUILD' },
      {'name': 'a_program' },
      {'name': 'executable.sh' },
    ]
    self.assertTarFileContent('test-tar-with-runfiles.tar', content)

  def test_tar_leading_dotslash(self):
    content = [
      {'name': './loremipsum.txt'},
    ]
    self.assertTarFileContent('test_tar_leading_dotslash.tar', content)

  def test_tar_with_tree_artifact_and_strip_prefix(self):
    content = [
      {'name': 'a', 'isdir': True},
      {'name': 'a/a'},
      {'name': 'a/b'},
    ]
    self.assertTarFileContent('test-tree-input-with-strip-prefix.tar', content)


if __name__ == '__main__':
  unittest.main()
