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 event = mocker.Mock(spec=EventStorage) impression = mocker.Mock(spec=ImpressionStorage) recorder = StandardRecorder(impmanager, event, impression) recorder.record_treatment_stats(impressions, 1, 'some') assert recorder._impression_storage.put.mock_calls[0][1][0] == impressions
def setup_method(self): """Prepare storages with test data.""" split_storage = InMemorySplitStorage() segment_storage = InMemorySegmentStorage() split_fn = os.path.join(os.path.dirname(__file__), 'files', 'splitChanges.json') with open(split_fn, 'r') as flo: data = json.loads(flo.read()) for split in data['splits']: split_storage.put(splits.from_raw(split)) segment_fn = os.path.join(os.path.dirname(__file__), 'files', 'segmentEmployeesChanges.json') with open(segment_fn, 'r') as flo: data = json.loads(flo.read()) segment_storage.put(segments.from_raw(data)) segment_fn = os.path.join(os.path.dirname(__file__), 'files', 'segmentHumanBeignsChanges.json') with open(segment_fn, 'r') as flo: data = json.loads(flo.read()) segment_storage.put(segments.from_raw(data)) storages = { 'splits': split_storage, 'segments': segment_storage, 'impressions': InMemoryImpressionStorage(5000), 'events': InMemoryEventStorage(5000), 'telemetry': InMemoryTelemetryStorage() } impmanager = ImpressionsManager(ImpressionsMode.OPTIMIZED, True) recorder = StandardRecorder(impmanager, storages['telemetry'], storages['events'], storages['impressions']) self.factory = SplitFactory('some_api_key', storages, True, recorder) # pylint:disable=attribute-defined-outside-init
def test_track(self, mocker): """Test that destroy/destroyed calls are forwarded to the factory.""" split_storage = mocker.Mock(spec=SplitStorage) segment_storage = mocker.Mock(spec=SegmentStorage) impression_storage = mocker.Mock(spec=ImpressionStorage) event_storage = mocker.Mock(spec=EventStorage) event_storage.put.return_value = True def _get_storage_mock(name): return { 'splits': split_storage, 'segments': segment_storage, 'impressions': impression_storage, 'events': event_storage, }[name] factory = mocker.Mock(spec=SplitFactory) factory._get_storage = _get_storage_mock destroyed_mock = mocker.PropertyMock() destroyed_mock.return_value = False factory._waiting_fork.return_value = False type(factory).destroyed = destroyed_mock factory._apikey = 'test' mocker.patch('splitio.client.client.utctime_ms', new=lambda: 1000) impmanager = mocker.Mock(spec=ImpressionManager) recorder = StandardRecorder(impmanager, event_storage, impression_storage) client = Client(factory, recorder, True) assert client.track('key', 'user', 'purchase', 12) is True assert mocker.call([ EventWrapper( event=Event('key', 'user', 'purchase', 12, 1000, None), size=1024 ) ]) in event_storage.put.mock_calls
def test_destroy(self, mocker): """Test that destroy/destroyed calls are forwarded to the factory.""" 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] factory = mocker.Mock(spec=SplitFactory) destroyed_mock = mocker.PropertyMock() type(factory).destroyed = destroyed_mock impmanager = mocker.Mock(spec=ImpressionManager) recorder = StandardRecorder(impmanager, event_storage, impression_storage) client = Client(factory, recorder, True) client.destroy() assert factory.destroy.mock_calls == [mocker.call()] assert client.destroyed is not None assert destroyed_mock.mock_calls == [mocker.call()]
def _build_localhost_factory(cfg): """Build and return a localhost factory for testing/development purposes.""" storages = { 'splits': InMemorySplitStorage(), 'segments': InMemorySegmentStorage(), # not used, just to avoid possible future errors. 'impressions': LocalhostImpressionsStorage(), 'events': LocalhostEventsStorage(), } synchronizers = SplitSynchronizers( LocalSplitSynchronizer(cfg['splitFile'], storages['splits']), None, None, None, None, ) tasks = SplitTasks( SplitSynchronizationTask( synchronizers.split_sync.synchronize_splits, cfg['featuresRefreshRate'], ), None, None, None, None, ) sdk_metadata = util.get_metadata(cfg) ready_event = threading.Event() synchronizer = LocalhostSynchronizer(synchronizers, tasks) manager = Manager(ready_event, synchronizer, None, False, sdk_metadata) manager.start() recorder = StandardRecorder( ImpressionsManager(cfg['impressionsMode'], True, None), storages['events'], storages['impressions'], ) return SplitFactory( 'localhost', storages, False, recorder, manager, ready_event )
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_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
def _build_in_memory_factory(api_key, cfg, sdk_url=None, events_url=None, # pylint:disable=too-many-arguments,too-many-locals auth_api_base_url=None, streaming_api_base_url=None): """Build and return a split factory tailored to the supplied config.""" if not input_validator.validate_factory_instantiation(api_key): return None http_client = HttpClient( sdk_url=sdk_url, events_url=events_url, auth_url=auth_api_base_url, timeout=cfg.get('connectionTimeout') ) sdk_metadata = util.get_metadata(cfg) apis = { 'auth': AuthAPI(http_client, api_key, sdk_metadata), 'splits': SplitsAPI(http_client, api_key, sdk_metadata), 'segments': SegmentsAPI(http_client, api_key, sdk_metadata), 'impressions': ImpressionsAPI(http_client, api_key, sdk_metadata, cfg['impressionsMode']), 'events': EventsAPI(http_client, api_key, sdk_metadata), } if not input_validator.validate_apikey_type(apis['segments']): return None storages = { 'splits': InMemorySplitStorage(), 'segments': InMemorySegmentStorage(), 'impressions': InMemoryImpressionStorage(cfg['impressionsQueueSize']), 'events': InMemoryEventStorage(cfg['eventsQueueSize']), } imp_manager = ImpressionsManager( cfg['impressionsMode'], True, _wrap_impression_listener(cfg['impressionListener'], sdk_metadata)) synchronizers = SplitSynchronizers( SplitSynchronizer(apis['splits'], storages['splits']), SegmentSynchronizer(apis['segments'], storages['splits'], storages['segments']), ImpressionSynchronizer(apis['impressions'], storages['impressions'], cfg['impressionsBulkSize']), EventSynchronizer(apis['events'], storages['events'], cfg['eventsBulkSize']), ImpressionsCountSynchronizer(apis['impressions'], imp_manager), ) tasks = SplitTasks( SplitSynchronizationTask( synchronizers.split_sync.synchronize_splits, cfg['featuresRefreshRate'], ), SegmentSynchronizationTask( synchronizers.segment_sync.synchronize_segments, cfg['segmentsRefreshRate'], ), ImpressionsSyncTask( synchronizers.impressions_sync.synchronize_impressions, cfg['impressionsRefreshRate'], ), EventsSyncTask(synchronizers.events_sync.synchronize_events, cfg['eventsPushRate']), ImpressionsCountSyncTask(synchronizers.impressions_count_sync.synchronize_counters) ) synchronizer = Synchronizer(synchronizers, tasks) preforked_initialization = cfg.get('preforkedInitialization', False) sdk_ready_flag = threading.Event() if not preforked_initialization else None manager = Manager(sdk_ready_flag, synchronizer, apis['auth'], cfg['streamingEnabled'], sdk_metadata, streaming_api_base_url, api_key[-4:]) storages['events'].set_queue_full_hook(tasks.events_task.flush) storages['impressions'].set_queue_full_hook(tasks.impressions_task.flush) recorder = StandardRecorder( imp_manager, storages['events'], storages['impressions'], ) if preforked_initialization: synchronizer.sync_all() synchronizer._split_synchronizers._segment_sync.shutdown() return SplitFactory(api_key, storages, cfg['labelsEnabled'], recorder, manager, preforked_initialization=preforked_initialization) initialization_thread = threading.Thread(target=manager.start, name="SDKInitializer") initialization_thread.setDaemon(True) initialization_thread.start() return SplitFactory(api_key, storages, cfg['labelsEnabled'], recorder, manager, sdk_ready_flag)