# Path: string_utils/generation.py
# def roman_range(stop: int, start: int = 1, step: int = 1) -> Generator:
#     """
#     Similarly to native Python's `range()`, returns a Generator object which generates a new roman number
#     on each iteration instead of an integer.
# 
#     *Example:*
# 
#     >>> for n in roman_range(7): print(n)
#     >>> # prints: I, II, III, IV, V, VI, VII
#     >>> for n in roman_range(start=7, stop=1, step=-1): print(n)
#     >>> # prints: VII, VI, V, IV, III, II, I
# 
#     :param stop: Number at which the generation must stop (must be <= 3999).
#     :param start: Number at which the generation must start (must be >= 1).
#     :param step: Increment of each generation step (default to 1).
#     :return: Generator of roman numbers.
#     """
# 
#     def validate(arg_value, arg_name, allow_negative=False):
#         msg = '"{}" must be an integer in the range 1-3999'.format(arg_name)
# 
#         if not isinstance(arg_value, int):
#             raise ValueError(msg)
# 
#         if allow_negative:
#             arg_value = abs(arg_value)
# 
#         if arg_value < 1 or arg_value > 3999:
#             raise ValueError(msg)
# 
#     def generate():
#         current = start
# 
#         # generate values for each step
#         while current != stop:
#             yield roman_encode(current)
#             current += step
# 
#         # last value to return
#         yield roman_encode(current)
# 
#     # checks each single argument value
#     validate(stop, 'stop')
#     validate(start, 'start')
#     validate(step, 'step', allow_negative=True)
# 
#     # checks if the provided configuration leads to a feasible iteration with respect to boundaries or not
#     forward_exceed = step > 0 and (start > stop or start + step > stop)
#     backward_exceed = step < 0 and (start < stop or start + step < stop)
#     if forward_exceed or backward_exceed:
#         raise OverflowError('Invalid start/stop/step configuration')
# 
#     return generate()


from unittest import TestCase
from string_utils.generation import roman_range
    def test_range_returns_expected_generator_for_start_stop_call_backward(self):
        generator = roman_range(start=10, stop=1, step=-1)

        self.assertEqual(generator.__next__(), 'X')
        self.assertEqual(generator.__next__(), 'IX')
        self.assertEqual(generator.__next__(), 'VIII')
        self.assertEqual(generator.__next__(), 'VII')
        self.assertEqual(generator.__next__(), 'VI')
        self.assertEqual(generator.__next__(), 'V')
        self.assertEqual(generator.__next__(), 'IV')
        self.assertEqual(generator.__next__(), 'III')
        self.assertEqual(generator.__next__(), 'II')
        self.assertEqual(generator.__next__(), 'I')

        # generator has been consumed, so another call will raise an exception
        self.assertRaises(StopIteration, generator.__next__)

    def test_range_returns_expected_generator_for_start_stop_step_call(self):
        generator = roman_range(start=10, stop=30, step=5)

        self.assertEqual(generator.__next__(), 'X')
        self.assertEqual(generator.__next__(), 'XV')
        self.assertEqual(generator.__next__(), 'XX')
        self.assertEqual(generator.__next__(), 'XXV')
        self.assertEqual(generator.__next__(), 'XXX')

        # generator has been consumed, so another call will raise an exception
        self.assertRaises(StopIteration, generator.__next__)

    def test_start_param_cannot_be_less_than_1(self):

# Path: string_utils/generation.py
# def roman_range(stop: int, start: int = 1, step: int = 1) -> Generator:
#     """
#     Similarly to native Python's `range()`, returns a Generator object which generates a new roman number
#     on each iteration instead of an integer.
# 
#     *Example:*
# 
#     >>> for n in roman_range(7): print(n)
#     >>> # prints: I, II, III, IV, V, VI, VII
#     >>> for n in roman_range(start=7, stop=1, step=-1): print(n)
#     >>> # prints: VII, VI, V, IV, III, II, I
# 
#     :param stop: Number at which the generation must stop (must be <= 3999).
#     :param start: Number at which the generation must start (must be >= 1).
#     :param step: Increment of each generation step (default to 1).
#     :return: Generator of roman numbers.
#     """
# 
#     def validate(arg_value, arg_name, allow_negative=False):
#         msg = '"{}" must be an integer in the range 1-3999'.format(arg_name)
# 
#         if not isinstance(arg_value, int):
#             raise ValueError(msg)
# 
#         if allow_negative:
#             arg_value = abs(arg_value)
# 
#         if arg_value < 1 or arg_value > 3999:
#             raise ValueError(msg)
# 
#     def generate():
#         current = start
# 
#         # generate values for each step
#         while current != stop:
#             yield roman_encode(current)
#             current += step
# 
#         # last value to return
#         yield roman_encode(current)
# 
#     # checks each single argument value
#     validate(stop, 'stop')
#     validate(start, 'start')
#     validate(step, 'step', allow_negative=True)
# 
#     # checks if the provided configuration leads to a feasible iteration with respect to boundaries or not
#     forward_exceed = step > 0 and (start > stop or start + step > stop)
#     backward_exceed = step < 0 and (start < stop or start + step < stop)
#     if forward_exceed or backward_exceed:
#         raise OverflowError('Invalid start/stop/step configuration')
# 
#     return generate()


# Path: tests/test_roman_range.py
from unittest import TestCase
from string_utils.generation import roman_range

    def test_range_returns_expected_generator_for_start_stop_call_backward(self):
        generator = roman_range(start=10, stop=1, step=-1)

        self.assertEqual(generator.__next__(), 'X')
        self.assertEqual(generator.__next__(), 'IX')
        self.assertEqual(generator.__next__(), 'VIII')
        self.assertEqual(generator.__next__(), 'VII')
        self.assertEqual(generator.__next__(), 'VI')
        self.assertEqual(generator.__next__(), 'V')
        self.assertEqual(generator.__next__(), 'IV')
        self.assertEqual(generator.__next__(), 'III')
        self.assertEqual(generator.__next__(), 'II')
        self.assertEqual(generator.__next__(), 'I')

        # generator has been consumed, so another call will raise an exception
        self.assertRaises(StopIteration, generator.__next__)

    def test_range_returns_expected_generator_for_start_stop_step_call(self):
        generator = roman_range(start=10, stop=30, step=5)

        self.assertEqual(generator.__next__(), 'X')
        self.assertEqual(generator.__next__(), 'XV')
        self.assertEqual(generator.__next__(), 'XX')
        self.assertEqual(generator.__next__(), 'XXV')
        self.assertEqual(generator.__next__(), 'XXX')

        # generator has been consumed, so another call will raise an exception
        self.assertRaises(StopIteration, generator.__next__)

    def test_start_param_cannot_be_less_than_1(self):

        self.assertRaises(ValueError, lambda: roman_range(start=0, stop=10))