github.com/GoogleCloudPlatform/testgrid@v0.0.174/pkg/pubsub/pubsub_test.go (about)

     1  /*
     2  Copyright 2021 The TestGrid Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package pubsub
    18  
    19  import (
    20  	"context"
    21  	"sync"
    22  	"testing"
    23  	"time"
    24  
    25  	"cloud.google.com/go/pubsub"
    26  	"github.com/GoogleCloudPlatform/testgrid/util/gcs"
    27  	"github.com/google/go-cmp/cmp"
    28  	"github.com/sirupsen/logrus"
    29  )
    30  
    31  func mustPath(t *testing.T, s string) *gcs.Path {
    32  	p, err := gcs.NewPath(s)
    33  	if err != nil {
    34  		t.Fatalf("gcs.NewPath(%q): %v", s, err)
    35  	}
    36  	return p
    37  }
    38  
    39  type fakeAcker struct {
    40  	acks  []string
    41  	nacks []string
    42  }
    43  
    44  func (fa *fakeAcker) Ack(m *pubsub.Message) {
    45  	fa.acks = append(fa.acks, m.ID)
    46  }
    47  
    48  func (fa *fakeAcker) Nack(m *pubsub.Message) {
    49  	fa.nacks = append(fa.nacks, m.ID)
    50  }
    51  
    52  func TestSendToReceivers(t *testing.T) {
    53  	now := time.Now().Round(time.Second)
    54  	cases := []struct {
    55  		name     string
    56  		ctx      context.Context
    57  		send     Sender
    58  		want     []*Notification
    59  		wantAcks fakeAcker
    60  		err      error
    61  	}{
    62  		{
    63  			name: "empty",
    64  			ctx:  context.Background(),
    65  			send: func(ctx context.Context, receive func(context.Context, *pubsub.Message)) error {
    66  				/*
    67  				   for _, msg := range []*pubsub.Message{
    68  				       {},
    69  				   }{
    70  				       receive(ctx, msg)
    71  				   }
    72  				*/
    73  				return nil
    74  			},
    75  		},
    76  		{
    77  			name: "basic",
    78  			ctx:  context.Background(),
    79  			send: func(ctx context.Context, receive func(context.Context, *pubsub.Message)) error {
    80  				for _, msg := range []*pubsub.Message{
    81  					{
    82  						ID: "good-bar",
    83  						Attributes: map[string]string{
    84  							keyBucket:     "foo",
    85  							keyObject:     "bar",
    86  							keyTime:       now.Format(time.RFC3339),
    87  							keyEvent:      string(Finalize),
    88  							keyGeneration: "100",
    89  						},
    90  					},
    91  					{
    92  						ID: "bad-path",
    93  						Attributes: map[string]string{
    94  							keyBucket:     "ignor?e-bad-path",
    95  							keyTime:       now.Add(time.Second).Format(time.RFC3339),
    96  							keyEvent:      string(Delete),
    97  							keyGeneration: "50",
    98  						},
    99  					},
   100  					{
   101  						ID: "bad-hash-path",
   102  						Attributes: map[string]string{
   103  							keyBucket:     "random-bucket",
   104  							keyObject:     "ignore-path#with-a-hash",
   105  							keyTime:       now.Add(time.Second).Format(time.RFC3339),
   106  							keyEvent:      string(Delete),
   107  							keyGeneration: "50",
   108  						},
   109  					},
   110  					{
   111  						ID: "bad-control-path",
   112  						Attributes: map[string]string{
   113  							keyBucket:     "random-bucket",
   114  							keyObject:     "ignore-path\x00with-a-control-char",
   115  							keyTime:       now.Add(time.Second).Format(time.RFC3339),
   116  							keyEvent:      string(Delete),
   117  							keyGeneration: "50",
   118  						},
   119  					},
   120  					{
   121  						ID: "bad-time",
   122  						Attributes: map[string]string{
   123  							keyBucket:     "foo",
   124  							keyObject:     "old/bar",
   125  							keyTime:       "ignore bad time",
   126  							keyEvent:      string(Delete),
   127  							keyGeneration: "50",
   128  						},
   129  					},
   130  					{
   131  						ID: "bad-gen",
   132  						Attributes: map[string]string{
   133  							keyBucket:     "foo",
   134  							keyObject:     "old/bar",
   135  							keyTime:       now.Add(time.Second).Format(time.RFC3339),
   136  							keyEvent:      string(Delete),
   137  							keyGeneration: "50.0", // bad gen
   138  						},
   139  					},
   140  					{
   141  						ID: "good-random-event",
   142  						Attributes: map[string]string{
   143  							keyBucket:     "foo",
   144  							keyObject:     "old/bar",
   145  							keyTime:       now.Add(time.Second).Format(time.RFC3339),
   146  							keyEvent:      "random event",
   147  							keyGeneration: "50",
   148  						},
   149  					},
   150  					{
   151  						ID: "good-old-bar",
   152  						Attributes: map[string]string{
   153  							keyBucket:     "foo",
   154  							keyObject:     "old/bar",
   155  							keyTime:       now.Add(time.Second).Format(time.RFC3339),
   156  							keyEvent:      string(Delete),
   157  							keyGeneration: "50",
   158  						},
   159  					},
   160  				} {
   161  					receive(ctx, msg)
   162  				}
   163  				return nil
   164  			},
   165  			wantAcks: fakeAcker{
   166  				acks: []string{
   167  					"good-bar",
   168  					"bad-path",
   169  					"bad-hash-path",
   170  					"bad-control-path",
   171  					"good-random-event",
   172  					"good-old-bar",
   173  				},
   174  				nacks: []string{
   175  					"bad-time",
   176  					"bad-gen",
   177  				},
   178  			},
   179  			want: []*Notification{
   180  				{
   181  					Path:       *mustPath(t, "gs://foo/bar"),
   182  					Event:      Finalize,
   183  					Time:       now,
   184  					Generation: 100,
   185  				},
   186  				{
   187  					Path:       *mustPath(t, "gs://foo/old/bar"),
   188  					Event:      Event("random event"),
   189  					Time:       now.Add(time.Second),
   190  					Generation: 50,
   191  				},
   192  				{
   193  					Path:       *mustPath(t, "gs://foo/old/bar"),
   194  					Event:      Delete,
   195  					Time:       now.Add(time.Second),
   196  					Generation: 50,
   197  				},
   198  			},
   199  		},
   200  	}
   201  
   202  	for _, tc := range cases {
   203  		t.Run(tc.name, func(t *testing.T) {
   204  			ch := make(chan *Notification)
   205  			var got []*Notification
   206  			var wg sync.WaitGroup
   207  			wg.Add(1)
   208  			go func() {
   209  				defer wg.Done()
   210  				for n := range ch {
   211  					got = append(got, n)
   212  				}
   213  			}()
   214  
   215  			var gotAcks fakeAcker
   216  			err := sendToReceivers(tc.ctx, logrus.WithField("name", tc.name), tc.send, ch, &gotAcks)
   217  			close(ch)
   218  			wg.Wait()
   219  			switch {
   220  			case err != tc.err:
   221  				t.Errorf("sendToReceivers() wanted error %v, got %v", tc.err, err)
   222  			default:
   223  				if diff := cmp.Diff(tc.want, got, cmp.AllowUnexported(gcs.Path{})); diff != "" {
   224  					t.Errorf("sendToReceivers() got unexpected diff (-want +got):\n%s", diff)
   225  				}
   226  				if diff := cmp.Diff(tc.wantAcks.acks, gotAcks.acks); diff != "" {
   227  					t.Errorf("sendToReceivers() got unexpected ack diff (-want +got):\n%s", diff)
   228  				}
   229  				if diff := cmp.Diff(tc.wantAcks.nacks, gotAcks.nacks); diff != "" {
   230  					t.Errorf("sendToReceivers() got unexpected nack diff (-want +got):\n%s", diff)
   231  				}
   232  			}
   233  		})
   234  	}
   235  }