#! /usr/bin/env python
# -*- coding: utf-8 -*-

# test/test_enum.py
# Part of enum, a package providing enumerated types for Python.
#
# Copyright © 2007–2009 Ben Finney <ben+python@benfinney.id.au>
# This is free software; you may copy, modify and/or distribute this work
# under the terms of the GNU General Public License, version 2 or later
# or, at your option, the terms of the Python license.
""" Unit test for ‘enum’ module.
    """

import unittest

import pyutilib.enum.enum as enum


class Mock_Enum(object):
    """ Mock object for Enum testing. """

    def __init__(self, *keys):
        """ Set up a new instance. """
        pass


def setup_enum_value_fixtures(testcase):
    """ Set up fixtures for test cases using ‘EnumValue’. """

    testcase.bad_keys = [None, 0, 1, (), Mock_Enum()]

    testcase.other_values = [
        None,
        0,
        1,
        (),
        Mock_Enum(),
        "bogus-str",
        enum.EnumValue(Mock_Enum(), 0, 'bogus-enum'),
    ]

    testcase.planets = [
        ('mercury', "Mercury"),
        ('venus', "Venus"),
        ('earth', "Earth"),
        ('mars', "Mars"),
        ('jupiter', "Jupiter"),
        ('saturn', "Saturn"),
        ('uranus', "Uranus"),
        ('neptune', "Neptune"),
    ]
    planet_keys = [key for (key, name) in testcase.planets]

    colour_keys = [
        'red',
        'green',
        'blue',
        'yellow',
        'orange',
        'purple',
        'white',
        'black',
    ]

    simple_keys = ['spam', 'eggs', 'beans']
    testcase.SimpleEnum = testcase.enum_factory_class(*simple_keys)

    Colour = testcase.enum_factory_class(*colour_keys)
    Planet = testcase.enum_factory_class(*planet_keys)
    testcase.valid_values = {
        Colour: dict(
            keys=colour_keys,),
        Planet: dict(
            keys=planet_keys,),
    }

    for enumtype, params in testcase.valid_values.items():
        params['enumtype'] = enumtype
        values = {}
        for i, key in enumerate(params['keys']):
            values[key] = enum.EnumValue(enumtype, i, key)
        params['values'] = values


class XTest_Module(object):
    """ Test case for the module. """

    def setUp(self):
        """ Set up test fixtures. """
        from sys import modules
        self.module = modules['enum']

    def test_author_name_is_string(self):
        # Module should have __author_name__ string.
        mod_author_name = self.module.__author_name__
        self.assertTrue(isinstance(mod_author_name, basestring))

    def test_author_email_is_string(self):
        # Module should have __author_email__ string.
        mod_author_email = self.module.__author_email__
        self.assertTrue(isinstance(mod_author_email, basestring))

    def test_author_is_string(self):
        # Module should have __author__ string.
        mod_author = self.module.__author__
        self.assertTrue(isinstance(mod_author, basestring))

    def test_author_contains_name(self):
        # Module __author__ string should contain author name.
        mod_author = self.module.__author__
        mod_author_name = self.module.__author_name__
        self.assertTrue(mod_author.startswith(mod_author_name))

    def test_author_contains_email(self):
        # Module __author__ string should contain author email.
        mod_author = self.module.__author__
        mod_author_email = self.module.__author_email__
        self.assertTrue(mod_author.endswith("<%(mod_author_email)s>" % vars()))

    def test_date_is_string(self):
        # Module should have __date__ string.
        mod_date = self.module.__date__
        self.assertTrue(isinstance(mod_date, basestring))

    def test_copyright_is_string(self):
        # Module should have __copyright__ string.
        mod_copyright = self.module.__copyright__
        self.assertTrue(isinstance(mod_copyright, basestring))

    def test_copyright_contains_name(self):
        # Module __copyright__ string should contain author name.
        mod_copyright = self.module.__copyright__
        mod_author_name = self.module.__author_name__
        self.assertTrue(mod_copyright.endswith(mod_author_name))

    def test_copyright_contains_begin_year(self):
        # Module __copyright__ string should contain beginning year.
        mod_copyright = self.module.__copyright__
        year_begin = self.module._copyright_year_begin
        self.assertTrue(year_begin in mod_copyright)

    def test_copyright_contains_latest_year(self):
        # Module __copyright__ string should contain latest year..
        mod_copyright = self.module.__copyright__
        year_latest = self.module.__date__.split('-')[0]
        self.assertTrue(year_latest in mod_copyright)

    def test_license_is_string(self):
        # Module should have __license__ string.
        mod_license = self.module.__license__
        self.assertTrue(isinstance(mod_license, basestring))

    def test_url_is_string(self):
        # Module should have __url__ string.
        mod_url = self.module.__url__
        self.assertTrue(isinstance(mod_url, basestring))

    def test_version_is_string(self):
        # Module should have __version__ string.
        mod_version = self.module.__version__
        self.assertTrue(isinstance(mod_version, basestring))


