|
- // Copyright 2017 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- package pstest
-
- import (
- "fmt"
- "io"
- "testing"
- "time"
-
- "github.com/golang/protobuf/ptypes"
-
- "cloud.google.com/go/internal/testutil"
- "golang.org/x/net/context"
- pb "google.golang.org/genproto/googleapis/pubsub/v1"
- "google.golang.org/grpc"
- )
-
- func TestTopics(t *testing.T) {
- pclient, _, server := newFake(t)
- ctx := context.Background()
- var topics []*pb.Topic
- for i := 1; i < 3; i++ {
- topics = append(topics, mustCreateTopic(t, pclient, &pb.Topic{
- Name: fmt.Sprintf("projects/P/topics/T%d", i),
- Labels: map[string]string{"num": fmt.Sprintf("%d", i)},
- }))
- }
- if got, want := len(server.gServer.topics), len(topics); got != want {
- t.Fatalf("got %d topics, want %d", got, want)
- }
- for _, top := range topics {
- got, err := pclient.GetTopic(ctx, &pb.GetTopicRequest{Topic: top.Name})
- if err != nil {
- t.Fatal(err)
- }
- if !testutil.Equal(got, top) {
- t.Errorf("\ngot %+v\nwant %+v", got, top)
- }
- }
-
- res, err := pclient.ListTopics(ctx, &pb.ListTopicsRequest{Project: "projects/P"})
- if err != nil {
- t.Fatal(err)
- }
- if got, want := res.Topics, topics; !testutil.Equal(got, want) {
- t.Errorf("\ngot %+v\nwant %+v", got, want)
- }
-
- for _, top := range topics {
- if _, err := pclient.DeleteTopic(ctx, &pb.DeleteTopicRequest{Topic: top.Name}); err != nil {
- t.Fatal(err)
- }
- }
- if got, want := len(server.gServer.topics), 0; got != want {
- t.Fatalf("got %d topics, want %d", got, want)
- }
- }
-
- func TestSubscriptions(t *testing.T) {
- pclient, sclient, server := newFake(t)
- ctx := context.Background()
- topic := mustCreateTopic(t, pclient, &pb.Topic{Name: "projects/P/topics/T"})
- var subs []*pb.Subscription
- for i := 0; i < 3; i++ {
- subs = append(subs, mustCreateSubscription(t, sclient, &pb.Subscription{
- Name: fmt.Sprintf("projects/P/subscriptions/S%d", i),
- Topic: topic.Name,
- AckDeadlineSeconds: int32(10 * (i + 1)),
- }))
- }
-
- if got, want := len(server.gServer.subs), len(subs); got != want {
- t.Fatalf("got %d subscriptions, want %d", got, want)
- }
- for _, s := range subs {
- got, err := sclient.GetSubscription(ctx, &pb.GetSubscriptionRequest{Subscription: s.Name})
- if err != nil {
- t.Fatal(err)
- }
- if !testutil.Equal(got, s) {
- t.Errorf("\ngot %+v\nwant %+v", got, s)
- }
- }
-
- res, err := sclient.ListSubscriptions(ctx, &pb.ListSubscriptionsRequest{Project: "projects/P"})
- if err != nil {
- t.Fatal(err)
- }
- if got, want := res.Subscriptions, subs; !testutil.Equal(got, want) {
- t.Errorf("\ngot %+v\nwant %+v", got, want)
- }
-
- res2, err := pclient.ListTopicSubscriptions(ctx, &pb.ListTopicSubscriptionsRequest{Topic: topic.Name})
- if err != nil {
- t.Fatal(err)
- }
- if got, want := len(res2.Subscriptions), len(subs); got != want {
- t.Fatalf("got %d subs, want %d", got, want)
- }
- for i, got := range res2.Subscriptions {
- want := subs[i].Name
- if !testutil.Equal(got, want) {
- t.Errorf("\ngot %+v\nwant %+v", got, want)
- }
- }
-
- for _, s := range subs {
- if _, err := sclient.DeleteSubscription(ctx, &pb.DeleteSubscriptionRequest{Subscription: s.Name}); err != nil {
- t.Fatal(err)
- }
- }
- if got, want := len(server.gServer.subs), 0; got != want {
- t.Fatalf("got %d subscriptions, want %d", got, want)
- }
- }
-
- func TestPublish(t *testing.T) {
- s := NewServer()
- var ids []string
- for i := 0; i < 3; i++ {
- ids = append(ids, s.Publish("projects/p/topics/t", []byte("hello"), nil))
- }
- s.Wait()
- ms := s.Messages()
- if got, want := len(ms), len(ids); got != want {
- t.Errorf("got %d messages, want %d", got, want)
- }
- for i, id := range ids {
- if got, want := ms[i].ID, id; got != want {
- t.Errorf("got %s, want %s", got, want)
- }
- }
-
- m := s.Message(ids[1])
- if m == nil {
- t.Error("got nil, want a message")
- }
- }
-
- // Note: this sets the fake's "now" time, so it is senstive to concurrent changes to "now".
- func publish(t *testing.T, pclient pb.PublisherClient, topic *pb.Topic, messages []*pb.PubsubMessage) map[string]*pb.PubsubMessage {
- pubTime := time.Now()
- now.Store(func() time.Time { return pubTime })
- defer func() { now.Store(time.Now) }()
-
- res, err := pclient.Publish(context.Background(), &pb.PublishRequest{
- Topic: topic.Name,
- Messages: messages,
- })
- if err != nil {
- t.Fatal(err)
- }
- tsPubTime, err := ptypes.TimestampProto(pubTime)
- if err != nil {
- t.Fatal(err)
- }
- want := map[string]*pb.PubsubMessage{}
- for i, id := range res.MessageIds {
- want[id] = &pb.PubsubMessage{
- Data: messages[i].Data,
- Attributes: messages[i].Attributes,
- MessageId: id,
- PublishTime: tsPubTime,
- }
- }
- return want
- }
-
- func TestStreamingPull(t *testing.T) {
- // A simple test of streaming pull.
- pclient, sclient, _ := newFake(t)
- top := mustCreateTopic(t, pclient, &pb.Topic{Name: "projects/P/topics/T"})
- sub := mustCreateSubscription(t, sclient, &pb.Subscription{
- Name: "projects/P/subscriptions/S",
- Topic: top.Name,
- AckDeadlineSeconds: 10,
- })
-
- want := publish(t, pclient, top, []*pb.PubsubMessage{
- {Data: []byte("d1")},
- {Data: []byte("d2")},
- {Data: []byte("d3")},
- })
- got := pubsubMessages(pullN(t, len(want), sclient, sub))
- if diff := testutil.Diff(got, want); diff != "" {
- t.Error(diff)
- }
- }
-
- func TestStreamingPullAck(t *testing.T) {
- // Ack each message as it arrives. Make sure we don't see dups.
- minAckDeadlineSecs = 1
- pclient, sclient, _ := newFake(t)
- top := mustCreateTopic(t, pclient, &pb.Topic{Name: "projects/P/topics/T"})
- sub := mustCreateSubscription(t, sclient, &pb.Subscription{
- Name: "projects/P/subscriptions/S",
- Topic: top.Name,
- AckDeadlineSeconds: 1,
- })
-
- _ = publish(t, pclient, top, []*pb.PubsubMessage{
- {Data: []byte("d1")},
- {Data: []byte("d2")},
- {Data: []byte("d3")},
- })
-
- got := map[string]bool{}
- spc := mustStartPull(t, sclient, sub)
- time.AfterFunc(time.Duration(3*minAckDeadlineSecs)*time.Second, func() {
- if err := spc.CloseSend(); err != nil {
- t.Errorf("CloseSend: %v", err)
- }
- })
-
- for {
- res, err := spc.Recv()
- if err == io.EOF {
- break
- }
- if err != nil {
- t.Fatal(err)
- }
- req := &pb.StreamingPullRequest{}
- for _, m := range res.ReceivedMessages {
- if got[m.Message.MessageId] {
- t.Fatal("duplicate message")
- }
- got[m.Message.MessageId] = true
- req.AckIds = append(req.AckIds, m.AckId)
- }
- if err := spc.Send(req); err != nil {
- t.Fatal(err)
- }
- }
- }
-
- func TestAcknowledge(t *testing.T) {
- ctx := context.Background()
- pclient, sclient, srv := newFake(t)
- top := mustCreateTopic(t, pclient, &pb.Topic{Name: "projects/P/topics/T"})
- sub := mustCreateSubscription(t, sclient, &pb.Subscription{
- Name: "projects/P/subscriptions/S",
- Topic: top.Name,
- AckDeadlineSeconds: 10,
- })
-
- publish(t, pclient, top, []*pb.PubsubMessage{
- {Data: []byte("d1")},
- {Data: []byte("d2")},
- {Data: []byte("d3")},
- })
- msgs := pullN(t, 3, sclient, sub)
- var ackIDs []string
- for _, m := range msgs {
- ackIDs = append(ackIDs, m.AckId)
- }
- if _, err := sclient.Acknowledge(ctx, &pb.AcknowledgeRequest{
- Subscription: sub.Name,
- AckIds: ackIDs,
- }); err != nil {
- t.Fatal(err)
- }
- smsgs := srv.Messages()
- if got, want := len(smsgs), 3; got != want {
- t.Fatalf("got %d messages, want %d", got, want)
- }
- for _, sm := range smsgs {
- if sm.Acks != 1 {
- t.Errorf("message %s: got %d acks, want 1", sm.ID, sm.Acks)
- }
- }
- }
-
- func TestModAck(t *testing.T) {
- ctx := context.Background()
- pclient, sclient, _ := newFake(t)
- top := mustCreateTopic(t, pclient, &pb.Topic{Name: "projects/P/topics/T"})
- sub := mustCreateSubscription(t, sclient, &pb.Subscription{
- Name: "projects/P/subscriptions/S",
- Topic: top.Name,
- AckDeadlineSeconds: 10,
- })
-
- publish(t, pclient, top, []*pb.PubsubMessage{
- {Data: []byte("d1")},
- {Data: []byte("d2")},
- {Data: []byte("d3")},
- })
- msgs := pullN(t, 3, sclient, sub)
- var ackIDs []string
- for _, m := range msgs {
- ackIDs = append(ackIDs, m.AckId)
- }
- if _, err := sclient.ModifyAckDeadline(ctx, &pb.ModifyAckDeadlineRequest{
- Subscription: sub.Name,
- AckIds: ackIDs,
- AckDeadlineSeconds: 0,
- }); err != nil {
- t.Fatal(err)
- }
- // Having nacked all three messages, we should see them again.
- msgs = pullN(t, 3, sclient, sub)
- if got, want := len(msgs), 3; got != want {
- t.Errorf("got %d messages, want %d", got, want)
- }
- }
-
- func TestAckDeadline(t *testing.T) {
- // Messages should be resent after they expire.
- pclient, sclient, _ := newFake(t)
- minAckDeadlineSecs = 2
- top := mustCreateTopic(t, pclient, &pb.Topic{Name: "projects/P/topics/T"})
- sub := mustCreateSubscription(t, sclient, &pb.Subscription{
- Name: "projects/P/subscriptions/S",
- Topic: top.Name,
- AckDeadlineSeconds: minAckDeadlineSecs,
- })
-
- _ = publish(t, pclient, top, []*pb.PubsubMessage{
- {Data: []byte("d1")},
- {Data: []byte("d2")},
- {Data: []byte("d3")},
- })
-
- got := map[string]int{}
- spc := mustStartPull(t, sclient, sub)
- // In 5 seconds the ack deadline will expire twice, so we should see each message
- // exactly three times.
- time.AfterFunc(5*time.Second, func() {
- if err := spc.CloseSend(); err != nil {
- t.Errorf("CloseSend: %v", err)
- }
- })
- for {
- res, err := spc.Recv()
- if err == io.EOF {
- break
- }
- if err != nil {
- t.Fatal(err)
- }
- for _, m := range res.ReceivedMessages {
- got[m.Message.MessageId]++
- }
- }
- for id, n := range got {
- if n != 3 {
- t.Errorf("message %s: saw %d times, want 3", id, n)
- }
- }
- }
-
- func TestMultiSubs(t *testing.T) {
- // Each subscription gets every message.
- pclient, sclient, _ := newFake(t)
- top := mustCreateTopic(t, pclient, &pb.Topic{Name: "projects/P/topics/T"})
- sub1 := mustCreateSubscription(t, sclient, &pb.Subscription{
- Name: "projects/P/subscriptions/S1",
- Topic: top.Name,
- AckDeadlineSeconds: 10,
- })
- sub2 := mustCreateSubscription(t, sclient, &pb.Subscription{
- Name: "projects/P/subscriptions/S2",
- Topic: top.Name,
- AckDeadlineSeconds: 10,
- })
-
- want := publish(t, pclient, top, []*pb.PubsubMessage{
- {Data: []byte("d1")},
- {Data: []byte("d2")},
- {Data: []byte("d3")},
- })
- got1 := pubsubMessages(pullN(t, len(want), sclient, sub1))
- got2 := pubsubMessages(pullN(t, len(want), sclient, sub2))
- if diff := testutil.Diff(got1, want); diff != "" {
- t.Error(diff)
- }
- if diff := testutil.Diff(got2, want); diff != "" {
- t.Error(diff)
- }
- }
-
- func TestMultiStreams(t *testing.T) {
- // Messages are handed out to the streams of a subscription in round-robin order.
- pclient, sclient, _ := newFake(t)
- top := mustCreateTopic(t, pclient, &pb.Topic{Name: "projects/P/topics/T"})
- sub := mustCreateSubscription(t, sclient, &pb.Subscription{
- Name: "projects/P/subscriptions/S",
- Topic: top.Name,
- AckDeadlineSeconds: 10,
- })
- want := publish(t, pclient, top, []*pb.PubsubMessage{
- {Data: []byte("d1")},
- {Data: []byte("d2")},
- {Data: []byte("d3")},
- {Data: []byte("d4")},
- })
- streams := []pb.Subscriber_StreamingPullClient{
- mustStartPull(t, sclient, sub),
- mustStartPull(t, sclient, sub),
- }
- got := map[string]*pb.PubsubMessage{}
- for i := 0; i < 2; i++ {
- for _, st := range streams {
- res, err := st.Recv()
- if err != nil {
- t.Fatal(err)
- }
- m := res.ReceivedMessages[0]
- got[m.Message.MessageId] = m.Message
- }
- }
- if diff := testutil.Diff(got, want); diff != "" {
- t.Error(diff)
- }
- }
-
- func TestStreamingPullTimeout(t *testing.T) {
- pclient, sclient, srv := newFake(t)
- timeout := 200 * time.Millisecond
- srv.SetStreamTimeout(timeout)
- top := mustCreateTopic(t, pclient, &pb.Topic{Name: "projects/P/topics/T"})
- sub := mustCreateSubscription(t, sclient, &pb.Subscription{
- Name: "projects/P/subscriptions/S",
- Topic: top.Name,
- AckDeadlineSeconds: 10,
- })
- stream := mustStartPull(t, sclient, sub)
- time.Sleep(2 * timeout)
- _, err := stream.Recv()
- if err != io.EOF {
- t.Errorf("got %v, want io.EOF", err)
- }
- }
-
- func mustStartPull(t *testing.T, sc pb.SubscriberClient, sub *pb.Subscription) pb.Subscriber_StreamingPullClient {
- spc, err := sc.StreamingPull(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- if err := spc.Send(&pb.StreamingPullRequest{Subscription: sub.Name}); err != nil {
- t.Fatal(err)
- }
- return spc
- }
-
- func pullN(t *testing.T, n int, sc pb.SubscriberClient, sub *pb.Subscription) map[string]*pb.ReceivedMessage {
- spc := mustStartPull(t, sc, sub)
- got := map[string]*pb.ReceivedMessage{}
- for i := 0; i < n; i++ {
- res, err := spc.Recv()
- if err != nil {
- t.Fatal(err)
- }
- for _, m := range res.ReceivedMessages {
- got[m.Message.MessageId] = m
- }
- }
- if err := spc.CloseSend(); err != nil {
- t.Fatal(err)
- }
- res, err := spc.Recv()
- if err != io.EOF {
- t.Fatalf("Recv returned <%v> instead of EOF; res = %v", err, res)
- }
- return got
- }
-
- func pubsubMessages(rms map[string]*pb.ReceivedMessage) map[string]*pb.PubsubMessage {
- ms := map[string]*pb.PubsubMessage{}
- for k, rm := range rms {
- ms[k] = rm.Message
- }
- return ms
- }
-
- func mustCreateTopic(t *testing.T, pc pb.PublisherClient, topic *pb.Topic) *pb.Topic {
- top, err := pc.CreateTopic(context.Background(), topic)
- if err != nil {
- t.Fatal(err)
- }
- return top
- }
-
- func mustCreateSubscription(t *testing.T, sc pb.SubscriberClient, sub *pb.Subscription) *pb.Subscription {
- sub, err := sc.CreateSubscription(context.Background(), sub)
- if err != nil {
- t.Fatal(err)
- }
- return sub
- }
-
- func newFake(t *testing.T) (pb.PublisherClient, pb.SubscriberClient, *Server) {
- srv := NewServer()
- conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure())
- if err != nil {
- t.Fatal(err)
- }
- return pb.NewPublisherClient(conn), pb.NewSubscriberClient(conn), srv
- }
|