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 }