class Test_EnumException(unittest.TestCase):
    """ Test case for the Enum exception classes. """

    def setUp(self):
        """ Set up test fixtures. """
        self.valid_exceptions = {
            enum.EnumEmptyError: dict(
                min_args=0,
                types=(enum.EnumException, AssertionError),),
            enum.EnumBadKeyError: dict(
                min_args=1,
                types=(enum.EnumException, TypeError),),
            enum.EnumBadIndexError: dict(
                min_args=2,
                types=(enum.EnumException, AssertionError),),
            enum.EnumBadTypeError: dict(
                min_args=2,
                types=(enum.EnumException, TypeError),),
            enum.EnumImmutableError: dict(
                min_args=1,
                types=(enum.EnumException, TypeError),),
        }

        for exc_type, params in self.valid_exceptions.items():
            args = (None,) * params['min_args']
            instance = exc_type(*args)
            self.valid_exceptions[exc_type]['instance'] = instance

    def test_EnumException_abstract(self):
        # The module exception base class should be abstract.
        self.assertRaises(NotImplementedError, enum.EnumException)

    def test_exception_instance(self):
        # Exception instance should be created.
        for exc_type, params in self.valid_exceptions.items():
            instance = params['instance']
            self.assertTrue(instance)

    def test_exception_types(self):
        # Exception instances should match expected types.
        for exc_type, params in self.valid_exceptions.items():
            instance = params['instance']
            for match_type in params['types']:
                self.assertTrue(
                    isinstance(instance, match_type),
                    msg=("instance: %(instance)r, match_type: %(match_type)s") %
                    vars())


