github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/pubsub/gcppubsub/gcppubsub_test.go (about)

     1  // Copyright 2018 The Go Cloud Development Kit Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gcppubsub
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"path"
    21  	"strings"
    22  	"sync/atomic"
    23  	"testing"
    24  
    25  	raw "cloud.google.com/go/pubsub/apiv1"
    26  	"gocloud.dev/gcp"
    27  	"gocloud.dev/internal/testing/setup"
    28  	"gocloud.dev/pubsub"
    29  	"gocloud.dev/pubsub/driver"
    30  	"gocloud.dev/pubsub/drivertest"
    31  	pubsubpb "google.golang.org/genproto/googleapis/pubsub/v1"
    32  	"google.golang.org/grpc/codes"
    33  	"google.golang.org/grpc/status"
    34  )
    35  
    36  // projectID is the project ID that was used during the last test run using
    37  // --record.
    38  const projectID = "go-cloud-test-216917"
    39  
    40  type harness struct {
    41  	closer    func()
    42  	pubClient *raw.PublisherClient
    43  	subClient *raw.SubscriberClient
    44  	numTopics uint32 // atomic
    45  	numSubs   uint32 // atomic
    46  }
    47  
    48  func newHarness(ctx context.Context, t *testing.T) (drivertest.Harness, error) {
    49  	conn, done := setup.NewGCPgRPCConn(ctx, t, endPoint, "pubsub")
    50  	pubClient, err := PublisherClient(ctx, conn)
    51  	if err != nil {
    52  		return nil, fmt.Errorf("making publisher client: %v", err)
    53  	}
    54  	subClient, err := SubscriberClient(ctx, conn)
    55  	if err != nil {
    56  		return nil, fmt.Errorf("making subscription client: %v", err)
    57  	}
    58  	return &harness{closer: done, pubClient: pubClient, subClient: subClient, numTopics: 0, numSubs: 0}, nil
    59  }
    60  
    61  func (h *harness) CreateTopic(ctx context.Context, testName string) (dt driver.Topic, cleanup func(), err error) {
    62  	// We may encounter topics that were created by a previous test run and were
    63  	// not properly cleaned up. In such a case delete the existing topic and create
    64  	// a new topic with a higher topic number (to avoid cool-off issues between
    65  	// deletion and re-creation).
    66  	for {
    67  		topicName := fmt.Sprintf("%s-topic-%d", sanitize(testName), atomic.AddUint32(&h.numTopics, 1))
    68  		topicPath := fmt.Sprintf("projects/%s/topics/%s", projectID, topicName)
    69  		dt, cleanup, err := createTopic(ctx, h.pubClient, topicName, topicPath)
    70  		if err != nil && status.Code(err) == codes.AlreadyExists {
    71  			// Delete the topic and retry.
    72  			h.pubClient.DeleteTopic(ctx, &pubsubpb.DeleteTopicRequest{Topic: topicPath})
    73  			continue
    74  		}
    75  		return dt, cleanup, err
    76  	}
    77  }
    78  
    79  func createTopic(ctx context.Context, pubClient *raw.PublisherClient, topicName, topicPath string) (dt driver.Topic, cleanup func(), err error) {
    80  	_, err = pubClient.CreateTopic(ctx, &pubsubpb.Topic{Name: topicPath})
    81  	if err != nil {
    82  		return nil, nil, err
    83  	}
    84  	dt = openTopic(pubClient, path.Join("projects", projectID, "topics", topicName))
    85  	cleanup = func() {
    86  		pubClient.DeleteTopic(ctx, &pubsubpb.DeleteTopicRequest{Topic: topicPath})
    87  	}
    88  	return dt, cleanup, nil
    89  }
    90  
    91  func (h *harness) MakeNonexistentTopic(ctx context.Context) (driver.Topic, error) {
    92  	return openTopic(h.pubClient, path.Join("projects", projectID, "topics", "nonexistent-topic")), nil
    93  }
    94  
    95  func (h *harness) CreateSubscription(ctx context.Context, dt driver.Topic, testName string) (ds driver.Subscription, cleanup func(), err error) {
    96  	// We may encounter subscriptions that were created by a previous test run
    97  	// and were not properly cleaned up. In such a case delete the existing
    98  	// subscription and create a new subscription with a higher subscription
    99  	// number (to avoid cool-off issues between deletion and re-creation).
   100  	for {
   101  		subName := fmt.Sprintf("%s-subscription-%d", sanitize(testName), atomic.AddUint32(&h.numSubs, 1))
   102  		subPath := fmt.Sprintf("projects/%s/subscriptions/%s", projectID, subName)
   103  		ds, cleanup, err := createSubscription(ctx, h.subClient, dt, subName, subPath)
   104  		if err != nil && status.Code(err) == codes.AlreadyExists {
   105  			// Delete the subscription and retry.
   106  			h.subClient.DeleteSubscription(ctx, &pubsubpb.DeleteSubscriptionRequest{Subscription: subPath})
   107  			continue
   108  		}
   109  		return ds, cleanup, err
   110  	}
   111  }
   112  
   113  func createSubscription(ctx context.Context, subClient *raw.SubscriberClient, dt driver.Topic, subName, subPath string) (ds driver.Subscription, cleanup func(), err error) {
   114  	t := dt.(*topic)
   115  	_, err = subClient.CreateSubscription(ctx, &pubsubpb.Subscription{
   116  		Name:  subPath,
   117  		Topic: t.path,
   118  	})
   119  	if err != nil {
   120  		return nil, nil, err
   121  	}
   122  	ds = openSubscription(subClient, path.Join("projects", projectID, "subscriptions", subName), nil)
   123  	cleanup = func() {
   124  		subClient.DeleteSubscription(ctx, &pubsubpb.DeleteSubscriptionRequest{Subscription: subPath})
   125  	}
   126  	return ds, cleanup, nil
   127  }
   128  
   129  func (h *harness) MakeNonexistentSubscription(ctx context.Context) (driver.Subscription, func(), error) {
   130  	return openSubscription(h.subClient, path.Join("projects", projectID, "subscriptions", "nonexistent-subscription"), nil), func() {}, nil
   131  }
   132  
   133  func (h *harness) Close() {
   134  	h.pubClient.Close()
   135  	h.subClient.Close()
   136  	h.closer()
   137  }
   138  
   139  func (h *harness) MaxBatchSizes() (int, int) {
   140  	return sendBatcherOpts.MaxBatchSize, ackBatcherOpts.MaxBatchSize
   141  }
   142  
   143  func (*harness) SupportsMultipleSubscriptions() bool { return true }
   144  
   145  func TestConformance(t *testing.T) {
   146  	asTests := []drivertest.AsTest{gcpAsTest{}}
   147  	drivertest.RunConformanceTests(t, newHarness, asTests)
   148  }
   149  
   150  func BenchmarkGcpPubSub(b *testing.B) {
   151  	ctx := context.Background()
   152  	creds, err := gcp.DefaultCredentials(ctx)
   153  	if err != nil {
   154  		b.Fatal(err)
   155  	}
   156  
   157  	// Connect.
   158  	conn, cleanup, err := Dial(ctx, gcp.CredentialsTokenSource(creds))
   159  	if err != nil {
   160  		b.Fatal(err)
   161  	}
   162  	defer cleanup()
   163  
   164  	// Make topic.
   165  	pc, err := PublisherClient(ctx, conn)
   166  	if err != nil {
   167  		b.Fatal(err)
   168  	}
   169  	topicName := fmt.Sprintf("%s-topic", b.Name())
   170  	topicPath := fmt.Sprintf("projects/%s/topics/%s", projectID, topicName)
   171  	dt, cleanup1, err := createTopic(ctx, pc, topicName, topicPath)
   172  	if err != nil {
   173  		b.Fatal(err)
   174  	}
   175  	defer cleanup1()
   176  	topic := pubsub.NewTopic(dt, nil)
   177  	defer topic.Shutdown(ctx)
   178  
   179  	// Make subscription.
   180  	sc, err := SubscriberClient(ctx, conn)
   181  	if err != nil {
   182  		b.Fatal(err)
   183  	}
   184  	subName := fmt.Sprintf("%s-subscription", b.Name())
   185  	subPath := fmt.Sprintf("projects/%s/subscriptions/%s", projectID, subName)
   186  	ds, cleanup2, err := createSubscription(ctx, sc, dt, subName, subPath)
   187  	if err != nil {
   188  		b.Fatal(err)
   189  	}
   190  	defer cleanup2()
   191  	sub := pubsub.NewSubscription(ds, defaultRecvBatcherOpts, ackBatcherOpts)
   192  	defer sub.Shutdown(ctx)
   193  
   194  	drivertest.RunBenchmarks(b, topic, sub)
   195  }
   196  
   197  type gcpAsTest struct{}
   198  
   199  func (gcpAsTest) Name() string {
   200  	return "gcp test"
   201  }
   202  
   203  func (gcpAsTest) TopicCheck(topic *pubsub.Topic) error {
   204  	var c2 raw.PublisherClient
   205  	if topic.As(&c2) {
   206  		return fmt.Errorf("cast succeeded for %T, want failure", &c2)
   207  	}
   208  	var c3 *raw.PublisherClient
   209  	if !topic.As(&c3) {
   210  		return fmt.Errorf("cast failed for %T", &c3)
   211  	}
   212  	return nil
   213  }
   214  
   215  func (gcpAsTest) SubscriptionCheck(sub *pubsub.Subscription) error {
   216  	var c2 raw.SubscriberClient
   217  	if sub.As(&c2) {
   218  		return fmt.Errorf("cast succeeded for %T, want failure", &c2)
   219  	}
   220  	var c3 *raw.SubscriberClient
   221  	if !sub.As(&c3) {
   222  		return fmt.Errorf("cast failed for %T", &c3)
   223  	}
   224  	return nil
   225  }
   226  
   227  func (gcpAsTest) TopicErrorCheck(t *pubsub.Topic, err error) error {
   228  	var s *status.Status
   229  	if !t.ErrorAs(err, &s) {
   230  		return fmt.Errorf("failed to convert %v (%T) to a gRPC Status", err, err)
   231  	}
   232  	if s.Code() != codes.NotFound {
   233  		return fmt.Errorf("got code %s, want NotFound", s.Code())
   234  	}
   235  	return nil
   236  }
   237  
   238  func (gcpAsTest) SubscriptionErrorCheck(sub *pubsub.Subscription, err error) error {
   239  	var s *status.Status
   240  	if !sub.ErrorAs(err, &s) {
   241  		return fmt.Errorf("failed to convert %v (%T) to a gRPC Status", err, err)
   242  	}
   243  	if s.Code() != codes.NotFound {
   244  		return fmt.Errorf("got code %s, want NotFound", s.Code())
   245  	}
   246  	return nil
   247  }
   248  
   249  func (gcpAsTest) MessageCheck(m *pubsub.Message) error {
   250  	var pm pubsubpb.PubsubMessage
   251  	if m.As(&pm) {
   252  		return fmt.Errorf("cast succeeded for %T, want failure", &pm)
   253  	}
   254  	var ppm *pubsubpb.PubsubMessage
   255  	if !m.As(&ppm) {
   256  		return fmt.Errorf("cast failed for %T", &ppm)
   257  	}
   258  	var prm *pubsubpb.ReceivedMessage
   259  	if !m.As(&prm) {
   260  		return fmt.Errorf("cast failed for %T", &prm)
   261  	}
   262  	return nil
   263  }
   264  
   265  func (gcpAsTest) BeforeSend(as func(interface{}) bool) error {
   266  	var ppm *pubsubpb.PubsubMessage
   267  	if !as(&ppm) {
   268  		return fmt.Errorf("cast failed for %T", &ppm)
   269  	}
   270  	return nil
   271  }
   272  
   273  func (gcpAsTest) AfterSend(as func(interface{}) bool) error {
   274  	var msgId string
   275  	if !as(&msgId) {
   276  		return fmt.Errorf("cast failed for %T", &msgId)
   277  	}
   278  	return nil
   279  }
   280  
   281  func sanitize(testName string) string {
   282  	return strings.Replace(testName, "/", "_", -1)
   283  }
   284  
   285  func TestOpenTopic(t *testing.T) {
   286  	ctx := context.Background()
   287  	creds, err := setup.FakeGCPCredentials(ctx)
   288  	if err != nil {
   289  		t.Fatal(err)
   290  	}
   291  	projID, err := gcp.DefaultProjectID(creds)
   292  	if err != nil {
   293  		t.Fatal(err)
   294  	}
   295  	conn, cleanup, err := Dial(ctx, gcp.CredentialsTokenSource(creds))
   296  	if err != nil {
   297  		t.Fatal(err)
   298  	}
   299  	defer cleanup()
   300  	pc, err := PublisherClient(ctx, conn)
   301  	if err != nil {
   302  		t.Fatal(err)
   303  	}
   304  	topic := OpenTopic(pc, projID, "my-topic", nil)
   305  	defer topic.Shutdown(ctx)
   306  	err = topic.Send(ctx, &pubsub.Message{Body: []byte("hello world")})
   307  	if err == nil {
   308  		t.Error("got nil, want error")
   309  	}
   310  
   311  	// Repeat with OpenTopicByPath.
   312  	topic, err = OpenTopicByPath(pc, path.Join("projects", string(projID), "topics", "my-topic"), nil)
   313  	if err != nil {
   314  		t.Fatal(err)
   315  	}
   316  	defer topic.Shutdown(ctx)
   317  	err = topic.Send(ctx, &pubsub.Message{Body: []byte("hello world")})
   318  	if err == nil {
   319  		t.Error("got nil, want error")
   320  	}
   321  
   322  	// Try an invalid path.
   323  	_, err = OpenTopicByPath(pc, "my-topic", nil)
   324  	if err == nil {
   325  		t.Error("got nil, want error")
   326  	}
   327  }
   328  
   329  func TestOpenSubscription(t *testing.T) {
   330  	ctx := context.Background()
   331  	creds, err := setup.FakeGCPCredentials(ctx)
   332  	if err != nil {
   333  		t.Fatal(err)
   334  	}
   335  	projID, err := gcp.DefaultProjectID(creds)
   336  	if err != nil {
   337  		t.Fatal(err)
   338  	}
   339  	conn, cleanup, err := Dial(ctx, gcp.CredentialsTokenSource(creds))
   340  	if err != nil {
   341  		t.Fatal(err)
   342  	}
   343  	defer cleanup()
   344  	sc, err := SubscriberClient(ctx, conn)
   345  	if err != nil {
   346  		t.Fatal(err)
   347  	}
   348  	sub := OpenSubscription(sc, projID, "my-subscription", nil)
   349  	defer sub.Shutdown(ctx)
   350  	_, err = sub.Receive(ctx)
   351  	if err == nil {
   352  		t.Error("got nil, want error")
   353  	}
   354  
   355  	// Repeat with OpenSubscriptionByPath.
   356  	sub, err = OpenSubscriptionByPath(sc, path.Join("projects", string(projID), "subscriptions", "my-subscription"), nil)
   357  	if err != nil {
   358  		t.Fatal(err)
   359  	}
   360  	defer sub.Shutdown(ctx)
   361  	_, err = sub.Receive(ctx)
   362  	if err == nil {
   363  		t.Error("got nil, want error")
   364  	}
   365  
   366  	// Try an invalid path.
   367  	_, err = OpenSubscriptionByPath(sc, "my-subscription", nil)
   368  	if err == nil {
   369  		t.Error("got nil, want error")
   370  	}
   371  }
   372  
   373  func TestOpenTopicFromURL(t *testing.T) {
   374  	cleanup := setup.FakeGCPDefaultCredentials(t)
   375  	defer cleanup()
   376  
   377  	tests := []struct {
   378  		URL     string
   379  		WantErr bool
   380  	}{
   381  		// OK, short form.
   382  		{"gcppubsub://myproject/mytopic", false},
   383  		// OK, long form.
   384  		{"gcppubsub://projects/myproject/topic/mytopic", false},
   385  		// Invalid parameter.
   386  		{"gcppubsub://myproject/mytopic?param=value", true},
   387  	}
   388  
   389  	ctx := context.Background()
   390  	for _, test := range tests {
   391  		topic, err := pubsub.OpenTopic(ctx, test.URL)
   392  		if (err != nil) != test.WantErr {
   393  			t.Errorf("%s: got error %v, want error %v", test.URL, err, test.WantErr)
   394  		}
   395  		if topic != nil {
   396  			topic.Shutdown(ctx)
   397  		}
   398  	}
   399  }
   400  
   401  func TestOpenSubscriptionFromURL(t *testing.T) {
   402  	cleanup := setup.FakeGCPDefaultCredentials(t)
   403  	defer cleanup()
   404  
   405  	tests := []struct {
   406  		URL     string
   407  		WantErr bool
   408  	}{
   409  		// OK, short form.
   410  		{"gcppubsub://myproject/mysub", false},
   411  		// OK, long form.
   412  		{"gcppubsub://projects/myproject/subscriptions/mysub", false},
   413  		// Invalid parameter.
   414  		{"gcppubsub://myproject/mysub?param=value", true},
   415  		// Valid parameters
   416  		{"gcppubsub://projects/myproject/subscriptions/mysub?max_recv_batch_size=1", false},
   417  		// Invalid parameters
   418  		{"gcppubsub://projects/myproject/subscriptions/mysub?max_recv_batch_size=0", true},
   419  		// Invalid parameters
   420  		{"gcppubsub://projects/myproject/subscriptions/mysub?max_recv_batch_size=1001", true},
   421  	}
   422  
   423  	ctx := context.Background()
   424  	for _, test := range tests {
   425  		sub, err := pubsub.OpenSubscription(ctx, test.URL)
   426  		if (err != nil) != test.WantErr {
   427  			t.Errorf("%s: got error %v, want error %v", test.URL, err, test.WantErr)
   428  		}
   429  		if sub != nil {
   430  			sub.Shutdown(ctx)
   431  		}
   432  	}
   433  }