# Copyright (C) 2010, 2011  Lars Wirzenius
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


import stat
import unittest

import summainlib


class FakeChecksummer(object):

    def update(self, data):
        pass

    def hexdigest(self):
        return 'abc'


class FakeOpenFile(object):

    def __call__(self, filename):
        self.data = 'some data'
        return self

    def read(self, amount):
        data = self.data[:amount]
        self.data = self.data[len(data):]
        return data

    def close(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, a, b, c):
        pass


class FakeReadlink(object):

    def __init__(self, parent):
        self.parent = parent

    def __call__(self, filename):
        if stat.S_ISLNK(self.parent.st[summainlib.RESULT_MODE]):
            self.target = 'symlink'
        else:
            self.target = ''
        return self.target


class FilesystemObjectTests(unittest.TestCase):

    def setUp(self):
        self.st = {
            summainlib.RESULT_MTIME_SEC: 1262307723,
            summainlib.RESULT_MTIME_NSEC: 123456789,
            summainlib.RESULT_MODE: stat.S_IFREG | 0644,
            summainlib.RESULT_INO: 12765,
            summainlib.RESULT_DEV: 42,
            summainlib.RESULT_NLINK: 2,
            summainlib.RESULT_SIZE: 1,
            summainlib.RESULT_UID: 0,
            summainlib.RESULT_GID: 0
        }

        self.nn = summainlib.NumberNormalizer()
        self.pn = summainlib.SamePath()
        self.exclude = []

    def new(self, name, mode=None):
        if mode is not None:
            self.st[summainlib.RESULT_MODE] = mode
        return summainlib.FilesystemObject(
            name,
            self.nn,
            self.pn,
            self.exclude,
            stat_result=self.st,
            sha1=FakeChecksummer(),
            sha224=FakeChecksummer(),
            sha256=FakeChecksummer(),
            sha384=FakeChecksummer(),
            sha512=FakeChecksummer(),
            md5=FakeChecksummer(),
            open_file=FakeOpenFile(),
            readlink=FakeReadlink(self),
            xattrs={})

    def test_raises_keyerror_for_unknown_field(self):
        self.assertRaises(KeyError, self.new('foo').__getitem__,
                          'UNKNOWNHASH')

    def test_formats_simple_name_identically(self):
        self.assertEqual(self.new('foo')['Name'], 'foo')

    def test_formats_space_correctly(self):
        self.assertEqual(self.new('foo bar')['Name'], 'foo%20bar')

    def test_formats_mtime_correctly(self):
        self.assertEqual(self.new('foo')['Mtime'],
                         '2010-01-01 01:02:03.123456789 +0000')

    def test_formats_mode_for_regular_file_correctly(self):
        self.assertEqual(self.new('foo')['Mode'], '100644')

    def test_formats_inode_number_correctly(self):
        # Note: normalization makes the result be 1.
        self.assertEqual(self.new('foo')['Ino'], '1')

    def test_formats_device_number_correctly(self):
        # Note: normalization makes the result be 1.
        self.assertEqual(self.new('foo')['Dev'], '1')

    def test_formats_link_count_correctly(self):
        self.assertEqual(self.new('foo')['Nlink'], '2')

    def test_formats_size_correctly(self):
        self.assertEqual(self.new('foo')['Size'], '1')

    def test_formats_uid_correctly(self):
        self.assertEqual(self.new('foo')['Uid'], '0')

    def test_formats_username_correctly(self):
        self.assertEqual(self.new('foo')['Username'], 'root')

    def test_formats_gid_correctly(self):
        self.assertEqual(self.new('foo')['Gid'], '0')

    def test_formats_group_correctly(self):
        self.assertEqual(self.new('foo')['Group'], 'root')

    def test_formats_checksums_correctly_for_regular_file(self):
        self.assertEqual(self.new('foo')['MD5'], 'abc')
        self.assertEqual(self.new('foo')['SHA1'], 'abc')
        self.assertEqual(self.new('foo')['SHA224'], 'abc')
        self.assertEqual(self.new('foo')['SHA256'], 'abc')
        self.assertEqual(self.new('foo')['SHA384'], 'abc')
        self.assertEqual(self.new('foo')['SHA512'], 'abc')

    def test_formats_checksums_correctly_for_special_file(self):
        self.st[summainlib.RESULT_MODE] = stat.S_IFDIR | 0755
        self.assertEqual(self.new('foo')['MD5'], '')
        self.assertEqual(self.new('foo')['SHA1'], '')
        self.assertEqual(self.new('foo')['SHA224'], '')
        self.assertEqual(self.new('foo')['SHA256'], '')
        self.assertEqual(self.new('foo')['SHA384'], '')
        self.assertEqual(self.new('foo')['SHA512'], '')

    def test_formats_target_correctly_for_symlink(self):
        self.st[summainlib.RESULT_MODE] = stat.S_IFLNK | 0777
        self.assertEqual(self.new('foo')['Target'], 'symlink')

    def test_formats_target_correctly_for_regular_file(self):
        self.assertEqual(self.new('foo')['Target'], '')

    def test_excludes_unwanted_fields_from_output(self):
        self.exclude = ['mtime']
        fso = self.new('/foo/bar', mode=stat.S_IFREG)
        self.assertEqual(fso['Mtime'], '')


