From 63d7649ed5e0252cbaf6bc1f0a02542f458b221f Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Tue, 21 Aug 2018 08:00:54 -0500 Subject: [PATCH] UUID sentinel Add a private _UUIDSentinels() class to oslo_utils.fixture that behaves like a mock.sentinel, but returns specifically UUIDs. Expose a (singleton) instance of it called uuidsentinel. This is mostly copied from [1], with the following differences: - We don't do the enforced-singleton thing [2]. But importing the uuidsentinel global yields the same behavior. - We don't do the local import thing [3][4], because we're already in the right lib, so no issues with circular imports. - Locking is added to make this threadsafe. (See discussion at [5]) Note that there is some question as to whether it is more appropriate for this to live here or in oslotest [6]. This has been discussed on the dev ML [7] and it was concluded [8] that it should live here. [1] https://github.com/openstack/nova/blob/722d5b477219f0a2435a9f4ad4d54c61b83219f1/nova/tests/uuidsentinel.py [2] https://github.com/openstack/nova/blob/722d5b477219f0a2435a9f4ad4d54c61b83219f1/nova/tests/uuidsentinel.py#L30 [3] https://github.com/openstack/nova/blob/722d5b477219f0a2435a9f4ad4d54c61b83219f1/nova/tests/uuidsentinel.py#L18-L19 [4] https://github.com/openstack/nova/blob/722d5b477219f0a2435a9f4ad4d54c61b83219f1/nova/tests/uuidsentinel.py#L26 [5] http://eavesdrop.openstack.org/irclogs/%23openstack-oslo/%23openstack-oslo.2018-08-20.log.html#t2018-08-20T20:10:33 [6] https://review.openstack.org/#/c/594068/ [7] http://lists.openstack.org/pipermail/openstack-dev/2018-August/133670.html [8] http://lists.openstack.org/pipermail/openstack-dev/2018-August/133861.html Change-Id: I214ff21b461fa1ca4b83476e1d0a763efe986217 --- oslo_utils/fixture.py | 41 ++++++++++++++++++++++++++++++++ oslo_utils/tests/test_fixture.py | 21 ++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/oslo_utils/fixture.py b/oslo_utils/fixture.py index 9526154e..1872c70e 100644 --- a/oslo_utils/fixture.py +++ b/oslo_utils/fixture.py @@ -20,9 +20,12 @@ Test fixtures. .. versionadded:: 1.3 """ +import threading + import fixtures from oslo_utils import timeutils +from oslo_utils import uuidutils class TimeFixture(fixtures.Fixture): @@ -49,3 +52,41 @@ class TimeFixture(fixtures.Fixture): def advance_time_seconds(self, seconds): """Advance overridden time by seconds.""" timeutils.advance_time_seconds(seconds) + + +class _UUIDSentinels(object): + """Registry of dynamically created, named, random UUID strings. + + An instance of this class will dynamically generate attributes as they are + referenced, associating a random UUID string with each. Thereafter, + referring to the same attribute will give the same UUID for the life of the + instance. Plan accordingly. + + Usage: + + from oslo_utils.fixture import uuidsentinel as uuids + ... + foo = uuids.foo + do_a_thing(foo) + # Referencing the same sentinel again gives the same value + assert foo == uuids.foo + # But a different one will be different + assert foo != uuids.bar + """ + def __init__(self): + self._sentinels = {} + self._lock = threading.Lock() + + def __getattr__(self, name): + if name.startswith('_'): + raise ValueError('Sentinels must not start with _') + with self._lock: + if name not in self._sentinels: + self._sentinels[name] = uuidutils.generate_uuid() + return self._sentinels[name] + + +# Singleton sentinel instance. Caveat emptor: using this multiple times in the +# same process (including across multiple modules) will result in the same +# values +uuidsentinel = _UUIDSentinels() diff --git a/oslo_utils/tests/test_fixture.py b/oslo_utils/tests/test_fixture.py index 3e0898ab..dafac2f3 100644 --- a/oslo_utils/tests/test_fixture.py +++ b/oslo_utils/tests/test_fixture.py @@ -17,9 +17,12 @@ import datetime from oslotest import base as test_base +import six from oslo_utils import fixture +from oslo_utils.fixture import uuidsentinel as uuids from oslo_utils import timeutils +from oslo_utils import uuidutils class TimeFixtureTest(test_base.BaseTestCase): @@ -61,3 +64,21 @@ class TimeFixtureTest(test_base.BaseTestCase): time_fixture.advance_time_seconds(2) expected_time = datetime.datetime(2015, 1, 2, 3, 4, 8, 7) self.assertEqual(expected_time, timeutils.utcnow()) + + +class UUIDSentinelsTest(test_base.BaseTestCase): + + def test_different_sentinel(self): + uuid1 = uuids.foobar + uuid2 = uuids.barfoo + self.assertNotEqual(uuid1, uuid2) + + def test_returns_uuid(self): + self.assertTrue(uuidutils.is_uuid_like(uuids.foo)) + + def test_returns_string(self): + self.assertIsInstance(uuids.foo, str) + + def test_with_underline_prefix(self): + ex = self.assertRaises(ValueError, getattr, uuids, '_foo') + self.assertIn("Sentinels must not start with _", six.text_type(ex))