diff --git a/splitio/engine/evaluator.py b/splitio/engine/evaluator.py index c6588c6f..3b27ad06 100644 --- a/splitio/engine/evaluator.py +++ b/splitio/engine/evaluator.py @@ -67,7 +67,8 @@ def eval_with_context(self, key, bucketing, feature_name, attrs, ctx): 'impression': { 'label': label, 'change_number': _change_number - } + }, + 'track': feature.trackImpressions } def _treatment_for_flag(self, flag, key, bucketing, attributes, ctx): diff --git a/splitio/engine/impressions/impressions.py b/splitio/engine/impressions/impressions.py index 541e2f36..b4545d1e 100644 --- a/splitio/engine/impressions/impressions.py +++ b/splitio/engine/impressions/impressions.py @@ -11,7 +11,7 @@ class ImpressionsMode(Enum): class Manager(object): # pylint:disable=too-few-public-methods """Impression manager.""" - def __init__(self, strategy, telemetry_runtime_producer): + def __init__(self, strategy, none_strategy, telemetry_runtime_producer): """ Construct a manger to track and forward impressions to the queue. @@ -23,19 +23,33 @@ def __init__(self, strategy, telemetry_runtime_producer): """ self._strategy = strategy + self._none_strategy = none_strategy self._telemetry_runtime_producer = telemetry_runtime_producer - def process_impressions(self, impressions): + def process_impressions(self, impressions_decorated): """ Process impressions. Impressions are analyzed to see if they've been seen before and counted. - :param impressions: List of impression objects with attributes - :type impressions: list[tuple[splitio.models.impression.Impression, dict]] + :param impressions_decorated: List of impression objects with attributes + :type impressions_decorated: list[tuple[splitio.models.impression.ImpressionDecorated, dict]] :return: processed and deduped impressions. :rtype: tuple(list[tuple[splitio.models.impression.Impression, dict]], list(int)) """ - for_log, for_listener, for_counter, for_unique_keys_tracker = self._strategy.process_impressions(impressions) - return for_log, len(impressions) - len(for_log), for_listener, for_counter, for_unique_keys_tracker + for_listener_all = [] + for_log_all = [] + for_counter_all = [] + for_unique_keys_tracker_all = [] + for impression_decorated, att in impressions_decorated: + if not impression_decorated.track: + for_log, for_listener, for_counter, for_unique_keys_tracker = self._none_strategy.process_impressions([(impression_decorated.Impression, att)]) + else: + for_log, for_listener, for_counter, for_unique_keys_tracker = self._strategy.process_impressions([(impression_decorated.Impression, att)]) + for_listener_all.extend(for_listener) + for_log_all.extend(for_log) + for_counter_all.extend(for_counter) + for_unique_keys_tracker_all.extend(for_unique_keys_tracker) + + return for_log_all, len(impressions_decorated) - len(for_log_all), for_listener_all, for_counter_all, for_unique_keys_tracker_all diff --git a/splitio/models/impressions.py b/splitio/models/impressions.py index b08d31fb..21daacae 100644 --- a/splitio/models/impressions.py +++ b/splitio/models/impressions.py @@ -16,6 +16,14 @@ ] ) +ImpressionDecorated = namedtuple( + 'ImpressionDecorated', + [ + 'Impression', + 'track' + ] +) + # pre-python3.7 hack to make previous_time optional Impression.__new__.__defaults__ = (None,) diff --git a/splitio/models/splits.py b/splitio/models/splits.py index b5158ac5..170327ab 100644 --- a/splitio/models/splits.py +++ b/splitio/models/splits.py @@ -10,7 +10,7 @@ SplitView = namedtuple( 'SplitView', - ['name', 'traffic_type', 'killed', 'treatments', 'change_number', 'configs', 'default_treatment', 'sets'] + ['name', 'traffic_type', 'killed', 'treatments', 'change_number', 'configs', 'default_treatment', 'sets', 'trackImpressions'] ) _DEFAULT_CONDITIONS_TEMPLATE = { @@ -73,7 +73,8 @@ def __init__( # pylint: disable=too-many-arguments traffic_allocation=None, traffic_allocation_seed=None, configurations=None, - sets=None + sets=None, + trackImpressions=None ): """ Class constructor. @@ -96,6 +97,8 @@ def __init__( # pylint: disable=too-many-arguments :type traffic_allocation_seed: int :pram sets: list of flag sets :type sets: list + :pram trackImpressions: track impressions flag + :type trackImpressions: boolean """ self._name = name self._seed = seed @@ -125,6 +128,7 @@ def __init__( # pylint: disable=too-many-arguments self._configurations = configurations self._sets = set(sets) if sets is not None else set() + self._trackImpressions = trackImpressions if trackImpressions is not None else True @property def name(self): @@ -186,6 +190,11 @@ def sets(self): """Return the flag sets of the split.""" return self._sets + @property + def trackImpressions(self): + """Return trackImpressions of the split.""" + return self._trackImpressions + def get_configurations_for(self, treatment): """Return the mapping of treatments to configurations.""" return self._configurations.get(treatment) if self._configurations else None @@ -214,7 +223,8 @@ def to_json(self): 'algo': self.algo.value, 'conditions': [c.to_json() for c in self.conditions], 'configurations': self._configurations, - 'sets': list(self._sets) + 'sets': list(self._sets), + 'trackImpressions': self._trackImpressions } def to_split_view(self): @@ -232,7 +242,8 @@ def to_split_view(self): self.change_number, self._configurations if self._configurations is not None else {}, self._default_treatment, - list(self._sets) if self._sets is not None else [] + list(self._sets) if self._sets is not None else [], + self._trackImpressions ) def local_kill(self, default_treatment, change_number): @@ -288,5 +299,6 @@ def from_raw(raw_split): traffic_allocation=raw_split.get('trafficAllocation'), traffic_allocation_seed=raw_split.get('trafficAllocationSeed'), configurations=raw_split.get('configurations'), - sets=set(raw_split.get('sets')) if raw_split.get('sets') is not None else [] + sets=set(raw_split.get('sets')) if raw_split.get('sets') is not None else [], + trackImpressions=raw_split.get('trackImpressions') if raw_split.get('trackImpressions') is not None else True ) diff --git a/splitio/recorder/recorder.py b/splitio/recorder/recorder.py index 31a5a7db..4c0ec155 100644 --- a/splitio/recorder/recorder.py +++ b/splitio/recorder/recorder.py @@ -151,7 +151,7 @@ def __init__(self, impressions_manager, event_storage, impression_storage, telem self._telemetry_evaluation_producer = telemetry_evaluation_producer self._telemetry_runtime_producer = telemetry_runtime_producer - def record_treatment_stats(self, impressions, latency, operation, method_name): + def record_treatment_stats(self, impressions_decorated, latency, operation, method_name): """ Record stats for treatment evaluation. @@ -165,7 +165,7 @@ def record_treatment_stats(self, impressions, latency, operation, method_name): try: if method_name is not None: self._telemetry_evaluation_producer.record_latency(operation, latency) - impressions, deduped, for_listener, for_counter, for_unique_keys_tracker = self._impressions_manager.process_impressions(impressions) + impressions, deduped, for_listener, for_counter, for_unique_keys_tracker = self._impressions_manager.process_impressions(impressions_decorated) if deduped > 0: self._telemetry_runtime_producer.record_impression_stats(telemetry.CounterConstants.IMPRESSIONS_DEDUPED, deduped) self._impression_storage.put(impressions) @@ -210,7 +210,7 @@ def __init__(self, impressions_manager, event_storage, impression_storage, telem self._telemetry_evaluation_producer = telemetry_evaluation_producer self._telemetry_runtime_producer = telemetry_runtime_producer - async def record_treatment_stats(self, impressions, latency, operation, method_name): + async def record_treatment_stats(self, impressions_decorated, latency, operation, method_name): """ Record stats for treatment evaluation. @@ -224,7 +224,7 @@ async def record_treatment_stats(self, impressions, latency, operation, method_n try: if method_name is not None: await self._telemetry_evaluation_producer.record_latency(operation, latency) - impressions, deduped, for_listener, for_counter, for_unique_keys_tracker = self._impressions_manager.process_impressions(impressions) + impressions, deduped, for_listener, for_counter, for_unique_keys_tracker = self._impressions_manager.process_impressions(impressions_decorated) if deduped > 0: await self._telemetry_runtime_producer.record_impression_stats(telemetry.CounterConstants.IMPRESSIONS_DEDUPED, deduped) @@ -277,7 +277,7 @@ def __init__(self, pipe, impressions_manager, event_storage, self._data_sampling = data_sampling self._telemetry_redis_storage = telemetry_redis_storage - def record_treatment_stats(self, impressions, latency, operation, method_name): + def record_treatment_stats(self, impressions_decorated, latency, operation, method_name): """ Record stats for treatment evaluation. @@ -294,7 +294,7 @@ def record_treatment_stats(self, impressions, latency, operation, method_name): if self._data_sampling < rnumber: return - impressions, deduped, for_listener, for_counter, for_unique_keys_tracker = self._impressions_manager.process_impressions(impressions) + impressions, deduped, for_listener, for_counter, for_unique_keys_tracker = self._impressions_manager.process_impressions(impressions_decorated) if impressions: pipe = self._make_pipe() self._impression_storage.add_impressions_to_pipe(impressions, pipe) @@ -367,7 +367,7 @@ def __init__(self, pipe, impressions_manager, event_storage, self._data_sampling = data_sampling self._telemetry_redis_storage = telemetry_redis_storage - async def record_treatment_stats(self, impressions, latency, operation, method_name): + async def record_treatment_stats(self, impressions_decorated, latency, operation, method_name): """ Record stats for treatment evaluation. @@ -384,7 +384,7 @@ async def record_treatment_stats(self, impressions, latency, operation, method_n if self._data_sampling < rnumber: return - impressions, deduped, for_listener, for_counter, for_unique_keys_tracker = self._impressions_manager.process_impressions(impressions) + impressions, deduped, for_listener, for_counter, for_unique_keys_tracker = self._impressions_manager.process_impressions(impressions_decorated) if impressions: pipe = self._make_pipe() self._impression_storage.add_impressions_to_pipe(impressions, pipe) diff --git a/tests/engine/test_evaluator.py b/tests/engine/test_evaluator.py index b56b7040..89631519 100644 --- a/tests/engine/test_evaluator.py +++ b/tests/engine/test_evaluator.py @@ -52,6 +52,7 @@ def test_evaluate_treatment_ok(self, mocker): assert result['impression']['change_number'] == 123 assert result['impression']['label'] == 'some_label' assert mocked_split.get_configurations_for.mock_calls == [mocker.call('on')] + assert result['track'] == mocked_split.trackImpressions def test_evaluate_treatment_ok_no_config(self, mocker): diff --git a/tests/engine/test_impressions.py b/tests/engine/test_impressions.py index d736829b..a7b7da68 100644 --- a/tests/engine/test_impressions.py +++ b/tests/engine/test_impressions.py @@ -5,7 +5,7 @@ from splitio.engine.impressions.impressions import Manager, ImpressionsMode from splitio.engine.impressions.manager import Hasher, Observer, Counter, truncate_time from splitio.engine.impressions.strategies import StrategyDebugMode, StrategyOptimizedMode, StrategyNoneMode -from splitio.models.impressions import Impression +from splitio.models.impressions import Impression, ImpressionDecorated from splitio.client.listener import ImpressionListenerWrapper import splitio.models.telemetry as ModelTelemetry from splitio.engine.telemetry import TelemetryStorageProducer @@ -105,14 +105,15 @@ def test_standalone_optimized(self, mocker): telemetry_producer = TelemetryStorageProducer(telemetry_storage) telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() - manager = Manager(StrategyOptimizedMode(), telemetry_runtime_producer) # no listener + manager = Manager(StrategyOptimizedMode(), StrategyNoneMode(), telemetry_runtime_producer) # no listener assert manager._strategy._observer is not None assert isinstance(manager._strategy, StrategyOptimizedMode) + assert isinstance(manager._none_strategy, StrategyNoneMode) # An impression that hasn't happened in the last hour (pt = None) should be tracked imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), None), - (Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), True), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), True), None) ]) assert for_unique_keys_tracker == [] @@ -122,7 +123,7 @@ def test_standalone_optimized(self, mocker): # Tracking the same impression a ms later should be empty imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), True), None) ]) assert imps == [] assert deduped == 1 @@ -130,7 +131,7 @@ def test_standalone_optimized(self, mocker): # Tracking an impression with a different key makes it to the queue imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), None) + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), True), None) ]) assert imps == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1)] assert deduped == 0 @@ -143,8 +144,8 @@ def test_standalone_optimized(self, mocker): # Track the same impressions but "one hour later" imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), None), - (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), True), None), + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), True), None) ]) assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3), Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1)] @@ -157,14 +158,14 @@ def test_standalone_optimized(self, mocker): # Test counting only from the second impression imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1), None) + (ImpressionDecorated(Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1), True), None) ]) assert for_counter == [] assert deduped == 0 assert for_unique_keys_tracker == [] imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1), None) + (ImpressionDecorated(Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1), True), None) ]) assert for_counter == [Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1, utc_now-1)] assert deduped == 1 @@ -179,14 +180,14 @@ def test_standalone_debug(self, mocker): utc_time_mock.return_value = utc_now mocker.patch('splitio.engine.impressions.strategies.utctime_ms', return_value=utc_time_mock()) - manager = Manager(StrategyDebugMode(), mocker.Mock()) # no listener + manager = Manager(StrategyDebugMode(), StrategyNoneMode(), mocker.Mock()) # no listener assert manager._strategy._observer is not None assert isinstance(manager._strategy, StrategyDebugMode) # An impression that hasn't happened in the last hour (pt = None) should be tracked imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), None), - (Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), True), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), True), None) ]) assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3)] @@ -195,7 +196,7 @@ def test_standalone_debug(self, mocker): # Tracking the same impression a ms later should return the impression imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), True), None) ]) assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, utc_now-3)] assert for_counter == [] @@ -203,7 +204,7 @@ def test_standalone_debug(self, mocker): # Tracking a in impression with a different key makes it to the queue imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), None) + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), True), None) ]) assert imps == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1)] assert for_counter == [] @@ -217,8 +218,8 @@ def test_standalone_debug(self, mocker): # Track the same impressions but "one hour later" imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), None), - (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), True), None), + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), True), None) ]) assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3), Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1)] @@ -236,13 +237,13 @@ def test_standalone_none(self, mocker): utc_time_mock.return_value = utc_now mocker.patch('splitio.engine.impressions.strategies.utctime_ms', return_value=utc_time_mock()) - manager = Manager(StrategyNoneMode(), mocker.Mock()) # no listener + manager = Manager(StrategyNoneMode(), StrategyNoneMode(), mocker.Mock()) # no listener assert isinstance(manager._strategy, StrategyNoneMode) # no impressions are tracked, only counter and mtk imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), None), - (Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), True), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), True), None) ]) assert imps == [] assert for_counter == [ @@ -253,13 +254,13 @@ def test_standalone_none(self, mocker): # Tracking the same impression a ms later should not return the impression and no change on mtk cache imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), True), None) ]) assert imps == [] # Tracking an impression with a different key, will only increase mtk imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k3', 'f1', 'on', 'l1', 123, None, utc_now-1), None) + (ImpressionDecorated(Impression('k3', 'f1', 'on', 'l1', 123, None, utc_now-1), True), None) ]) assert imps == [] assert for_unique_keys_tracker == [('k3', 'f1')] @@ -275,8 +276,8 @@ def test_standalone_none(self, mocker): # Track the same impressions but "one hour later", no changes on mtk imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), None), - (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), True), None), + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), True), None) ]) assert imps == [] assert for_counter == [ @@ -294,14 +295,14 @@ def test_standalone_optimized_listener(self, mocker): # mocker.patch('splitio.util.time.utctime_ms', return_value=utc_time_mock) mocker.patch('splitio.engine.impressions.strategies.utctime_ms', return_value=utc_time_mock()) - manager = Manager(StrategyOptimizedMode(), mocker.Mock()) + manager = Manager(StrategyOptimizedMode(), StrategyNoneMode(), mocker.Mock()) assert manager._strategy._observer is not None assert isinstance(manager._strategy, StrategyOptimizedMode) # An impression that hasn't happened in the last hour (pt = None) should be tracked imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), None), - (Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), True), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), True), None) ]) assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3)] @@ -312,7 +313,7 @@ def test_standalone_optimized_listener(self, mocker): # Tracking the same impression a ms later should return empty imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), True), None) ]) assert imps == [] assert deduped == 1 @@ -321,7 +322,7 @@ def test_standalone_optimized_listener(self, mocker): # Tracking a in impression with a different key makes it to the queue imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), None) + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), True), None) ]) assert imps == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1)] assert deduped == 0 @@ -336,8 +337,8 @@ def test_standalone_optimized_listener(self, mocker): # Track the same impressions but "one hour later" imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), None), - (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), True), None), + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), True), None) ]) assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3), Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1)] @@ -355,14 +356,14 @@ def test_standalone_optimized_listener(self, mocker): # Test counting only from the second impression imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1), None) + (ImpressionDecorated(Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1), True), None) ]) assert for_counter == [] assert deduped == 0 assert for_unique_keys_tracker == [] imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1), None) + (ImpressionDecorated(Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1), True), None) ]) assert for_counter == [ Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1, utc_now-1) @@ -381,13 +382,13 @@ def test_standalone_debug_listener(self, mocker): imps = [] listener = mocker.Mock(spec=ImpressionListenerWrapper) - manager = Manager(StrategyDebugMode(), mocker.Mock()) + manager = Manager(StrategyDebugMode(), StrategyNoneMode(), mocker.Mock()) assert isinstance(manager._strategy, StrategyDebugMode) # An impression that hasn't happened in the last hour (pt = None) should be tracked imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), None), - (Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), True), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), True), None) ]) assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3)] @@ -397,7 +398,7 @@ def test_standalone_debug_listener(self, mocker): # Tracking the same impression a ms later should return the imp imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), True), None) ]) assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, utc_now-3)] assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, utc_now-3), None)] @@ -406,7 +407,7 @@ def test_standalone_debug_listener(self, mocker): # Tracking a in impression with a different key makes it to the queue imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), None) + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), True), None) ]) assert imps == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1)] assert listen == [(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), None)] @@ -421,8 +422,8 @@ def test_standalone_debug_listener(self, mocker): # Track the same impressions but "one hour later" imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), None), - (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), True), None), + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), True), None) ]) assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3), Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1)] @@ -443,13 +444,13 @@ def test_standalone_none_listener(self, mocker): utc_time_mock.return_value = utc_now mocker.patch('splitio.engine.impressions.strategies.utctime_ms', return_value=utc_time_mock()) - manager = Manager(StrategyNoneMode(), mocker.Mock()) + manager = Manager(StrategyNoneMode(), StrategyNoneMode(), mocker.Mock()) assert isinstance(manager._strategy, StrategyNoneMode) # An impression that hasn't happened in the last hour (pt = None) should not be tracked imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), None), - (Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), True), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), True), None) ]) assert imps == [] assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), None), @@ -461,7 +462,7 @@ def test_standalone_none_listener(self, mocker): # Tracking the same impression a ms later should return empty, no updates on mtk imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), True), None) ]) assert imps == [] assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, None), None)] @@ -470,7 +471,7 @@ def test_standalone_none_listener(self, mocker): # Tracking a in impression with a different key update mtk imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), None) + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), True), None) ]) assert imps == [] assert listen == [(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), None)] @@ -485,8 +486,8 @@ def test_standalone_none_listener(self, mocker): # Track the same impressions but "one hour later" imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), None), - (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), True), None), + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), True), None) ]) assert imps == [] assert for_counter == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), @@ -496,3 +497,80 @@ def test_standalone_none_listener(self, mocker): (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, None), None) ] assert for_unique_keys_tracker == [('k1', 'f1'), ('k2', 'f1')] + + def test_impression_toggle_optimized(self, mocker): + """Test impressions manager in optimized mode with sdk in standalone mode.""" + + # Mock utc_time function to be able to play with the clock + utc_now = truncate_time(utctime_ms_reimplement()) + 1800 * 1000 + utc_time_mock = mocker.Mock() + utc_time_mock.return_value = utc_now + mocker.patch('splitio.engine.impressions.strategies.utctime_ms', return_value=utc_time_mock()) + telemetry_storage = InMemoryTelemetryStorage() + telemetry_producer = TelemetryStorageProducer(telemetry_storage) + telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() + + manager = Manager(StrategyOptimizedMode(), StrategyNoneMode(), telemetry_runtime_producer) # no listener + assert manager._strategy._observer is not None + assert isinstance(manager._strategy, StrategyOptimizedMode) + assert isinstance(manager._none_strategy, StrategyNoneMode) + + # An impression that hasn't happened in the last hour (pt = None) should be tracked + imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), False), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), True), None) + ]) + + assert for_unique_keys_tracker == [('k1', 'f1')] + assert imps == [Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3)] + assert deduped == 1 + + def test_impression_toggle_debug(self, mocker): + """Test impressions manager in optimized mode with sdk in standalone mode.""" + + # Mock utc_time function to be able to play with the clock + utc_now = truncate_time(utctime_ms_reimplement()) + 1800 * 1000 + utc_time_mock = mocker.Mock() + utc_time_mock.return_value = utc_now + mocker.patch('splitio.engine.impressions.strategies.utctime_ms', return_value=utc_time_mock()) + telemetry_storage = InMemoryTelemetryStorage() + telemetry_producer = TelemetryStorageProducer(telemetry_storage) + telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() + + manager = Manager(StrategyDebugMode(), StrategyNoneMode(), telemetry_runtime_producer) # no listener + assert manager._strategy._observer is not None + + # An impression that hasn't happened in the last hour (pt = None) should be tracked + imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), False), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), True), None) + ]) + + assert for_unique_keys_tracker == [('k1', 'f1')] + assert imps == [Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3)] + assert deduped == 1 + + def test_impression_toggle_none(self, mocker): + """Test impressions manager in optimized mode with sdk in standalone mode.""" + + # Mock utc_time function to be able to play with the clock + utc_now = truncate_time(utctime_ms_reimplement()) + 1800 * 1000 + utc_time_mock = mocker.Mock() + utc_time_mock.return_value = utc_now + mocker.patch('splitio.engine.impressions.strategies.utctime_ms', return_value=utc_time_mock()) + telemetry_storage = InMemoryTelemetryStorage() + telemetry_producer = TelemetryStorageProducer(telemetry_storage) + telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() + + strategy = StrategyNoneMode() + manager = Manager(strategy, strategy, telemetry_runtime_producer) # no listener + + # An impression that hasn't happened in the last hour (pt = None) should be tracked + imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), False), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), True), None) + ]) + + assert for_unique_keys_tracker == [('k1', 'f1'), ('k1', 'f2')] + assert imps == [] + assert deduped == 2 diff --git a/tests/models/test_splits.py b/tests/models/test_splits.py index 9cd4bbfa..66718e71 100644 --- a/tests/models/test_splits.py +++ b/tests/models/test_splits.py @@ -60,7 +60,8 @@ class SplitTests(object): 'configurations': { 'on': '{"color": "blue", "size": 13}' }, - 'sets': ['set1', 'set2'] + 'sets': ['set1', 'set2'], + 'trackImpressions': True } def test_from_raw(self): @@ -81,6 +82,7 @@ def test_from_raw(self): assert parsed.get_configurations_for('on') == '{"color": "blue", "size": 13}' assert parsed._configurations == {'on': '{"color": "blue", "size": 13}'} assert parsed.sets == {'set1', 'set2'} + assert parsed.trackImpressions == True def test_get_segment_names(self, mocker): """Test fetching segment names.""" @@ -107,6 +109,7 @@ def test_to_json(self): assert as_json['algo'] == 2 assert len(as_json['conditions']) == 2 assert sorted(as_json['sets']) == ['set1', 'set2'] + assert as_json['trackImpressions'] is True def test_to_split_view(self): """Test SplitView creation.""" @@ -118,6 +121,7 @@ def test_to_split_view(self): assert as_split_view.traffic_type == self.raw['trafficTypeName'] assert set(as_split_view.treatments) == set(['on', 'off']) assert sorted(as_split_view.sets) == sorted(list(self.raw['sets'])) + assert as_split_view.trackImpressions == self.raw['trackImpressions'] def test_incorrect_matcher(self): """Test incorrect matcher in split model parsing."""