#!/usr/bin/env python3
"""Unit test suite for the fs_config_genertor.py tool."""

import tempfile
import textwrap
import unittest

from fs_config_generator import AID
from fs_config_generator import AIDHeaderParser
from fs_config_generator import FSConfigFileParser
from fs_config_generator import FSConfig
from fs_config_generator import Utils


# Disable protected access so we can test class internal
# methods. Also, disable invalid-name as some of the
# class method names are over length.
# pylint: disable=protected-access,invalid-name
class Tests(unittest.TestCase):
    """Test class for unit tests"""

    def test_is_overlap(self):
        """Test overlap detection helper"""

        self.assertTrue(AIDHeaderParser._is_overlap((0, 1), (1, 2)))

        self.assertTrue(AIDHeaderParser._is_overlap((0, 100), (90, 200)))

        self.assertTrue(AIDHeaderParser._is_overlap((20, 50), (1, 101)))

        self.assertFalse(AIDHeaderParser._is_overlap((0, 100), (101, 200)))

        self.assertFalse(AIDHeaderParser._is_overlap((-10, 0), (10, 20)))

    def test_in_any_range(self):
        """Test if value in range"""

        self.assertFalse(Utils.in_any_range(50, [(100, 200), (1, 2), (1, 1)]))
        self.assertFalse(Utils.in_any_range(250, [(100, 200), (1, 2), (1, 1)]))

        self.assertTrue(Utils.in_any_range(100, [(100, 200), (1, 2), (1, 1)]))
        self.assertTrue(Utils.in_any_range(200, [(100, 200), (1, 2), (1, 1)]))
        self.assertTrue(Utils.in_any_range(150, [(100, 200)]))

    def test_aid(self):
        """Test AID class constructor"""

        aid = AID('AID_FOO_BAR', '0xFF', 'myfakefile', '/system/bin/sh')
        self.assertEqual(aid.identifier, 'AID_FOO_BAR')
        self.assertEqual(aid.value, '0xFF')
        self.assertEqual(aid.found, 'myfakefile')
        self.assertEqual(aid.normalized_value, '255')
        self.assertEqual(aid.friendly, 'foo_bar')
        self.assertEqual(aid.login_shell, '/system/bin/sh')

        aid = AID('AID_MEDIA_EX', '1234', 'myfakefile', '/vendor/bin/sh')
        self.assertEqual(aid.identifier, 'AID_MEDIA_EX')
        self.assertEqual(aid.value, '1234')
        self.assertEqual(aid.found, 'myfakefile')
        self.assertEqual(aid.normalized_value, '1234')
        self.assertEqual(aid.friendly, 'mediaex')
        self.assertEqual(aid.login_shell, '/vendor/bin/sh')

    def test_aid_header_parser_good(self):
        """Test AID Header Parser good input file"""

        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
            temp_file.write(
                textwrap.dedent("""
                #define AID_FOO 1000
                #define AID_BAR 1001
                #define SOMETHING "something"
                #define AID_OEM_RESERVED_START 2900
                #define AID_OEM_RESERVED_END   2999
                #define AID_OEM_RESERVED_1_START  7000
                #define AID_OEM_RESERVED_1_END    8000
            """))
            temp_file.flush()

            parser = AIDHeaderParser(temp_file.name)
            ranges = parser.ranges
            aids = parser.aids

            self.assertTrue((2900, 2999) in ranges["vendor"])
            self.assertFalse((5000, 6000) in ranges["vendor"])

            for aid in aids:
                self.assertTrue(aid.normalized_value in ['1000', '1001'])
                self.assertFalse(aid.normalized_value in ['1', '2', '3'])

    def test_aid_header_parser_good_unordered(self):
        """Test AID Header Parser good unordered input file"""

        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
            temp_file.write(
                textwrap.dedent("""
                #define AID_FOO 1000
                #define AID_OEM_RESERVED_1_END    8000
                #define AID_BAR 1001
                #define SOMETHING "something"
                #define AID_OEM_RESERVED_END   2999
                #define AID_OEM_RESERVED_1_START  7000
                #define AID_OEM_RESERVED_START 2900
            """))
            temp_file.flush()

            parser = AIDHeaderParser(temp_file.name)
            ranges = parser.ranges
            aids = parser.aids

            self.assertTrue((2900, 2999) in ranges["vendor"])
            self.assertFalse((5000, 6000) in ranges["vendor"])

            for aid in aids:
                self.assertTrue(aid.normalized_value in ['1000', '1001'])
                self.assertFalse(aid.normalized_value in ['1', '2', '3'])

    def test_aid_header_parser_bad_aid(self):
        """Test AID Header Parser bad aid input file"""

        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
            temp_file.write(
                textwrap.dedent("""
                #define AID_FOO "bad"
            """))
            temp_file.flush()

            with self.assertRaises(SystemExit):
                AIDHeaderParser(temp_file.name)

    def test_aid_header_parser_bad_oem_range(self):
        """Test AID Header Parser bad oem range input file"""

        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
            temp_file.write(
                textwrap.dedent("""
                #define AID_OEM_RESERVED_START 2900
                #define AID_OEM_RESERVED_END   1800
            """))
            temp_file.flush()

            with self.assertRaises(SystemExit):
                AIDHeaderParser(temp_file.name)

    def test_aid_header_parser_bad_oem_range_no_end(self):
        """Test AID Header Parser bad oem range (no end) input file"""

        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
            temp_file.write(
                textwrap.dedent("""
                #define AID_OEM_RESERVED_START 2900
            """))
            temp_file.flush()

            with self.assertRaises(SystemExit):
                AIDHeaderParser(temp_file.name)

    def test_aid_header_parser_bad_oem_range_no_start(self):
        """Test AID Header Parser bad oem range (no start) input file"""

        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
            temp_file.write(
                textwrap.dedent("""
                #define AID_OEM_RESERVED_END 2900
            """))
            temp_file.flush()

            with self.assertRaises(SystemExit):
                AIDHeaderParser(temp_file.name)

    def test_aid_header_parser_bad_oem_range_duplicated(self):
        """Test AID Header Parser bad oem range (no start) input file"""

        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
            temp_file.write(
                textwrap.dedent("""
                #define AID_OEM_RESERVED_START 2000
                #define AID_OEM_RESERVED_END 2900
                #define AID_OEM_RESERVED_START 3000
                #define AID_OEM_RESERVED_END 3900
            """))
            temp_file.flush()

            with self.assertRaises(SystemExit):
                AIDHeaderParser(temp_file.name)

    def test_aid_header_parser_bad_oem_range_mismatch_start_end(self):
        """Test AID Header Parser bad oem range mismatched input file"""

        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
            temp_file.write(
                textwrap.dedent("""
                #define AID_OEM_RESERVED_START 2900
                #define AID_OEM_RESERVED_2_END 2900
            """))
            temp_file.flush()

            with self.assertRaises(SystemExit):
                AIDHeaderParser(temp_file.name)

    def test_aid_header_parser_bad_duplicate_ranges(self):
        """Test AID Header Parser exits cleanly on duplicate AIDs"""

        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
            temp_file.write(
                textwrap.dedent("""
                #define AID_FOO 100
                #define AID_BAR 100
            """))
            temp_file.flush()

            with self.assertRaises(SystemExit):
                AIDHeaderParser(temp_file.name)

    def test_aid_header_parser_no_bad_aids(self):
        """Test AID Header Parser that it doesn't contain:
        Ranges, ie things the end with "_START" or "_END"
        AID_APP
        AID_USER
        For more details see:
          - https://android-review.googlesource.com/#/c/313024
          - https://android-review.googlesource.com/#/c/313169
        """

        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
            temp_file.write(
                textwrap.dedent("""
                #define AID_APP              10000 /* TODO: switch users over to AID_APP_START */
                #define AID_APP_START        10000 /* first app user */
                #define AID_APP_END          19999 /* last app user */

                #define AID_CACHE_GID_START  20000 /* start of gids for apps to mark cached data */
                #define AID_CACHE_GID_END    29999 /* end of gids for apps to mark cached data */

                #define AID_SHARED_GID_START 50000 /* start of gids for apps in each user to share */
                #define AID_SHARED_GID_END   59999 /* end of gids for apps in each user to share */

                #define AID_ISOLATED_START   99000 /* start of uids for fully isolated sandboxed processes */
                #define AID_ISOLATED_END     99999 /* end of uids for fully isolated sandboxed processes */

                #define AID_USER            100000 /* TODO: switch users over to AID_USER_OFFSET */
                #define AID_USER_OFFSET     100000 /* offset for uid ranges for each user */
            """))
            temp_file.flush()

            parser = AIDHeaderParser(temp_file.name)
            aids = parser.aids

            bad_aids = ['_START', '_END', 'AID_APP', 'AID_USER']

            for aid in aids:
                self.assertFalse(
                    any(bad in aid.identifier for bad in bad_aids),
                    'Not expecting keywords "%s" in aids "%s"' %
                    (str(bad_aids), str([tmp.identifier for tmp in aids])))

    def test_fs_config_file_parser_good(self):
        """Test FSConfig Parser good input file"""

        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
            temp_file.write(
                textwrap.dedent("""
                [/system/bin/file]
                user: AID_FOO
                group: AID_SYSTEM
                mode: 0777
                caps: BLOCK_SUSPEND

                [/vendor/path/dir/]
                user: AID_FOO
                group: AID_SYSTEM
                mode: 0777
                caps: 0

                [AID_OEM1]
                # 5001 in base16
                value: 0x1389
            """))
            temp_file.flush()

            parser = FSConfigFileParser([temp_file.name], {"oem1": [(5000, 5999)]})
            files = parser.files
            dirs = parser.dirs
            aids = parser.aids

            self.assertEqual(len(files), 1)
            self.assertEqual(len(dirs), 1)
            self.assertEqual(len(aids), 1)

            aid = aids[0]
            fcap = files[0]
            dcap = dirs[0]

            self.assertEqual(fcap,
                             FSConfig('0777', 'AID_FOO', 'AID_SYSTEM',
                                      'CAP_BLOCK_SUSPEND',
                                      '/system/bin/file', temp_file.name))

            self.assertEqual(dcap,
                             FSConfig('0777', 'AID_FOO', 'AID_SYSTEM', '0',
                                      '/vendor/path/dir/', temp_file.name))

            self.assertEqual(aid, AID('AID_OEM1', '0x1389', temp_file.name, '/bin/sh'))

    def test_fs_config_file_parser_bad(self):
        """Test FSConfig Parser bad input file"""

        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
            temp_file.write(
                textwrap.dedent("""
                [/system/bin/file]
                caps: BLOCK_SUSPEND
            """))
            temp_file.flush()

            with self.assertRaises(SystemExit):
                FSConfigFileParser([temp_file.name], {})

    def test_fs_config_file_parser_bad_aid_range(self):
        """Test FSConfig Parser bad aid range value input file"""

        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
            temp_file.write(
                textwrap.dedent("""
                [AID_OEM1]
                value: 25
            """))
            temp_file.flush()

            with self.assertRaises(SystemExit):
                FSConfigFileParser([temp_file.name], {"oem1": [(5000, 5999)]})

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