Example #1
0
    def test_get_treatment(self, mocker):
        """Test get_treatment execution paths."""
        split_storage = mocker.Mock(spec=SplitStorage)
        segment_storage = mocker.Mock(spec=SegmentStorage)
        impression_storage = mocker.Mock(spec=ImpressionStorage)
        event_storage = mocker.Mock(spec=EventStorage)
        telemetry_storage = mocker.Mock(spec=TelemetryStorage)
        def _get_storage_mock(name):
            return {
                'splits': split_storage,
                'segments': segment_storage,
                'impressions': impression_storage,
                'events': event_storage,
                'telemetry': telemetry_storage
            }[name]

        destroyed_property = mocker.PropertyMock()
        destroyed_property.return_value = False

        factory = mocker.Mock(spec=SplitFactory)
        factory._get_storage.side_effect = _get_storage_mock
        type(factory).destroyed = destroyed_property

        mocker.patch('splitio.client.client.time.time', new=lambda: 1)
        mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5)

        client = Client(factory, True, None)
        client._evaluator = mocker.Mock(spec=Evaluator)
        client._evaluator.evaluate_treatment.return_value = {
            'treatment': 'on',
            'configurations': None,
            'impression': {
                'label': 'some_label',
                'change_number': 123
            }
        }
        client._logger = mocker.Mock()
        client._send_impression_to_listener = mocker.Mock()

        assert client.get_treatment('some_key', 'some_feature') == 'on'
        assert mocker.call(
            [Impression('some_key', 'some_feature', 'on', 'some_label', 123, None, 1000)]
        ) in impression_storage.put.mock_calls
        assert mocker.call('sdk.getTreatment', 5) in telemetry_storage.inc_latency.mock_calls
        assert client._logger.mock_calls == []
        assert mocker.call(
            Impression('some_key', 'some_feature', 'on', 'some_label', 123, None, 1000),
            None
        ) in client._send_impression_to_listener.mock_calls

        # Test with exception:
        split_storage.get_change_number.return_value = -1
        def _raise(*_):
            raise Exception('something')
        client._evaluator.evaluate_treatment.side_effect = _raise
        assert client.get_treatment('some_key', 'some_feature') == 'control'
        assert mocker.call(
            [Impression('some_key', 'some_feature', 'control', 'exception', -1, None, 1000)]
        ) in impression_storage.put.mock_calls
        assert len(telemetry_storage.inc_latency.mock_calls) == 2
Example #2
0
    def test_wrap_impressions(self, mocker):
        """Test wrap impressions."""
        adapter = mocker.Mock(spec=RedisAdapter)
        metadata = get_metadata({})
        storage = RedisImpressionsStorage(adapter, metadata)

        impressions = [
            Impression('key1', 'feature1', 'on', 'some_label', 123456, 'buck1',
                       321654),
            Impression('key2', 'feature2', 'on', 'some_label', 123456, 'buck1',
                       321654),
            Impression('key3', 'feature2', 'on', 'some_label', 123456, 'buck1',
                       321654),
            Impression('key4', 'feature1', 'on', 'some_label', 123456, 'buck1',
                       321654)
        ]

        to_validate = [json.dumps({
            'm': {  # METADATA PORTION
                's': metadata.sdk_version,
                'n': metadata.instance_name,
                'i': metadata.instance_ip,
            },
            'i': {  # IMPRESSION PORTION
                'k': impression.matching_key,
                'b': impression.bucketing_key,
                'f': impression.feature_name,
                't': impression.treatment,
                'r': impression.label,
                'c': impression.change_number,
                'm': impression.time,
            }
        }) for impression in impressions]

        assert storage._wrap_impressions(impressions) == to_validate
Example #3
0
 def test_normal_operation(self, mocker):
     """Test that the task works properly under normal circumstances."""
     storage = mocker.Mock(spec=ImpressionStorage)
     impressions = [
         Impression('key1', 'split1', 'on', 'l1', 123456, 'b1', 321654),
         Impression('key2', 'split1', 'on', 'l1', 123456, 'b1', 321654),
         Impression('key3', 'split2', 'off', 'l1', 123456, 'b1', 321654),
         Impression('key4', 'split2', 'on', 'l1', 123456, 'b1', 321654),
         Impression('key5', 'split3', 'off', 'l1', 123456, 'b1', 321654)
     ]
     storage.pop_many.return_value = impressions
     api = mocker.Mock(spec=ImpressionsAPI)
     api.flush_impressions.return_value = HttpResponse(200, '')
     impression_synchronizer = ImpressionSynchronizer(api, storage, 5)
     task = impressions_sync.ImpressionsSyncTask(
         impression_synchronizer.synchronize_impressions, 1)
     task.start()
     time.sleep(2)
     assert task.is_running()
     assert storage.pop_many.mock_calls[0] == mocker.call(5)
     assert api.flush_impressions.mock_calls[0] == mocker.call(impressions)
     stop_event = threading.Event()
     calls_now = len(api.flush_impressions.mock_calls)
     task.stop(stop_event)
     stop_event.wait(5)
     assert stop_event.is_set()
     assert len(api.flush_impressions.mock_calls) > calls_now
Example #4
0
 def test_pipelined_recorder(self, mocker):
     impressions = [
         Impression('k1', 'f1', 'on', 'l1', 123, None, None),
         Impression('k1', 'f2', 'on', 'l1', 123, None, None)
     ]
     redis = mocker.Mock(spec=RedisAdapter)
     impmanager = mocker.Mock(spec=ImpressionsManager)
     impmanager.process_impressions.return_value = impressions
     event = mocker.Mock(spec=EventStorage)
     impression = mocker.Mock(spec=ImpressionStorage)
     recorder = PipelinedRecorder(redis, impmanager, event, impression)
     recorder.record_treatment_stats(impressions, 1, 'some')
     assert recorder._impression_storage.put.mock_calls[0][1][0] == impressions