class FilesystemObjectNormalizedNumbersTests(unittest.TestCase):

    def setUp(self):
        self.ino = 0
        self.dev = 0
        self.nn = summainlib.NumberNormalizer()
        self.pn = summainlib.SamePath()
        self.exclude = []
        self.checksums = ['SHA1']

    def reset(self):
        self.dev += 1
        self.nn.reset()

    def new(self, name):
        st = {
            summainlib.RESULT_INO: self.ino,
            summainlib.RESULT_DEV: self.dev,
            summainlib.RESULT_MTIME_SEC: 0,
            summainlib.RESULT_MTIME_NSEC: 0,
            summainlib.RESULT_MODE: stat.S_IFREG | 0,
            summainlib.RESULT_NLINK: 1,
            summainlib.RESULT_SIZE: 0,
            summainlib.RESULT_UID: 0,
            summainlib.RESULT_GID: 0
        }
        self.ino += 1
        return summainlib.FilesystemObject(
            name,
            self.nn,
            self.pn,
            self.exclude,
            stat_result=st,
            sha1=FakeChecksummer(),
            sha224=FakeChecksummer(),
            sha256=FakeChecksummer(),
            sha384=FakeChecksummer(),
            sha512=FakeChecksummer(),
            md5=FakeChecksummer(),
            open_file=FakeOpenFile(),
            readlink=FakeReadlink(self),
            xattrs={})

    def test_inode_numbers_are_repeatable(self):
        a1 = self.new('foo')
        a2 = self.new('bar')
        # Counter lazy evaluation.
        a1['Dev']
        a1['Ino']
        a2['Dev']
        a2['Ino']
        self.reset()
        b1 = self.new('foo')
        b2 = self.new('bar')
        self.assertEqual(a1['Dev'], b1['Dev'])
        self.assertEqual(a1['Ino'], b1['Ino'])
        self.assertEqual(a2['Dev'], b2['Dev'])
        self.assertEqual(a2['Ino'], b2['Ino'])


class NumberNormalizerTests(unittest.TestCase):

    def setUp(self):
        self.nn = summainlib.NumberNormalizer()

    def test_returns_1_2_3_regardless_of_input_numbers(self):
        self.assertEqual([self.nn.get_ino(i) for i in [10, 11, 12]],
                         [1, 2, 3])

    def test_returns_1_1_1_when_input_number_is_repeated(self):
        self.assertEqual([self.nn.get_ino(i) for i in [10, 10, 10]],
                         [1, 1, 1])


class PathNormalizerTests(unittest.TestCase):

    def setUp(self):
        self.pn = summainlib.PathNormalizer('secret')

    def test_returns_different_paths_for_different_inputs(self):
        self.assertNotEqual(self.pn.normalize('/foo/bar'),
                            self.pn.normalize('/ping/pong'))

    def test_returns_same_paths_for_same_input(self):
        self.assertEqual(self.pn.normalize('/foo/bar'),
                         self.pn.normalize('/foo/bar'))
