From c15bfaf3089cc8fa0acccd40644b54e656c5945a Mon Sep 17 00:00:00 2001 From: Martin Redolatti Date: Tue, 18 Apr 2017 01:26:44 -0300 Subject: [PATCH] Add tests for algo field. Fixed bug that resulted in algo=2 being treated as legacy hash --- splitio/hashfns/__init__.py | 5 +- splitio/redis_support.py | 18 +-- splitio/splits.py | 9 +- splitio/tests/algoSplits.json | 203 ++++++++++++++++++++++++++++++++ splitio/tests/test_splits.py | 88 ++++++++++++++ splitio/tests/test_splitters.py | 8 +- splitio/uwsgi.py | 6 +- 7 files changed, 317 insertions(+), 20 deletions(-) create mode 100644 splitio/tests/algoSplits.json diff --git a/splitio/hashfns/__init__.py b/splitio/hashfns/__init__.py index 079a3562..1923859f 100644 --- a/splitio/hashfns/__init__.py +++ b/splitio/hashfns/__init__.py @@ -6,6 +6,7 @@ from __future__ import absolute_import, division, print_function, \ unicode_literals +from splitio.splits import HashAlgorithm from splitio.hashfns import legacy try: @@ -23,8 +24,8 @@ def _murmur_hash(key, seed): _HASH_ALGORITHMS = { - 'legacy': legacy.legacy_hash, - 'murmur': _murmur_hash + HashAlgorithm.LEGACY: legacy.legacy_hash, + HashAlgorithm.MURMUR: _murmur_hash } diff --git a/splitio/redis_support.py b/splitio/redis_support.py index 128bd08b..8d7c9d34 100644 --- a/splitio/redis_support.py +++ b/splitio/redis_support.py @@ -23,7 +23,7 @@ def missing_redis_dependencies(*args, **kwargs): from splitio.matchers import UserDefinedSegmentMatcher from splitio.metrics import BUCKETS from splitio.segments import Segment -from splitio.splits import Split, SplitParser, HashAlgorithm +from splitio.splits import Split, SplitParser from splitio.impressions import Impression from splitio.utils import bytes_to_string @@ -202,7 +202,6 @@ def add_split(self, split_name, split): def get_split(self, split_name): to_decode = self._redis.get(self._get_split_key(split_name)) - if to_decode is None: return None @@ -665,9 +664,12 @@ def _parse_matcher_in_segment(self, partial_split, matcher, block_until_ready=Fa class RedisSplit(Split): - def __init__(self, name, seed, killed, default_treatment, traffic_type_name, status, change_number, conditions=None, segment_cache=None, algo=HashAlgorithm.LEGACY): - """A split implementation that mantains a reference to the segment cache so segments can - be easily pickled and unpickled. + def __init__(self, name, seed, killed, default_treatment, traffic_type_name, + status, change_number, conditions=None, segment_cache=None, + algo=None): + ''' + A split implementation that mantains a reference to the segment cache + so segments can be easily pickled and unpickled. :param name: Name of the feature :type name: unicode :param seed: Seed @@ -680,8 +682,10 @@ def __init__(self, name, seed, killed, default_treatment, traffic_type_name, sta :type conditions: list :param segment_cache: A segment cache :type segment_cache: SegmentCache - """ - super(RedisSplit, self).__init__(name, seed, killed, default_treatment, traffic_type_name, status, change_number, conditions) + ''' + super(RedisSplit, self).__init__(name, seed, killed, default_treatment, + traffic_type_name, status, + change_number, conditions, algo) self._segment_cache = segment_cache @property diff --git a/splitio/splits.py b/splitio/splits.py index 32823e89..845da3e9 100644 --- a/splitio/splits.py +++ b/splitio/splits.py @@ -34,14 +34,13 @@ class HashAlgorithm(Enum): """ Hash algorithm names """ - LEGACY = "legacy" - MURMUR = "murmur" + LEGACY = 1 + MURMUR = 2 class Split(object): def __init__(self, name, seed, killed, default_treatment, traffic_type_name, - status, change_number, conditions=None, - algo=HashAlgorithm.LEGACY): + status, change_number, conditions=None, algo=None): """ A class that represents a split. It associates a feature name with a set of matchers (responsible of telling which condition to use) and @@ -65,7 +64,7 @@ def __init__(self, name, seed, killed, default_treatment, traffic_type_name, self._status = status self._change_number = change_number self._conditions = conditions if conditions is not None else [] - self._algo = algo + self._algo = HashAlgorithm(algo) if algo else HashAlgorithm.LEGACY @property def name(self): diff --git a/splitio/tests/algoSplits.json b/splitio/tests/algoSplits.json new file mode 100644 index 00000000..83583742 --- /dev/null +++ b/splitio/tests/algoSplits.json @@ -0,0 +1,203 @@ +{ + "splits": [ + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "some_feature_1", + "changeNumber":1325599980, + "algo": 1, + "seed": -1222652054, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "WHITELIST", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": { + "whitelist": [ + "whitelisted_user" + ] + } + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + } + ] + }, + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + } + ] + } + ] + }, + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "some_feature_2", + "algo": 2, + "changeNumber":1325599980, + "seed": 1699838640, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ] + } + ] + }, + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "some_feature_3", + "algo": null, + "changeNumber":1325599980, + "seed": -480091424, + "status": "ACTIVE", + "killed": true, + "defaultTreatment": "defTreatment", + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "defTreatment", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + } + ] + } + ] + }, + { + "orgId": null, + "environment": null, + "trafficTypeId": null, + "trafficTypeName": null, + "name": "some_feature_4", + "seed": 1548363147, + "changeNumber":1325599980, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "employees" + }, + "whitelistMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + } + ] + }, + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "human_beigns" + }, + "whitelistMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 30 + }, + { + "treatment": "off", + "size": 70 + } + ] + } + ] + } + ], + "since": -1, + "till": 1457726098069 +} diff --git a/splitio/tests/test_splits.py b/splitio/tests/test_splits.py index a2175340..632da98c 100644 --- a/splitio/tests/test_splits.py +++ b/splitio/tests/test_splits.py @@ -9,12 +9,19 @@ from unittest import TestCase +import json from splitio.splits import (InMemorySplitFetcher, SelfRefreshingSplitFetcher, SplitChangeFetcher, ApiSplitChangeFetcher, SplitParser, AllKeysSplit, CacheBasedSplitFetcher) from splitio.matchers import (AndCombiner, AllKeysMatcher, UserDefinedSegmentMatcher, WhitelistMatcher, AttributeMatcher) from splitio.tests.utils import MockUtilsMixin +from os.path import join, dirname +from splitio.hashfns import _murmur_hash, get_hash_fn +from splitio.hashfns.legacy import legacy_hash +from splitio.splits import HashAlgorithm +from splitio.redis_support import get_redis, RedisSegmentCache, RedisSplitParser +from splitio.uwsgi import get_uwsgi, UWSGISegmentCache, UWSGISplitParser class InMemorySplitFetcherTests(TestCase): @@ -897,3 +904,84 @@ def test_fetch_results_get_split_result(self): """Test that fetch returns the result of calling get split on the cache""" self.assertEqual(self.some_split_cache.get_split.return_value, self.split_fetcher.fetch(self.some_feature)) + + +class RedisCacheAlgoFieldTests(TestCase): + def setUp(self): + ''' + ''' + fn = join(dirname(__file__), 'algoSplits.json') + with open(fn, 'r') as flo: + rawData = json.load(flo)['splits'] + self._testData = [{ + 'body': rawData[0], + 'algo': HashAlgorithm.LEGACY, + 'hashfn': legacy_hash + }, + { + 'body': rawData[1], + 'algo': HashAlgorithm.MURMUR, + 'hashfn': _murmur_hash + }, + { + 'body': rawData[2], + 'algo': HashAlgorithm.LEGACY, + 'hashfn': legacy_hash + }, + { + 'body': rawData[3], + 'algo': HashAlgorithm.LEGACY, + 'hashfn': legacy_hash + }] + + def testAlgoHandlers(self): + ''' + ''' + redis = get_redis({}) + segment_cache = RedisSegmentCache(redis) + split_parser = RedisSplitParser(segment_cache) + for sp in self._testData: + print(sp) + split = split_parser.parse(sp['body'], True) + self.assertEqual(split.algo, sp['algo']) + self.assertEqual(get_hash_fn(split.algo), sp['hashfn']) + + +class UWSGICacheAlgoFieldTests(TestCase): + def setUp(self): + ''' + ''' + fn = join(dirname(__file__), 'algoSplits.json') + with open(fn, 'r') as flo: + rawData = json.load(flo)['splits'] + self._testData = [{ + 'body': rawData[0], + 'algo': HashAlgorithm.LEGACY, + 'hashfn': legacy_hash + }, + { + 'body': rawData[1], + 'algo': HashAlgorithm.MURMUR, + 'hashfn': _murmur_hash + }, + { + 'body': rawData[2], + 'algo': HashAlgorithm.LEGACY, + 'hashfn': legacy_hash + }, + { + 'body': rawData[3], + 'algo': HashAlgorithm.LEGACY, + 'hashfn': legacy_hash + }] + + def testAlgoHandlers(self): + ''' + ''' + uwsgi = get_uwsgi(True) + segment_cache = UWSGISegmentCache(uwsgi) + split_parser = UWSGISplitParser(segment_cache) + for sp in self._testData: + split = split_parser.parse(sp['body'], True) + self.assertEqual(split.algo, sp['algo']) + self.assertEqual(get_hash_fn(split.algo), sp['hashfn']) diff --git a/splitio/tests/test_splitters.py b/splitio/tests/test_splitters.py index fb49e809..ba8abbfd 100644 --- a/splitio/tests/test_splitters.py +++ b/splitio/tests/test_splitters.py @@ -118,7 +118,7 @@ def test_with_sample_data(self): """ Tests basic hash against expected values using alphanumeric values """ - hashfn = _HASH_ALGORITHMS['legacy'] + hashfn = _HASH_ALGORITHMS[HashAlgorithm.LEGACY] with open(join(dirname(__file__), 'sample-data.jsonl')) as f: for line in map(loads, f): seed, key, hash_, bucket = line @@ -128,7 +128,7 @@ def test_with_non_alpha_numeric_sample_data(self): """ Tests basic hash against expected values using non alphanumeric values """ - hashfn = _HASH_ALGORITHMS['legacy'] + hashfn = _HASH_ALGORITHMS[HashAlgorithm.LEGACY] with io.open(join(dirname(__file__), 'sample-data-non-alpha-numeric.jsonl'), 'r', encoding='utf-8') as f: for line in map(loads, f): seed, key, hash_, bucket = line @@ -138,7 +138,7 @@ def test_murmur_with_sample_data(self): """ Tests murmur32 hash against expected values using alphanumeric values """ - hashfn = _HASH_ALGORITHMS['murmur'] + hashfn = _HASH_ALGORITHMS[HashAlgorithm.MURMUR] with open(join(dirname(__file__), 'murmur3-sample-data-v2.csv')) as f: for line in f: seed, key, hash_, bucket = line.split(',') @@ -148,7 +148,7 @@ def test_murmur_with_non_alpha_numeric_sample_data(self): """ Tests murmur32 hash against expected values using non alphanumeric values """ - hashfn = _HASH_ALGORITHMS['murmur'] + hashfn = _HASH_ALGORITHMS[HashAlgorithm.MURMUR] with io.open(join(dirname(__file__), 'murmur3-sample-data-non-alpha-numeric-v2.csv'), 'r', encoding='utf-8') as f: for line in f: seed, key, hash_, bucket = line.split(',') diff --git a/splitio/uwsgi.py b/splitio/uwsgi.py index 55da1870..b085762c 100644 --- a/splitio/uwsgi.py +++ b/splitio/uwsgi.py @@ -389,7 +389,7 @@ def _parse_matcher_in_segment(self, partial_split, matcher, block_until_ready=Fa return delegate class UWSGISplit(Split): - def __init__(self, name, seed, killed, default_treatment, traffic_type_name, status, change_number, conditions=None, segment_cache=None, algo=HashAlgorithm.LEGACY): + def __init__(self, name, seed, killed, default_treatment, traffic_type_name, status, change_number, conditions=None, segment_cache=None, algo=None): """A split implementation that mantains a reference to the segment cache so segments can be easily pickled and unpickled. :param name: Name of the feature @@ -405,7 +405,9 @@ def __init__(self, name, seed, killed, default_treatment, traffic_type_name, sta :param segment_cache: A segment cache :type segment_cache: SegmentCache """ - super(UWSGISplit, self).__init__(name, seed, killed, default_treatment, traffic_type_name, status, change_number, conditions) + super(UWSGISplit, self).__init__( + name, seed, killed, default_treatment, traffic_type_name, status, + change_number, conditions, algo) self._segment_cache = segment_cache @property