Example #5
0
 def test_put_pop_impressions(self, mocker):
     """Test storing and fetching impressions."""
     uwsgi = get_uwsgi(True)
     storage = UWSGIImpressionStorage(uwsgi)
     impressions = [
         Impression('key1', 'feature1', 'on', 'some_label', 123456, 'buck1', 321654),
         Impression('key2', 'feature2', 'on', 'some_label', 123456, 'buck1', 321654),
         Impression('key3', 'feature2', 'on', 'some_label', 123456, 'buck1', 321654),
         Impression('key4', 'feature1', 'on', 'some_label', 123456, 'buck1', 321654)
     ]
     storage.put(impressions)
     res = storage.pop_many(10)
     assert res == impressions
Example #6
0
    def test_add_impressions(self, mocker):
        """Test that adding impressions to storage works."""
        adapter = mocker.Mock(spec=RedisAdapter)
        metadata = get_metadata({})
        storage = RedisImpressionsStorage(adapter, metadata)

        impressions = [
            Impression('key1', 'feature1', 'on', 'some_label', 123456, 'buck1',
                       321654),
            Impression('key2', 'feature2', 'on', 'some_label', 123456, 'buck1',
                       321654),
            Impression('key3', 'feature2', 'on', 'some_label', 123456, 'buck1',
                       321654),
            Impression('key4', 'feature1', 'on', 'some_label', 123456, 'buck1',
                       321654)
        ]

        assert storage.put(impressions) is True

        to_validate = [json.dumps({
            'm': {  # METADATA PORTION
                's': metadata.sdk_version,
                'n': metadata.instance_name,
                'i': metadata.instance_ip,
            },
            'i': {  # IMPRESSION PORTION
                'k': impression.matching_key,
                'b': impression.bucketing_key,
                'f': impression.feature_name,
                't': impression.treatment,
                'r': impression.label,
                'c': impression.change_number,
                'm': impression.time,
            }
        }) for impression in impressions]

        assert adapter.rpush.mock_calls == [
            mocker.call('SPLITIO.impressions', *to_validate)
        ]

        # Assert that if an exception is thrown it's caught and False is returned
        adapter.reset_mock()

        def _raise_exc(*_):
            raise RedisAdapterException('something')

        adapter.rpush.side_effect = _raise_exc
        assert storage.put(impressions) is False
Example #7
0
    def pop_many(self, count):
        """
        Pop the oldest N impressions from storage.

        :param count: Number of impressions to pop.
        :type count: int
        """
        with UWSGILock(self._uwsgi, self._LOCK_IMPRESSION_KEY):
            try:
                current = json.loads(
                    self._uwsgi.cache_get(
                        self._IMPRESSIONS_KEY,
                        _SPLITIO_IMPRESSIONS_CACHE_NAMESPACE))
            except TypeError:
                return []

            self._uwsgi.cache_update(self._IMPRESSIONS_KEY,
                                     json.dumps(current[count:]), 0,
                                     _SPLITIO_IMPRESSIONS_CACHE_NAMESPACE)

        return [
            Impression(impression['matching_key'], impression['feature_name'],
                       impression['treatment'], impression['label'],
                       impression['change_number'],
                       impression['bucketing_key'], impression['time'])
            for impression in current[:count]
        ]
Example #8
0
    def test_tracking_and_popping(self):
        """Test adding impressions counts and popping them."""
        counter = Counter()
        utc_now = utctime_ms_reimplement()
        utc_1_hour_after = utc_now + (3600 * 1000)
        counter.track([
            Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now),
            Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now),
            Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now)
        ])

        counter.track([
            Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now),
            Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now)
        ])

        counter.track([
            Impression('k1', 'f1', 'on', 'l1', 123, None, utc_1_hour_after),
            Impression('k1', 'f2', 'on', 'l1', 123, None, utc_1_hour_after)
        ])

        assert set(counter.pop_all()) == set([
            Counter.CountPerFeature('f1', truncate_time(utc_now), 3),
            Counter.CountPerFeature('f2', truncate_time(utc_now), 2),
            Counter.CountPerFeature('f1', truncate_time(utc_1_hour_after), 1),
            Counter.CountPerFeature('f2', truncate_time(utc_1_hour_after), 1)
        ])
        assert len(counter._data) == 0
        assert set(counter.pop_all()) == set()
    def test_clear(self):
        """Test clear method."""
        storage = InMemoryImpressionStorage(100)
        storage.put(
            [Impression('key1', 'feature1', 'on', 'l1', 123456, 'b1', 321654)])

        assert storage._impressions.qsize() == 1
        storage.clear()
        assert storage._impressions.qsize() == 0
Example #10
0
    def test_standard_recorder(self, mocker):
        impressions = [
            Impression('k1', 'f1', 'on', 'l1', 123, None, None),
            Impression('k1', 'f2', 'on', 'l1', 123, None, None)
        ]
        impmanager = mocker.Mock(spec=ImpressionsManager)
        impmanager.process_impressions.return_value = impressions
        telemetry = mocker.Mock(spec=TelemetryStorage)
        event = mocker.Mock(spec=EventStorage)
        impression = mocker.Mock(spec=ImpressionStorage)
        recorder = StandardRecorder(impmanager, telemetry, event, impression)
        recorder.record_treatment_stats(impressions, 1, 'some')

        assert recorder._impression_storage.put.mock_calls[0][1][
            0] == impressions
        assert recorder._telemetry_storage.inc_latency.mock_calls == [
            mocker.call('some', 1)
        ]
 def test_queue_full_hook(self, mocker):
     """Test queue_full_hook is executed when the queue is full."""
     storage = InMemoryImpressionStorage(100)
     queue_full_hook = mocker.Mock()
     storage.set_queue_full_hook(queue_full_hook)
     impressions = [
         Impression('key%d' % i, 'feature1', 'on', 'l1', 123456, 'b1',
                    321654) for i in range(0, 101)
     ]
     storage.put(impressions)
     assert queue_full_hook.mock_calls == mocker.call()
