@@ -502,6 +502,70 @@ def finish_bundle(self):
502502@unittest .skipIf (pubsub is None , 'GCP dependencies are not installed' )
503503@mock .patch ('google.cloud.pubsub.SubscriberClient' )
504504class TestReadFromPubSub (unittest .TestCase ):
505+ def setUp (self ):
506+ _PubSubReadEvaluator ._subscription_cache .clear ()
507+ _PubSubReadEvaluator ._subscriber_client_cache .clear ()
508+
509+ def test_subscriber_client_is_reused_for_transform (self , mock_pubsub ):
510+ class Transform (object ):
511+ pass
512+
513+ transform = Transform ()
514+ first_client = _PubSubReadEvaluator ._get_subscriber_client (transform )
515+ second_client = _PubSubReadEvaluator ._get_subscriber_client (transform )
516+
517+ self .assertIs (first_client , second_client )
518+ mock_pubsub .assert_called_once_with ()
519+ first_client .close .assert_not_called ()
520+
521+ def test_subscription_creation_does_not_hold_global_cache_lock (
522+ self , mock_pubsub ):
523+ class Transform (object ):
524+ pass
525+
526+ def subscription_path (project , subscription ):
527+ return 'projects/%s/subscriptions/%s' % (project , subscription )
528+
529+ transform = Transform ()
530+ client = mock_pubsub .return_value
531+ client .subscription_path .side_effect = subscription_path
532+ client .topic_path .return_value = 'projects/topic_project/topics/topic'
533+ global_lock_available = []
534+
535+ def create_subscription (name , topic ):
536+ self .assertTrue (name .startswith ('projects/sub_project/subscriptions/' ))
537+ self .assertEqual ('projects/topic_project/topics/topic' , topic )
538+ acquired = _PubSubReadEvaluator ._subscriber_client_cache_lock .acquire (
539+ blocking = False )
540+ global_lock_available .append (acquired )
541+ if acquired :
542+ _PubSubReadEvaluator ._subscriber_client_cache_lock .release ()
543+
544+ client .create_subscription .side_effect = create_subscription
545+
546+ sub_name = _PubSubReadEvaluator .get_subscription (
547+ transform , 'topic_project' , 'topic' , 'sub_project' , None )
548+
549+ self .assertTrue (global_lock_available )
550+ self .assertTrue (global_lock_available [0 ])
551+ self .assertTrue (sub_name .startswith ('projects/sub_project/subscriptions/' ))
552+
553+ def test_subscriber_client_cleanup_is_idempotent (self , unused_mock_pubsub ):
554+ client = mock .Mock ()
555+ subscriber_client = transform_evaluator ._PubSubSubscriberClient (client )
556+ subscriber_client .set_temporary_subscription ('subscription' )
557+
558+ subscriber_client .close ()
559+ subscriber_client .close ()
560+
561+ client .assert_has_calls ([
562+ mock .call .delete_subscription (subscription = 'subscription' ),
563+ mock .call .close ()
564+ ])
565+ client .delete_subscription .assert_called_once_with (
566+ subscription = 'subscription' )
567+ client .close .assert_called_once_with ()
568+
505569 def test_read_messages_success (self , mock_pubsub ):
506570 data = b'data'
507571 publish_time_secs = 1520861821
@@ -533,7 +597,8 @@ def test_read_messages_success(self, mock_pubsub):
533597 mock_pubsub .return_value .acknowledge .assert_has_calls (
534598 [mock .call (subscription = mock .ANY , ack_ids = [ack_id ])])
535599
536- mock_pubsub .return_value .close .assert_has_calls ([mock .call ()])
600+ mock_pubsub .assert_called_once_with ()
601+ mock_pubsub .return_value .close .assert_not_called ()
537602
538603 def test_read_strings_success (self , mock_pubsub ):
539604 data = '🤷 ¯\\ _(ツ)_/¯'
@@ -555,7 +620,7 @@ def test_read_strings_success(self, mock_pubsub):
555620 mock_pubsub .return_value .acknowledge .assert_has_calls (
556621 [mock .call (subscription = mock .ANY , ack_ids = [ack_id ])])
557622
558- mock_pubsub .return_value .close .assert_has_calls ([ mock . call ()] )
623+ mock_pubsub .return_value .close .assert_not_called ( )
559624
560625 def test_read_data_success (self , mock_pubsub ):
561626 data_encoded = '🤷 ¯\\ _(ツ)_/¯' .encode ('utf-8' )
@@ -575,7 +640,7 @@ def test_read_data_success(self, mock_pubsub):
575640 mock_pubsub .return_value .acknowledge .assert_has_calls (
576641 [mock .call (subscription = mock .ANY , ack_ids = [ack_id ])])
577642
578- mock_pubsub .return_value .close .assert_has_calls ([ mock . call ()] )
643+ mock_pubsub .return_value .close .assert_not_called ( )
579644
580645 def test_read_messages_timestamp_attribute_milli_success (self , mock_pubsub ):
581646 data = b'data'
@@ -610,7 +675,7 @@ def test_read_messages_timestamp_attribute_milli_success(self, mock_pubsub):
610675 mock_pubsub .return_value .acknowledge .assert_has_calls (
611676 [mock .call (subscription = mock .ANY , ack_ids = [ack_id ])])
612677
613- mock_pubsub .return_value .close .assert_has_calls ([ mock . call ()] )
678+ mock_pubsub .return_value .close .assert_not_called ( )
614679
615680 def test_read_messages_timestamp_attribute_rfc3339_success (self , mock_pubsub ):
616681 data = b'data'
@@ -645,7 +710,7 @@ def test_read_messages_timestamp_attribute_rfc3339_success(self, mock_pubsub):
645710 mock_pubsub .return_value .acknowledge .assert_has_calls (
646711 [mock .call (subscription = mock .ANY , ack_ids = [ack_id ])])
647712
648- mock_pubsub .return_value .close .assert_has_calls ([ mock . call ()] )
713+ mock_pubsub .return_value .close .assert_not_called ( )
649714
650715 def test_read_messages_timestamp_attribute_missing (self , mock_pubsub ):
651716 data = b'data'
@@ -681,7 +746,7 @@ def test_read_messages_timestamp_attribute_missing(self, mock_pubsub):
681746 mock_pubsub .return_value .acknowledge .assert_has_calls (
682747 [mock .call (subscription = mock .ANY , ack_ids = [ack_id ])])
683748
684- mock_pubsub .return_value .close .assert_has_calls ([ mock . call ()] )
749+ mock_pubsub .return_value .close .assert_not_called ( )
685750
686751 def test_read_messages_timestamp_attribute_fail_parse (self , mock_pubsub ):
687752 data = b'data'
@@ -710,7 +775,7 @@ def test_read_messages_timestamp_attribute_fail_parse(self, mock_pubsub):
710775 p .run ()
711776 mock_pubsub .return_value .acknowledge .assert_not_called ()
712777
713- mock_pubsub .return_value .close .assert_has_calls ([ mock . call ()] )
778+ mock_pubsub .return_value .close .assert_not_called ( )
714779
715780 def test_read_message_id_label_unsupported (self , unused_mock_pubsub ):
716781 # id_label is unsupported in DirectRunner.
0 commit comments