-
Notifications
You must be signed in to change notification settings - Fork 45
/
Copy pathfeature_store_test_base.py
132 lines (105 loc) · 5.01 KB
/
feature_store_test_base.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
from ldclient.interfaces import FeatureStore
from ldclient.versioned_data_kind import FEATURES
from ldclient.testing.builders import *
from abc import abstractmethod
import pytest
# The basic test suite to be run against all feature store implementations.
#
# FeatureStoreTestBase and FeatureStoreTester are used only by test_in_memory_feature_store. For all
# database integrations, see testing.integrations.persistent_feature_store_test_base which extends
# them with additional tests.
class FeatureStoreTester:
@abstractmethod
def create_feature_store(self) -> FeatureStore:
pass
class StoreTestScope:
def __init__(self, store: FeatureStore):
self.__store = store
@property
def store(self) -> FeatureStore:
return self.__store
# These magic methods allow the scope to be automatically cleaned up in a "with" block
def __enter__(self):
return self.__store
def __exit__(self, type, value, traceback):
if hasattr(self.store, "stop"): # stop was not originally required for all feature store implementations
self.__store.stop()
# FeatureStoreTestBase is meant to be used as follows:
# - A subclass adds a pytest fixture called "tester" that will return a series of instances of
# some subclass of FeatureStoreTester. This allows the entire test suite to be repeated with
# different store configurations.
# - Tests in this class use "with self.store(tester)" or "with self.inited_store(tester)" to
# create an instance of the store and ensure that it is torn down afterward.
class FeatureStoreTestBase:
@abstractmethod
def all_testers(self):
pass
def store(self, tester):
return StoreTestScope(tester.create_feature_store())
def inited_store(self, tester):
scope = StoreTestScope(tester.create_feature_store())
scope.store.init({
FEATURES: {
'foo': self.make_feature('foo', 10).to_json_dict(),
'bar': self.make_feature('bar', 10).to_json_dict(),
}
})
return scope
@staticmethod
def make_feature(key, ver):
return FlagBuilder(key).version(ver).on(True).variations(True, False).salt('abc').build()
def test_not_initialized_before_init(self, tester):
with self.store(tester) as store:
assert store.initialized is False
def test_initialized(self, tester):
with self.inited_store(tester) as store:
assert store.initialized is True
def test_get_existing_feature(self, tester):
with self.inited_store(tester) as store:
expected = self.make_feature('foo', 10)
flag = store.get(FEATURES, 'foo', lambda x: x)
assert flag == expected
def test_get_nonexisting_feature(self, tester):
with self.inited_store(tester) as store:
assert store.get(FEATURES, 'biz', lambda x: x) is None
def test_get_all_versions(self, tester):
with self.inited_store(tester) as store:
result = store.all(FEATURES, lambda x: x)
assert len(result) == 2
assert result.get('foo') == self.make_feature('foo', 10)
assert result.get('bar') == self.make_feature('bar', 10)
def test_upsert_with_newer_version(self, tester):
with self.inited_store(tester) as store:
new_ver = self.make_feature('foo', 11)
store.upsert(FEATURES, new_ver)
assert store.get(FEATURES, 'foo', lambda x: x) == new_ver
def test_upsert_with_older_version(self, tester):
with self.inited_store(tester) as store:
new_ver = self.make_feature('foo', 9)
expected = self.make_feature('foo', 10)
store.upsert(FEATURES, new_ver)
assert store.get(FEATURES, 'foo', lambda x: x) == expected
def test_upsert_with_new_feature(self, tester):
with self.inited_store(tester) as store:
new_ver = self.make_feature('biz', 1)
store.upsert(FEATURES, new_ver)
assert store.get(FEATURES, 'biz', lambda x: x) == new_ver
def test_delete_with_newer_version(self, tester):
with self.inited_store(tester) as store:
store.delete(FEATURES, 'foo', 11)
assert store.get(FEATURES, 'foo', lambda x: x) is None
def test_delete_unknown_feature(self, tester):
with self.inited_store(tester) as store:
store.delete(FEATURES, 'biz', 11)
assert store.get(FEATURES, 'biz', lambda x: x) is None
def test_delete_with_older_version(self, tester):
with self.inited_store(tester) as store:
store.delete(FEATURES, 'foo', 9)
expected = self.make_feature('foo', 10)
assert store.get(FEATURES, 'foo', lambda x: x) == expected
def test_upsert_older_version_after_delete(self, tester):
with self.inited_store(tester) as store:
store.delete(FEATURES, 'foo', 11)
old_ver = self.make_feature('foo', 9)
store.upsert(FEATURES, old_ver)
assert store.get(FEATURES, 'foo', lambda x: x) is None