Example #12
0
    def test_pipelined_recorder(self, mocker):
        impressions = [
            Impression('k1', 'f1', 'on', 'l1', 123, None, None),
            Impression('k1', 'f2', 'on', 'l1', 123, None, None)
        ]
        redis = mocker.Mock(spec=RedisAdapter)
        impmanager = mocker.Mock(spec=ImpressionsManager)
        impmanager.process_impressions.return_value = impressions
        telemetry = mocker.Mock(spec=TelemetryPipelinedStorage)
        event = mocker.Mock(spec=EventStorage)
        impression = mocker.Mock(spec=ImpressionPipelinedStorage)
        recorder = PipelinedRecorder(redis, impmanager, telemetry, event,
                                     impression)
        recorder.record_treatment_stats(impressions, 1, 'some')

        assert recorder._impression_storage.add_impressions_to_pipe.mock_calls[
            0][1][0] == impressions
        assert recorder._telemetry_storage.add_latency_to_pipe.mock_calls[0][
            1][0] == 'some'
        assert recorder._telemetry_storage.add_latency_to_pipe.mock_calls[0][
            1][1] == 1
Example #13
0
    def test_sampled_recorder(self, mocker):
        impressions = [
            Impression('k1', 'f1', 'on', 'l1', 123, None, None),
            Impression('k1', 'f2', 'on', 'l1', 123, None, None)
        ]
        redis = mocker.Mock(spec=RedisAdapter)
        impmanager = mocker.Mock(spec=ImpressionsManager)
        impmanager.process_impressions.return_value = impressions
        event = mocker.Mock(spec=EventStorage)
        impression = mocker.Mock(spec=ImpressionStorage)
        recorder = PipelinedRecorder(redis, impmanager, event, impression, 0.5)

        def put(x):
            return

        recorder._impression_storage.put.side_effect = put

        for _ in range(100):
            recorder.record_treatment_stats(impressions, 1, 'some')
        print(recorder._impression_storage.put.call_count)
        assert recorder._impression_storage.put.call_count < 80
Example #14
0
    def test_add_impressions_to_pipe(self, mocker):
        """Test that adding impressions to storage works."""
        adapter = mocker.Mock(spec=RedisAdapter)
        metadata = get_metadata({})
        storage = RedisImpressionsStorage(adapter, metadata)

        impressions = [
            Impression('key1', 'feature1', 'on', 'some_label', 123456, 'buck1',
                       321654),
            Impression('key2', 'feature2', 'on', 'some_label', 123456, 'buck1',
                       321654),
            Impression('key3', 'feature2', 'on', 'some_label', 123456, 'buck1',
                       321654),
            Impression('key4', 'feature1', 'on', 'some_label', 123456, 'buck1',
                       321654)
        ]

        to_validate = [json.dumps({
            'm': {  # METADATA PORTION
                's': metadata.sdk_version,
                'n': metadata.instance_name,
                'i': metadata.instance_ip,
            },
            'i': {  # IMPRESSION PORTION
                'k': impression.matching_key,
                'b': impression.bucketing_key,
                'f': impression.feature_name,
                't': impression.treatment,
                'r': impression.label,
                'c': impression.change_number,
                'm': impression.time,
            }
        }) for impression in impressions]

        storage.add_impressions_to_pipe(impressions, adapter)
        assert adapter.rpush.mock_calls == [
            mocker.call('SPLITIO.impressions', *to_validate)
        ]
Example #15
0
    def _build_impression(  # pylint: disable=too-many-arguments
            self, matching_key, feature_name, treatment, label, change_number,
            bucketing_key, imp_time):
        """Build an impression."""
        if not self._labels_enabled:
            label = None

        return Impression(matching_key=matching_key,
                          feature_name=feature_name,
                          treatment=treatment,
                          label=label,
                          change_number=change_number,
                          bucketing_key=bucketing_key,
                          time=imp_time)
Example #16
0
    def test_and_set(self, impression):
        """
        Examine an impression to determine and set it's previous time accordingly.

        :param impression: Impression to track
        :type impression: splitio.models.impressions.Impression

        :returns: Impression with populated previous time
        :rtype: splitio.models.impressions.Impression
        """
        previous_time = self._cache.test_and_set(
            self._hasher.process(impression), impression.time)
        return Impression(impression.matching_key, impression.feature_name,
                          impression.treatment, impression.label,
                          impression.change_number, impression.bucketing_key,
                          impression.time, previous_time)
