From 10b069921e74f0d0411fb105349471e0f9a79f29 Mon Sep 17 00:00:00 2001 From: David Douard <david.douard@sdfa3.org> Date: Thu, 12 Mar 2020 16:01:55 +0100 Subject: [PATCH] model: improve a bit the TimestampWithTimezone model - add a validator for negative_utc (can be True iff offset is 0), - update the timestamps_with_timezone hypothesis strategy, - add low-level tests for it. --- swh/model/hypothesis_strategies.py | 20 +++++-- swh/model/model.py | 5 ++ swh/model/tests/test_model.py | 92 +++++++++++++++++++++++++++++- 3 files changed, 111 insertions(+), 6 deletions(-) diff --git a/swh/model/hypothesis_strategies.py b/swh/model/hypothesis_strategies.py index 19c3fd31..542b457e 100644 --- a/swh/model/hypothesis_strategies.py +++ b/swh/model/hypothesis_strategies.py @@ -6,8 +6,9 @@ import attr import datetime +from hypothesis import assume from hypothesis.strategies import ( - binary, builds, characters, composite, dictionaries, + binary, booleans, builds, characters, composite, dictionaries, from_regex, integers, just, lists, none, one_of, sampled_from, sets, text, tuples, ) @@ -65,11 +66,20 @@ def timestamps(): microseconds=integers(0, 1000000)) -def timestamps_with_timezone(): - return builds( - TimestampWithTimezone, +@composite +def timestamps_with_timezone( + draw, timestamp=timestamps(), - offset=integers(min_value=-14*60, max_value=14*60)) + offset=integers(min_value=-14*60, max_value=14*60), + negative_utc=booleans()): + timestamp = draw(timestamp) + offset = draw(offset) + negative_utc = draw(negative_utc) + assume(not (negative_utc and offset)) + return TimestampWithTimezone( + timestamp=timestamp, + offset=offset, + negative_utc=negative_utc) def origins(): diff --git a/swh/model/model.py b/swh/model/model.py index eb2ec15a..6fdfe329 100644 --- a/swh/model/model.py +++ b/swh/model/model.py @@ -180,6 +180,11 @@ class TimestampWithTimezone(BaseModel): # you'll find in the wild... raise ValueError('offset too large: %d minutes' % value) + @negative_utc.validator + def check_negative_utc(self, attribute, value): + if self.offset and value: + raise ValueError("negative_utc can only be True is offset=0") + @classmethod def from_dict(cls, obj: Union[Dict, datetime.datetime, int]): """Builds a TimestampWithTimezone from any of the formats diff --git a/swh/model/tests/test_model.py b/swh/model/tests/test_model.py index 35d5695d..e167d5a7 100644 --- a/swh/model/tests/test_model.py +++ b/swh/model/tests/test_model.py @@ -18,9 +18,11 @@ from swh.model.model import ( MissingData, Person ) from swh.model.hashutil import hash_to_bytes, MultiHash + from swh.model.hypothesis_strategies import ( - objects, origins, origin_visits, origin_visit_updates + objects, origins, origin_visits, origin_visit_updates, timestamps ) + from swh.model.identifiers import ( directory_identifier, revision_identifier, release_identifier, snapshot_identifier @@ -72,6 +74,13 @@ def test_todict_origin_visit_updates(origin_visit_update): assert origin_visit_update == type(origin_visit_update).from_dict(obj) +# Timestamp + +@given(timestamps()) +def test_timestamps_strategy(timestamp): + attr.validate(timestamp) + + def test_timestamp_seconds(): attr.validate(Timestamp(seconds=0, microseconds=0)) with pytest.raises(AttributeTypeError): @@ -99,6 +108,87 @@ def test_timestamp_microseconds(): Timestamp(seconds=0, microseconds=-1) +def test_timestamp_from_dict(): + assert Timestamp.from_dict({'seconds': 10, 'microseconds': 5}) + + with pytest.raises(AttributeTypeError): + Timestamp.from_dict({'seconds': '10', 'microseconds': 5}) + + with pytest.raises(AttributeTypeError): + Timestamp.from_dict({'seconds': 10, 'microseconds': '5'}) + with pytest.raises(ValueError): + Timestamp.from_dict({'seconds': 0, 'microseconds': -1}) + + Timestamp.from_dict({'seconds': 0, 'microseconds': 10**6 - 1}) + with pytest.raises(ValueError): + Timestamp.from_dict({'seconds': 0, 'microseconds': 10**6}) + + +# TimestampWithTimezone + +def test_timestampwithtimezone(): + ts = Timestamp(seconds=0, microseconds=0) + tstz = TimestampWithTimezone( + timestamp=ts, + offset=0, + negative_utc=False) + attr.validate(tstz) + assert tstz.negative_utc is False + + attr.validate(TimestampWithTimezone( + timestamp=ts, + offset=10, + negative_utc=False)) + + attr.validate(TimestampWithTimezone( + timestamp=ts, + offset=-10, + negative_utc=False)) + + tstz = TimestampWithTimezone( + timestamp=ts, + offset=0, + negative_utc=True) + attr.validate(tstz) + assert tstz.negative_utc is True + + with pytest.raises(AttributeTypeError): + TimestampWithTimezone( + timestamp=datetime.datetime.now(), + offset=0, + negative_utc=False) + + with pytest.raises(AttributeTypeError): + TimestampWithTimezone( + timestamp=ts, + offset='0', + negative_utc=False) + + with pytest.raises(AttributeTypeError): + TimestampWithTimezone( + timestamp=ts, + offset=1.0, + negative_utc=False) + + with pytest.raises(AttributeTypeError): + TimestampWithTimezone( + timestamp=ts, + offset=1, + negative_utc=0) + + with pytest.raises(ValueError): + TimestampWithTimezone( + timestamp=ts, + offset=1, + negative_utc=True) + + with pytest.raises(ValueError): + TimestampWithTimezone( + timestamp=ts, + offset=-1, + negative_utc=True) + + def test_timestampwithtimezone_from_datetime(): tz = datetime.timezone(datetime.timedelta(minutes=+60)) date = datetime.datetime( -- GitLab