class Test_EnumValue(unittest.TestCase):
    """ Test case for the EnumValue class. """

    enum_factory_class = Mock_Enum

    def setUp(self):
        """ Set up the test fixtures. """
        setup_enum_value_fixtures(self)

    def test_instantiate(self):
        # Creating an EnumValue instance should succeed.
        for enumtype, params in self.valid_values.items():
            for key, instance in params['values'].items():
                self.assertTrue(instance)

    def test_enumtype_equal(self):
        # EnumValue should export its enum type.
        for enumtype, params in self.valid_values.items():
            for key, instance in params['values'].items():
                self.assertEqual(enumtype, instance.enumtype)

    def test_key_equal(self):
        # EnumValue should export its string key.
        for enumtype, params in self.valid_values.items():
            for key, instance in params['values'].items():
                self.assertEqual(key, instance.key)

    def test_str_key(self):
        # String value for EnumValue should be its key string.
        for enumtype, params in self.valid_values.items():
            for key, instance in params['values'].items():
                self.assertEqual(key, str(instance))

    def test_index_equal(self):
        # EnumValue should export its sequence index.
        for enumtype, params in self.valid_values.items():
            for i, key in enumerate(params['keys']):
                instance = params['values'][key]
                self.assertEqual(i, instance.index)

    def test_repr(self):
        # Representation of EnumValue should be meaningful.
        for enumtype, params in self.valid_values.items():
            for i, key in enumerate(params['keys']):
                instance = params['values'][key]
                repr_str = repr(instance)
                self.assertTrue(repr_str.startswith('EnumValue('))
                self.assertTrue(repr_str.count(repr(enumtype)))
                self.assertTrue(repr_str.count(repr(i)))
                self.assertTrue(repr_str.count(repr(key)))
                self.assertTrue(repr_str.endswith(')'))

    def test_hash_equal(self):
        # Each EnumValue instance should have same hash as its value.
        for enumtype, params in self.valid_values.items():
            for i, key in enumerate(params['keys']):
                instance = params['values'][key]
                self.assertEqual(hash(i), hash(instance))

    def test_hash_unequal(self):
        # Different EnumValue instances should have different hashes.
        for enumtype, params in self.valid_values.items():
            for i, key in enumerate(params['keys']):
                instance = params['values'][key]
                for j, other_key in enumerate(params['keys']):
                    if i == j:
                        continue
                    other_instance = params['values'][other_key]
                    self.assertNotEqual(hash(instance), hash(other_instance))

    def Xtest_comparison_method_has_matching_name(self):
        """ Comparison method should have matching name for attribute. """
        for compare_func in compare_functions:
            func_name = compare_func.__name__
            expect_name = func_name
            compare_method = getattr(enum.EnumValue, func_name)
            self.assertEqual(expect_name, compare_method.__name__)

    def Xtest_comparison_method_has_docstring(self):
        """ Comparison method should have docstring. """
        for compare_func in compare_functions:
            func_name = compare_func.__name__
            compare_method = getattr(enum.EnumValue, func_name)
            self.assertTrue(isinstance(compare_method.__doc__, basestring))

    def test_compare_equal(self):
        # An EnumValue should compare equal to its value.
        for enumtype, params in self.valid_values.items():
            for i, key in enumerate(params['keys']):
                instance = params['values'][key]
                self.assertEqual(instance, enum.EnumValue(enumtype, i, key))

    def test_compare_unequal(self):
        # An EnumValue should compare different to other values.
        for enumtype, params in self.valid_values.items():
            for i, key in enumerate(params['keys']):
                instance = params['values'][key]
                self.assertNotEqual(instance,
                                    enum.EnumValue(enumtype, None, None))

    def XXtest_compare_sequence(self):
        # EnumValue instances should compare as their sequence order.
        for enumtype, params in self.valid_values.items():
            for i, left_key in enumerate(params['keys']):
                for j, right_key in enumerate(params['keys']):
                    for compare_func in compare_functions:
                        self.assertEqual(
                            compare_func(i, j), compare_func(
                                params['values'][left_key],
                                enum.EnumValue(enumtype, j, right_key)))

    def Xtest_compare_different_enum(self):
        """ An EnumValue should not implement comparison to other enums. """
        for enumtype, params in self.valid_values.items():
            for i, key in enumerate(params['keys']):
                for compare_func in compare_functions:
                    instance = params['values'][key]
                    test_value = enum.EnumValue(self.SimpleEnum, i, key)
                    compare_method = getattr(instance, compare_func.__name__)
                    compare_result = compare_method(test_value)
                    self.assertEqual(NotImplemented, compare_result)

    def Xtest_compare_non_enum(self):
        """ An EnumValue should not implement comparison to other types. """
        test_value = enum.EnumValue(self.SimpleEnum, 0, 'test')
        for other_value in self.other_values:
            for compare_func in compare_functions:
                compare_method = getattr(test_value, compare_func.__name__)
                compare_result = compare_method(other_value)
                self.assertEqual(NotImplemented, compare_result)

    def Xtest_compare_equality_different_enum(self):
        """ An EnumValue should compare inequal to values of other enums. """
        for enumtype, params in self.valid_values.items():
            for i, key in enumerate(params['keys']):
                instance = params['values'][key]
                test_value = enum.EnumValue(self.SimpleEnum, i, key)
                self.assertNotEqual(test_value, instance)

    def Xtest_compare_equality_non_enum(self):
        """ An EnumValue should compare inequal to any other value. """
        test_value = enum.EnumValue(self.SimpleEnum, 0, 'test')
        for other_value in self.other_values:
            self.assertNotEqual(test_value, other_value)

    def test_sequence_other_values(self):
        # An EnumValue should compare sequentially to other values.
        test_value = enum.EnumValue(self.SimpleEnum, 0, 'test')
        test_list = list(self.other_values)
        test_list.append(test_value)
        test_list.sort()
        self.assertTrue(test_value in test_list)

    def test_value_key(self):
        # An EnumValue should have the specified key.
        for enumtype, params in self.valid_values.items():
            for key, instance in params['values'].items():
                self.assertEqual(key, instance.key)

    def test_value_enumtype(self):
        # An EnumValue should have its associated enumtype.
        for enumtype, params in self.valid_values.items():
            for key, instance in params['values'].items():
                self.assertEqual(enumtype, instance.enumtype)