Example #17
0
    def test_changes_are_reflected(self):
        """Test that change in any field changes the resulting hash."""
        total = set()
        hasher = Hasher()
        total.add(
            hasher.process(
                Impression('key1', 'feature1', 'on', 'killed', 123, None,
                           456)))
        total.add(
            hasher.process(
                Impression('key2', 'feature1', 'on', 'killed', 123, None,
                           456)))
        total.add(
            hasher.process(
                Impression('key1', 'feature2', 'on', 'killed', 123, None,
                           456)))
        total.add(
            hasher.process(
                Impression('key1', 'feature1', 'off', 'killed', 123, None,
                           456)))
        total.add(
            hasher.process(
                Impression('key1', 'feature1', 'on', 'not killed', 123, None,
                           456)))
        total.add(
            hasher.process(
                Impression('key1', 'feature1', 'on', 'killed', 321, None,
                           456)))
        assert len(total) == 6

        # Re-adding the first-one should not increase the number of different hashes
        total.add(
            hasher.process(
                Impression('key1', 'feature1', 'on', 'killed', 123, None,
                           456)))
        assert len(total) == 6
Example #18
0
    def test_get_treatment_with_config(self, mocker):
        """Test get_treatment execution paths."""
        split_storage = mocker.Mock(spec=SplitStorage)
        segment_storage = mocker.Mock(spec=SegmentStorage)
        impression_storage = mocker.Mock(spec=ImpressionStorage)
        event_storage = mocker.Mock(spec=EventStorage)

        def _get_storage_mock(name):
            return {
                'splits': split_storage,
                'segments': segment_storage,
                'impressions': impression_storage,
                'events': event_storage,
            }[name]

        destroyed_property = mocker.PropertyMock()
        destroyed_property.return_value = False

        factory = mocker.Mock(spec=SplitFactory)
        factory._get_storage.side_effect = _get_storage_mock
        factory._waiting_fork.return_value = False
        type(factory).destroyed = destroyed_property

        mocker.patch('splitio.client.client.utctime_ms', new=lambda: 1000)
        mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5)

        impmanager = mocker.Mock(spec=ImpressionManager)
        recorder = StandardRecorder(impmanager, event_storage, impression_storage)
        client = Client(factory, recorder, True)
        client._evaluator = mocker.Mock(spec=Evaluator)
        client._evaluator.evaluate_feature.return_value = {
            'treatment': 'on',
            'configurations': '{"some_config": True}',
            'impression': {
                'label': 'some_label',
                'change_number': 123
            }
        }
        _logger = mocker.Mock()
        client._send_impression_to_listener = mocker.Mock()

        assert client.get_treatment_with_config(
            'some_key',
            'some_feature'
        ) == ('on', '{"some_config": True}')
        assert mocker.call(
            [(Impression('some_key', 'some_feature', 'on', 'some_label', 123, None, 1000), None)]
        ) in impmanager.process_impressions.mock_calls
        assert _logger.mock_calls == []

        # Test with client not ready
        ready_property = mocker.PropertyMock()
        ready_property.return_value = False
        type(factory).ready = ready_property
        impmanager.process_impressions.reset_mock()
        assert client.get_treatment_with_config('some_key', 'some_feature', {'some_attribute': 1}) == ('control', None)
        assert mocker.call(
            [(Impression('some_key', 'some_feature', 'control', Label.NOT_READY, mocker.ANY, mocker.ANY, mocker.ANY),
              {'some_attribute': 1})]
        ) in impmanager.process_impressions.mock_calls

        # Test with exception:
        ready_property.return_value = True
        split_storage.get_change_number.return_value = -1

        def _raise(*_):
            raise Exception('something')
        client._evaluator.evaluate_feature.side_effect = _raise
        assert client.get_treatment_with_config('some_key', 'some_feature') == ('control', None)
        assert mocker.call(
            [(Impression('some_key', 'some_feature', 'control', 'exception', -1, None, 1000), None)]
        ) in impmanager.process_impressions.mock_calls
    def test_post_impressions(self, mocker):
        """Test impressions posting API call."""
        httpclient = mocker.Mock(spec=client.HttpClient)
        httpclient.post.return_value = client.HttpResponse(200, '')
        sdk_metadata = SdkMetadata('python-1.2.3', 'some_machine_name',
                                   '123.123.123.123')
        impressions_api = impressions.ImpressionsAPI(httpclient,
                                                     'some_api_key',
                                                     sdk_metadata)
        response = impressions_api.flush_impressions([
            Impression('k1', 'f1', 'on', 'l1', 123456, 'b1', 321654),
            Impression('k2', 'f2', 'off', 'l1', 123456, 'b1', 321654),
            Impression('k3', 'f1', 'on', 'l1', 123456, 'b1', 321654),
        ])

        call_made = httpclient.post.mock_calls[0]

        # validate positional arguments
        assert call_made[1] == ('events', '/testImpressions/bulk',
                                'some_api_key')

        # validate key-value args (headers)
        assert call_made[2]['extra_headers'] == {
            'SplitSDKVersion': 'python-1.2.3',
            'SplitSDKMachineIP': '123.123.123.123',
            'SplitSDKMachineName': 'some_machine_name'
        }

        # validate key-value args (body)
        assert call_made[2]['body'] == [{
            'testName':
            'f1',
            'keyImpressions': [
                {
                    'keyName': 'k1',
                    'bucketingKey': 'b1',
                    'treatment': 'on',
                    'label': 'l1',
                    'time': 321654,
                    'changeNumber': 123456
                },
                {
                    'keyName': 'k3',
                    'bucketingKey': 'b1',
                    'treatment': 'on',
                    'label': 'l1',
                    'time': 321654,
                    'changeNumber': 123456
                },
            ],
        }, {
            'testName':
            'f2',
            'keyImpressions': [
                {
                    'keyName': 'k2',
                    'bucketingKey': 'b1',
                    'treatment': 'off',
                    'label': 'l1',
                    'time': 321654,
                    'changeNumber': 123456
                },
            ]
        }]

        httpclient.reset_mock()

        def raise_exception(*args, **kwargs):
            raise client.HttpClientException('some_message')

        httpclient.post.side_effect = raise_exception
        with pytest.raises(APIException) as exc_info:
            response = impressions_api.flush_impressions([
                Impression('k1', 'f1', 'on', 'l1', 123456, 'b1', 321654),
                Impression('k2', 'f2', 'off', 'l1', 123456, 'b1', 321654),
                Impression('k3', 'f1', 'on', 'l1', 123456, 'b1', 321654),
            ])
            assert exc_info.type == APIException
            assert exc_info.value.message == 'some_message'
