"""Test script for the grp module."""

import random
import string
import unittest
from test.support import import_helper


grp = import_helper.import_module('grp')

class GroupDatabaseTestCase(unittest.TestCase):

    def check_value(self, value):
        # check that a grp tuple has the entries and
        # attributes promised by the docs
        self.assertEqual(len(value), 4)
        self.assertEqual(value[0], value.gr_name)
        self.assertIsInstance(value.gr_name, str)
        self.assertEqual(value[1], value.gr_passwd)
        self.assertIsInstance(value.gr_passwd, str)
        self.assertEqual(value[2], value.gr_gid)
        self.assertIsInstance(value.gr_gid, int)
        self.assertEqual(value[3], value.gr_mem)
        self.assertIsInstance(value.gr_mem, list)

    def test_values(self):
        entries = grp.getgrall()

        for e in entries:
            self.check_value(e)

    def test_values_extended(self):
        entries = grp.getgrall()
        if len(entries) > 1000:  # Huge group file (NIS?) -- skip the rest
            self.skipTest('huge group file, extended test skipped')

        for e in entries:
            e2 = grp.getgrgid(e.gr_gid)
            self.check_value(e2)
            self.assertEqual(e2.gr_gid, e.gr_gid)
            name = e.gr_name
            if name.startswith('+') or name.startswith('-'):
                # NIS-related entry
                continue
            e2 = grp.getgrnam(name)
            self.check_value(e2)
            # There are instances where getgrall() returns group names in
            # lowercase while getgrgid() returns proper casing.
            # Discovered on Ubuntu 5.04 (custom).
            self.assertEqual(e2.gr_name.lower(), name.lower())

    def test_errors(self):
        self.assertRaises(TypeError, grp.getgrgid)
        self.assertRaises(TypeError, grp.getgrgid, 3.14)
        self.assertRaises(TypeError, grp.getgrgid, 0.0)
        self.assertRaises(TypeError, grp.getgrgid, 0, 0)
        # should be out of gid_t range
        self.assertRaises(OverflowError, grp.getgrgid, 2**128)
        self.assertRaises(OverflowError, grp.getgrgid, -2**128)
        self.assertRaises(TypeError, grp.getgrnam)
        self.assertRaises(TypeError, grp.getgrnam, 42)
        self.assertRaises(TypeError, grp.getgrnam, b'root')
        self.assertRaises(TypeError, grp.getgrnam, 'root', 0)
        # embedded null character
        self.assertRaisesRegex(ValueError, 'null', grp.getgrnam, 'a\x00b')
        self.assertRaisesRegex(ValueError, 'null', grp.getgrnam, 'root\x00')
        self.assertRaises(UnicodeEncodeError, grp.getgrnam, 'roo\udc74')
        self.assertRaises(KeyError, grp.getgrnam, '')
        self.assertRaises(TypeError, grp.getgrall, 42)

        # Find a non-existent group name.
        # getgrall() will not necessarily report all existing groups
        # (typical for LDAP based directories in big organizations).
        for _ in range(30):
            fakename = ''.join(random.choices(string.ascii_lowercase, k=6))
            try:
                grp.getgrnam(fakename)
            except KeyError:
                break
        else:
            self.fail('Cannot find non-existent group name')

        # Find a non-existent gid.
        maxgid = 2**31
        for _ in range(30):
            fakegid = random.randrange(maxgid)
            try:
                grp.getgrgid(fakegid)
            except KeyError:
                break
            except OverflowError:
                if maxgid == 2**31:
                    maxgid = 2**16-1
                elif maxgid == 2**16-1:
                    maxgid = 2**15
                else:
                    raise
        else:
            self.fail('Cannot find non-existent gid')


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