class Test_Enum(unittest.TestCase):
    """ Test case for the Enum class. """

    enum_factory_class = enum.Enum

    def setUp(self):
        """ Set up the test fixtures. """
        setup_enum_value_fixtures(self)

    def test_empty_enum(self):
        # Enum constructor should refuse empty keys sequence.
        self.assertRaises(enum.EnumEmptyError, enum.Enum)

    def test_bad_key(self):
        # Enum constructor should refuse non-string keys.
        for key in self.bad_keys:
            args = ("valid", key, "valid")
            self.assertRaises(enum.EnumBadKeyError, enum.Enum, *args)

    def test_bad_index(self):
        self.assertRaises(enum.EnumBadIndexError, enum.Enum,
                          (enum.EnumValue(None, 1, 'a')))

    def test_bad_type(self):
        self.assertRaises(enum.EnumBadTypeError, enum.Enum, *(
            enum.EnumValue(None, 0, 'a'), enum.EnumValue(1, 1, 'b')))

    def test_value_attributes(self):
        # Enumeration should have attributes for each value.
        for enumtype, params in self.valid_values.items():
            for i, key in enumerate(params['keys']):
                instance = getattr(enumtype, key)
                test_value = enum.EnumValue(enumtype, i, key)
                self.assertEqual(test_value, instance)

    def test_length(self):
        # Enumeration should have length of its value set.
        for enumtype, params in self.valid_values.items():
            self.assertEqual(len(params['values']), len(enumtype))

    def test_value_items(self):
        # Enumeration should have items for each value.
        for enumtype, params in self.valid_values.items():
            for i, key in enumerate(params['keys']):
                value = enumtype[i]
                test_value = enum.EnumValue(enumtype, i, key)
                self.assertEqual(test_value, value)

    def test_iterable(self):
        # Enumeration class should iterate over its values.
        for enumtype, params in self.valid_values.items():
            for i, value in enumerate(enumtype):
                key = params['keys'][i]
                test_value = params['values'][key]
            self.assertEqual(value, test_value)

    def test_iterate_sequence(self):
        # Enumeration iteration should match specified sequence.
        for enumtype, params in self.valid_values.items():
            values_dict = params['values']
            values_seq = [values_dict[key] for key in params['keys']]
            enum_seq = [val for val in enumtype]
            self.assertEqual(values_seq, enum_seq)
            self.assertNotEqual(values_seq.reverse(), enum_seq)

    def XXtest_membership_bogus(self):
        """ Enumeration should not contain bogus values. """
        for enumtype, params in self.valid_values.items():
            for value in self.other_values:
                print(value in self.other_values, value)
                self.assertFalse(value in enumtype)

    def test_membership_value(self):
        # Enumeration should contain explicit value.
        for enumtype, params in self.valid_values.items():
            for i, key in enumerate(params['keys']):
                value = params['values'][key]
                self.assertTrue(value in enumtype)

    def test_membership_key(self):
        # Enumeration should contain key string.
        for enumtype, params in self.valid_values.items():
            for key in params['keys']:
                self.assertTrue(key in enumtype)

    def test_add_attribute(self):
        # Enumeration should refuse attribute addition.
        for enumtype, params in self.valid_values.items():
            self.assertRaises(enum.EnumImmutableError, setattr, enumtype,
                              'bogus', "bogus")

    def test_modify_attribute(self):
        # Enumeration should refuse attribute modification.
        for enumtype, params in self.valid_values.items():
            for key in params['keys']:
                self.assertRaises(enum.EnumImmutableError, setattr, enumtype,
                                  key, "bogus")

    def test_delete_attribute(self):
        # Enumeration should refuse attribute deletion.
        for enumtype, params in self.valid_values.items():
            for key in params['keys']:
                self.assertRaises(enum.EnumImmutableError, delattr, enumtype,
                                  key)

    def test_add_item(self):
        # Enumeration should refuse item addition.
        for enumtype, params in self.valid_values.items():
            index = len(params['keys'])
            self.assertRaises(enum.EnumImmutableError, enumtype.__setitem__,
                              index, "bogus")

    def test_modify_item(self):
        # Enumeration should refuse item modification.
        for enumtype, params in self.valid_values.items():
            for i, key in enumerate(params['keys']):
                self.assertRaises(enum.EnumImmutableError, enumtype.__setitem__,
                                  i, "bogus")

    def test_delete_item(self):
        # Enumeration should refuse item deletion.
        for enumtype, params in self.valid_values.items():
            for i, key in enumerate(params['keys']):
                self.assertRaises(enum.EnumImmutableError, enumtype.__delitem__,
                                  i)


def suite():
    """ Create the test suite for this module. """
    from sys import modules
    loader = unittest.TestLoader()
    suite = loader.loadTestsFromModule(modules[__name__])
    return suite


def __main__(argv=None):
    """ Mainline function for this module. """
    import sys as _sys
    if not argv:
        argv = _sys.argv

    exitcode = None
    try:
        unittest.main(argv=argv, defaultTest='suite')
    except SystemExit as exc:
        exitcode = exc.code

    return exitcode


if __name__ == '__main__':
    import sys
    exitcode = __main__(sys.argv)
    sys.exit(exitcode)

# Local variables:
# mode: python
# End:
# vim: filetype=python fileencoding=utf-8 :