Example #20
0
    def test_non_standalone_debug_listener(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.util.utctime_ms', new=utc_time_mock)

        listener = mocker.Mock(spec=ImpressionListenerWrapper)
        manager = Manager(ImpressionsMode.DEBUG, False,
                          listener)  # no listener
        assert manager._counter is None
        assert manager._observer is None
        assert manager._listener is not None

        # An impression that hasn't happened in the last hour (pt = None) should be tracked
        imps = 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)
        ])
        assert imps == [
            Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now - 3),
            Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now - 3)
        ]

        # Tracking the same impression a ms later should return the imp
        imps = manager.process_impressions([
            (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now - 2), None)
        ])
        assert imps == [
            Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now - 2)
        ]

        # Tracking a in impression with a different key makes it to the queue
        imps = manager.process_impressions([
            (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now - 1), None)
        ])
        assert imps == [
            Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now - 1)
        ]

        # Advance the perceived clock one hour
        old_utc = utc_now  # save it to compare captured impressions
        utc_now += 3600 * 1000
        utc_time_mock.return_value = utc_now

        # Track the same impressions but "one hour later"
        imps = 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)
        ])
        assert imps == [
            Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now - 1),
            Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now - 2)
        ]

        assert listener.log_impression.mock_calls == [
            mocker.call(
                Impression('k1', 'f1', 'on', 'l1', 123, None, old_utc - 3),
                None),
            mocker.call(
                Impression('k1', 'f2', 'on', 'l1', 123, None, old_utc - 3),
                None),
            mocker.call(
                Impression('k1', 'f1', 'on', 'l1', 123, None, old_utc - 2),
                None),
            mocker.call(
                Impression('k2', 'f1', 'on', 'l1', 123, None, old_utc - 1),
                None),
            mocker.call(
                Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now - 1),
                None),
            mocker.call(
                Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now - 2),
                None)
        ]
Example #21
0
    def test_previous_time_properly_calculated(self):
        """Test that the previous time is properly set."""
        observer = Observer(5)
        assert (observer.test_and_set(
            Impression('key1', 'f1', 'on', 'killed', 123, None,
                       456)) == Impression('key1', 'f1', 'on', 'killed', 123,
                                           None, 456))
        assert (observer.test_and_set(
            Impression('key1', 'f1', 'on', 'killed', 123, None,
                       457)) == Impression('key1', 'f1', 'on', 'killed', 123,
                                           None, 457, 456))

        # Add 5 new impressions to evict the first one and check that previous time is None again
        assert (observer.test_and_set(
            Impression('key2', 'f1', 'on', 'killed', 123, None,
                       456)) == Impression('key2', 'f1', 'on', 'killed', 123,
                                           None, 456))
        assert (observer.test_and_set(
            Impression('key3', 'f1', 'on', 'killed', 123, None,
                       456)) == Impression('key3', 'f1', 'on', 'killed', 123,
                                           None, 456))
        assert (observer.test_and_set(
            Impression('key4', 'f1', 'on', 'killed', 123, None,
                       456)) == Impression('key4', 'f1', 'on', 'killed', 123,
                                           None, 456))
        assert (observer.test_and_set(
            Impression('key5', 'f1', 'on', 'killed', 123, None,
                       456)) == Impression('key5', 'f1', 'on', 'killed', 123,
                                           None, 456))
        assert (observer.test_and_set(
            Impression('key6', 'f1', 'on', 'killed', 123, None,
                       456)) == Impression('key6', 'f1', 'on', 'killed', 123,
                                           None, 456))

        # Re-process the first-one
        assert (observer.test_and_set(
            Impression('key1', 'f1', 'on', 'killed', 123, None,
                       456)) == Impression('key1', 'f1', 'on', 'killed', 123,
                                           None, 456))
Example #22
0
    def test_standalone_optimized_listener(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.util.utctime_ms', new=utc_time_mock)

        listener = mocker.Mock(spec=ImpressionListenerWrapper)

        manager = Manager(listener=listener)  # no listener
        assert manager._counter is not None
        assert manager._observer is not None
        assert manager._listener is not None

        # An impression that hasn't happened in the last hour (pt = None) should be tracked
        imps = 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)
        ])
        assert imps == [
            Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now - 3),
            Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now - 3)
        ]

        # Tracking the same impression a ms later should return empty
        imps = manager.process_impressions([
            (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now - 2), None)
        ])
        assert imps == []

        # Tracking a in impression with a different key makes it to the queue
        imps = manager.process_impressions([
            (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now - 1), None)
        ])
        assert imps == [
            Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now - 1)
        ]

        # Advance the perceived clock one hour
        old_utc = utc_now  # save it to compare captured impressions
        utc_now += 3600 * 1000
        utc_time_mock.return_value = utc_now

        # Track the same impressions but "one hour later"
        imps = 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)
        ])
        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)
        ]

        assert len(
            manager._observer._cache._data) == 3  # distinct impressions seen
        assert len(
            manager._counter._data
        ) == 3  # 2 distinct features. 1 seen in 2 different timeframes

        assert set(manager._counter.pop_all()) == set([
            Counter.CountPerFeature('f1', truncate_time(old_utc), 3),
            Counter.CountPerFeature('f2', truncate_time(old_utc), 1),
            Counter.CountPerFeature('f1', truncate_time(utc_now), 2)
        ])

        assert listener.log_impression.mock_calls == [
            mocker.call(
                Impression('k1', 'f1', 'on', 'l1', 123, None, old_utc - 3),
                None),
            mocker.call(
                Impression('k1', 'f2', 'on', 'l1', 123, None, old_utc - 3),
                None),
            mocker.call(
                Impression('k1', 'f1', 'on', 'l1', 123, None, old_utc - 2,
                           old_utc - 3), None),
            mocker.call(
                Impression('k2', 'f1', 'on', 'l1', 123, None, old_utc - 1),
                None),
            mocker.call(
                Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now - 1,
                           old_utc - 3), None),
            mocker.call(
                Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now - 2,
                           old_utc - 1), None)
        ]
