Skip to content

Commit e17b971

Browse files
author
Bryan Jen
authored
Merge pull request #46 from davejohnston/FFM-5263
[FFM-5263]: Fix pre-req evaluations
2 parents 9738e96 + dc9d59b commit e17b971

File tree

5 files changed

+94
-48
lines changed

5 files changed

+94
-48
lines changed

featureflags/evaluations/clause.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ def to_dict(self) -> Dict[str, Any]:
7777
@classmethod
7878
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
7979
d = src_dict.copy()
80-
id = d.pop("id")
80+
# If clause ID is missing, then default to empty
81+
id = d.pop("id", "")
8182

8283
attribute = d.pop("attribute")
8384

featureflags/evaluations/evaluator.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from featureflags.repository import QueryInterface
2323
from featureflags.util import log
2424

25-
2625
EMPTY_VARIATION = Variation(identifier="", value=None)
2726

2827

@@ -114,7 +113,7 @@ def _check_target_in_segment(self, segments: List[str],
114113

115114
# Should Target be included via segment rules
116115
if segment.rules and self._evaluate_clauses(segment.rules,
117-
target):
116+
target):
118117
log.debug('Target %s included in segment %s via rules\n',
119118
target.name, segment.name)
120119
return True
@@ -221,9 +220,9 @@ def _evaluate_variation_map(self, var_target_map: List[VariationMap],
221220

222221
for variation_map in var_target_map:
223222
if not isinstance(variation_map.targets, Unset) and next(
224-
(val for val in variation_map.targets
225-
if not isinstance(val, Unset) and val.identifier ==
226-
target.identifier), None) is not None:
223+
(val for val in variation_map.targets
224+
if not isinstance(val, Unset) and val.identifier ==
225+
target.identifier), None) is not None:
227226
log.debug("Evaluate variation map with result %s",
228227
variation_map.variation)
229228
return variation_map.variation
@@ -293,7 +292,7 @@ def _check_prerequisite(self, parent: FeatureConfig,
293292
log.info('Pre requisite flag %s should have the variations %s',
294293
config.feature, pqs.variations)
295294

296-
if not isinstance(variation, Unset) and variation.identifier \
295+
if isinstance(variation, Unset) or variation.identifier \
297296
not in pqs.variations:
298297
return False
299298
else:

featureflags/evaluations/segment.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,17 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
109109

110110
tags.append(tags_item)
111111

112-
included = [Target.from_dict(target)
113-
for target in d.pop("included", UNSET)]
114-
excluded = [Target.from_dict(target)
115-
for target in d.pop("excluded", UNSET)]
112+
# If included list is present and not empty, then extract the values
113+
included = []
114+
if "included" in d and d["included"]:
115+
included = [Target.from_dict(target)
116+
for target in d.pop("included", UNSET)]
117+
118+
# If excluded list is present and not empty, then extract the values
119+
excluded = []
120+
if "excluded" in d and d["excluded"]:
121+
excluded = [Target.from_dict(target)
122+
for target in d.pop("excluded", UNSET)]
116123

117124
rules: Clauses = Clauses()
118125
_rules = d.pop("rules", UNSET)

tests/integration/ff-test-cases

Submodule ff-test-cases updated 93 files
Lines changed: 75 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import pytest
32
import json
43
import os
@@ -12,52 +11,77 @@
1211
from featureflags.repository import Repository
1312
from featureflags.evaluations.evaluator import Evaluator
1413

15-
1614
BASE_PATH = os.path.dirname(os.path.realpath(__file__))
1715
PATH = cwd = BASE_PATH + "/ff-test-cases/tests/"
1816

19-
2017
cache = LRUCache()
2118
repository = Repository(cache)
2219
evaluator = Evaluator(repository)
2320

2421

2522
@define
26-
class Usecase:
27-
flag: FeatureConfig
23+
class Test:
24+
flag: str
25+
target: str
26+
expected: Any
27+
28+
@classmethod
29+
def from_dict(cls, source: Dict[str, Any]) -> 'Test':
30+
return Test(source["flag"], source.get("target", "default"),
31+
source["expected"])
32+
33+
34+
@define
35+
class Feature:
36+
flags: List[FeatureConfig]
2837
segments: List[Segment]
2938
targets: List[Target]
30-
expected: Dict[str, Any]
39+
tests: List[Test]
3140

3241
@classmethod
33-
def from_dict(cls, source: Dict[str, Any]) -> 'Usecase':
34-
flag = FeatureConfig.from_dict(source["flag"])
42+
def from_dict(cls, source: Dict[str, Any]) -> 'Feature':
43+
flags = [FeatureConfig.from_dict(flag) for flag in source["flags"]]
3544

3645
segments = []
3746
if "segments" in source:
3847
segments = [Segment.from_dict(segment)
3948
for segment in source["segments"]]
40-
targets = [Target.from_dict(target) for target in source["targets"]]
41-
expected = source["expected"]
42-
return Usecase(flag=flag, segments=segments, targets=targets,
43-
expected=expected)
49+
targets = []
50+
if "targets" in source:
51+
targets = [Target.from_dict(target) for target in source["targets"]]
52+
53+
tests = [Test.from_dict(test) for test in source["tests"]]
4454

55+
return Feature(flags=flags, segments=segments, targets=targets,
56+
tests=tests)
4557

46-
def load_json_test_file(filename: str) -> Usecase:
58+
59+
@define
60+
class TestCase:
61+
name: str
62+
flag: str
63+
target: str
64+
expected: str
65+
targets: List[Target]
66+
67+
68+
def load_json_test_file(filename: str) -> Feature:
4769
result = None
4870
with open(filename, 'r') as f:
49-
result = Usecase.from_dict(json.load(f))
71+
result = Feature.from_dict(json.load(f))
5072
return result
5173

5274

5375
def load_test_files():
5476
result = []
55-
for file in os.listdir(PATH):
56-
if file.endswith(".json"):
57-
fname = os.path.join(PATH, file)
58-
usecase = load_json_test_file(fname)
59-
usecase.flag.feature += file
60-
result.append((fname, usecase))
77+
for r, d, f in os.walk(PATH):
78+
for file in f:
79+
if '.json' in file:
80+
fname = os.path.join(r, file)
81+
print(f'Loading File: {fname}.')
82+
usecase = load_json_test_file(fname)
83+
# usecase.flag.feature += file
84+
result.append((fname, usecase))
6185
return result
6286

6387

@@ -66,42 +90,57 @@ def usecase_provider():
6690

6791
result = []
6892
for fname, usecase in usecases:
69-
repository.set_flag(usecase.flag)
93+
for flag in usecase.flags:
94+
repository.set_flag(flag)
7095

7196
for segment in usecase.segments:
7297
repository.set_segment(segment)
7398

74-
for identifier, value in usecase.expected.items():
75-
result.append((fname, identifier, value, usecase))
99+
for test_case in usecase.tests:
100+
result.append(
101+
TestCase(name='given flag {} then target {} should get {}'
102+
.format(test_case.flag,
103+
test_case.target,
104+
test_case.expected),
105+
flag=test_case.flag,
106+
target=test_case.target,
107+
expected=test_case.expected,
108+
targets=usecase.targets))
76109

77110
return result
78111

79112

80-
@pytest.mark.parametrize('fname,identifier,expected,usecase',
81-
usecase_provider())
82-
def test_evaluator(fname, identifier, expected, usecase):
113+
def feature_name(tc):
114+
return tc.name
115+
116+
117+
@pytest.mark.parametrize('tc', usecase_provider(), ids=feature_name)
118+
def test_evaluator(tc: TestCase):
83119
target = None
84-
if identifier != "_no_target":
120+
if tc.target != "_no_target":
85121
target = next(
86-
(val for val in usecase.targets if val.identifier == identifier),
122+
(val for val in tc.targets if val.identifier == tc.target),
87123
None
88124
)
89125

90126
got = None
91127

92128
switch_kind = {
93129
FeatureConfigKind.BOOLEAN:
94-
lambda: evaluator.evaluate(usecase.flag.feature, target).bool(),
130+
lambda: evaluator.evaluate(tc.flag, target).bool(default=False),
95131
FeatureConfigKind.STRING:
96-
lambda: evaluator.evaluate(usecase.flag.feature, target).string(),
132+
lambda: evaluator.evaluate(tc.flag, target).string(
133+
default="failed"),
97134
FeatureConfigKind.INT:
98-
lambda: evaluator.evaluate(usecase.flag.feature, target).int(),
99-
FeatureConfigKind.NUMBER:
100-
lambda: evaluator.evaluate(usecase.flag.feature, target).number(),
135+
lambda: evaluator.evaluate(tc.flag, target).number(default=0.100),
101136
FeatureConfigKind.JSON:
102-
lambda: evaluator.evaluate(usecase.flag.feature, target).json(),
137+
lambda: evaluator.evaluate(tc.flag, target).json(default={}),
103138
}
104139

105-
got = switch_kind[usecase.flag.kind]()
140+
kind = repository.get_flag(tc.flag).kind
141+
got = switch_kind[kind]()
142+
143+
if kind == FeatureConfigKind.JSON:
144+
tc.expected = json.loads(tc.expected)
106145

107-
assert got == expected
146+
assert got == tc.expected

0 commit comments

Comments
 (0)