From af64b3f19e727fc9c5617bcc8163d53fcdf40c43 Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Mon, 17 Jul 2023 17:35:14 +0300 Subject: [PATCH] interval: validate limits Before this patch, any value was allowed for interval attributes. Now we use the same rules as in Tarantool. A couple of issues were met while developing this patch, follow [1, 2] for core updates. 1. https://github.com/tarantool/tarantool/issues/8878 2. https://github.com/tarantool/tarantool/issues/8887 --- CHANGELOG.md | 3 + tarantool/msgpack_ext/types/interval.py | 58 ++++ test/suites/test_interval.py | 337 ++++++++++++++++++++++++ 3 files changed, 398 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35b5dcdc..1e9aff0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed +- Validate `tarantool.Interval` limits with the same rules as in Tarantool. + ### Fixed - `tarantool.Interval` arithmetic with weeks - `tarantool.Interval` weeks display in `str()` and `repr()` diff --git a/tarantool/msgpack_ext/types/interval.py b/tarantool/msgpack_ext/types/interval.py index 54551a18..e44dcce0 100644 --- a/tarantool/msgpack_ext/types/interval.py +++ b/tarantool/msgpack_ext/types/interval.py @@ -16,6 +16,60 @@ 8: 'adjust', } +# https://github.com/tarantool/tarantool/blob/ff57f990f359f6d7866c1947174d8ba0e97b1ea6/src/lua/datetime.lua#L112-L146 +SECS_PER_DAY = 86400 + +MIN_DATE_YEAR = -5879610 +MIN_DATE_MONTH = 6 +MIN_DATE_DAY = 22 +MAX_DATE_YEAR = 5879611 +MAX_DATE_MONTH = 7 +MAX_DATE_DAY = 11 + +AVERAGE_DAYS_YEAR = 365.25 +AVERAGE_WEEK_YEAR = AVERAGE_DAYS_YEAR / 7 +INT_MAX = 2147483647 +MAX_YEAR_RANGE = MAX_DATE_YEAR - MIN_DATE_YEAR +MAX_MONTH_RANGE = MAX_YEAR_RANGE * 12 +MAX_WEEK_RANGE = MAX_YEAR_RANGE * AVERAGE_WEEK_YEAR +MAX_DAY_RANGE = MAX_YEAR_RANGE * AVERAGE_DAYS_YEAR +MAX_HOUR_RANGE = MAX_DAY_RANGE * 24 +MAX_MIN_RANGE = MAX_HOUR_RANGE * 60 +MAX_SEC_RANGE = MAX_DAY_RANGE * SECS_PER_DAY +MAX_NSEC_RANGE = INT_MAX + +max_val = { + 'year': MAX_YEAR_RANGE, + 'month': MAX_MONTH_RANGE, + 'week': MAX_WEEK_RANGE, + 'day': MAX_DAY_RANGE, + 'hour': MAX_HOUR_RANGE, + 'minute': MAX_MIN_RANGE, + 'sec': MAX_SEC_RANGE, + 'nsec': MAX_NSEC_RANGE, +} + + +def verify_range(intv): + """ + Check allowed values. Approach is the same as in tarantool/tarantool. + + :param intv: Raw interval to verify. + :type intv: :class:`~tarantool.Interval` + + :raise: :exc:`ValueError` + + :meta private: + """ + + for field_name, range_max in max_val.items(): + val = getattr(intv, field_name) + # Tarantool implementation has a bug + # https://github.com/tarantool/tarantool/issues/8878 + if (val > range_max) or (val < -range_max): + raise ValueError(f"value {val} of {field_name} is out of " + f"allowed range [{-range_max}, {range_max}]") + # https://github.com/tarantool/c-dt/blob/cec6acebb54d9e73ea0b99c63898732abd7683a6/dt_arithmetic.h#L34 class Adjust(Enum): @@ -92,6 +146,8 @@ def __init__(self, *, year=0, month=0, week=0, :param adjust: Interval adjustment rule. Refer to :meth:`~tarantool.Datetime.__add__`. :type adjust: :class:`~tarantool.IntervalAdjust`, optional + + :raise: :exc:`ValueError` """ self.year = year @@ -104,6 +160,8 @@ def __init__(self, *, year=0, month=0, week=0, self.nsec = nsec self.adjust = adjust + verify_range(self) + def __add__(self, other): """ Valid operations: diff --git a/test/suites/test_interval.py b/test/suites/test_interval.py index 6917b49c..72d14815 100644 --- a/test/suites/test_interval.py +++ b/test/suites/test_interval.py @@ -13,6 +13,16 @@ from tarantool.error import MsgpackError from tarantool.msgpack_ext.packer import default as packer_default from tarantool.msgpack_ext.unpacker import ext_hook as unpacker_ext_hook +from tarantool.msgpack_ext.types.interval import ( + MAX_YEAR_RANGE, + MAX_MONTH_RANGE, + MAX_WEEK_RANGE, + MAX_DAY_RANGE, + MAX_HOUR_RANGE, + MAX_MIN_RANGE, + MAX_SEC_RANGE, + MAX_NSEC_RANGE, +) from .lib.tarantool_server import TarantoolServer from .lib.skip import skip_or_run_datetime_test @@ -198,6 +208,230 @@ def test_interval_positional_init(self): 'str': 'tarantool.Interval(year=1, month=2, week=3, day=4, hour=1, ' 'minute=2, sec=3000, nsec=10000000, adjust=Adjust.NONE)', }, + 'min_year_interval': { + 'python': tarantool.Interval(year=-int(MAX_YEAR_RANGE)), + 'msgpack': (b'\x02\x00\xd2\xff\x4c\x91\x8b\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{year=-{int(MAX_YEAR_RANGE)} + 1}}) - " + r"datetime.interval.new({year=1})", + 'str': f'tarantool.Interval(year=-{int(MAX_YEAR_RANGE)}, month=0, week=0, day=0, ' + 'hour=0, minute=0, sec=0, nsec=0, adjust=Adjust.NONE)', + }, + 'max_year_interval': { + 'python': tarantool.Interval(year=int(MAX_YEAR_RANGE)), + 'msgpack': (b'\x02\x00\xce\x00\xb3\x6e\x75\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{year={int(MAX_YEAR_RANGE)} - 1}}) + " + r"datetime.interval.new({year=1})", + 'str': f'tarantool.Interval(year={int(MAX_YEAR_RANGE)}, month=0, week=0, day=0, ' + 'hour=0, minute=0, sec=0, nsec=0, adjust=Adjust.NONE)', + }, + 'min_month_interval': { + 'python': tarantool.Interval(month=-int(MAX_MONTH_RANGE)), + 'msgpack': (b'\x02\x01\xd2\xf7\x96\xd2\x84\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{month=-{int(MAX_MONTH_RANGE)} + 1}}) - " + r"datetime.interval.new({month=1})", + 'str': f'tarantool.Interval(year=0, month=-{int(MAX_MONTH_RANGE)}, week=0, day=0, ' + 'hour=0, minute=0, sec=0, nsec=0, adjust=Adjust.NONE)', + }, + 'max_month_interval': { + 'python': tarantool.Interval(month=int(MAX_MONTH_RANGE)), + 'msgpack': (b'\x02\x01\xce\x08\x69\x2d\x7c\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{month={int(MAX_MONTH_RANGE)} - 1}}) + " + r"datetime.interval.new({month=1})", + 'str': f'tarantool.Interval(year=0, month={int(MAX_MONTH_RANGE)}, week=0, day=0, ' + 'hour=0, minute=0, sec=0, nsec=0, adjust=Adjust.NONE)', + }, + 'min_week_interval': { + 'python': tarantool.Interval(week=-int(MAX_WEEK_RANGE)), + 'msgpack': (b'\x02\x02\xd2\xdb\x6d\x85\xa8\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{week=-{int(MAX_WEEK_RANGE)} + 1}}) - " + r"datetime.interval.new({week=1})", + 'str': f'tarantool.Interval(year=0, month=0, week=-{int(MAX_WEEK_RANGE)}, day=0, ' + 'hour=0, minute=0, sec=0, nsec=0, adjust=Adjust.NONE)', + }, + 'max_week_interval': { + 'python': tarantool.Interval(week=int(MAX_WEEK_RANGE)), + 'msgpack': (b'\x02\x02\xce\x24\x92\x7a\x58\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{week={int(MAX_WEEK_RANGE)} - 1}}) + " + r"datetime.interval.new({week=1})", + 'str': f'tarantool.Interval(year=0, month=0, week={int(MAX_WEEK_RANGE)}, day=0, ' + 'hour=0, minute=0, sec=0, nsec=0, adjust=Adjust.NONE)', + }, + 'min_day_interval': { + 'python': tarantool.Interval(day=-int(MAX_DAY_RANGE)), + 'msgpack': (b'\x02\x03\xd3\xff\xff\xff\xfe\xff\xfe\xa7\x92\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{day=-{int(MAX_DAY_RANGE)} + 1}}) - " + r"datetime.interval.new({day=1})", + 'str': f'tarantool.Interval(year=0, month=0, week=0, day=-{int(MAX_DAY_RANGE)}, ' + 'hour=0, minute=0, sec=0, nsec=0, adjust=Adjust.NONE)', + 'tarantool_8887_issue': True, + }, + 'max_day_interval': { + 'python': tarantool.Interval(day=int(MAX_DAY_RANGE)), + 'msgpack': (b'\x02\x03\xcf\x00\x00\x00\x01\x00\x01\x58\x6e\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{day={int(MAX_DAY_RANGE)} - 1}}) + " + r"datetime.interval.new({day=1})", + 'str': f'tarantool.Interval(year=0, month=0, week=0, day={int(MAX_DAY_RANGE)}, hour=0, ' + 'minute=0, sec=0, nsec=0, adjust=Adjust.NONE)', + 'tarantool_8887_issue': True, + }, + 'min_int32_day_interval': { + 'python': tarantool.Interval(day=-2147483648), + 'msgpack': (b'\x02\x03\xd2\x80\x00\x00\x00\x08\x01'), + 'tarantool': r"datetime.interval.new({day=-2147483648})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=-2147483648, hour=0, ' + 'minute=0, sec=0, nsec=0, adjust=Adjust.NONE)', + }, + 'max_int32_day_interval': { + 'python': tarantool.Interval(day=2147483647), + 'msgpack': (b'\x02\x03\xce\x7f\xff\xff\xff\x08\x01'), + 'tarantool': r"datetime.interval.new({day=2147483647})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=2147483647, hour=0, ' + 'minute=0, sec=0, nsec=0, adjust=Adjust.NONE)', + }, + 'min_hour_interval': { + 'python': tarantool.Interval(hour=-int(MAX_HOUR_RANGE)), + 'msgpack': (b'\x02\x04\xd3\xff\xff\xff\xe7\xff\xdf\xb5\xaa\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{hour=-{MAX_HOUR_RANGE} + 1}}) - " + r"datetime.interval.new({hour=1})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=0, ' + f'hour=-{int(MAX_HOUR_RANGE)}, minute=0, sec=0, nsec=0, adjust=Adjust.NONE)', + 'tarantool_8887_issue': True, + }, + 'max_hour_interval': { + 'python': tarantool.Interval(hour=int(MAX_HOUR_RANGE)), + 'msgpack': (b'\x02\x04\xcf\x00\x00\x00\x18\x00\x20\x4a\x56\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{hour={MAX_HOUR_RANGE} - 1}}) + " + r"datetime.interval.new({hour=1})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=0, ' + f'hour={int(MAX_HOUR_RANGE)}, minute=0, sec=0, nsec=0, adjust=Adjust.NONE)', + 'tarantool_8887_issue': True, + }, + 'min_int32_hour_interval': { + 'python': tarantool.Interval(hour=-2147483648), + 'msgpack': (b'\x02\x04\xd2\x80\x00\x00\x00\x08\x01'), + 'tarantool': r"datetime.interval.new({hour=-2147483648})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=0, hour=-2147483648, ' + 'minute=0, sec=0, nsec=0, adjust=Adjust.NONE)', + }, + 'max_int32_hour_interval': { + 'python': tarantool.Interval(hour=2147483647), + 'msgpack': (b'\x02\x04\xce\x7f\xff\xff\xff\x08\x01'), + 'tarantool': r"datetime.interval.new({hour=2147483647})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=0, hour=2147483647, ' + 'minute=0, sec=0, nsec=0, adjust=Adjust.NONE)', + }, + 'min_minute_interval': { + 'python': tarantool.Interval(minute=-int(MAX_MIN_RANGE)), + 'msgpack': (b'\x02\x05\xd3\xff\xff\xfa\x5f\xf8\x6e\x93\xd8\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{min=-{int(MAX_MIN_RANGE)} + 1}}) - " + r"datetime.interval.new({min=1})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=0, hour=0, ' + f'minute=-{int(MAX_MIN_RANGE)}, sec=0, nsec=0, adjust=Adjust.NONE)', + 'tarantool_8887_issue': True, + }, + 'max_minute_interval': { + 'python': tarantool.Interval(minute=int(MAX_MIN_RANGE)), + 'msgpack': (b'\x02\x05\xcf\x00\x00\x05\xa0\x07\x91\x6c\x28\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{min={int(MAX_MIN_RANGE)} - 1}}) + " + r"datetime.interval.new({min=1})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=0, hour=0, ' + f'minute={int(MAX_MIN_RANGE)}, sec=0, nsec=0, adjust=Adjust.NONE)', + 'tarantool_8887_issue': True, + }, + 'min_int32_minute_interval': { + 'python': tarantool.Interval(minute=-2147483648), + 'msgpack': (b'\x02\x05\xd2\x80\x00\x00\x00\x08\x01'), + 'tarantool': r"datetime.interval.new({min=-2147483648})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=0, hour=0, ' + 'minute=-2147483648, sec=0, nsec=0, adjust=Adjust.NONE)', + }, + 'max_int32_minute_interval': { + 'python': tarantool.Interval(minute=2147483647), + 'msgpack': (b'\x02\x05\xce\x7f\xff\xff\xff\x08\x01'), + 'tarantool': r"datetime.interval.new({min=2147483647})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=0, hour=0, ' + 'minute=2147483647, sec=0, nsec=0, adjust=Adjust.NONE)', + }, + 'min_sec_interval': { + 'python': tarantool.Interval(sec=-int(MAX_SEC_RANGE)), + 'msgpack': (b'\x02\x06\xd3\xff\xfe\xae\x7e\x39\xea\xa6\xa0\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{sec=-{int(MAX_SEC_RANGE)} + 1}}) - " + r"datetime.interval.new({sec=1})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=0, hour=0, ' + f'minute=0, sec=-{int(MAX_SEC_RANGE)}, nsec=0, adjust=Adjust.NONE)', + 'tarantool_8887_issue': True, + }, + 'max_sec_interval': { + 'python': tarantool.Interval(sec=int(MAX_SEC_RANGE)), + 'msgpack': (b'\x02\x06\xcf\x00\x01\x51\x81\xc6\x15\x59\x60\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{sec={int(MAX_SEC_RANGE)} - 1}}) + " + r"datetime.interval.new({sec=1})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=0, hour=0, ' + f'minute=0, sec={int(MAX_SEC_RANGE)}, nsec=0, adjust=Adjust.NONE)', + 'tarantool_8887_issue': True, + }, + 'min_int32_sec_interval': { + 'python': tarantool.Interval(sec=-2147483648), + 'msgpack': (b'\x02\x06\xd2\x80\x00\x00\x00\x08\x01'), + 'tarantool': r"datetime.interval.new({sec=-2147483648})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=0, hour=0, ' + 'minute=0, sec=-2147483648, nsec=0, adjust=Adjust.NONE)', + }, + 'max_int32_sec_interval': { + 'python': tarantool.Interval(sec=2147483647), + 'msgpack': (b'\x02\x06\xce\x7f\xff\xff\xff\x08\x01'), + 'tarantool': r"datetime.interval.new({sec=2147483647})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=0, hour=0, ' + 'minute=0, sec=2147483647, nsec=0, adjust=Adjust.NONE)', + }, + 'min_nsec_interval': { + 'python': tarantool.Interval(nsec=-int(MAX_NSEC_RANGE)), + 'msgpack': (b'\x02\x07\xd2\x80\x00\x00\x01\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{nsec=-{int(MAX_NSEC_RANGE)} + 1}}) - " + r"datetime.interval.new({nsec=1})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=0, hour=0, ' + f'minute=0, sec=0, nsec=-{int(MAX_NSEC_RANGE)}, adjust=Adjust.NONE)', + }, + 'max_nsec_interval': { + 'python': tarantool.Interval(nsec=int(MAX_NSEC_RANGE)), + 'msgpack': (b'\x02\x07\xce\x7f\xff\xff\xff\x08\x01'), + # Reason why Tarantool datetime is so weird here: + # https://github.com/tarantool/tarantool/issues/8878 + 'tarantool': f"datetime.interval.new({{nsec={int(MAX_NSEC_RANGE)} - 1}}) + " + r"datetime.interval.new({nsec=1})", + 'str': 'tarantool.Interval(year=0, month=0, week=0, day=0, hour=0, ' + f'minute=0, sec=0, nsec={int(MAX_NSEC_RANGE)}, adjust=Adjust.NONE)', + }, } def test_msgpack_decode(self): @@ -215,6 +449,9 @@ def test_msgpack_decode(self): def test_tarantool_decode(self): for name, case in self.cases.items(): with self.subTest(msg=name): + if ('tarantool_8887_issue' in case) and (case['tarantool_8887_issue'] is True): + self.skipTest('See https://github.com/tarantool/tarantool/issues/8887') + self.adm(f"box.space['test']:replace{{'{name}', {case['tarantool']}, 'field'}}") self.assertSequenceEqual(self.con.select('test', name), @@ -230,6 +467,9 @@ def test_msgpack_encode(self): def test_tarantool_encode(self): for name, case in self.cases.items(): with self.subTest(msg=name): + if ('tarantool_8887_issue' in case) and (case['tarantool_8887_issue'] is True): + self.skipTest('See https://github.com/tarantool/tarantool/issues/8887') + self.con.insert('test', [name, case['python'], 'field']) lua_eval = f""" @@ -266,6 +506,87 @@ def test_unknown_adjust_decode(self): MsgpackError, '3 is not a valid Adjust', lambda: unpacker_ext_hook(6, case, self.con._unpacker_factory())) + out_of_range_cases = { + 'year_too_small': { + 'kwargs': {'year': -int(MAX_YEAR_RANGE + 1)}, + 'range': MAX_YEAR_RANGE, + }, + 'year_too_large': { + 'kwargs': {'year': int(MAX_YEAR_RANGE + 1)}, + 'range': MAX_YEAR_RANGE, + }, + 'month_too_small': { + 'kwargs': {'month': -int(MAX_MONTH_RANGE + 1)}, + 'range': MAX_MONTH_RANGE, + }, + 'month_too_big': { + 'kwargs': {'month': int(MAX_MONTH_RANGE + 1)}, + 'range': MAX_MONTH_RANGE, + }, + 'week_too_small': { + 'kwargs': {'week': -int(MAX_WEEK_RANGE + 1)}, + 'range': MAX_WEEK_RANGE, + }, + 'week_too_big': { + 'kwargs': {'week': int(MAX_WEEK_RANGE + 1)}, + 'range': MAX_WEEK_RANGE, + }, + 'day_too_small': { + 'kwargs': {'day': -int(MAX_DAY_RANGE + 1)}, + 'range': MAX_DAY_RANGE, + }, + 'day_too_big': { + 'kwargs': {'day': int(MAX_DAY_RANGE + 1)}, + 'range': MAX_DAY_RANGE, + }, + 'hour_too_small': { + 'kwargs': {'hour': -int(MAX_HOUR_RANGE + 1)}, + 'range': MAX_HOUR_RANGE, + }, + 'hour_too_big': { + 'kwargs': {'hour': int(MAX_HOUR_RANGE + 1)}, + 'range': MAX_HOUR_RANGE, + }, + 'minute_too_small': { + 'kwargs': {'minute': -int(MAX_MIN_RANGE + 1)}, + 'range': MAX_MIN_RANGE, + }, + 'minute_too_big': { + 'kwargs': {'minute': int(MAX_MIN_RANGE + 1)}, + 'range': MAX_MIN_RANGE, + }, + 'sec_too_small': { + 'kwargs': {'sec': -int(MAX_SEC_RANGE + 1)}, + 'range': MAX_SEC_RANGE, + }, + 'sec_too_big': { + 'kwargs': {'sec': int(MAX_SEC_RANGE + 1)}, + 'range': MAX_SEC_RANGE, + }, + 'nsec_too_small': { + 'kwargs': {'nsec': -int(MAX_NSEC_RANGE + 1)}, + 'range': MAX_NSEC_RANGE, + }, + 'nsec_too_big': { + 'kwargs': {'nsec': int(MAX_NSEC_RANGE + 1)}, + 'range': MAX_NSEC_RANGE, + }, + } + + def test_out_of_range(self): + # pylint: disable=cell-var-from-loop + + for name, case in self.out_of_range_cases.items(): + with self.subTest(msg=name): + name = next(iter(case['kwargs'])) + val = case['kwargs'][name] + self.assertRaisesRegex( + ValueError, re.escape( + f"value {val} of {name} is out of " + f"allowed range [{-case['range']}, {case['range']}]" + ), + lambda: tarantool.Interval(**case['kwargs'])) + arithmetic_cases = { 'year': { 'arg_1': tarantool.Interval(year=2), @@ -369,6 +690,22 @@ def test_tarantool_interval_subtraction(self): self.assertSequenceEqual(self.con.call('sub', case['arg_1'], case['arg_2']), [case['res_sub']]) + def test_addition_overflow(self): + self.assertRaisesRegex( + ValueError, re.escape( + f"value {int(MAX_YEAR_RANGE) + 1} of year is out of " + f"allowed range [{-MAX_YEAR_RANGE}, {MAX_YEAR_RANGE}]" + ), + lambda: tarantool.Interval(year=int(MAX_YEAR_RANGE)) + tarantool.Interval(year=1)) + + def test_subtraction_overflow(self): + self.assertRaisesRegex( + ValueError, re.escape( + f"value {-int(MAX_YEAR_RANGE) - 1} of year is out of " + f"allowed range [{-MAX_YEAR_RANGE}, {MAX_YEAR_RANGE}]" + ), + lambda: tarantool.Interval(year=-int(MAX_YEAR_RANGE)) - tarantool.Interval(year=1)) + @classmethod def tearDownClass(cls): cls.con.close()