Example #23
0
    def test_non_standalone_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.util.utctime_ms', new=utc_time_mock)

        manager = Manager(ImpressionsMode.OPTIMIZED, False)  # no listener
        assert manager._counter is None
        assert manager._observer is None
        assert manager._listener is None

        # An impression that hasn't happened in the last hour (pt = None) should be tracked
        imps = 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)
        ])
        assert imps == [
            Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now - 3),
            Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now - 3)
        ]

        # Tracking the same impression a ms later should not be empty
        imps = manager.process_impressions([
            (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now - 2), None)
        ])
        assert imps == [
            Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now - 2)
        ]

        # Tracking a in impression with a different key makes it to the queue
        imps = manager.process_impressions([
            (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now - 1), None)
        ])
        assert imps == [
            Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now - 1)
        ]

        # Advance the perceived clock one hour
        utc_now += 3600 * 1000
        utc_time_mock.return_value = utc_now

        # Track the same impressions but "one hour later"
        imps = 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)
        ])
        assert imps == [
            Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now - 1),
            Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now - 2)
        ]
Example #24
0
    def test_get_treatments_with_config(self, mocker):
        """Test get_treatment execution paths."""
        split_storage = mocker.Mock(spec=SplitStorage)
        segment_storage = mocker.Mock(spec=SegmentStorage)
        impression_storage = mocker.Mock(spec=ImpressionStorage)
        event_storage = mocker.Mock(spec=EventStorage)
        telemetry_storage = mocker.Mock(spec=TelemetryStorage)

        def _get_storage_mock(name):
            return {
                'splits': split_storage,
                'segments': segment_storage,
                'impressions': impression_storage,
                'events': event_storage,
                'telemetry': telemetry_storage
            }[name]

        destroyed_property = mocker.PropertyMock()
        destroyed_property.return_value = False

        factory = mocker.Mock(spec=SplitFactory)
        factory._get_storage.side_effect = _get_storage_mock
        factory._waiting_fork.return_value = False
        type(factory).destroyed = destroyed_property

        mocker.patch('splitio.client.client.utctime_ms', new=lambda: 1000)
        mocker.patch('splitio.client.client.get_latency_bucket_index',
                     new=lambda x: 5)

        impmanager = mocker.Mock(spec=ImpressionManager)
        recorder = StandardRecorder(impmanager, telemetry_storage,
                                    event_storage, impression_storage)
        client = Client(factory, recorder, True)
        client._evaluator = mocker.Mock(spec=Evaluator)
        evaluation = {
            'treatment': 'on',
            'configurations': '{"color": "red"}',
            'impression': {
                'label': 'some_label',
                'change_number': 123
            }
        }
        client._evaluator.evaluate_features.return_value = {
            'f1': evaluation,
            'f2': evaluation
        }
        _logger = mocker.Mock()
        assert client.get_treatments_with_config('key', ['f1', 'f2']) == {
            'f1': ('on', '{"color": "red"}'),
            'f2': ('on', '{"color": "red"}')
        }

        impressions_called = impmanager.process_impressions.mock_calls[0][1][0]
        assert (Impression('key', 'f1', 'on', 'some_label', 123, None,
                           1000), None) in impressions_called
        assert (Impression('key', 'f2', 'on', 'some_label', 123, None,
                           1000), None) in impressions_called
        assert mocker.call('sdk.getTreatmentsWithConfig',
                           5) in telemetry_storage.inc_latency.mock_calls
        assert _logger.mock_calls == []

        # Test with client not ready
        ready_property = mocker.PropertyMock()
        ready_property.return_value = False
        type(factory).ready = ready_property
        impmanager.process_impressions.reset_mock()
        assert client.get_treatments_with_config('some_key', ['some_feature'],
                                                 {'some_attribute': 1}) == {
                                                     'some_feature':
                                                     ('control', None)
                                                 }
        assert mocker.call([
            (Impression('some_key', 'some_feature', 'control', Label.NOT_READY,
                        mocker.ANY, mocker.ANY, mocker.ANY), {
                            'some_attribute': 1
                        })
        ]) in impmanager.process_impressions.mock_calls

        # Test with exception:
        ready_property.return_value = True
        split_storage.get_change_number.return_value = -1

        def _raise(*_):
            raise Exception('something')

        client._evaluator.evaluate_features.side_effect = _raise
        assert client.get_treatments_with_config('key', ['f1', 'f2']) == {
            'f1': ('control', None),
            'f2': ('control', None)
        }
        assert len(telemetry_storage.inc_latency.mock_calls) == 2
