class BatchEventProcessorTest(base.BaseTest): DEFAULT_QUEUE_CAPACITY = 1000 MAX_BATCH_SIZE = 10 MAX_DURATION_SEC = 0.2 MAX_TIMEOUT_INTERVAL_SEC = 0.1 TEST_TIMEOUT = 0.3 def setUp(self, *args, **kwargs): base.BaseTest.setUp(self, 'config_dict_with_multiple_experiments') self.test_user_id = 'test_user' self.event_name = 'test_event' self.event_queue = queue.Queue(maxsize=self.DEFAULT_QUEUE_CAPACITY) self.optimizely.logger = NoOpLogger() self.notification_center = self.optimizely.notification_center def tearDown(self): self.event_processor.stop() def _build_conversion_event(self, event_name, project_config=None): config = project_config or self.project_config return UserEventFactory.create_conversion_event( config, event_name, self.test_user_id, {}, {}) def _set_event_processor(self, event_dispatcher, logger): self.event_processor = BatchEventProcessor( event_dispatcher, logger, True, self.event_queue, self.MAX_BATCH_SIZE, self.MAX_DURATION_SEC, self.MAX_TIMEOUT_INTERVAL_SEC, self.optimizely.notification_center, ) def test_drain_on_stop(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) user_event = self._build_conversion_event(self.event_name) self.event_processor.process(user_event) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(self.TEST_TIMEOUT) self.assertStrictTrue(event_dispatcher.compare_events()) self.assertEqual(0, self.event_processor.event_queue.qsize()) def test_flush_on_max_timeout(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) user_event = self._build_conversion_event(self.event_name) self.event_processor.process(user_event) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(self.TEST_TIMEOUT) self.assertStrictTrue(event_dispatcher.compare_events()) self.assertEqual(0, self.event_processor.event_queue.qsize()) def test_flush_once_max_timeout(self): event_dispatcher = CustomEventDispatcher() self.optimizely.logger = NoOpLogger() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) user_event = self._build_conversion_event(self.event_name) self.event_processor.process(user_event) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(self.TEST_TIMEOUT) self.assertStrictTrue(event_dispatcher.compare_events()) self.assertEqual(0, self.event_processor.event_queue.qsize()) self.assertTrue(mock_config_logging.debug.called) mock_config_logging.debug.assert_any_call( 'Received event of type ConversionEvent for user test_user.') mock_config_logging.debug.assert_any_call('Flushing batch size 1') mock_config_logging.debug.assert_any_call( 'Flush interval deadline. Flushed batch.') self.assertTrue(mock_config_logging.debug.call_count == 3) self.optimizely.logger = NoOpLogger() def test_flush_max_batch_size(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) for i in range(0, self.MAX_BATCH_SIZE): user_event = self._build_conversion_event(self.event_name) self.event_processor.process(user_event) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(self.TEST_TIMEOUT) self.assertStrictTrue(event_dispatcher.compare_events()) self.assertEqual(0, self.event_processor.event_queue.qsize()) def test_flush(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) user_event = self._build_conversion_event(self.event_name) self.event_processor.process(user_event) self.event_processor.flush() event_dispatcher.expect_conversion(self.event_name, self.test_user_id) self.event_processor.process(user_event) self.event_processor.flush() event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(self.TEST_TIMEOUT) self.assertStrictTrue(event_dispatcher.compare_events()) self.assertEqual(0, self.event_processor.event_queue.qsize()) def test_flush_on_mismatch_revision(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) self.project_config.revision = 1 self.project_config.project_id = 'X' user_event_1 = self._build_conversion_event(self.event_name, self.project_config) self.event_processor.process(user_event_1) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) self.project_config.revision = 2 self.project_config.project_id = 'X' user_event_2 = self._build_conversion_event(self.event_name, self.project_config) self.event_processor.process(user_event_2) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(self.TEST_TIMEOUT) self.assertStrictTrue(event_dispatcher.compare_events()) self.assertEqual(0, self.event_processor.event_queue.qsize()) def test_flush_on_mismatch_project_id(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) self.project_config.revision = 1 self.project_config.project_id = 'X' user_event_1 = self._build_conversion_event(self.event_name, self.project_config) self.event_processor.process(user_event_1) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) self.project_config.revision = 1 self.project_config.project_id = 'Y' user_event_2 = self._build_conversion_event(self.event_name, self.project_config) self.event_processor.process(user_event_2) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(self.TEST_TIMEOUT) self.assertStrictTrue(event_dispatcher.compare_events()) self.assertEqual(0, self.event_processor.event_queue.qsize()) def test_stop_and_start(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) user_event = self._build_conversion_event(self.event_name, self.project_config) self.event_processor.process(user_event) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(self.TEST_TIMEOUT) self.assertStrictTrue(event_dispatcher.compare_events()) self.event_processor.stop() self.event_processor.process(user_event) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) self.event_processor.start() self.assertStrictTrue(self.event_processor.is_running) self.event_processor.stop() self.assertStrictFalse(self.event_processor.is_running) self.assertEqual(0, self.event_processor.event_queue.qsize()) def test_init__invalid_batch_size(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, self.optimizely.logger, True, self.event_queue, 5.5, self.MAX_DURATION_SEC, self.MAX_TIMEOUT_INTERVAL_SEC, ) # default batch size is 10. self.assertEqual(10, self.event_processor.batch_size) mock_config_logging.info.assert_called_with( 'Using default value 10 for batch_size.') def test_init__NaN_batch_size(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, self.optimizely.logger, True, self.event_queue, 'batch_size', self.MAX_DURATION_SEC, self.MAX_TIMEOUT_INTERVAL_SEC, ) # default batch size is 10. self.assertEqual(10, self.event_processor.batch_size) mock_config_logging.info.assert_called_with( 'Using default value 10 for batch_size.') def test_init__invalid_flush_interval(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, mock_config_logging, True, self.event_queue, self.MAX_BATCH_SIZE, 0, self.MAX_TIMEOUT_INTERVAL_SEC, ) # default flush interval is 30s. self.assertEqual(datetime.timedelta(seconds=30), self.event_processor.flush_interval) mock_config_logging.info.assert_called_with( 'Using default value 30 for flush_interval.') def test_init__float_flush_interval(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, mock_config_logging, True, self.event_queue, self.MAX_BATCH_SIZE, 0.5, self.MAX_TIMEOUT_INTERVAL_SEC, ) # default flush interval is 30s. self.assertEqual(datetime.timedelta(seconds=0.5), self.event_processor.flush_interval) def test_init__float_flush_deadline(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, mock_config_logging, True, self.event_queue, self.MAX_BATCH_SIZE, 0.5, self.MAX_TIMEOUT_INTERVAL_SEC, ) # default flush interval is 30s. self.assertTrue( isinstance(self.event_processor.flushing_interval_deadline, float)) def test_init__bool_flush_interval(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, self.optimizely.logger, True, self.event_queue, self.MAX_BATCH_SIZE, True, self.MAX_TIMEOUT_INTERVAL_SEC, ) # default flush interval is 30s. self.assertEqual(datetime.timedelta(seconds=30), self.event_processor.flush_interval) mock_config_logging.info.assert_called_with( 'Using default value 30 for flush_interval.') def test_init__string_flush_interval(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, self.optimizely.logger, True, self.event_queue, self.MAX_BATCH_SIZE, 'True', self.MAX_TIMEOUT_INTERVAL_SEC, ) # default flush interval is 30s. self.assertEqual(datetime.timedelta(seconds=30), self.event_processor.flush_interval) mock_config_logging.info.assert_called_with( 'Using default value 30 for flush_interval.') def test_init__invalid_timeout_interval(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, self.optimizely.logger, True, self.event_queue, self.MAX_BATCH_SIZE, self.MAX_DURATION_SEC, -100, ) # default timeout interval is 5s. self.assertEqual(datetime.timedelta(seconds=5), self.event_processor.timeout_interval) mock_config_logging.info.assert_called_with( 'Using default value 5 for timeout_interval.') def test_init__NaN_timeout_interval(self): event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, self.optimizely.logger, True, self.event_queue, self.MAX_BATCH_SIZE, self.MAX_DURATION_SEC, False, ) # default timeout interval is 5s. self.assertEqual(datetime.timedelta(seconds=5), self.event_processor.timeout_interval) mock_config_logging.info.assert_called_with( 'Using default value 5 for timeout_interval.') def test_notification_center__on_log_event(self): mock_event_dispatcher = mock.Mock() callback_hit = [False] def on_log_event(log_event): self.assertStrictTrue(isinstance(log_event, LogEvent)) callback_hit[0] = True self.optimizely.notification_center.add_notification_listener( enums.NotificationTypes.LOG_EVENT, on_log_event) with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(mock_event_dispatcher, mock_config_logging) user_event = self._build_conversion_event(self.event_name, self.project_config) self.event_processor.process(user_event) self.event_processor.stop() self.assertEqual(True, callback_hit[0]) self.assertEqual( 1, len(self.optimizely.notification_center.notification_listeners[ enums.NotificationTypes.LOG_EVENT]), ) def test_warning_log_level_on_queue_overflow(self): """ Test that a warning log is created when events overflow the queue. """ # create scenario where the batch size (MAX_BATCH_SIZE) is significantly larger than the queue size # use smaller batch size and higher timeout to avoid test flakiness test_max_queue_size = 10 self.MAX_BATCH_SIZE = 1000 event_dispatcher = CustomEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, self.optimizely.logger, True, queue.Queue(maxsize=test_max_queue_size), ) for i in range(0, self.MAX_BATCH_SIZE): user_event = self._build_conversion_event(self.event_name) self.event_processor.process(user_event) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(self.TEST_TIMEOUT) # queue is flushed, even though events overflow self.assertEqual(0, self.event_processor.event_queue.qsize()) mock_config_logging.warning.assert_called_with( 'Payload not accepted by the queue. Current size: {}'.format( str(test_max_queue_size)))
class BatchEventProcessorTest(base.BaseTest): DEFAULT_QUEUE_CAPACITY = 1000 MAX_BATCH_SIZE = 10 MAX_DURATION_SEC = 1 MAX_TIMEOUT_INTERVAL_SEC = 5 def setUp(self, *args, **kwargs): base.BaseTest.setUp(self, 'config_dict_with_multiple_experiments') self.test_user_id = 'test_user' self.event_name = 'test_event' self.event_queue = queue.Queue(maxsize=self.DEFAULT_QUEUE_CAPACITY) self.optimizely.logger = SimpleLogger() self.notification_center = self.optimizely.notification_center def tearDown(self): self.event_processor.stop() def _build_conversion_event(self, event_name, project_config=None): config = project_config or self.project_config return UserEventFactory.create_conversion_event(config, event_name, self.test_user_id, {}, {}) def _set_event_processor(self, event_dispatcher, logger): self.event_processor = BatchEventProcessor( event_dispatcher, logger, True, self.event_queue, self.MAX_BATCH_SIZE, self.MAX_DURATION_SEC, self.MAX_TIMEOUT_INTERVAL_SEC, self.optimizely.notification_center, ) def test_drain_on_stop(self): event_dispatcher = TestEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) user_event = self._build_conversion_event(self.event_name) self.event_processor.process(user_event) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(5) self.assertStrictTrue(event_dispatcher.compare_events()) self.assertEqual(0, self.event_processor.event_queue.qsize()) def test_flush_on_max_timeout(self): event_dispatcher = TestEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) user_event = self._build_conversion_event(self.event_name) self.event_processor.process(user_event) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(3) self.assertStrictTrue(event_dispatcher.compare_events()) self.assertEqual(0, self.event_processor.event_queue.qsize()) def test_flush_max_batch_size(self): event_dispatcher = TestEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) for i in range(0, self.MAX_BATCH_SIZE): user_event = self._build_conversion_event(self.event_name) self.event_processor.process(user_event) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(1) self.assertStrictTrue(event_dispatcher.compare_events()) self.assertEqual(0, self.event_processor.event_queue.qsize()) def test_flush(self): event_dispatcher = TestEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) user_event = self._build_conversion_event(self.event_name) self.event_processor.process(user_event) self.event_processor.flush() event_dispatcher.expect_conversion(self.event_name, self.test_user_id) self.event_processor.process(user_event) self.event_processor.flush() event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(3) self.assertStrictTrue(event_dispatcher.compare_events()) self.assertEqual(0, self.event_processor.event_queue.qsize()) def test_flush_on_mismatch_revision(self): event_dispatcher = TestEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) self.project_config.revision = 1 self.project_config.project_id = 'X' user_event_1 = self._build_conversion_event(self.event_name, self.project_config) self.event_processor.process(user_event_1) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) self.project_config.revision = 2 self.project_config.project_id = 'X' user_event_2 = self._build_conversion_event(self.event_name, self.project_config) self.event_processor.process(user_event_2) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(3) self.assertStrictTrue(event_dispatcher.compare_events()) self.assertEqual(0, self.event_processor.event_queue.qsize()) def test_flush_on_mismatch_project_id(self): event_dispatcher = TestEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) self.project_config.revision = 1 self.project_config.project_id = 'X' user_event_1 = self._build_conversion_event(self.event_name, self.project_config) self.event_processor.process(user_event_1) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) self.project_config.revision = 1 self.project_config.project_id = 'Y' user_event_2 = self._build_conversion_event(self.event_name, self.project_config) self.event_processor.process(user_event_2) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(3) self.assertStrictTrue(event_dispatcher.compare_events()) self.assertEqual(0, self.event_processor.event_queue.qsize()) def test_stop_and_start(self): event_dispatcher = TestEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(event_dispatcher, mock_config_logging) user_event = self._build_conversion_event(self.event_name, self.project_config) self.event_processor.process(user_event) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) time.sleep(3) self.assertStrictTrue(event_dispatcher.compare_events()) self.event_processor.stop() self.event_processor.process(user_event) event_dispatcher.expect_conversion(self.event_name, self.test_user_id) self.event_processor.start() self.assertStrictTrue(self.event_processor.is_running) self.event_processor.stop() self.assertStrictFalse(self.event_processor.is_running) self.assertEqual(0, self.event_processor.event_queue.qsize()) def test_init__invalid_batch_size(self): event_dispatcher = TestEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, self.optimizely.logger, True, self.event_queue, 5.5, self.MAX_DURATION_SEC, self.MAX_TIMEOUT_INTERVAL_SEC, ) # default batch size is 10. self.assertEqual(10, self.event_processor.batch_size) mock_config_logging.info.assert_called_with('Using default value 10 for batch_size.') def test_init__NaN_batch_size(self): event_dispatcher = TestEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, self.optimizely.logger, True, self.event_queue, 'batch_size', self.MAX_DURATION_SEC, self.MAX_TIMEOUT_INTERVAL_SEC, ) # default batch size is 10. self.assertEqual(10, self.event_processor.batch_size) mock_config_logging.info.assert_called_with('Using default value 10 for batch_size.') def test_init__invalid_flush_interval(self): event_dispatcher = TestEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, mock_config_logging, True, self.event_queue, self.MAX_BATCH_SIZE, 0, self.MAX_TIMEOUT_INTERVAL_SEC, ) # default flush interval is 30s. self.assertEqual(datetime.timedelta(seconds=30), self.event_processor.flush_interval) mock_config_logging.info.assert_called_with('Using default value 30 for flush_interval.') def test_init__bool_flush_interval(self): event_dispatcher = TestEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, self.optimizely.logger, True, self.event_queue, self.MAX_BATCH_SIZE, True, self.MAX_TIMEOUT_INTERVAL_SEC, ) # default flush interval is 30s. self.assertEqual(datetime.timedelta(seconds=30), self.event_processor.flush_interval) mock_config_logging.info.assert_called_with('Using default value 30 for flush_interval.') def test_init__string_flush_interval(self): event_dispatcher = TestEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, self.optimizely.logger, True, self.event_queue, self.MAX_BATCH_SIZE, 'True', self.MAX_TIMEOUT_INTERVAL_SEC, ) # default flush interval is 30s. self.assertEqual(datetime.timedelta(seconds=30), self.event_processor.flush_interval) mock_config_logging.info.assert_called_with('Using default value 30 for flush_interval.') def test_init__invalid_timeout_interval(self): event_dispatcher = TestEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, self.optimizely.logger, True, self.event_queue, self.MAX_BATCH_SIZE, self.MAX_DURATION_SEC, -100, ) # default timeout interval is 5s. self.assertEqual(datetime.timedelta(seconds=5), self.event_processor.timeout_interval) mock_config_logging.info.assert_called_with('Using default value 5 for timeout_interval.') def test_init__NaN_timeout_interval(self): event_dispatcher = TestEventDispatcher() with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self.event_processor = BatchEventProcessor( event_dispatcher, self.optimizely.logger, True, self.event_queue, self.MAX_BATCH_SIZE, self.MAX_DURATION_SEC, False, ) # default timeout interval is 5s. self.assertEqual(datetime.timedelta(seconds=5), self.event_processor.timeout_interval) mock_config_logging.info.assert_called_with('Using default value 5 for timeout_interval.') def test_notification_center__on_log_event(self): mock_event_dispatcher = mock.Mock() callback_hit = [False] def on_log_event(log_event): self.assertStrictTrue(isinstance(log_event, LogEvent)) callback_hit[0] = True self.optimizely.notification_center.add_notification_listener(enums.NotificationTypes.LOG_EVENT, on_log_event) with mock.patch.object(self.optimizely, 'logger') as mock_config_logging: self._set_event_processor(mock_event_dispatcher, mock_config_logging) user_event = self._build_conversion_event(self.event_name, self.project_config) self.event_processor.process(user_event) self.event_processor.stop() self.assertEqual(True, callback_hit[0]) self.assertEqual( 1, len(self.optimizely.notification_center.notification_listeners[enums.NotificationTypes.LOG_EVENT]), )