diff --git a/swh/model/hypothesis_strategies.py b/swh/model/hypothesis_strategies.py --- 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 @@ 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 --- a/swh/model/model.py +++ b/swh/model/model.py @@ -180,6 +180,11 @@ # 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 --- a/swh/model/tests/test_model.py +++ b/swh/model/tests/test_model.py @@ -18,9 +18,11 @@ 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 @@ 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 @@ 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(