Example #25
0
class ImpressionsAPITests(object):
    """Impressions API test cases."""
    impressions = [
        Impression('k1', 'f1', 'on', 'l1', 123456, 'b1', 321654),
        Impression('k2', 'f2', 'off', 'l1', 123456, 'b1', 321654),
        Impression('k3', 'f1', 'on', 'l1', 123456, 'b1', 321654)
    ]
    expectedImpressions = [{
        'testName':
        'f1',
        'keyImpressions': [
            {
                'keyName': 'k1',
                'bucketingKey': 'b1',
                'treatment': 'on',
                'label': 'l1',
                'time': 321654,
                'changeNumber': 123456
            },
            {
                'keyName': 'k3',
                'bucketingKey': 'b1',
                'treatment': 'on',
                'label': 'l1',
                'time': 321654,
                'changeNumber': 123456
            },
        ],
    }, {
        'testName':
        'f2',
        'keyImpressions': [
            {
                'keyName': 'k2',
                'bucketingKey': 'b1',
                'treatment': 'off',
                'label': 'l1',
                'time': 321654,
                'changeNumber': 123456
            },
        ]
    }]

    def test_post_impressions(self, mocker):
        """Test impressions posting API call."""
        httpclient = mocker.Mock(spec=client.HttpClient)
        httpclient.post.return_value = client.HttpResponse(200, '')
        cfg = DEFAULT_CONFIG.copy()
        cfg.update({
            'IPAddressesEnabled': True,
            'machineName': 'some_machine_name',
            'machineIp': '123.123.123.123'
        })
        sdk_metadata = get_metadata(cfg)
        impressions_api = impressions.ImpressionsAPI(httpclient,
                                                     'some_api_key',
                                                     sdk_metadata)
        response = impressions_api.flush_impressions(self.impressions)

        call_made = httpclient.post.mock_calls[0]

        # validate positional arguments
        assert call_made[1] == ('events', '/testImpressions/bulk',
                                'some_api_key')

        # validate key-value args (headers)
        assert call_made[2]['extra_headers'] == {
            'SplitSDKVersion': 'python-%s' % __version__,
            'SplitSDKMachineIP': '123.123.123.123',
            'SplitSDKMachineName': 'some_machine_name'
        }

        # validate key-value args (body)
        assert call_made[2]['body'] == self.expectedImpressions

        httpclient.reset_mock()

        def raise_exception(*args, **kwargs):
            raise client.HttpClientException('some_message')

        httpclient.post.side_effect = raise_exception
        with pytest.raises(APIException) as exc_info:
            response = impressions_api.flush_impressions(self.impressions)
            assert exc_info.type == APIException
            assert exc_info.value.message == 'some_message'

    def test_post_impressions_ip_address_disabled(self, mocker):
        """Test impressions posting API call."""
        httpclient = mocker.Mock(spec=client.HttpClient)
        httpclient.post.return_value = client.HttpResponse(200, '')
        cfg = DEFAULT_CONFIG.copy()
        cfg.update({'IPAddressesEnabled': False})
        sdk_metadata = get_metadata(cfg)
        impressions_api = impressions.ImpressionsAPI(httpclient,
                                                     'some_api_key',
                                                     sdk_metadata)
        response = impressions_api.flush_impressions(self.impressions)

        call_made = httpclient.post.mock_calls[0]

        # validate positional arguments
        assert call_made[1] == ('events', '/testImpressions/bulk',
                                'some_api_key')

        # validate key-value args (headers)
        assert call_made[2]['extra_headers'] == {
            'SplitSDKVersion': 'python-%s' % __version__,
        }

        # validate key-value args (body)
        assert call_made[2]['body'] == self.expectedImpressions
    def test_push_pop_impressions(self):
        """Test pushing and retrieving impressions."""
        storage = InMemoryImpressionStorage(100)
        storage.put(
            [Impression('key1', 'feature1', 'on', 'l1', 123456, 'b1', 321654)])
        storage.put(
            [Impression('key2', 'feature1', 'on', 'l1', 123456, 'b1', 321654)])
        storage.put(
            [Impression('key3', 'feature1', 'on', 'l1', 123456, 'b1', 321654)])

        # Assert impressions are retrieved in the same order they are inserted.
        assert storage.pop_many(1) == [
            Impression('key1', 'feature1', 'on', 'l1', 123456, 'b1', 321654)
        ]
        assert storage.pop_many(1) == [
            Impression('key2', 'feature1', 'on', 'l1', 123456, 'b1', 321654)
        ]
        assert storage.pop_many(1) == [
            Impression('key3', 'feature1', 'on', 'l1', 123456, 'b1', 321654)
        ]

        # Assert inserting multiple impressions at once works and maintains order.
        impressions = [
            Impression('key1', 'feature1', 'on', 'l1', 123456, 'b1', 321654),
            Impression('key2', 'feature1', 'on', 'l1', 123456, 'b1', 321654),
            Impression('key3', 'feature1', 'on', 'l1', 123456, 'b1', 321654)
        ]
        assert storage.put(impressions)

        # Assert impressions are retrieved in the same order they are inserted.
        assert storage.pop_many(1) == [
            Impression('key1', 'feature1', 'on', 'l1', 123456, 'b1', 321654)
        ]
        assert storage.pop_many(1) == [
            Impression('key2', 'feature1', 'on', 'l1', 123456, 'b1', 321654)
        ]
        assert storage.pop_many(1) == [
            Impression('key3', 'feature1', 'on', 'l1', 123456, 'b1', 321654)
        ]
Example #27
0
class ImpressionsAPITests(object):
    """Impressions API test cases."""
    impressions = [
        Impression('k1', 'f1', 'on', 'l1', 123456, 'b1', 321654),
        Impression('k2', 'f2', 'off', 'l1', 123456, 'b1', 321654),
        Impression('k3', 'f1', 'on', 'l1', 123456, 'b1', 321654)
    ]
    expectedImpressions = [{
        'f':
        'f1',
        'i': [
            {
                'k': 'k1',
                'b': 'b1',
                't': 'on',
                'r': 'l1',
                'm': 321654,
                'c': 123456,
                'pt': None
            },
            {
                'k': 'k3',
                'b': 'b1',
                't': 'on',
                'r': 'l1',
                'm': 321654,
                'c': 123456,
                'pt': None
            },
        ],
    }, {
        'f':
        'f2',
        'i': [
            {
                'k': 'k2',
                'b': 'b1',
                't': 'off',
                'r': 'l1',
                'm': 321654,
                'c': 123456,
                'pt': None
            },
        ]
    }]

    counters = [
        Counter.CountPerFeature('f1', 123, 2),
        Counter.CountPerFeature('f2', 123, 123),
        Counter.CountPerFeature('f1', 456, 111),
        Counter.CountPerFeature('f2', 456, 222)
    ]

    expected_counters = {
        'pf': [
            {
                'f': 'f1',
                'm': 123,
                'rc': 2
            },
            {
                'f': 'f2',
                'm': 123,
                'rc': 123
            },
            {
                'f': 'f1',
                'm': 456,
                'rc': 111
            },
            {
                'f': 'f2',
                'm': 456,
                'rc': 222
            },
        ]
    }

    def test_post_impressions(self, mocker):
        """Test impressions posting API call."""
        httpclient = mocker.Mock(spec=client.HttpClient)
        httpclient.post.return_value = client.HttpResponse(200, '')
        cfg = DEFAULT_CONFIG.copy()
        cfg.update({
            'IPAddressesEnabled': True,
            'machineName': 'some_machine_name',
            'machineIp': '123.123.123.123'
        })
        sdk_metadata = get_metadata(cfg)
        impressions_api = impressions.ImpressionsAPI(httpclient,
                                                     'some_api_key',
                                                     sdk_metadata)
        response = impressions_api.flush_impressions(self.impressions)

        call_made = httpclient.post.mock_calls[0]

        # validate positional arguments
        assert call_made[1] == ('events', '/testImpressions/bulk',
                                'some_api_key')

        # validate key-value args (headers)
        assert call_made[2]['extra_headers'] == {
            'SplitSDKVersion': 'python-%s' % __version__,
            'SplitSDKMachineIP': '123.123.123.123',
            'SplitSDKMachineName': 'some_machine_name',
            'SplitSDKImpressionsMode': 'OPTIMIZED'
        }

        # validate key-value args (body)
        assert call_made[2]['body'] == self.expectedImpressions

        httpclient.reset_mock()

        def raise_exception(*args, **kwargs):
            raise client.HttpClientException('some_message')

        httpclient.post.side_effect = raise_exception
        with pytest.raises(APIException) as exc_info:
            response = impressions_api.flush_impressions(self.impressions)
            assert exc_info.type == APIException
            assert exc_info.value.message == 'some_message'

    def test_post_impressions_ip_address_disabled(self, mocker):
        """Test impressions posting API call."""
        httpclient = mocker.Mock(spec=client.HttpClient)
        httpclient.post.return_value = client.HttpResponse(200, '')
        cfg = DEFAULT_CONFIG.copy()
        cfg.update({'IPAddressesEnabled': False})
        sdk_metadata = get_metadata(cfg)
        impressions_api = impressions.ImpressionsAPI(httpclient,
                                                     'some_api_key',
                                                     sdk_metadata,
                                                     ImpressionsMode.DEBUG)
        response = impressions_api.flush_impressions(self.impressions)

        call_made = httpclient.post.mock_calls[0]

        # validate positional arguments
        assert call_made[1] == ('events', '/testImpressions/bulk',
                                'some_api_key')

        # validate key-value args (headers)
        assert call_made[2]['extra_headers'] == {
            'SplitSDKVersion': 'python-%s' % __version__,
            'SplitSDKImpressionsMode': 'DEBUG'
        }

        # validate key-value args (body)
        assert call_made[2]['body'] == self.expectedImpressions

    def test_post_counters(self, mocker):
        """Test impressions posting API call."""
        httpclient = mocker.Mock(spec=client.HttpClient)
        httpclient.post.return_value = client.HttpResponse(200, '')
        cfg = DEFAULT_CONFIG.copy()
        cfg.update({
            'IPAddressesEnabled': True,
            'machineName': 'some_machine_name',
            'machineIp': '123.123.123.123'
        })
        sdk_metadata = get_metadata(cfg)
        impressions_api = impressions.ImpressionsAPI(httpclient,
                                                     'some_api_key',
                                                     sdk_metadata)
        response = impressions_api.flush_counters(self.counters)

        call_made = httpclient.post.mock_calls[0]

        # validate positional arguments
        assert call_made[1] == ('events', '/testImpressions/count',
                                'some_api_key')

        # validate key-value args (headers)
        assert call_made[2]['extra_headers'] == {
            'SplitSDKVersion': 'python-%s' % __version__,
            'SplitSDKMachineIP': '123.123.123.123',
            'SplitSDKMachineName': 'some_machine_name',
            'SplitSDKImpressionsMode': 'OPTIMIZED'
        }

        # validate key-value args (body)
        assert call_made[2]['body'] == self.expected_counters

        httpclient.reset_mock()

        def raise_exception(*args, **kwargs):
            raise client.HttpClientException('some_message')

        httpclient.post.side_effect = raise_exception
        with pytest.raises(APIException) as exc_info:
            response = impressions_api.flush_counters(self.counters)
            assert exc_info.type == APIException
            assert exc_info.value.message == 'some_message'